From 5810cda5857aafb4568cc5901aa4c3a8ae1a223e Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 12 Oct 2023 21:28:58 +0200 Subject: [PATCH 001/168] dev --- development.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index d6126715..4b0445f3 100644 --- a/development.md +++ b/development.md @@ -492,8 +492,6 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: exiting preview doesn't refresh - [x] Mode should know if a refresh is required after leaving them. -## Current dev - ### Version 0.1.22 - [x] FIX: copying 0 bytes crash progress bar thread @@ -564,6 +562,12 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Not accessible file in tree mode crashes the application - [x] Look for nvim listen address in `ss -l` output +## Current dev + +### Version 0.1.23 + +- [ ] preview every archive + ## TODO - [ ] while second window is opened, if the selection is below half screen, it's not shown anymore. From 965b37c95eb9f2bda9fd55b6178eb9a8b2618148 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 12 Oct 2023 21:29:21 +0200 Subject: [PATCH 002/168] prepare 0.1.23 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d572a1f5..78219cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,7 +849,7 @@ dependencies = [ [[package]] name = "fm-tui" -version = "0.1.22" +version = "0.1.23" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index bbe9b5b1..47372a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fm-tui" -version = "0.1.22" +version = "0.1.23" authors = ["Quentin Konieczko "] edition = "2021" license-file = "LICENSE.txt" From 8ad9408eef3b56d1f1ecdcf08137139a72e36904 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 12 Oct 2023 21:54:55 +0200 Subject: [PATCH 003/168] preview tar archive --- development.md | 2 +- src/constant_strings_paths.rs | 2 ++ src/decompress.rs | 20 ++++++++++++++++++ src/preview.rs | 40 ++++++++++++++++++++++++++--------- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/development.md b/development.md index 4b0445f3..e3536f42 100644 --- a/development.md +++ b/development.md @@ -566,7 +566,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. ### Version 0.1.23 -- [ ] preview every archive +- [x] preview tar archive ## TODO diff --git a/src/constant_strings_paths.rs b/src/constant_strings_paths.rs index dfac76c9..49bd5988 100644 --- a/src/constant_strings_paths.rs +++ b/src/constant_strings_paths.rs @@ -184,3 +184,5 @@ pub const CRYPTSETUP: &str = "cryptsetup"; pub const LSOF: &str = "lsof"; /// neovim executable pub const NVIM: &str = "nvim"; +/// tar executable +pub const TAR: &str = "tar"; diff --git a/src/decompress.rs b/src/decompress.rs index 916293a3..a2141592 100644 --- a/src/decompress.rs +++ b/src/decompress.rs @@ -4,6 +4,8 @@ use std::fs::File; use std::path::Path; use tar::Archive; +use crate::constant_strings_paths::TAR; + /// Decompress a zipped compressed file into its parent directory. pub fn decompress_zip(source: &Path) -> Result<()> { let file = File::open(source)?; @@ -53,3 +55,21 @@ where let zip = zip::ZipArchive::new(file)?; Ok(zip.file_names().map(|f| f.to_owned()).collect()) } + +/// List files contained in a tar.something file. +/// Will return an error if `tar tvf source` can't list the content. +pub fn list_files_tar

(source: P) -> Result> +where + P: AsRef, +{ + if let Ok(output) = std::process::Command::new(TAR) + .arg("tvf") + .arg(source.as_ref().display().to_string()) + .output() + { + let output = String::from_utf8(output.stdout).unwrap_or_default(); + let content = output.lines().map(|l| l.to_owned()).collect(); + return Ok(content); + } + Err(anyhow::anyhow!("Tar couldn't read the file content")) +} diff --git a/src/preview.rs b/src/preview.rs index be5f18b4..5e451c33 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -24,7 +24,7 @@ use crate::constant_strings_paths::{ THUMBNAIL_PATH, UEBERZUG, }; use crate::content_window::ContentWindow; -use crate::decompress::list_files_zip; +use crate::decompress::{list_files_tar, list_files_zip}; use crate::fileinfo::{FileInfo, FileKind}; use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; @@ -41,7 +41,7 @@ pub enum Preview { Text(TextContent), Binary(BinaryContent), Pdf(PdfContent), - Archive(ZipContent), + Archive(ArchiveContent), Ueberzug(Ueberzug), Media(MediaContent), Directory(Directory), @@ -87,7 +87,9 @@ impl Preview { Some(2), )?)), FileKind::NormalFile => match file_info.extension.to_lowercase().as_str() { - e if is_ext_compressed(e) => Ok(Self::Archive(ZipContent::new(&file_info.path)?)), + e if is_ext_compressed(e) => { + Ok(Self::Archive(ArchiveContent::new(&file_info.path, e)?)) + } e if is_ext_pdf(e) => Ok(Self::Pdf(PdfContent::new(&file_info.path))), e if is_ext_image(e) && is_program_in_path(UEBERZUG) => { Ok(Self::Ueberzug(Ueberzug::image(&file_info.path)?)) @@ -665,17 +667,24 @@ impl PdfContent { } } -/// Holds a list of file of an archive as returned by `ZipArchive::file_names`. +/// Holds a list of file of an archive as returned by +/// `ZipArchive::file_names` or from a `tar tvf` command. /// A generic error message prevent it from returning an error. #[derive(Clone)] -pub struct ZipContent { +pub struct ArchiveContent { length: usize, content: Vec, } -impl ZipContent { - fn new(path: &Path) -> Result { - let content = list_files_zip(path).unwrap_or(vec!["Invalid Zip content".to_owned()]); +impl ArchiveContent { + fn new(path: &Path, ext: &str) -> Result { + let content = match ext { + "zip" => list_files_zip(path).unwrap_or(vec!["Invalid Zip content".to_owned()]), + "zst" | "gz" | "bz" | "xz" | "gzip" | "bzip2" => { + list_files_tar(path).unwrap_or(vec!["Invalid Tar content".to_owned()]) + } + _ => vec![format!("Unsupported format: {ext}")], + }; Ok(Self { length: content.len(), @@ -1140,7 +1149,7 @@ impl_window!(HLContent, VecSyntaxedString); impl_window!(TextContent, String); impl_window!(BinaryContent, Line); impl_window!(PdfContent, String); -impl_window!(ZipContent, String); +impl_window!(ArchiveContent, String); impl_window!(MediaContent, String); impl_window!(Directory, ColoredTriplet); impl_window!(Diff, String); @@ -1153,7 +1162,18 @@ impl_window!(FifoCharDevice, String); fn is_ext_compressed(ext: &str) -> bool { matches!( ext, - "zip" | "gzip" | "bzip2" | "xz" | "lzip" | "lzma" | "tar" | "mtree" | "raw" | "7z" + "zip" + | "gzip" + | "bzip2" + | "xz" + | "lzip" + | "lzma" + | "tar" + | "mtree" + | "raw" + | "7z" + | "gz" + | "zst" ) } From 86d6df43dbdf286052d2210b893bbb70baf3ce7a Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 13 Oct 2023 13:22:14 +0200 Subject: [PATCH 004/168] gallery skeleton --- development.md | 1 + src/preview.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/development.md b/development.md index e3536f42..ace31e57 100644 --- a/development.md +++ b/development.md @@ -567,6 +567,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. ### Version 0.1.23 - [x] preview tar archive +- [ ] gallery mode for image folders ## TODO diff --git a/src/preview.rs b/src/preview.rs index 5e451c33..bcb1cf9e 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -51,6 +51,7 @@ pub enum Preview { Socket(Socket), BlockDevice(BlockDevice), FifoCharDevice(FifoCharDevice), + Gallery(Gallery), #[default] Empty, } @@ -78,6 +79,13 @@ impl Preview { colors: &Colors, ) -> Result { match file_info.file_kind { + FileKind::Directory if is_image_folder(file_info) => Ok(Self::Gallery(Gallery::new( + &file_info.path, + users_cache, + colors, + &status.selected_non_mut().filter, + status.selected_non_mut().show_hidden, + )?)), FileKind::Directory => Ok(Self::Directory(Directory::new( &file_info.path, users_cache, @@ -269,6 +277,7 @@ impl Preview { Self::Socket(socket) => socket.len(), Self::BlockDevice(blockdevice) => blockdevice.len(), Self::FifoCharDevice(fifo) => fifo.len(), + Self::Gallery(gallery) => gallery.len(), } } @@ -288,6 +297,35 @@ fn read_nb_lines(path: &Path, size_limit: usize) -> Result> { .collect()) } +/// Gallery preview for folders with many images +pub struct Gallery { + content: Vec>, + length: usize, +} + +impl Gallery { + fn new( + path: &Path, + users_cache: &UsersCache, + colors: &Colors, + filter: &FilterKind, + show_hidden: bool, + ) -> Result { + let content = Self::make_gallery(); + Ok(Self { + length: content.len(), + content, + }) + } + + fn make_gallery() -> Vec> { + vec![] + } + + fn len(&self) -> usize { + self.content.len() + } +} /// Preview a socket file with `ss -lpmepiT` #[derive(Clone, Default)] pub struct Socket { @@ -1235,6 +1273,10 @@ fn is_ext_epub(ext: &str) -> bool { ext == "epub" } +fn is_image_folder(file_info: &FileInfo) -> bool { + true +} + fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> std::thread::Result { let prev_hook = panic::take_hook(); panic::set_hook(Box::new(|_| {})); From 69a6cfc5514c0b1f0b24709ce9a24166341c1817 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 13 Oct 2023 21:01:51 +0200 Subject: [PATCH 005/168] remove gallery preview --- development.md | 1 - src/preview.rs | 42 ------------------------------------------ 2 files changed, 43 deletions(-) diff --git a/development.md b/development.md index ace31e57..e3536f42 100644 --- a/development.md +++ b/development.md @@ -567,7 +567,6 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. ### Version 0.1.23 - [x] preview tar archive -- [ ] gallery mode for image folders ## TODO diff --git a/src/preview.rs b/src/preview.rs index bcb1cf9e..5e451c33 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -51,7 +51,6 @@ pub enum Preview { Socket(Socket), BlockDevice(BlockDevice), FifoCharDevice(FifoCharDevice), - Gallery(Gallery), #[default] Empty, } @@ -79,13 +78,6 @@ impl Preview { colors: &Colors, ) -> Result { match file_info.file_kind { - FileKind::Directory if is_image_folder(file_info) => Ok(Self::Gallery(Gallery::new( - &file_info.path, - users_cache, - colors, - &status.selected_non_mut().filter, - status.selected_non_mut().show_hidden, - )?)), FileKind::Directory => Ok(Self::Directory(Directory::new( &file_info.path, users_cache, @@ -277,7 +269,6 @@ impl Preview { Self::Socket(socket) => socket.len(), Self::BlockDevice(blockdevice) => blockdevice.len(), Self::FifoCharDevice(fifo) => fifo.len(), - Self::Gallery(gallery) => gallery.len(), } } @@ -297,35 +288,6 @@ fn read_nb_lines(path: &Path, size_limit: usize) -> Result> { .collect()) } -/// Gallery preview for folders with many images -pub struct Gallery { - content: Vec>, - length: usize, -} - -impl Gallery { - fn new( - path: &Path, - users_cache: &UsersCache, - colors: &Colors, - filter: &FilterKind, - show_hidden: bool, - ) -> Result { - let content = Self::make_gallery(); - Ok(Self { - length: content.len(), - content, - }) - } - - fn make_gallery() -> Vec> { - vec![] - } - - fn len(&self) -> usize { - self.content.len() - } -} /// Preview a socket file with `ss -lpmepiT` #[derive(Clone, Default)] pub struct Socket { @@ -1273,10 +1235,6 @@ fn is_ext_epub(ext: &str) -> bool { ext == "epub" } -fn is_image_folder(file_info: &FileInfo) -> bool { - true -} - fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> std::thread::Result { let prev_hook = panic::take_hook(); panic::set_hook(Box::new(|_| {})); From 37fa3b679af9b7cbbcd794cb0ae2f4b36738a0c0 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 13 Oct 2023 21:02:08 +0200 Subject: [PATCH 006/168] make patch content enumerate use non mutable ref --- src/fileinfo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 89412efc..dd6927ae 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -519,7 +519,7 @@ impl PathContent { } /// Returns an enumeration of the files (`FileInfo`) in content. - pub fn enumerate(&mut self) -> Enumerate> { + pub fn enumerate(&self) -> Enumerate> { self.content.iter().enumerate() } From 5098df7451f7dd186d1965f475483a75aac7b79c Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 13 Oct 2023 22:02:23 +0200 Subject: [PATCH 007/168] refactor preview matcher. First match the extension --- src/event_exec.rs | 7 +- src/preview.rs | 230 ++++++++++++++++++++-------------------------- 2 files changed, 104 insertions(+), 133 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index 2b1b113f..7d0f50a5 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -30,7 +30,7 @@ use crate::opener::{ execute_in_child_without_output_with_path, InternalVariant, }; use crate::password::{PasswordKind, PasswordUsage}; -use crate::preview::is_ext_image; +use crate::preview::ExtensionKind; use crate::preview::Preview; use crate::selectable_content::SelectableContent; use crate::shell_parser::ShellCommandParser; @@ -1016,7 +1016,10 @@ impl EventAction { let Some(fileinfo) = tab.path_content.selected() else { return Ok(()); }; - if !is_ext_image(&fileinfo.extension) { + if !matches!( + ExtensionKind::matcher(&fileinfo.extension), + ExtensionKind::Image, + ) { return Ok(()); } let Some(path_str) = tab.path_content.selected_path_string() else { diff --git a/src/preview.rs b/src/preview.rs index 5e451c33..a85b3e12 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -32,6 +32,58 @@ use crate::status::Status; use crate::tree::{ColoredString, Tree}; use crate::utils::{filename_from_path, is_program_in_path}; +/// Different kind of extension for grouped by previewers. +/// Any extension we can preview should be matched here. +#[derive(Default)] +pub enum ExtensionKind { + Archive, + Image, + Audio, + Video, + Font, + Svg, + Pdf, + Iso, + Notebook, + Doc, + Epub, + #[default] + Unknown, +} + +impl ExtensionKind { + /// Match any known extension against an extension kind. + pub fn matcher(ext: &str) -> Self { + match ext { + "zip" | "gzip" | "bzip2" | "xz" | "lzip" | "lzma" | "tar" | "mtree" | "raw" | "7z" + | "gz" | "zst" => Self::Archive, + "png" | "jpg" | "jpeg" | "tiff" | "heif" | "gif" | "cr2" | "nef" | "orf" | "sr2" => { + Self::Image + } + "ogg" | "ogm" | "riff" | "mp2" | "mp3" | "wm" | "qt" | "ac3" | "dts" | "aac" + | "mac" | "flac" => Self::Audio, + "mkv" | "webm" | "mpeg" | "mp4" | "avi" | "flv" | "mpg" => Self::Video, + "ttf" | "otf" => Self::Font, + "svg" | "svgz" => Self::Svg, + "pdf" => Self::Pdf, + "iso" => Self::Iso, + "ipynb" => Self::Notebook, + "doc" | "docx" | "odt" | "sxw" => Self::Doc, + "epub" => Self::Epub, + _ => Self::Unknown, + } + } +} + +#[derive(Clone, Default)] +pub enum TextKind { + HELP, + LOG, + EPUB, + #[default] + TEXTFILE, +} + /// Different kind of preview used to display some informaitons /// About the file. /// We check if it's an archive first, then a pdf file, an image, a media file @@ -55,15 +107,6 @@ pub enum Preview { Empty, } -#[derive(Clone, Default)] -pub enum TextKind { - HELP, - LOG, - EPUB, - #[default] - TEXTFILE, -} - impl Preview { const CONTENT_INSPECTOR_MIN_SIZE: usize = 1024; @@ -86,53 +129,54 @@ impl Preview { status.selected_non_mut().show_hidden, Some(2), )?)), - FileKind::NormalFile => match file_info.extension.to_lowercase().as_str() { - e if is_ext_compressed(e) => { - Ok(Self::Archive(ArchiveContent::new(&file_info.path, e)?)) - } - e if is_ext_pdf(e) => Ok(Self::Pdf(PdfContent::new(&file_info.path))), - e if is_ext_image(e) && is_program_in_path(UEBERZUG) => { - Ok(Self::Ueberzug(Ueberzug::image(&file_info.path)?)) - } - e if is_ext_audio(e) && is_program_in_path(MEDIAINFO) => { - Ok(Self::Media(MediaContent::new(&file_info.path)?)) - } - e if is_ext_video(e) - && is_program_in_path(UEBERZUG) - && is_program_in_path(FFMPEG) => - { - Ok(Self::Ueberzug(Ueberzug::video_thumbnail(&file_info.path)?)) + FileKind::NormalFile => { + let extension = &file_info.extension.to_lowercase(); + match ExtensionKind::matcher(extension) { + ExtensionKind::Archive => Ok(Self::Archive(ArchiveContent::new( + &file_info.path, + extension, + )?)), + ExtensionKind::Pdf => Ok(Self::Pdf(PdfContent::new(&file_info.path))), + ExtensionKind::Image if is_program_in_path(UEBERZUG) => { + Ok(Self::Ueberzug(Ueberzug::image(&file_info.path)?)) + } + ExtensionKind::Audio if is_program_in_path(MEDIAINFO) => { + Ok(Self::Media(MediaContent::new(&file_info.path)?)) + } + ExtensionKind::Video + if is_program_in_path(UEBERZUG) && is_program_in_path(FFMPEG) => + { + Ok(Self::Ueberzug(Ueberzug::video_thumbnail(&file_info.path)?)) + } + ExtensionKind::Font + if is_program_in_path(UEBERZUG) && is_program_in_path(FONTIMAGE) => + { + Ok(Self::Ueberzug(Ueberzug::font_thumbnail(&file_info.path)?)) + } + ExtensionKind::Svg + if is_program_in_path(UEBERZUG) && is_program_in_path(RSVG_CONVERT) => + { + Ok(Self::Ueberzug(Ueberzug::svg_thumbnail(&file_info.path)?)) + } + ExtensionKind::Iso if is_program_in_path(ISOINFO) => { + Ok(Self::Iso(Iso::new(&file_info.path)?)) + } + ExtensionKind::Notebook if is_program_in_path(JUPYTER) => { + Ok(Self::notebook(&file_info.path) + .context("Preview: Couldn't parse notebook")?) + } + ExtensionKind::Doc if is_program_in_path(PANDOC) => { + Ok(Self::doc(&file_info.path).context("Preview: Couldn't parse doc")?) + } + ExtensionKind::Epub if is_program_in_path(PANDOC) => { + Ok(Self::epub(&file_info.path).context("Preview: Couldn't parse epub")?) + } + _ => match Self::preview_syntaxed(extension, &file_info.path) { + Some(syntaxed_preview) => Ok(syntaxed_preview), + None => Self::preview_text_or_binary(file_info), + }, } - e if is_ext_font(e) - && is_program_in_path(UEBERZUG) - && is_program_in_path(FONTIMAGE) => - { - Ok(Self::Ueberzug(Ueberzug::font_thumbnail(&file_info.path)?)) - } - e if is_ext_svg(e) - && is_program_in_path(UEBERZUG) - && is_program_in_path(RSVG_CONVERT) => - { - Ok(Self::Ueberzug(Ueberzug::svg_thumbnail(&file_info.path)?)) - } - e if is_ext_iso(e) && is_program_in_path(ISOINFO) => { - Ok(Self::Iso(Iso::new(&file_info.path)?)) - } - e if is_ext_notebook(e) && is_program_in_path(JUPYTER) => { - Ok(Self::notebook(&file_info.path) - .context("Preview: Couldn't parse notebook")?) - } - e if is_ext_doc(e) && is_program_in_path(PANDOC) => { - Ok(Self::doc(&file_info.path).context("Preview: Couldn't parse doc")?) - } - e if is_ext_epub(e) && is_program_in_path(PANDOC) => { - Ok(Self::epub(&file_info.path).context("Preview: Couldn't parse epub")?) - } - e => match Self::preview_syntaxed(e, &file_info.path) { - Some(syntaxed_preview) => Ok(syntaxed_preview), - None => Self::preview_text_or_binary(file_info), - }, - }, + } FileKind::Socket if is_program_in_path(SS) => Ok(Self::socket(file_info)), FileKind::BlockDevice if is_program_in_path(LSBLK) => Ok(Self::blockdevice(file_info)), FileKind::Fifo | FileKind::CharDevice if is_program_in_path(LSOF) => { @@ -1159,82 +1203,6 @@ impl_window!(Socket, String); impl_window!(BlockDevice, String); impl_window!(FifoCharDevice, String); -fn is_ext_compressed(ext: &str) -> bool { - matches!( - ext, - "zip" - | "gzip" - | "bzip2" - | "xz" - | "lzip" - | "lzma" - | "tar" - | "mtree" - | "raw" - | "7z" - | "gz" - | "zst" - ) -} - -/// True iff the extension is a known (by me) image extension. -pub fn is_ext_image(ext: &str) -> bool { - matches!( - ext, - "png" | "jpg" | "jpeg" | "tiff" | "heif" | "gif" | "raw" | "cr2" | "nef" | "orf" | "sr2" - ) -} - -fn is_ext_audio(ext: &str) -> bool { - matches!( - ext, - "ogg" - | "ogm" - | "riff" - | "mp2" - | "mp3" - | "wm" - | "qt" - | "ac3" - | "dts" - | "aac" - | "mac" - | "flac" - ) -} - -fn is_ext_video(ext: &str) -> bool { - matches!(ext, "mkv" | "webm" | "mpeg" | "mp4" | "avi" | "flv" | "mpg") -} - -fn is_ext_font(ext: &str) -> bool { - matches!(ext, "ttf" | "otf") -} - -fn is_ext_svg(ext: &str) -> bool { - matches!(ext, "svg" | "svgz") -} - -fn is_ext_pdf(ext: &str) -> bool { - ext == "pdf" -} - -fn is_ext_iso(ext: &str) -> bool { - ext == "iso" -} - -fn is_ext_notebook(ext: &str) -> bool { - ext == "ipynb" -} - -fn is_ext_doc(ext: &str) -> bool { - matches!(ext, "doc" | "docx" | "odt" | "sxw") -} - -fn is_ext_epub(ext: &str) -> bool { - ext == "epub" -} - fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> std::thread::Result { let prev_hook = panic::take_hook(); panic::set_hook(Box::new(|_| {})); From 48b903217ee17a0cb59da3f316cadd6b941861cf Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 14 Oct 2023 14:01:15 +0200 Subject: [PATCH 008/168] refactor opener and ExtensionKind --- src/opener.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/opener.rs b/src/opener.rs index fe02fbae..a81d5d46 100644 --- a/src/opener.rs +++ b/src/opener.rs @@ -51,8 +51,8 @@ pub enum ExtensionKind { // TODO: move those associations to a config file impl ExtensionKind { - fn parse(ext: &str) -> Self { - match ext.to_lowercase().as_str() { + fn matcher(ext: &str) -> Self { + match ext { "avif" | "bmp" | "gif" | "png" | "jpg" | "jpeg" | "pgm" | "ppm" | "webp" | "tiff" => { Self::Bitmap } @@ -126,7 +126,6 @@ impl OpenerAssociation { ExtensionKind::Text, OpenerInfo::external(DEFAULT_TEXT_OPENER), ), - (ExtensionKind::Default, OpenerInfo::external(DEFAULT_OPENER)), ( ExtensionKind::Vectorial, OpenerInfo::external(DEFAULT_VECTORIAL_OPENER), @@ -138,23 +137,24 @@ impl OpenerAssociation { ( ExtensionKind::Internal(InternalVariant::DecompressZip), OpenerInfo::internal(ExtensionKind::Internal(InternalVariant::DecompressZip)) - .unwrap(), + .unwrap_or_default(), ), ( ExtensionKind::Internal(InternalVariant::DecompressGz), OpenerInfo::internal(ExtensionKind::Internal(InternalVariant::DecompressGz)) - .unwrap(), + .unwrap_or_default(), ), ( ExtensionKind::Internal(InternalVariant::DecompressXz), OpenerInfo::internal(ExtensionKind::Internal(InternalVariant::DecompressXz)) - .unwrap(), + .unwrap_or_default(), ), ( ExtensionKind::Internal(InternalVariant::NotSupported), OpenerInfo::internal(ExtensionKind::Internal(InternalVariant::NotSupported)) - .unwrap(), + .unwrap_or_default(), ), + (ExtensionKind::Default, OpenerInfo::external(DEFAULT_OPENER)), ]), } } @@ -189,7 +189,8 @@ macro_rules! open_file_with { impl OpenerAssociation { fn opener_info(&self, ext: &str) -> Option<&OpenerInfo> { - self.association.get(&ExtensionKind::parse(ext)) + self.association + .get(&ExtensionKind::matcher(&ext.to_lowercase())) } fn update_from_file(&mut self, yaml: &serde_yaml::value::Value) { @@ -237,6 +238,16 @@ pub struct OpenerInfo { use_term: bool, } +impl Default for OpenerInfo { + fn default() -> Self { + Self { + external_program: Some("/usr/bin/xdg-open".to_owned()), + internal_variant: None, + use_term: false, + } + } +} + impl OpenerInfo { fn external(opener_pair: (&str, bool)) -> Self { let opener = opener_pair.0; From 7ebe29d47843ecdff4ff50d8e5f70b507af3cdb3 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 14 Oct 2023 18:19:50 +0200 Subject: [PATCH 009/168] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file --- development.md | 1 + src/event_dispatch.rs | 2 ++ src/event_exec.rs | 5 ++++- src/flagged.rs | 6 ++++++ src/mode.rs | 5 ++++- src/status.rs | 6 ++++++ 6 files changed, 23 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index e3536f42..b701659b 100644 --- a/development.md +++ b/development.md @@ -567,6 +567,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. ### Version 0.1.23 - [x] preview tar archive +- [x] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file ## TODO diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index 844924fe..9a0c94ee 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -95,6 +95,8 @@ impl EventDispatcher { Mode::Navigate(Navigate::EncryptedDrive) if c == 'm' => status.mount_encrypted_drive(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'g' => status.go_to_encrypted_drive(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'u' => status.umount_encrypted_drive(), + Mode::Navigate(Navigate::Jump) if c == ' ' => status.jump_remove_selected_flagged(), + Mode::Navigate(Navigate::Jump) if c == 'u' => status.clear_flags_and_reset_view(), Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => status.marks_jump_char(c, colors), Mode::Navigate(Navigate::Marks(MarkAction::New)) => status.marks_new(c, colors), Mode::Preview | Mode::Navigate(_) => { diff --git a/src/event_exec.rs b/src/event_exec.rs index 7d0f50a5..6ffc781f 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -713,7 +713,10 @@ impl EventAction { LeaveMode::password(status, kind, colors, dest, action)? } Mode::InputSimple(InputSimple::Remote) => LeaveMode::remote(status.selected())?, - Mode::Navigate(Navigate::Jump) => LeaveMode::jump(status)?, + Mode::Navigate(Navigate::Jump) => { + must_refresh = false; + LeaveMode::jump(status)? + } Mode::Navigate(Navigate::History) => LeaveMode::history(status.selected())?, Mode::Navigate(Navigate::Shortcut) => LeaveMode::shortcut(status.selected())?, Mode::Navigate(Navigate::Trash) => LeaveMode::trash(status)?, diff --git a/src/flagged.rs b/src/flagged.rs index e8b487b3..5d32971d 100644 --- a/src/flagged.rs +++ b/src/flagged.rs @@ -60,6 +60,12 @@ impl Flagged { .map(|p| p.as_path()) .collect() } + + /// Remove the selected file from the flagged files. + pub fn remove_selected(&mut self) { + self.content.remove(self.index); + self.index = 0; + } } impl_selectable_content!(PathBuf, Flagged); diff --git a/src/mode.rs b/src/mode.rs index 2919bb0a..0832ea13 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -204,7 +204,10 @@ impl fmt::Display for Mode { Mode::InputCompleted(InputCompleted::Nothing) => write!(f, "Nothing: "), Mode::InputCompleted(InputCompleted::Command) => write!(f, "Command: "), Mode::Navigate(Navigate::Marks(_)) => write!(f, "Marks jump:"), - Mode::Navigate(Navigate::Jump) => write!(f, "Jump : "), + Mode::Navigate(Navigate::Jump) => write!( + f, + "Flagged files: go to file -- remove flag -- remove all flags" + ), Mode::Navigate(Navigate::History) => write!(f, "History :"), Mode::Navigate(Navigate::Shortcut) => write!(f, "Shortcut :"), Mode::Navigate(Navigate::Trash) => write!(f, "Trash :"), diff --git a/src/status.rs b/src/status.rs index a9272015..1b50615c 100644 --- a/src/status.rs +++ b/src/status.rs @@ -317,6 +317,12 @@ impl Status { self.reset_tabs_view() } + /// Remove a flag file from Jump mode + pub fn jump_remove_selected_flagged(&mut self) -> Result<()> { + self.flagged.remove_selected(); + Ok(()) + } + pub fn click( &mut self, row: u16, From 4d6cdc51b5deb4f03dfe17dbfe17883fb1d8b934 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 14 Oct 2023 20:14:56 +0200 Subject: [PATCH 010/168] dev --- development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/development.md b/development.md index b701659b..814a221d 100644 --- a/development.md +++ b/development.md @@ -568,6 +568,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] preview tar archive - [x] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file +- [ ] copy / move while existing file already exist should use another name ## TODO From 55e03cd91cfa18b1405b6919956f3c930f746fb1 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 00:19:42 +0200 Subject: [PATCH 011/168] FIX: copy / move while existing file already exist use another name --- development.md | 3 +- src/bulkrename.rs | 15 +------- src/copy_move.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++-- src/utils.rs | 14 +++++++ 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/development.md b/development.md index 814a221d..5c58000d 100644 --- a/development.md +++ b/development.md @@ -568,7 +568,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] preview tar archive - [x] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file -- [ ] copy / move while existing file already exist should use another name +- [x] FIX: copy / move while existing file already exist use another name + It allows fm to create copies of file in the same directory. ## TODO diff --git a/src/bulkrename.rs b/src/bulkrename.rs index 1bbc6787..441147f5 100644 --- a/src/bulkrename.rs +++ b/src/bulkrename.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, Result}; use log::info; -use rand::Rng; use std::io::{BufRead, Write}; use std::path::{Path, PathBuf}; use std::thread; @@ -11,6 +10,7 @@ use crate::impl_selectable_content; use crate::log::write_log_line; use crate::opener::Opener; use crate::status::Status; +use crate::utils::random_name; /// Struct holding informations about files about to be renamed. /// We only need to know which are the original filenames and which @@ -90,20 +90,9 @@ impl<'a> Bulkrename<'a> { Ok(std::fs::metadata(filepath)?.modified()?) } - fn random_name() -> String { - let mut rand_str = String::with_capacity(14); - rand_str.push_str("fm-"); - rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(7) - .for_each(|ch| rand_str.push(ch as char)); - rand_str.push_str(".txt"); - rand_str - } - fn generate_random_filepath() -> Result { let mut filepath = PathBuf::from(&TMP_FOLDER_PATH); - filepath.push(Self::random_name()); + filepath.push(random_name()); Ok(filepath) } diff --git a/src/copy_move.rs b/src/copy_move.rs index dd49f952..9773db85 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::thread; -use anyhow::Result; +use anyhow::{Context, Result}; use fs_extra; use indicatif::{InMemoryTerm, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle}; use log::info; @@ -13,7 +13,7 @@ use crate::constant_strings_paths::NOTIFY_EXECUTABLE; use crate::fileinfo::human_size; use crate::log::write_log_line; use crate::opener::execute_in_child; -use crate::utils::is_program_in_path; +use crate::utils::{is_program_in_path, random_name}; /// Display the updated progress bar on the terminal. fn handle_progress_display( @@ -125,6 +125,21 @@ impl CopyMove { /// A progress bar is displayed on the passed terminal. /// A notification is then sent to the user if a compatible notification system /// is installed. +/// +/// If a file is copied or moved to a folder which already contains a file with the same name, +/// the copie/moved file has a `_` appended to its name. +/// +/// This is done by : +/// 1. creating a random temporary folder in the destination, +/// 2. moving / copying every file there, +/// 3. moving all file to their final destination, appending enough `_` to get an unique file name, +/// 4. deleting the now empty temporary folder. +/// +/// 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. +/// +/// In this scenario we have to wait for the copy to end before moving back the file. +/// Sadly, the copy is now blocking. pub fn copy_move( copy_or_move: CopyMove, sources: Vec, @@ -136,8 +151,15 @@ pub fn copy_move( let handle_progress = move |process_info: fs_extra::TransitProcess| { handle_progress_display(&in_mem, &pb, &term, process_info) }; - let dest = dest.to_owned(); - let _ = thread::spawn(move || { + let dest_has_existing_file = check_existing_file(&sources, dest)?; + let final_dest = dest; + let dest = if dest_has_existing_file { + create_temporary_destination(dest)? + } else { + dest.to_owned() + }; + let temp_dest = dest.clone(); + let copy_move_thread = thread::spawn(move || { let transfered_bytes = match copy_or_move.copier()(&sources, &dest, &options, handle_progress) { Ok(transfered_bytes) => transfered_bytes, @@ -151,9 +173,76 @@ pub fn copy_move( copy_or_move.log_and_notify(human_size(transfered_bytes)); }); + if dest_has_existing_file { + let _ = copy_move_thread.join(); + move_copied_files_to_dest(&temp_dest, final_dest)?; + } + Ok(()) +} + +/// 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 { + let mut temp_dest = std::path::PathBuf::from(dest); + let rand_str = random_name(); + temp_dest.push(rand_str); + std::fs::create_dir(&temp_dest)?; + Ok(temp_dest.display().to_string()) +} + +/// Move every file from `temp_dest` to `final_dest` and delete `temp_dest`. +/// If the `final_dest` already contains a file with the same name, +/// the moved file has enough `_` appended to its name to make it unique. +/// The now empty `temp_dest` is then deleted. +fn move_copied_files_to_dest(temp_dest: &str, final_dest: &str) -> Result<()> { + for file in std::fs::read_dir(temp_dest).context("Unreachable folder")? { + let file = file.context("File don't exist")?; + move_copied_file_to_dest(file, final_dest)?; + } + + std::fs::remove_dir(temp_dest)?; + + Ok(()) +} + +/// Move a single file to `final_dest`. +/// If the file already exists in `final_dest` the moved one has engough '_' appended +/// to its name to make it unique. +fn move_copied_file_to_dest(file: std::fs::DirEntry, final_dest: &str) -> Result<()> { + let mut file_name = file + .file_name() + .to_str() + .context("Couldn't cast the filename")? + .to_owned(); + let mut old_dest = std::path::PathBuf::from(final_dest); + old_dest.push(&file_name); + while old_dest.exists() { + old_dest.pop(); + file_name.push('_'); + old_dest.push(&file_name); + } + std::fs::rename(file.path(), old_dest)?; Ok(()) } +/// True iff `dest` contains any file with the same file name as one of `sources`. +fn check_existing_file(sources: &[PathBuf], dest: &str) -> Result { + 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); + if new_path.exists() { + return Ok(true); + } + } + Ok(false) +} + /// Send a notification to the desktop. /// Does nothing if "notify-send" isn't installed. fn notify(text: &str) -> Result<()> { diff --git a/src/utils.rs b/src/utils.rs index 23059915..23d59cb0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,7 @@ use std::path::Path; use anyhow::{Context, Result}; use copypasta::{ClipboardContext, ClipboardProvider}; +use rand::Rng; use sysinfo::{Disk, DiskExt}; use tuikit::term::Term; use users::{get_current_uid, get_user_by_uid}; @@ -149,3 +150,16 @@ pub fn open_in_current_neovim(path_str: &str, nvim_server: &str) { let command = &format!(":e {path_str}:set number:close"); let _ = nvim(nvim_server, command); } + +/// Creates a random string. +/// The string starts with `fm-` and contains 7 random alphanumeric characters. +pub fn random_name() -> String { + let mut rand_str = String::with_capacity(14); + rand_str.push_str("fm-"); + rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(7) + .for_each(|ch| rand_str.push(ch as char)); + rand_str.push_str(".txt"); + rand_str +} From a31218e74b086d302105c3df00417ece80191db2 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 14:35:46 +0200 Subject: [PATCH 012/168] Don't block main thread when copying with filename conflict --- src/copy_move.rs | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/copy_move.rs b/src/copy_move.rs index 9773db85..cae1f5ba 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -23,7 +23,7 @@ fn handle_progress_display( process_info: fs_extra::TransitProcess, ) -> fs_extra::dir::TransitProcessResult { pb.set_position(progress_bar_position(&process_info)); - let _ = term.print_with_attr(1, 1, &in_mem.contents(), CopyMove::attr()); + let _ = term.print_with_attr(2, 1, &in_mem.contents(), CopyMove::attr()); let _ = term.present(); fs_extra::dir::TransitProcessResult::ContinueOrAbort } @@ -137,9 +137,6 @@ 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. -/// -/// In this scenario we have to wait for the copy to end before moving back the file. -/// Sadly, the copy is now blocking. pub fn copy_move( copy_or_move: CopyMove, sources: Vec, @@ -151,15 +148,14 @@ pub fn copy_move( let handle_progress = move |process_info: fs_extra::TransitProcess| { handle_progress_display(&in_mem, &pb, &term, process_info) }; - let dest_has_existing_file = check_existing_file(&sources, dest)?; - let final_dest = dest; - let dest = if dest_has_existing_file { + let has_filename_conflict = check_filename_conflict(&sources, dest)?; + let final_dest = dest.to_owned(); + let dest = if has_filename_conflict { create_temporary_destination(dest)? } else { dest.to_owned() }; - let temp_dest = dest.clone(); - let copy_move_thread = thread::spawn(move || { + let _ = thread::spawn(move || { let transfered_bytes = match copy_or_move.copier()(&sources, &dest, &options, handle_progress) { Ok(transfered_bytes) => transfered_bytes, @@ -171,12 +167,16 @@ pub fn copy_move( let _ = c_term.send_event(Event::User(())); + if has_filename_conflict { + if let Err(e) = move_copied_files_to_dest(&dest, &final_dest) { + log::info!("Move temp files back error: {e:?}") + } + if let Err(e) = delete_temp_dest(&dest) { + log::info!("Delete temp dir error: {e:?}") + } + } copy_or_move.log_and_notify(human_size(transfered_bytes)); }); - if dest_has_existing_file { - let _ = copy_move_thread.join(); - move_copied_files_to_dest(&temp_dest, final_dest)?; - } Ok(()) } @@ -193,22 +193,26 @@ fn create_temporary_destination(dest: &str) -> Result { /// Move every file from `temp_dest` to `final_dest` and delete `temp_dest`. /// If the `final_dest` already contains a file with the same name, /// the moved file has enough `_` appended to its name to make it unique. -/// The now empty `temp_dest` is then deleted. fn move_copied_files_to_dest(temp_dest: &str, final_dest: &str) -> Result<()> { for file in std::fs::read_dir(temp_dest).context("Unreachable folder")? { let file = file.context("File don't exist")?; - move_copied_file_to_dest(file, final_dest)?; + move_single_file_to_dest(file, final_dest)?; } + Ok(()) +} +/// Delete the temporary folder used when copying files. +/// An error is returned if the temporary foldern isn't empty which +/// should always be the case. +fn delete_temp_dest(temp_dest: &str) -> Result<()> { std::fs::remove_dir(temp_dest)?; - Ok(()) } /// Move a single file to `final_dest`. /// If the file already exists in `final_dest` the moved one has engough '_' appended /// to its name to make it unique. -fn move_copied_file_to_dest(file: std::fs::DirEntry, final_dest: &str) -> Result<()> { +fn move_single_file_to_dest(file: std::fs::DirEntry, final_dest: &str) -> Result<()> { let mut file_name = file .file_name() .to_str() @@ -221,12 +225,18 @@ fn move_copied_file_to_dest(file: std::fs::DirEntry, final_dest: &str) -> Result file_name.push('_'); old_dest.push(&file_name); } + log::info!( + "moving back {file} to {old_dest} - existing ? {exist}", + file = file.path().display(), + old_dest = old_dest.display(), + exist = old_dest.exists() + ); std::fs::rename(file.path(), old_dest)?; Ok(()) } /// True iff `dest` contains any file with the same file name as one of `sources`. -fn check_existing_file(sources: &[PathBuf], dest: &str) -> Result { +fn check_filename_conflict(sources: &[PathBuf], dest: &str) -> Result { for file in sources { let filename = file .file_name() From f1d9159a09ebf7a921c72c0efd8d0650637478fd Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 14:36:09 +0200 Subject: [PATCH 013/168] dev --- development.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/development.md b/development.md index 5c58000d..0a269de8 100644 --- a/development.md +++ b/development.md @@ -569,7 +569,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] preview tar archive - [x] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file - [x] FIX: copy / move while existing file already exist use another name - It allows fm to create copies of file in the same directory. + + It allows fm to create copies of file in the same directory. ## TODO From 02eda6cbd51e3922416f0cc7abdd1a6594876160 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 15:14:59 +0200 Subject: [PATCH 014/168] Use a ConflictHandler struct to deal with conflicting filenames while copying/moving --- src/copy_move.rs | 205 ++++++++++++++++++++++++++++------------------- 1 file changed, 124 insertions(+), 81 deletions(-) diff --git a/src/copy_move.rs b/src/copy_move.rs index cae1f5ba..0d2427c2 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -148,109 +148,152 @@ pub fn copy_move( let handle_progress = move |process_info: fs_extra::TransitProcess| { handle_progress_display(&in_mem, &pb, &term, process_info) }; - let has_filename_conflict = check_filename_conflict(&sources, dest)?; - let final_dest = dest.to_owned(); - let dest = if has_filename_conflict { - create_temporary_destination(dest)? - } else { - dest.to_owned() - }; + let conflict_handler = ConflictHandler::new(dest, &sources)?; + let _ = thread::spawn(move || { - let transfered_bytes = - match copy_or_move.copier()(&sources, &dest, &options, handle_progress) { - Ok(transfered_bytes) => transfered_bytes, - Err(e) => { - info!("copy move couldn't copy: {e:?}"); - 0 - } - }; + let transfered_bytes = match copy_or_move.copier()( + &sources, + &conflict_handler.temp_dest, + &options, + handle_progress, + ) { + Ok(transfered_bytes) => transfered_bytes, + Err(e) => { + info!("copy move couldn't copy: {e:?}"); + 0 + } + }; let _ = c_term.send_event(Event::User(())); - if has_filename_conflict { - if let Err(e) = move_copied_files_to_dest(&dest, &final_dest) { - log::info!("Move temp files back error: {e:?}") - } - if let Err(e) = delete_temp_dest(&dest) { - log::info!("Delete temp dir error: {e:?}") - } + if let Err(e) = conflict_handler.solve_conflicts() { + info!("Conflict Handler error: {e}"); } + copy_or_move.log_and_notify(human_size(transfered_bytes)); }); Ok(()) } -/// 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 { - let mut temp_dest = std::path::PathBuf::from(dest); - let rand_str = random_name(); - temp_dest.push(rand_str); - std::fs::create_dir(&temp_dest)?; - Ok(temp_dest.display().to_string()) +/// Deal with conflicting filenames during a copy or a move. +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, + /// 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, } -/// Move every file from `temp_dest` to `final_dest` and delete `temp_dest`. -/// If the `final_dest` already contains a file with the same name, -/// the moved file has enough `_` appended to its name to make it unique. -fn move_copied_files_to_dest(temp_dest: &str, final_dest: &str) -> Result<()> { - for file in std::fs::read_dir(temp_dest).context("Unreachable folder")? { - let file = file.context("File don't exist")?; - move_single_file_to_dest(file, final_dest)?; +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 { + let has_conflict = ConflictHandler::check_filename_conflict(sources, dest)?; + let temp_dest: String; + let final_dest: Option; + if has_conflict { + temp_dest = Self::create_temporary_destination(dest)?; + final_dest = Some(dest.to_owned()); + } else { + temp_dest = dest.to_owned(); + final_dest = None; + }; + + Ok(Self { + temp_dest, + has_conflict, + final_dest, + }) } - Ok(()) -} -/// Delete the temporary folder used when copying files. -/// An error is returned if the temporary foldern isn't empty which -/// should always be the case. -fn delete_temp_dest(temp_dest: &str) -> Result<()> { - std::fs::remove_dir(temp_dest)?; - Ok(()) -} + /// 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 { + let mut temp_dest = std::path::PathBuf::from(dest); + let rand_str = random_name(); + temp_dest.push(rand_str); + std::fs::create_dir(&temp_dest)?; + Ok(temp_dest.display().to_string()) + } + + /// Move every file from `temp_dest` to `final_dest` and delete `temp_dest`. + /// If the `final_dest` already contains a file with the same name, + /// the moved file has enough `_` appended to its name to make it unique. + fn move_copied_files_to_dest(&self) -> Result<()> { + for file in std::fs::read_dir(&self.temp_dest).context("Unreachable folder")? { + let file = file.context("File don't exist")?; + self.move_single_file_to_dest(file)?; + } + Ok(()) + } -/// Move a single file to `final_dest`. -/// If the file already exists in `final_dest` the moved one has engough '_' appended -/// to its name to make it unique. -fn move_single_file_to_dest(file: std::fs::DirEntry, final_dest: &str) -> Result<()> { - let mut file_name = file - .file_name() - .to_str() - .context("Couldn't cast the filename")? - .to_owned(); - let mut old_dest = std::path::PathBuf::from(final_dest); - old_dest.push(&file_name); - while old_dest.exists() { - old_dest.pop(); - file_name.push('_'); - old_dest.push(&file_name); + /// Delete the temporary folder used when copying files. + /// An error is returned if the temporary foldern isn't empty which + /// should always be the case. + fn delete_temp_dest(&self) -> Result<()> { + std::fs::remove_dir(&self.temp_dest)?; + Ok(()) } - log::info!( - "moving back {file} to {old_dest} - existing ? {exist}", - file = file.path().display(), - old_dest = old_dest.display(), - exist = old_dest.exists() - ); - std::fs::rename(file.path(), old_dest)?; - Ok(()) -} -/// True iff `dest` contains any file with the same file name as one of `sources`. -fn check_filename_conflict(sources: &[PathBuf], dest: &str) -> Result { - for file in sources { - let filename = file + /// Move a single file to `final_dest`. + /// If the file already exists in `final_dest` the moved one has enough '_' appended + /// to its name to make it unique. + fn move_single_file_to_dest(&self, file: std::fs::DirEntry) -> Result<()> { + let mut file_name = file .file_name() - .context("Couldn't read filename")? .to_str() - .context("Couldn't cast filename into str")? + .context("Couldn't cast the filename")? .to_owned(); - let mut new_path = std::path::PathBuf::from(dest); - new_path.push(&filename); - if new_path.exists() { - return Ok(true); + + let mut final_dest = std::path::PathBuf::from( + self.final_dest + .clone() + .context("Final dest shouldn't be None")?, + ); + final_dest.push(&file_name); + while final_dest.exists() { + final_dest.pop(); + file_name.push('_'); + final_dest.push(&file_name); + } + std::fs::rename(file.path(), final_dest)?; + Ok(()) + } + + /// True iff `dest` contains any file with the same file name as one of `sources`. + fn check_filename_conflict(sources: &[PathBuf], dest: &str) -> Result { + 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); + if new_path.exists() { + return Ok(true); + } + } + Ok(false) + } + + /// Does nothing if there's no conflicting filenames during the copy/move. + /// Move back every file, appending '_' to their name until the name is unique. + /// Delete the temp folder. + fn solve_conflicts(&self) -> Result<()> { + if self.has_conflict { + self.move_copied_files_to_dest()?; + self.delete_temp_dest()?; } + Ok(()) } - Ok(false) } /// Send a notification to the desktop. From 29fcb756428d68c8e36945b143bc2048f5e547ae Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 15:15:39 +0200 Subject: [PATCH 015/168] def --- development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/development.md b/development.md index 0a269de8..938528a8 100644 --- a/development.md +++ b/development.md @@ -569,6 +569,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] preview tar archive - [x] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file - [x] FIX: copy / move while existing file already exist use another name +- [ ] Jump mode (display flagged files) should allow to delete / trash the flagged files It allows fm to create copies of file in the same directory. From 3703f2ef72b3fbf7222d129a04643e67b1d8b6ea Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 17:00:30 +0200 Subject: [PATCH 016/168] Jump mode (display flagged files) should allow to delete / trash the flagged files --- development.md | 2 +- src/event_dispatch.rs | 2 ++ src/mode.rs | 2 +- src/status.rs | 28 ++++++++++++++++++++++++++++ src/term_manager.rs | 2 +- 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/development.md b/development.md index 938528a8..23017b08 100644 --- a/development.md +++ b/development.md @@ -569,7 +569,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] preview tar archive - [x] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file - [x] FIX: copy / move while existing file already exist use another name -- [ ] Jump mode (display flagged files) should allow to delete / trash the flagged files +- [x] Jump mode (display flagged files) should allow to delete / trash the flagged files It allows fm to create copies of file in the same directory. diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index 9a0c94ee..eabc4ba8 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -97,6 +97,8 @@ impl EventDispatcher { Mode::Navigate(Navigate::EncryptedDrive) if c == 'u' => status.umount_encrypted_drive(), Mode::Navigate(Navigate::Jump) if c == ' ' => status.jump_remove_selected_flagged(), Mode::Navigate(Navigate::Jump) if c == 'u' => status.clear_flags_and_reset_view(), + Mode::Navigate(Navigate::Jump) if c == 'x' => status.delete_single_flagged(), + Mode::Navigate(Navigate::Jump) if c == 'X' => status.trash_single_flagged(), Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => status.marks_jump_char(c, colors), Mode::Navigate(Navigate::Marks(MarkAction::New)) => status.marks_new(c, colors), Mode::Preview | Mode::Navigate(_) => { diff --git a/src/mode.rs b/src/mode.rs index 0832ea13..429f5b77 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -206,7 +206,7 @@ impl fmt::Display for Mode { Mode::Navigate(Navigate::Marks(_)) => write!(f, "Marks jump:"), Mode::Navigate(Navigate::Jump) => write!( f, - "Flagged files: go to file -- remove flag -- remove all flags" + "Flagged files: go to file -- remove flag -- unflag all -- delete -- trash" ), Mode::Navigate(Navigate::History) => write!(f, "History :"), Mode::Navigate(Navigate::Shortcut) => write!(f, "Shortcut :"), diff --git a/src/status.rs b/src/status.rs index 1b50615c..5b588ccd 100644 --- a/src/status.rs +++ b/src/status.rs @@ -323,6 +323,34 @@ impl Status { Ok(()) } + /// Move the selected flagged file to the trash. + pub fn trash_single_flagged(&mut self) -> Result<()> { + let filepath = self + .flagged + .selected() + .context("no flagged file")? + .to_owned(); + self.flagged.remove_selected(); + self.trash.trash(&filepath)?; + Ok(()) + } + + /// Delete the selected flagged file. + pub fn delete_single_flagged(&mut self) -> Result<()> { + let filepath = self + .flagged + .selected() + .context("no flagged file")? + .to_owned(); + self.flagged.remove_selected(); + if filepath.is_dir() { + std::fs::remove_dir_all(filepath)?; + } else { + std::fs::remove_file(filepath)?; + } + Ok(()) + } + pub fn click( &mut self, row: u16, diff --git a/src/term_manager.rs b/src/term_manager.rs index cc225ef7..983441ea 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -699,7 +699,7 @@ impl<'a> WinSecondary<'a> { canvas.print( 1, 0, - "Enter: restore the selected file - x: delete permanently", + "Enter: restore the selected file -- x: delete permanently", )?; let content = &selectable.content(); if content.is_empty() { From 86529a2b2515717a9197856430b290bceed61714 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 21:43:09 +0200 Subject: [PATCH 017/168] binary preview also display parsed ASCII strings --- development.md | 3 +-- src/preview.rs | 41 +++++++++++++++++++++++++++++++++++++---- src/term_manager.rs | 3 ++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/development.md b/development.md index 23017b08..1e6a1908 100644 --- a/development.md +++ b/development.md @@ -570,8 +570,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Jump mode : 'Space' Toggle flag, 'u' remove all flags, 'Enter' jump to the file - [x] FIX: copy / move while existing file already exist use another name - [x] Jump mode (display flagged files) should allow to delete / trash the flagged files - - It allows fm to create copies of file in the same directory. +- [x] binary preview also display parsed ASCII strings ## TODO diff --git a/src/preview.rs b/src/preview.rs index a85b3e12..38cb3904 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -652,8 +652,10 @@ impl Line { Self { line } } - fn format(&self) -> String { - let mut s = "".to_owned(); + /// Format a line of 16 bytes as BigEndian, separated by spaces. + /// Every byte is zero filled if necessary. + fn format_hex(&self) -> String { + let mut s = String::new(); for (i, byte) in self.line.iter().enumerate() { let _ = write!(s, "{byte:02x}"); if i % 2 == 1 { @@ -663,10 +665,41 @@ impl Line { s } + /// Format a line of 16 bytes as an ASCII string. + /// Non ASCII printable bytes are replaced by dots. + fn format_as_ascii(&self) -> String { + let mut line_of_char = String::new(); + for byte in self.line.iter() { + if *byte < 31 || *byte > 126 { + line_of_char.push('.') + } else if let Some(c) = char::from_u32(*byte as u32) { + line_of_char.push(c); + } else { + line_of_char.push(' ') + } + } + line_of_char + } + /// Print line of pair of bytes in hexadecimal, 16 bytes long. + /// It uses BigEndian notation, regardless of platform usage. /// It tries to imitates the output of hexdump. - pub fn print(&self, canvas: &mut dyn tuikit::canvas::Canvas, row: usize, offset: usize) { - let _ = canvas.print(row, offset + 2, &self.format()); + pub fn print_bytes(&self, canvas: &mut dyn tuikit::canvas::Canvas, row: usize, offset: usize) { + let _ = canvas.print(row, offset + 2, &self.format_hex()); + } + + /// Print a line as an ASCII string + /// Non ASCII printable bytes are replaced by dots. + pub fn print_ascii(&self, canvas: &mut dyn tuikit::canvas::Canvas, row: usize, offset: usize) { + let _ = canvas.print_with_attr( + row, + offset + 2, + &self.format_as_ascii(), + Attr { + fg: Color::LIGHT_YELLOW, + ..Default::default() + }, + ); } } diff --git a/src/term_manager.rs b/src/term_manager.rs index 983441ea..bbd003ee 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -447,7 +447,8 @@ impl<'a> WinMain<'a> { &format_line_nr_hex(i + 1 + window.top, line_number_width_hex), Self::ATTR_LINE_NR, )?; - line.print(canvas, row, line_number_width_hex + 1); + line.print_bytes(canvas, row, line_number_width_hex + 1); + line.print_ascii(canvas, row, line_number_width_hex + 43); } } Preview::Ueberzug(image) => { From cc6e1e1d04d10c7f357177164bb1881ec6507f3a Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 15 Oct 2023 21:50:00 +0200 Subject: [PATCH 018/168] skim fuzzy find (ctrl-f) starts from current dir, not current selected file --- development.md | 1 + src/status.rs | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/development.md b/development.md index 1e6a1908..ad95d98b 100644 --- a/development.md +++ b/development.md @@ -571,6 +571,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: copy / move while existing file already exist use another name - [x] Jump mode (display flagged files) should allow to delete / trash the flagged files - [x] binary preview also display parsed ASCII strings +- [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file ## TODO diff --git a/src/status.rs b/src/status.rs index 5b588ccd..5a7ccdd7 100644 --- a/src/status.rs +++ b/src/status.rs @@ -211,11 +211,8 @@ impl Status { pub fn skim_output_to_tab(&mut self) -> Result<()> { let skim = self.skimer.search_filename( self.selected_non_mut() - .selected() - .context("skim: no selected file")? - .path - .to_str() - .context("skim error")?, + .path_content_str() + .context("Couldn't parse current directory")?, ); let Some(output) = skim.first() else { return Ok(()); From 97e1545171c4495443a6e1ecad899a52cdf6a138 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 16 Oct 2023 08:51:21 +0200 Subject: [PATCH 019/168] open/file pick flagged files if there are, use selected file instead --- development.md | 1 + src/event_exec.rs | 64 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/development.md b/development.md index ad95d98b..1f197889 100644 --- a/development.md +++ b/development.md @@ -572,6 +572,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Jump mode (display flagged files) should allow to delete / trash the flagged files - [x] binary preview also display parsed ASCII strings - [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file +- [x] open/file pick flagged files if there are, use selected file instead ## TODO diff --git a/src/event_exec.rs b/src/event_exec.rs index 6ffc781f..561a5d84 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -338,14 +338,37 @@ impl EventAction { Ok(()) } - /// Open a file with custom opener. + /// Open files with custom opener. + /// If there's no flagged file, the selected is chosen. + /// Otherwise, it will open the flagged files (not the flagged directories) with + /// their respective opener. + /// Directories aren't opened since it will lead nowhere, it would only replace the + /// current tab multiple times. It may change in the future. + /// Another strange behavior, it doesn't regroup files by opener : opening multiple + /// text file will create a process per file. + /// This may also change in the future. pub fn open_file(status: &mut Status) -> Result<()> { - let filepath = &status - .selected_non_mut() - .selected() - .context("event open file, Empty directory")? - .path - .clone(); + if status.flagged.is_empty() { + let filepath = &status + .selected_non_mut() + .selected() + .context("event open file, Empty directory")? + .path + .clone(); + Self::open_filepath(status, filepath)?; + } else { + let content = status.flagged.content().clone(); + for flagged in content.iter() { + if !flagged.is_dir() { + Self::open_filepath(status, flagged)? + } + } + } + Ok(()) + } + + /// Open a single file with its own opener + fn open_filepath(status: &mut Status, filepath: &path::Path) -> Result<()> { let opener = status.opener.open_info(filepath); if let Some(InternalVariant::NotSupported) = opener.internal_variant.as_ref() { status.mount_iso_drive()?; @@ -437,7 +460,9 @@ impl EventAction { tab.set_mode(Mode::Navigate(Navigate::Shortcut)); Ok(()) } - /// Send a signal to parent NVIM process, picking the selected file. + /// Send a signal to parent NVIM process, picking files. + /// If there's no flagged file, it picks the selected one. + /// otherwise, flagged files are picked. /// If no RPC server were provided at launch time - which may happen for /// reasons unknow to me - it does nothing. /// It requires the "nvim-send" application to be in $PATH. @@ -447,14 +472,21 @@ impl EventAction { return Ok(()); }; let nvim_server = status.nvim_server.clone(); - let tab = status.selected(); - let Some(fileinfo) = tab.selected() else { - return Ok(()); - }; - let Some(path_str) = fileinfo.path.to_str() else { - return Ok(()); - }; - open_in_current_neovim(path_str, &nvim_server); + if status.flagged.is_empty() { + let tab = status.selected(); + let Some(fileinfo) = tab.selected() else { + return Ok(()); + }; + let Some(path_str) = fileinfo.path.to_str() else { + return Ok(()); + }; + open_in_current_neovim(path_str, &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) + } + } Ok(()) } From f2568d1b7e6c932e0a7172d7bc24fb5ab3ddba92 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 16 Oct 2023 08:52:33 +0200 Subject: [PATCH 020/168] dev --- development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/development.md b/development.md index 1f197889..8a7c2bea 100644 --- a/development.md +++ b/development.md @@ -573,6 +573,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] binary preview also display parsed ASCII strings - [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file - [x] open/file pick flagged files if there are, use selected file instead +- [ ] regroup openers when opening multiple files. Requires a lot of change... ## TODO From 087bf81a88d58236b28072c53163d85479a2dfd7 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 16 Oct 2023 17:59:42 +0200 Subject: [PATCH 021/168] regroup openers when opening multiple files --- development.md | 2 +- src/event_exec.rs | 40 ++++------------------------------------ src/opener.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++- src/status.rs | 31 ++++++++++++++++++++++++++++++- 4 files changed, 80 insertions(+), 39 deletions(-) diff --git a/development.md b/development.md index 8a7c2bea..916450ec 100644 --- a/development.md +++ b/development.md @@ -573,7 +573,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] binary preview also display parsed ASCII strings - [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file - [x] open/file pick flagged files if there are, use selected file instead -- [ ] regroup openers when opening multiple files. Requires a lot of change... +- [x] regroup openers when opening multiple files. ## TODO diff --git a/src/event_exec.rs b/src/event_exec.rs index 561a5d84..fc294930 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -27,7 +27,7 @@ use crate::mocp::Mocp; use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation}; use crate::opener::{ execute_and_capture_output, execute_and_capture_output_without_check, execute_in_child, - execute_in_child_without_output_with_path, InternalVariant, + execute_in_child_without_output_with_path, }; use crate::password::{PasswordKind, PasswordUsage}; use crate::preview::ExtensionKind; @@ -344,45 +344,13 @@ impl EventAction { /// their respective opener. /// Directories aren't opened since it will lead nowhere, it would only replace the /// current tab multiple times. It may change in the future. - /// Another strange behavior, it doesn't regroup files by opener : opening multiple - /// text file will create a process per file. - /// This may also change in the future. + /// Only files which use an external opener are supported. pub fn open_file(status: &mut Status) -> Result<()> { if status.flagged.is_empty() { - let filepath = &status - .selected_non_mut() - .selected() - .context("event open file, Empty directory")? - .path - .clone(); - Self::open_filepath(status, filepath)?; + status.open_selected_file() } else { - let content = status.flagged.content().clone(); - for flagged in content.iter() { - if !flagged.is_dir() { - Self::open_filepath(status, flagged)? - } - } + status.open_flagged_files() } - Ok(()) - } - - /// Open a single file with its own opener - fn open_filepath(status: &mut Status, filepath: &path::Path) -> Result<()> { - let opener = status.opener.open_info(filepath); - if let Some(InternalVariant::NotSupported) = opener.internal_variant.as_ref() { - status.mount_iso_drive()?; - } else { - match status.opener.open(filepath) { - Ok(_) => (), - Err(e) => info!( - "Error opening {:?}: {:?}", - status.selected_non_mut().path_content.selected(), - e - ), - } - } - Ok(()) } /// Enter the rename mode. diff --git a/src/opener.rs b/src/opener.rs index a81d5d46..f6d5760f 100644 --- a/src/opener.rs +++ b/src/opener.rs @@ -229,7 +229,7 @@ pub enum InternalVariant { /// A way to open one kind of files. /// It's either an internal method or an external program. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct OpenerInfo { /// The external program used to open the file. pub external_program: Option, @@ -321,6 +321,46 @@ impl Opener { } } + /// Open multiple files. + /// Files sharing an opener are opened in a single command ie.: `nvim a.txt b.rs c.py`. + /// Only files opened with an external opener are supported. + pub fn open_multiple(&self, file_paths: &[PathBuf]) -> Result<()> { + let openers = self.regroup_openers(file_paths); + for (open_info, file_paths) in openers.iter() { + let file_paths_str = Self::collect_paths_as_str(file_paths); + let mut args: Vec<&str> = vec![open_info.external_program.as_ref().unwrap()]; + args.extend(&file_paths_str); + self.open_with_args(args, open_info.use_term)?; + } + Ok(()) + } + + /// Create an hashmap of openers -> [files]. + /// Each file in the collection share the same opener. + fn regroup_openers(&self, file_paths: &[PathBuf]) -> HashMap> { + let mut openers: HashMap> = HashMap::new(); + for file_path in file_paths.iter() { + let open_info = self.get_opener(extract_extension(file_path)); + if open_info.external_program.is_some() { + openers + .entry(open_info.to_owned()) + .and_modify(|files| files.push((*file_path).to_owned())) + .or_insert(vec![(*file_path).to_owned()]); + } + } + openers + } + + /// Convert a slice of `PathBuf` into their string representation. + /// Files which are directory are skipped. + fn collect_paths_as_str(file_paths: &[PathBuf]) -> Vec<&str> { + file_paths + .iter() + .filter(|fp| !fp.is_dir()) + .filter_map(|fp| fp.to_str()) + .collect() + } + /// Open a file, using the configured method. /// It may fail if the program changed after reading the config file. /// It may also fail if the program can't handle this kind of files. @@ -370,6 +410,10 @@ impl Opener { .to_str() .context("open with: can't parse filepath to str")?; let args = vec![program, strpath]; + self.open_with_args(args, use_term) + } + + fn open_with_args(&self, args: Vec<&str>, use_term: bool) -> Result { if use_term { self.open_terminal(args) } else { diff --git a/src/status.rs b/src/status.rs index 5a7ccdd7..aa48a50c 100644 --- a/src/status.rs +++ b/src/status.rs @@ -28,7 +28,7 @@ use crate::log::write_log_line; use crate::marks::Marks; use crate::mode::{InputSimple, Mode, NeedConfirmation}; use crate::mount_help::MountHelper; -use crate::opener::Opener; +use crate::opener::{InternalVariant, Opener}; use crate::password::{ drop_sudo_privileges, execute_sudo_command_with_password, reset_sudo_faillock, PasswordHolder, PasswordKind, PasswordUsage, @@ -508,6 +508,35 @@ impl Status { self.force_clear = true; } + /// Open a the selected file with its opener + pub fn open_selected_file(&mut self) -> Result<()> { + let filepath = &self + .selected_non_mut() + .selected() + .context("Empty directory")? + .path + .clone(); + let opener = self.opener.open_info(filepath); + if let Some(InternalVariant::NotSupported) = opener.internal_variant.as_ref() { + self.mount_iso_drive()?; + } else { + match self.opener.open(filepath) { + Ok(_) => (), + Err(e) => info!( + "Error opening {:?}: {:?}", + self.selected_non_mut().path_content.selected(), + e + ), + } + } + Ok(()) + } + + /// Open every flagged file with their respective opener. + pub fn open_flagged_files(&mut self) -> Result<()> { + self.opener.open_multiple(self.flagged.content()) + } + /// Mount the currently selected file (which should be an .iso file) to /// `/run/media/$CURRENT_USER/fm_iso` /// Ask a sudo password first if needed. It should always be the case. From 3f87c20106326eef1275fbeb41073245cabef862 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 16 Oct 2023 18:38:30 +0200 Subject: [PATCH 022/168] insert a space before files --- src/term_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/term_manager.rs b/src/term_manager.rs index bbd003ee..e3cfbd4b 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -359,7 +359,7 @@ impl<'a> WinMain<'a> { if status.flagged.contains(&file.path) { attr.effect |= Effect::BOLD | Effect::UNDERLINE; } - canvas.print_with_attr(row, 0, &string, attr)?; + canvas.print_with_attr(row, 1, &string, attr)?; } self.second_line(status, tab, canvas)?; if !self.attributes.has_window_below { From 5262cad1c98306d91640535d990ddd9d55144d7f Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 16 Oct 2023 18:41:53 +0200 Subject: [PATCH 023/168] remove useless fm.sh --- fm.sh | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100755 fm.sh diff --git a/fm.sh b/fm.sh deleted file mode 100755 index bb2858e0..00000000 --- a/fm.sh +++ /dev/null @@ -1,31 +0,0 @@ -# This file should be sourced - -# [unix.stackexchange](https://unix.stackexchange.com/a/450752/515688) - -# Depends on what you're going to do, another solution can be creating a function instead of a script. -# -# Example: -# -# Create a function in a file, let's say /home/aidin/my-cd-script: -# -# function my-cd() { -# cd /to/my/path -# } -# Then include it in your bashrc or zshrc file: -# -# # Somewhere in rc file -# source /home/aidin/my-cd-script -# Now you can use it like a command: -# $ my-cd - -# start fm and capture it's output -dest=$(/home/quentin/gclem/dev/rust/fm/target/debug/fm) - -# if fm returned an output... -if [[ ! -z $dest ]] -then - # cd to it - cd $dest; -fi - -return 0 From f7f79df8262ad9d9d154324d1fc4b395bdf071dd Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 16 Oct 2023 19:52:39 +0200 Subject: [PATCH 024/168] remove useless mut --- development.md | 1 + src/main.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/development.md b/development.md index 916450ec..bd861220 100644 --- a/development.md +++ b/development.md @@ -574,6 +574,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file - [x] open/file pick flagged files if there are, use selected file instead - [x] regroup openers when opening multiple files. +- [ ] refresh after a while ## TODO diff --git a/src/main.rs b/src/main.rs index 364ea1f3..713b42f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,7 +67,7 @@ impl FM { } /// Return the last event received by the terminal - fn poll_event(&mut self) -> Result { + fn poll_event(&self) -> Result { self.event_reader.poll_event() } From 2e1a2d2b71d9064fb1c73acf7460b3530060ea5c Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 16 Oct 2023 22:38:58 +0200 Subject: [PATCH 025/168] refresh every 10 seconds. WIP refresh every 10 seconds. If no file in current dir has changed, nothing happens. BUT... if something changed, the whole view is refreshed, reseting the selected file :( It still needs some amelioration --- development.md | 6 +++- src/action_map.rs | 2 ++ src/event_dispatch.rs | 2 +- src/event_exec.rs | 5 ++++ src/main.rs | 68 +++++++++++++++++++++++++++++++++++++++++-- src/status.rs | 7 +++++ src/tab.rs | 9 ++++++ 7 files changed, 94 insertions(+), 5 deletions(-) diff --git a/development.md b/development.md index bd861220..c637d2d0 100644 --- a/development.md +++ b/development.md @@ -574,7 +574,11 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file - [x] open/file pick flagged files if there are, use selected file instead - [x] regroup openers when opening multiple files. -- [ ] refresh after a while +- [ ] refresh every 10 seconds. If no file in current dir has changed, nothing happens. + + BUT... if something changed, the whole view is refreshed, reseting the selected file :( + + It still needs some amelioration ## TODO diff --git a/src/action_map.rs b/src/action_map.rs index cd829ee0..05f3a087 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -70,6 +70,7 @@ pub enum ActionMap { PageUp, Preview, Quit, + RefreshIfNeeded, RefreshView, RegexMatch, RemoteMount, @@ -166,6 +167,7 @@ impl ActionMap { ActionMap::PageUp => EventAction::page_up(current_tab, colors), ActionMap::Preview => EventAction::preview(status, colors), ActionMap::Quit => EventAction::quit(current_tab), + ActionMap::RefreshIfNeeded => EventAction::refresh_if_needed(status), ActionMap::RefreshView => EventAction::refreshview(status, colors), ActionMap::RegexMatch => EventAction::regex_match(current_tab), ActionMap::RemoteMount => EventAction::remote_mount(current_tab), diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index eabc4ba8..fe22932e 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -52,7 +52,7 @@ impl EventDispatcher { status.click(row, col, current_height, colors)?; LeaveMode::right_click(status, colors)?; } - Event::User(_) => status.refresh_status(colors)?, + Event::User(_) => status.refresh_if_needed()?, Event::Resize { width, height } => status.resize(width, height)?, Event::Key(Key::Char(c)) => self.char(status, c, colors)?, Event::Key(key) => self.key_matcher(status, key, colors)?, diff --git a/src/event_exec.rs b/src/event_exec.rs index fc294930..a1b8cb66 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -816,6 +816,11 @@ impl EventAction { status.refresh_status(colors) } + pub fn refresh_if_needed(status: &mut Status) -> Result<()> { + status.encrypted_devices.update()?; + status.refresh_if_needed() + } + /// Display mediainfo details of an image pub fn mediainfo(tab: &mut Tab) -> Result<()> { if !is_program_in_path(MEDIAINFO) { diff --git a/src/main.rs b/src/main.rs index 713b42f4..bf7d75dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,8 @@ +use std::borrow::BorrowMut; +use std::sync::mpsc::{self, TryRecvError}; use std::sync::Arc; +use std::thread; +use std::time::Duration; use anyhow::Result; use log::info; @@ -29,6 +33,10 @@ struct FM { /// Since most are generated the first time an extension is met, /// we need to hold this. colors: Colors, + /// Refresher is used to force a refresh when a file has been modified externally. + /// It send `Event::User(())` every 10 seconds. + /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. + refresher: Refresher, } impl FM { @@ -37,7 +45,8 @@ impl FM { /// an `EventDispatcher`, /// a `Status`, /// a `Display`, - /// some `Colors`. + /// some `Colors`, + /// a `Refresher`. /// It reads and drops the configuration from the config file. /// If the config can't be parsed, it exits with error code 1. fn start() -> Result { @@ -54,8 +63,15 @@ impl FM { }); let help = Help::from_keybindings(&config.binds, &opener)?.help; let display = Display::new(term.clone()); - let status = Status::new(display.height()?, term, help, opener, &config.settings)?; + let status = Status::new( + display.height()?, + term.clone(), + help, + opener, + &config.settings, + )?; let colors = config.colors.clone(); + let refresher = Refresher::spawn(term); drop(config); Ok(Self { event_reader, @@ -63,6 +79,7 @@ impl FM { status, display, colors, + refresher, }) } @@ -109,13 +126,58 @@ impl FM { fn quit(self) -> Result<()> { self.display.show_cursor()?; let final_path = self.status.selected_path_str().to_owned(); - drop(self); + self.refresher.quit()?; print_on_quit(&final_path); info!("fm is shutting down"); Ok(()) } } +/// Allows refresh if the current path has been modified externally. +struct Refresher { + /// Sender of messages, used to terminate the thread properly + tx: mpsc::Sender<()>, + /// Handle to the `term::Event` sender thread. + handle: thread::JoinHandle<()>, +} + +impl Refresher { + /// Spawn a constantly thread sending refresh event to the terminal. + /// It also listen to a receiver for quit messages. + fn spawn(mut term: Arc) -> Self { + let (tx, rx) = mpsc::channel(); + let mut counter: u8 = 0; + let handle = thread::spawn(move || loop { + match rx.try_recv() { + Ok(_) | Err(TryRecvError::Disconnected) => { + log::info!("terminating refresher"); + let _ = term.show_cursor(true); + return; + } + Err(TryRecvError::Empty) => {} + } + counter += 1; + thread::sleep(Duration::from_millis(100)); + if counter >= 10 * 10 { + counter = 0; + let event = tuikit::prelude::Event::User(()); + if term.borrow_mut().send_event(event).is_err() { + break; + } + } + }); + Self { tx, handle } + } + + /// Send a quit message to the receiver, signaling it to quit. + /// Join the refreshing thread which should be terminated. + fn quit(self) -> Result<()> { + self.tx.send(())?; + let _ = self.handle.join(); + Ok(()) + } +} + /// Exit the application and log a message. /// Used when the config can't be read. fn exit_wrong_config() -> ! { diff --git a/src/status.rs b/src/status.rs index aa48a50c..54a8dc5c 100644 --- a/src/status.rs +++ b/src/status.rs @@ -697,6 +697,13 @@ impl Status { Ok(()) } + /// if the current selected folder has been modified less than 10 seconds + /// ago, the current view is refreshed + pub fn refresh_if_needed(&mut self) -> Result<()> { + self.selected().refresh_if_needed()?; + Ok(()) + } + /// When a rezise event occurs, we may hide the second panel if the width /// isn't sufficiant to display enough information. /// We also need to know the new height of the terminal to start scrolling diff --git a/src/tab.rs b/src/tab.rs index fe3c8a69..d81dddf9 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -146,6 +146,15 @@ impl Tab { Ok(()) } + pub fn refresh_if_needed(&mut self) -> Result<()> { + if self.path_content.path.metadata()?.modified()?.elapsed()? + < std::time::Duration::new(10, 0) + { + self.refresh_view()?; + } + Ok(()) + } + /// Move to the currently selected directory. /// Fail silently if the current directory is empty or if the selected /// file isn't a directory. From 169b2671289beb96452d63a3c4d4feb831fba6db Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 17 Oct 2023 09:38:25 +0200 Subject: [PATCH 026/168] Fix refresh if needed. Should work most of the times --- development.md | 6 +----- src/action_map.rs | 2 +- src/event_dispatch.rs | 3 ++- src/event_exec.rs | 6 +++--- src/status.rs | 7 ------- src/tab.rs | 7 +++++++ 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/development.md b/development.md index c637d2d0..d4260ac0 100644 --- a/development.md +++ b/development.md @@ -574,11 +574,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file - [x] open/file pick flagged files if there are, use selected file instead - [x] regroup openers when opening multiple files. -- [ ] refresh every 10 seconds. If no file in current dir has changed, nothing happens. - - BUT... if something changed, the whole view is refreshed, reseting the selected file :( - - It still needs some amelioration +- [x] refresh every 10 seconds. If no file in current dir has changed, nothing happens. ## TODO diff --git a/src/action_map.rs b/src/action_map.rs index 05f3a087..92cc4f8c 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -167,7 +167,7 @@ impl ActionMap { ActionMap::PageUp => EventAction::page_up(current_tab, colors), ActionMap::Preview => EventAction::preview(status, colors), ActionMap::Quit => EventAction::quit(current_tab), - ActionMap::RefreshIfNeeded => EventAction::refresh_if_needed(status), + ActionMap::RefreshIfNeeded => EventAction::refresh_if_needed(current_tab), ActionMap::RefreshView => EventAction::refreshview(status, colors), ActionMap::RegexMatch => EventAction::regex_match(current_tab), ActionMap::RemoteMount => EventAction::remote_mount(current_tab), diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index fe22932e..c5a55e3a 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -52,7 +52,8 @@ impl EventDispatcher { status.click(row, col, current_height, colors)?; LeaveMode::right_click(status, colors)?; } - Event::User(_) => status.refresh_if_needed()?, + Event::User(_) => status.selected().refresh_if_needed()?, + Event::Resize { width, height } => status.resize(width, height)?, Event::Key(Key::Char(c)) => self.char(status, c, colors)?, Event::Key(key) => self.key_matcher(status, key, colors)?, diff --git a/src/event_exec.rs b/src/event_exec.rs index a1b8cb66..d2b761ce 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -816,9 +816,9 @@ impl EventAction { status.refresh_status(colors) } - pub fn refresh_if_needed(status: &mut Status) -> Result<()> { - status.encrypted_devices.update()?; - status.refresh_if_needed() + /// Refresh the view if files were modified in current directory. + pub fn refresh_if_needed(tab: &mut Tab) -> Result<()> { + tab.refresh_if_needed() } /// Display mediainfo details of an image diff --git a/src/status.rs b/src/status.rs index 54a8dc5c..aa48a50c 100644 --- a/src/status.rs +++ b/src/status.rs @@ -697,13 +697,6 @@ impl Status { Ok(()) } - /// if the current selected folder has been modified less than 10 seconds - /// ago, the current view is refreshed - pub fn refresh_if_needed(&mut self) -> Result<()> { - self.selected().refresh_if_needed()?; - Ok(()) - } - /// When a rezise event occurs, we may hide the second panel if the width /// isn't sufficiant to display enough information. /// We also need to know the new height of the terminal to start scrolling diff --git a/src/tab.rs b/src/tab.rs index d81dddf9..c7bee05f 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -146,11 +146,18 @@ impl Tab { Ok(()) } + /// Refresh the view if files were modified in current directory. + /// If a refresh occurs, tries to select the same file as before. + /// If it can't, the first file (`.`) is selected. + /// Does nothing otherwise pub fn refresh_if_needed(&mut self) -> Result<()> { if self.path_content.path.metadata()?.modified()?.elapsed()? < std::time::Duration::new(10, 0) { + let selected_path = self.selected().context("no selected file")?.path.clone(); self.refresh_view()?; + let index = self.path_content.select_file(&selected_path); + self.scroll_to(index); } Ok(()) } From a45aa8b30eb1de87f08fea5a474280cff4200246 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 17 Oct 2023 09:39:33 +0200 Subject: [PATCH 027/168] dev --- development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/development.md b/development.md index d4260ac0..b4efd0d9 100644 --- a/development.md +++ b/development.md @@ -575,6 +575,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] open/file pick flagged files if there are, use selected file instead - [x] regroup openers when opening multiple files. - [x] refresh every 10 seconds. If no file in current dir has changed, nothing happens. +- [ ] avoid multiple refreshs if we edit files ourself ## TODO From 21f816f414453c6add452f86bae9ac5c821111e4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 17 Oct 2023 12:25:08 +0200 Subject: [PATCH 028/168] refactor refresh if needed --- src/tab.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index c7bee05f..a0ec2f30 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -149,19 +149,31 @@ impl Tab { /// Refresh the view if files were modified in current directory. /// If a refresh occurs, tries to select the same file as before. /// If it can't, the first file (`.`) is selected. - /// Does nothing otherwise + /// Does nothing outside of normal mode. pub fn refresh_if_needed(&mut self) -> Result<()> { - if self.path_content.path.metadata()?.modified()?.elapsed()? - < std::time::Duration::new(10, 0) - { - let selected_path = self.selected().context("no selected file")?.path.clone(); - self.refresh_view()?; - let index = self.path_content.select_file(&selected_path); - self.scroll_to(index); + if let Mode::Normal = self.mode { + if self.is_last_modification_happend_less_than(10)? { + self.refresh_and_reselect_file()? + } } Ok(()) } + /// True iff the last modification of current folder happened less than `seconds` ago. + fn is_last_modification_happend_less_than(&self, seconds: u64) -> Result { + Ok(self.path_content.path.metadata()?.modified()?.elapsed()? + < std::time::Duration::new(seconds, 0)) + } + + /// Refresh the folder, reselect the last selected file, move the window to it. + fn refresh_and_reselect_file(&mut self) -> Result<()> { + let selected_path = self.selected().context("no selected file")?.path.clone(); + self.refresh_view()?; + let index = self.path_content.select_file(&selected_path); + self.scroll_to(index); + Ok(()) + } + /// Move to the currently selected directory. /// Fail silently if the current directory is empty or if the selected /// file isn't a directory. From a8715f9e7c7c5f6be9951d00925a1e3761f7e6cb Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 18 Oct 2023 06:56:28 +0200 Subject: [PATCH 029/168] second pane follow. Not clean, makes skim hang --- development.md | 1 + src/action_map.rs | 24 +++---- src/event_dispatch.rs | 6 +- src/event_exec.rs | 164 ++++++++++++++++++++++++++---------------- src/preview.rs | 4 ++ src/skim.rs | 8 ++- src/status.rs | 27 +++++-- src/term_manager.rs | 41 ++++++----- 8 files changed, 171 insertions(+), 104 deletions(-) diff --git a/development.md b/development.md index b4efd0d9..703f0efa 100644 --- a/development.md +++ b/development.md @@ -575,6 +575,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] open/file pick flagged files if there are, use selected file instead - [x] regroup openers when opening multiple files. - [x] refresh every 10 seconds. If no file in current dir has changed, nothing happens. +- [ ] scroll in preview second screen - [ ] avoid multiple refreshs if we edit files ourself ## TODO diff --git a/src/action_map.rs b/src/action_map.rs index 92cc4f8c..3fa3d1ac 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -127,20 +127,20 @@ impl ActionMap { ActionMap::Diff => EventAction::diff(status), ActionMap::DragNDrop => EventAction::drag_n_drop(status), ActionMap::EncryptedDrive => EventAction::encrypted_drive(status), - ActionMap::End => EventAction::end(current_tab, colors), + ActionMap::End => EventAction::end(status, colors), ActionMap::Enter => EventAction::enter(status, colors), ActionMap::Exec => EventAction::exec(current_tab), ActionMap::Filter => EventAction::filter(current_tab), ActionMap::FlagAll => EventAction::flag_all(status), - ActionMap::FuzzyFind => EventAction::fuzzyfind(status), + ActionMap::FuzzyFind => EventAction::fuzzyfind(status, colors), ActionMap::FuzzyFindHelp => EventAction::fuzzyfind_help(status), - ActionMap::FuzzyFindLine => EventAction::fuzzyfind_line(status), - ActionMap::GoRoot => EventAction::go_root(current_tab), - ActionMap::GoStart => EventAction::go_start(status), + ActionMap::FuzzyFindLine => EventAction::fuzzyfind_line(status, colors), + ActionMap::GoRoot => EventAction::go_root(status, colors), + ActionMap::GoStart => EventAction::go_start(status, colors), ActionMap::Goto => EventAction::goto(current_tab), ActionMap::Help => EventAction::help(status), ActionMap::History => EventAction::history(current_tab), - ActionMap::Home => EventAction::home(current_tab), + ActionMap::Home => EventAction::home(status, colors), ActionMap::Jump => EventAction::jump(status), ActionMap::KeyHome => EventAction::key_home(status, colors), ActionMap::Log => EventAction::log(current_tab), @@ -149,7 +149,7 @@ impl ActionMap { ActionMap::MediaInfo => EventAction::mediainfo(current_tab), ActionMap::MocpAddToPlayList => EventAction::mocp_add_to_playlist(current_tab), ActionMap::MocpClearPlaylist => EventAction::mocp_clear_playlist(), - ActionMap::MocpGoToSong => EventAction::mocp_go_to_song(current_tab), + ActionMap::MocpGoToSong => EventAction::mocp_go_to_song(status, colors), ActionMap::MocpNext => EventAction::mocp_next(), ActionMap::MocpPrevious => EventAction::mocp_previous(), ActionMap::MocpTogglePause => EventAction::mocp_toggle_pause(status), @@ -163,8 +163,8 @@ impl ActionMap { ActionMap::NvimSetAddress => EventAction::set_nvim_server(status), ActionMap::OpenConfig => EventAction::open_config(status), ActionMap::OpenFile => EventAction::open_file(status), - ActionMap::PageDown => EventAction::page_down(current_tab, colors), - ActionMap::PageUp => EventAction::page_up(current_tab, colors), + ActionMap::PageDown => EventAction::page_down(status, colors), + ActionMap::PageUp => EventAction::page_up(status, colors), ActionMap::Preview => EventAction::preview(status, colors), ActionMap::Quit => EventAction::quit(current_tab), ActionMap::RefreshIfNeeded => EventAction::refresh_if_needed(current_tab), @@ -175,7 +175,7 @@ impl ActionMap { ActionMap::ResetMode => EventAction::reset_mode(current_tab), ActionMap::ReverseFlags => EventAction::reverse_flags(status), ActionMap::Search => EventAction::search(current_tab), - ActionMap::SearchNext => EventAction::search_next(current_tab), + ActionMap::SearchNext => EventAction::search_next(status, colors), ActionMap::SetWallpaper => EventAction::set_wallpaper(current_tab), ActionMap::Shell => EventAction::shell(status), ActionMap::ShellCommand => EventAction::shell_command(current_tab), @@ -188,11 +188,11 @@ impl ActionMap { ActionMap::ToggleDualPane => EventAction::toggle_dualpane(status), ActionMap::ToggleFlag => EventAction::toggle_flag(status), ActionMap::ToggleHidden => EventAction::toggle_hidden(status, colors), - ActionMap::TogglePreviewSecond => EventAction::toggle_preview_second(status), + ActionMap::TogglePreviewSecond => EventAction::toggle_preview_second(status, colors), ActionMap::TrashEmpty => EventAction::trash_empty(status), ActionMap::TrashMoveFile => EventAction::trash_move_file(status), ActionMap::TrashOpen => EventAction::trash_open(status), - ActionMap::TrashRestoreFile => LeaveMode::trash(status), + ActionMap::TrashRestoreFile => LeaveMode::trash(status, colors), ActionMap::Tree => EventAction::tree(status, colors), ActionMap::TreeFold => EventAction::tree_fold(current_tab, colors), ActionMap::TreeFoldAll => EventAction::tree_fold_all(current_tab, colors), diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index c5a55e3a..8331a9fd 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -59,11 +59,7 @@ impl EventDispatcher { Event::Key(key) => self.key_matcher(status, key, colors)?, _ => (), }; - if status.dual_pane && status.preview_second { - status.force_preview(colors) - } else { - Ok(()) - } + Ok(()) } fn key_matcher(&self, status: &mut Status, key: Key, colors: &Colors) -> Result<()> { diff --git a/src/event_exec.rs b/src/event_exec.rs index d2b761ce..db48e950 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -491,21 +491,26 @@ impl EventAction { } /// Move to $HOME aka ~. - pub fn home(tab: &mut Tab) -> Result<()> { + pub fn home(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); let home_cow = shellexpand::tilde("~"); let home: &str = home_cow.borrow(); let path = std::fs::canonicalize(home)?; - tab.set_pathcontent(&path) + tab.set_pathcontent(&path)?; + status.update_second_pane_for_preview(colors) } - pub fn go_root(tab: &mut Tab) -> Result<()> { + pub fn go_root(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); let root_path = std::path::PathBuf::from("/"); - tab.set_pathcontent(&root_path) + tab.set_pathcontent(&root_path)?; + status.update_second_pane_for_preview(colors) } - pub fn go_start(status: &mut Status) -> Result<()> { + pub fn go_start(status: &mut Status, colors: &Colors) -> Result<()> { let start_folder = status.start_folder.clone(); - status.selected().set_pathcontent(&start_folder) + status.selected().set_pathcontent(&start_folder)?; + status.update_second_pane_for_preview(colors) } /// Executes a `dragon-drop` command on the selected file. @@ -527,7 +532,8 @@ impl EventAction { Ok(()) } - pub fn search_next(tab: &mut Tab) -> Result<()> { + pub fn search_next(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); match tab.mode { Mode::Tree => (), _ => { @@ -536,6 +542,7 @@ impl EventAction { }; let next_index = (tab.path_content.index + 1) % tab.path_content.content.len(); tab.search_from(&searched, next_index); + status.update_second_pane_for_preview(colors)?; } } Ok(()) @@ -561,7 +568,7 @@ impl EventAction { Mode::Tree => tab.tree_select_prev(colors)?, _ => (), }; - Ok(()) + status.update_second_pane_for_preview(colors) } /// Move down one row in modes allowing movements. @@ -583,7 +590,7 @@ impl EventAction { Mode::Tree => status.selected().select_next(colors)?, _ => (), }; - Ok(()) + status.update_second_pane_for_preview(colors) } /// Move to parent in normal mode, @@ -591,15 +598,15 @@ impl EventAction { pub fn move_left(status: &mut Status, colors: &Colors) -> Result<()> { let tab = status.selected(); match tab.mode { - Mode::Normal => tab.move_to_parent(), - Mode::Tree => tab.tree_select_parent(colors), + Mode::Normal => tab.move_to_parent()?, + Mode::Tree => tab.tree_select_parent(colors)?, Mode::InputSimple(_) | Mode::InputCompleted(_) => { tab.input.cursor_left(); - Ok(()) } - _ => Ok(()), + _ => (), } + status.update_second_pane_for_preview(colors) } /// Move to child if any or open a regular file in normal mode. @@ -608,7 +615,10 @@ impl EventAction { let tab: &mut Tab = status.selected(); match tab.mode { Mode::Normal => LeaveMode::open_file(status), - Mode::Tree => tab.select_first_child(colors), + Mode::Tree => { + tab.select_first_child(colors)?; + status.update_second_pane_for_preview(colors) + } Mode::InputSimple(_) | Mode::InputCompleted(_) => { tab.input.cursor_right(); Ok(()) @@ -651,34 +661,51 @@ impl EventAction { Mode::Tree => tab.tree_go_to_root(colors)?, _ => tab.input.cursor_start(), }; - Ok(()) + status.update_second_pane_for_preview(colors) } /// Move to the bottom in any mode. - pub fn end(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn end(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); match tab.mode { Mode::Normal | Mode::Preview => tab.go_bottom(), Mode::Tree => tab.tree_go_to_bottom_leaf(colors)?, _ => tab.input.cursor_end(), }; - Ok(()) + status.update_second_pane_for_preview(colors) } /// Move up 10 lines in normal mode and preview. - pub fn page_up(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn page_up(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); match tab.mode { - Mode::Normal | Mode::Preview => tab.page_up(), - Mode::Tree => tab.tree_page_up(colors)?, + Mode::Normal => { + tab.page_up(); + status.update_second_pane_for_preview(colors)?; + } + Mode::Preview => tab.page_up(), + Mode::Tree => { + tab.tree_page_up(colors)?; + status.update_second_pane_for_preview(colors)?; + } _ => (), }; Ok(()) } /// Move down 10 lines in normal & preview mode. - pub fn page_down(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn page_down(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); match tab.mode { - Mode::Normal | Mode::Preview => tab.page_down(), - Mode::Tree => tab.tree_page_down(colors)?, + Mode::Normal => { + tab.page_down(); + status.update_second_pane_for_preview(colors)?; + } + Mode::Preview => tab.page_down(), + Mode::Tree => { + tab.tree_page_down(colors)?; + status.update_second_pane_for_preview(colors)?; + } _ => (), }; Ok(()) @@ -715,12 +742,12 @@ impl EventAction { Mode::InputSimple(InputSimple::Remote) => LeaveMode::remote(status.selected())?, Mode::Navigate(Navigate::Jump) => { must_refresh = false; - LeaveMode::jump(status)? + LeaveMode::jump(status, colors)? } - Mode::Navigate(Navigate::History) => LeaveMode::history(status.selected())?, - Mode::Navigate(Navigate::Shortcut) => LeaveMode::shortcut(status.selected())?, - Mode::Navigate(Navigate::Trash) => LeaveMode::trash(status)?, - Mode::Navigate(Navigate::Bulk) => LeaveMode::bulk(status)?, + Mode::Navigate(Navigate::History) => LeaveMode::history(status, colors)?, + Mode::Navigate(Navigate::Shortcut) => LeaveMode::shortcut(status, colors)?, + Mode::Navigate(Navigate::Trash) => LeaveMode::trash(status, colors)?, + Mode::Navigate(Navigate::Bulk) => LeaveMode::bulk(status, colors)?, Mode::Navigate(Navigate::ShellMenu) => LeaveMode::shellmenu(status)?, Mode::Navigate(Navigate::CliInfo) => { must_refresh = false; @@ -729,14 +756,16 @@ impl EventAction { } Mode::Navigate(Navigate::EncryptedDrive) => (), Mode::Navigate(Navigate::Marks(MarkAction::New)) => LeaveMode::marks_update(status)?, - Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => LeaveMode::marks_jump(status)?, + Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => { + LeaveMode::marks_jump(status, colors)? + } Mode::Navigate(Navigate::Compress) => LeaveMode::compress(status)?, Mode::InputCompleted(InputCompleted::Exec) => LeaveMode::exec(status.selected())?, Mode::InputCompleted(InputCompleted::Search) => { must_refresh = false; - LeaveMode::search(status.selected(), colors)? + LeaveMode::search(status, colors)? } - Mode::InputCompleted(InputCompleted::Goto) => LeaveMode::goto(status.selected())?, + Mode::InputCompleted(InputCompleted::Goto) => LeaveMode::goto(status, colors)?, Mode::InputCompleted(InputCompleted::Command) => LeaveMode::command(status, colors)?, Mode::Normal => LeaveMode::open_file(status)?, Mode::Tree => LeaveMode::tree(status, colors)?, @@ -761,10 +790,10 @@ impl EventAction { pub fn tab(status: &mut Status) -> Result<()> { match status.selected().mode { Mode::InputCompleted(_) => { - let tab: &mut Tab = status.selected(); + let tab = status.selected(); tab.input.replace(tab.completion.current_proposition()) } - Mode::Normal | Mode::Tree => status.next(), + Mode::Normal | Mode::Tree | Mode::Preview => status.next(), _ => (), }; Ok(()) @@ -773,20 +802,22 @@ impl EventAction { /// Change tab in normal mode. pub fn backtab(status: &mut Status) -> Result<()> { match status.selected().mode { - Mode::Normal | Mode::Tree => status.prev(), + Mode::Normal | Mode::Tree | Mode::Preview => status.prev(), _ => (), }; Ok(()) } /// Start a fuzzy find with skim. - pub fn fuzzyfind(status: &mut Status) -> Result<()> { - status.skim_output_to_tab() + pub fn fuzzyfind(status: &mut Status, colors: &Colors) -> Result<()> { + status.skim_output_to_tab()?; + status.update_second_pane_for_preview(colors) } /// Start a fuzzy find for a specific line with skim. - pub fn fuzzyfind_line(status: &mut Status) -> Result<()> { - status.skim_line_output_to_tab() + pub fn fuzzyfind_line(status: &mut Status, colors: &Colors) -> Result<()> { + status.skim_line_output_to_tab()?; + status.update_second_pane_for_preview(colors) } /// Start a fuzzy find for a keybinding with skim. @@ -813,7 +844,8 @@ impl EventAction { /// Refresh the current view, reloading the files. Move the selection to top. pub fn refreshview(status: &mut Status, colors: &Colors) -> Result<()> { status.encrypted_devices.update()?; - status.refresh_status(colors) + status.refresh_status(colors)?; + status.update_second_pane_for_preview(colors) } /// Refresh the view if files were modified in current directory. @@ -1009,9 +1041,9 @@ impl EventAction { } /// Toggle the second pane between preview & normal mode (files). - pub fn toggle_preview_second(status: &mut Status) -> Result<()> { + pub fn toggle_preview_second(status: &mut Status, colors: &Colors) -> Result<()> { status.preview_second = !status.preview_second; - Ok(()) + status.update_second_pane_for_preview(colors) } /// Set the current selected file as wallpaper with `nitrogen`. @@ -1055,12 +1087,15 @@ impl EventAction { } /// Add a song or a folder to MOC playlist. Start it first... - pub fn mocp_go_to_song(tab: &mut Tab) -> Result<()> { + pub fn mocp_go_to_song(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); if !is_mocp_installed() { write_log_line("mocp isn't installed".to_owned()); return Ok(()); } - Mocp::go_to_song(tab) + Mocp::go_to_song(tab)?; + + status.update_second_pane_for_preview(colors) } /// Toggle play/pause on MOC. @@ -1157,11 +1192,11 @@ pub struct LeaveMode {} impl LeaveMode { /// Restore a file from the trash if possible. /// Parent folders are created if needed. - pub fn trash(status: &mut Status) -> Result<()> { + pub fn trash(status: &mut Status, colors: &Colors) -> Result<()> { status.trash.restore()?; status.selected().reset_mode(); status.selected().refresh_view()?; - Ok(()) + status.update_second_pane_for_preview(colors) } /// Open the file with configured opener or enter the directory. @@ -1178,7 +1213,7 @@ impl LeaveMode { } /// Jump to the current mark. - pub fn marks_jump(status: &mut Status) -> Result<()> { + pub fn marks_jump(status: &mut Status, colors: &Colors) -> Result<()> { let marks = status.marks.clone(); let tab = status.selected(); if let Some((_, path)) = marks.selected() { @@ -1187,7 +1222,7 @@ impl LeaveMode { tab.window.reset(tab.path_content.content.len()); tab.input.reset(); } - Ok(()) + status.update_second_pane_for_preview(colors) } /// Update the selected mark with the current path. @@ -1209,8 +1244,9 @@ impl LeaveMode { Ok(()) } - pub fn bulk(status: &mut Status) -> Result<()> { - status.bulk.execute_bulk(status) + pub fn bulk(status: &mut Status, colors: &Colors) -> Result<()> { + status.bulk.execute_bulk(status)?; + status.update_second_pane_for_preview(colors) } pub fn shellmenu(status: &mut Status) -> Result<()> { @@ -1258,7 +1294,7 @@ impl LeaveMode { /// Execute a jump to the selected flagged file. /// If the user selected a directory, we jump inside it. /// Otherwise, we jump to the parent and select the file. - pub fn jump(status: &mut Status) -> Result<()> { + pub fn jump(status: &mut Status, colors: &Colors) -> Result<()> { let Some(jump_target) = status.flagged.selected() else { return Ok(()); }; @@ -1270,8 +1306,7 @@ impl LeaveMode { status.selected().set_pathcontent(target_dir)?; let index = status.selected().path_content.select_file(&jump_target); status.selected().scroll_to(index); - - Ok(()) + status.update_second_pane_for_preview(colors) } /// Select the first file matching the typed regex in current dir. @@ -1389,7 +1424,8 @@ impl LeaveMode { /// ie. If you typed `"jpg"` before, it will move to the first file /// whose filename contains `"jpg"`. /// The current order of files is used. - pub fn search(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn search(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); let searched = tab.input.string(); tab.input.reset(); if searched.is_empty() { @@ -1408,21 +1444,21 @@ impl LeaveMode { tab.directory.tree.select_root() }; tab.directory.make_preview(colors); - Ok(()) } _ => { let next_index = tab.path_content.index; tab.search_from(&searched, next_index); - Ok(()) } - } + }; + status.update_second_pane_for_preview(colors) } /// Move to the folder typed by the user. /// The first completion proposition is used, `~` expansion is done. /// If no result were found, no cd is done and we go back to normal mode /// silently. - pub fn goto(tab: &mut Tab) -> Result<()> { + pub fn goto(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); if tab.completion.is_empty() { return Ok(()); } @@ -1432,12 +1468,13 @@ impl LeaveMode { tab.history.push(&path); tab.set_pathcontent(&path)?; tab.window.reset(tab.path_content.content.len()); - Ok(()) + status.update_second_pane_for_preview(colors) } /// Move to the selected shortcut. /// It may fail if the user has no permission to visit the path. - pub fn shortcut(tab: &mut Tab) -> Result<()> { + pub fn shortcut(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); tab.input.reset(); let path = tab .shortcut @@ -1446,12 +1483,14 @@ impl LeaveMode { .clone(); tab.history.push(&path); tab.set_pathcontent(&path)?; - tab.refresh_view() + tab.refresh_view()?; + status.update_second_pane_for_preview(colors) } /// Move back to a previously visited path. /// It may fail if the user has no permission to visit the path - pub fn history(tab: &mut Tab) -> Result<()> { + pub fn history(status: &mut Status, colors: &Colors) -> Result<()> { + let tab = status.selected(); tab.input.reset(); let path = tab .history @@ -1460,7 +1499,8 @@ impl LeaveMode { .clone(); tab.set_pathcontent(&path)?; tab.history.drop_queue(); - tab.refresh_view() + tab.refresh_view()?; + status.update_second_pane_for_preview(colors) } /// Execute the selected node if it's a file else enter the directory. diff --git a/src/preview.rs b/src/preview.rs index 38cb3904..f76e0b83 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -1022,6 +1022,7 @@ impl Directory { pub fn select_root(&mut self, colors: &Colors) -> Result<()> { self.tree.select_root(); (self.selected_index, self.content) = self.tree.into_navigable_content(colors); + self.update_tree_position_from_index(colors)?; Ok(()) } @@ -1090,6 +1091,7 @@ impl Directory { pub fn select_first_child(&mut self, colors: &Colors) -> Result<()> { self.tree.select_first_child()?; (self.selected_index, self.content) = self.tree.into_navigable_content(colors); + self.update_tree_position_from_index(colors)?; Ok(()) } @@ -1097,6 +1099,7 @@ impl Directory { pub fn select_parent(&mut self, colors: &Colors) -> Result<()> { self.tree.select_parent()?; (self.selected_index, self.content) = self.tree.into_navigable_content(colors); + self.update_tree_position_from_index(colors)?; Ok(()) } @@ -1104,6 +1107,7 @@ impl Directory { pub fn go_to_bottom_leaf(&mut self, colors: &Colors) -> Result<()> { self.tree.go_to_bottom_leaf()?; (self.selected_index, self.content) = self.tree.into_navigable_content(colors); + self.update_tree_position_from_index(colors)?; Ok(()) } diff --git a/src/skim.rs b/src/skim.rs index f35b0eb5..b78041e0 100644 --- a/src/skim.rs +++ b/src/skim.rs @@ -44,11 +44,11 @@ impl Skimer { /// Call skim on its term. /// Returns the file whose line match a pattern from current folder using ripgrep or grep. - pub fn search_line_in_file(&self) -> Vec> { + pub fn search_line_in_file(&self, path_str: &str) -> Vec> { self.skim .run_internal( None, - "".to_owned(), + path_str.to_owned(), None, Some(self.file_matcher.to_owned()), ) @@ -89,7 +89,9 @@ impl SkimItem for StringWrapper { fn pick_first_installed<'a>(commands: &'a [&'a str]) -> Option<&'a str> { for command in commands { - let Some(program) = command.split_whitespace().next() else { continue }; + let Some(program) = command.split_whitespace().next() else { + continue; + }; if is_program_in_path(program) { return Some(command); } diff --git a/src/status.rs b/src/status.rs index aa48a50c..d2cf265b 100644 --- a/src/status.rs +++ b/src/status.rs @@ -224,7 +224,11 @@ impl Status { /// It calls skim, reads its output, then update the tab content. /// The output is splited at `:` since we only care about the path, not the line number. pub fn skim_line_output_to_tab(&mut self) -> Result<()> { - let skim = self.skimer.search_line_in_file(); + let skim = self.skimer.search_line_in_file( + self.selected_non_mut() + .path_content_str() + .context("Couldn't parse current directory")?, + ); let Some(output) = skim.first() else { return Ok(()); }; @@ -474,14 +478,27 @@ impl Status { Ok(()) } - /// Force a preview on the second pane - pub fn force_preview(&mut self, colors: &Colors) -> Result<()> { - let fileinfo = &self.tabs[0] + /// Check if the second pane should display a preview and force it. + pub fn update_second_pane_for_preview(&mut self, colors: &Colors) -> Result<()> { + if self.index == 0 && self.preview_second { + self.set_second_pane_for_preview(colors)?; + }; + Ok(()) + } + + /// Force preview the selected file of the first pane in the second pane. + /// Doesn't check if it has do. + pub fn set_second_pane_for_preview(&mut self, colors: &Colors) -> Result<()> { + self.tabs[1].set_mode(Mode::Preview); + + let fileinfo = self.tabs[0] .selected() .context("force preview: No file to select")?; let users_cache = &self.tabs[0].path_content.users_cache; - self.tabs[0].preview = + self.tabs[1].preview = Preview::new(fileinfo, users_cache, self, colors).unwrap_or_default(); + + self.tabs[1].window.reset(self.tabs[1].preview.len()); Ok(()) } diff --git a/src/term_manager.rs b/src/term_manager.rs index e3cfbd4b..f9775c2e 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -146,7 +146,7 @@ impl<'a> Draw for WinMain<'a> { _ => self.files(self.status, self.tab, canvas), }, }?; - self.first_line(self.tab, self.disk_space, canvas)?; + self.first_line(self.disk_space, canvas)?; Ok(()) } } @@ -173,9 +173,8 @@ impl<'a> WinMain<'a> { } fn preview_as_second_pane(&self, canvas: &mut dyn Canvas) -> Result<()> { - let tab = &self.status.tabs[0]; - let (_, height) = canvas.size()?; - self.preview(tab, &tab.preview.window_for_second_pane(height), canvas)?; + let tab = &self.status.tabs[1]; + self.preview(tab, &tab.window, canvas)?; draw_colored_strings(0, 0, self.default_preview_first_line(tab), canvas, false)?; Ok(()) } @@ -187,11 +186,11 @@ impl<'a> WinMain<'a> { /// something else. /// Returns the result of the number of printed chars. /// The colors are reversed when the tab is selected. It gives a visual indication of where he is. - fn first_line(&self, tab: &Tab, disk_space: &str, canvas: &mut dyn Canvas) -> Result<()> { + fn first_line(&self, disk_space: &str, canvas: &mut dyn Canvas) -> Result<()> { draw_colored_strings( 0, 0, - self.create_first_row(tab, disk_space)?, + self.create_first_row(disk_space)?, canvas, self.attributes.is_selected, ) @@ -289,21 +288,29 @@ impl<'a> WinMain<'a> { ] } + fn pick_previewed_fileinfo(&self) -> Option<&FileInfo> { + if self.status.dual_pane && self.status.preview_second { + self.status.tabs[0].selected() + } else { + self.status.selected_non_mut().selected() + } + } + fn default_preview_first_line(&self, tab: &Tab) -> Vec { - match tab.path_content.selected() { - Some(fileinfo) => { - let mut strings = vec![" Preview ".to_owned()]; - if !tab.preview.is_empty() { - strings.push(format!(" {} / {} ", tab.window.bottom, tab.preview.len())); - }; - strings.push(format!(" {} ", fileinfo.path.to_string_lossy())); - strings - } - None => vec!["".to_owned()], + if let Some(fileinfo) = self.pick_previewed_fileinfo() { + let mut strings = vec![" Preview ".to_owned()]; + if !tab.preview.is_empty() { + strings.push(format!(" {} / {} ", tab.window.bottom, tab.preview.len())); + }; + strings.push(format!(" {} ", fileinfo.path.display())); + strings + } else { + vec!["".to_owned()] } } - fn create_first_row(&self, tab: &Tab, disk_space: &str) -> Result> { + fn create_first_row(&self, disk_space: &str) -> Result> { + let tab = self.status.selected_non_mut(); let first_row = match tab.mode { Mode::Normal | Mode::Tree => self.normal_first_row(disk_space)?, Mode::Preview => match &tab.preview { From 97f22cca47814eab61117b98b73c23dd1d258aa4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 18 Oct 2023 07:10:45 +0200 Subject: [PATCH 030/168] TEMP FIX: disable force refresh since it hangs skim --- src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index bf7d75dd..f0a2381a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,10 +33,10 @@ struct FM { /// Since most are generated the first time an extension is met, /// we need to hold this. colors: Colors, - /// Refresher is used to force a refresh when a file has been modified externally. - /// It send `Event::User(())` every 10 seconds. - /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. - refresher: Refresher, + // /// Refresher is used to force a refresh when a file has been modified externally. + // /// It send `Event::User(())` every 10 seconds. + // /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. + // refresher: Refresher, } impl FM { @@ -71,7 +71,7 @@ impl FM { &config.settings, )?; let colors = config.colors.clone(); - let refresher = Refresher::spawn(term); + // let refresher = Refresher::spawn(term); drop(config); Ok(Self { event_reader, @@ -79,7 +79,7 @@ impl FM { status, display, colors, - refresher, + // refresher, }) } @@ -126,7 +126,7 @@ impl FM { fn quit(self) -> Result<()> { self.display.show_cursor()?; let final_path = self.status.selected_path_str().to_owned(); - self.refresher.quit()?; + // self.refresher.quit()?; print_on_quit(&final_path); info!("fm is shutting down"); Ok(()) From 47121e84baeff6a6297c5f17e3e91fb4d2e8d22c Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 18 Oct 2023 21:04:21 +0200 Subject: [PATCH 031/168] FIX sending `Event::User(())` events from refresher hangs skim. Use `Event(Key::AltPageUp)` which is now reserved. --- Cargo.lock | 1093 +++++++++++++++++++++-------------------- development.md | 3 +- src/copy_move.rs | 4 +- src/event_dispatch.rs | 5 +- src/keybindings.rs | 19 +- src/main.rs | 20 +- 6 files changed, 590 insertions(+), 554 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78219cd1..fa1312a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -10,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "adobe-cmap-parser" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aaf5066d68c8ec9656cfd3a96bc9de83d4883f183d6c6b8d742e36a4819dda" +checksum = "7d3da9d617508ab8102c22f05bd772fc225ecb4fde431e38a45284e5c129a4bc" dependencies = [ "pom 1.1.0", ] @@ -30,9 +39,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -61,11 +70,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arc-swap" @@ -81,19 +138,19 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.66" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -113,6 +170,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base-x" version = "0.2.11" @@ -121,15 +193,15 @@ checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "beef" @@ -152,6 +224,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "block" version = "0.1.6" @@ -160,24 +238,35 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -191,9 +280,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bzip2" @@ -218,11 +307,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -263,7 +353,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim 0.8.0", "textwrap", "unicode-width", @@ -272,40 +362,43 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.6" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "is-terminal", - "once_cell", "strsim 0.10.0", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "clap_lex" -version = "0.3.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" -dependencies = [ - "os_str_bytes", -] +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clipboard-win" @@ -318,26 +411,22 @@ dependencies = [ ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -377,15 +466,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -415,9 +504,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -425,9 +514,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -436,14 +525,14 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.7.1", + "memoffset 0.9.0", "scopeguard", ] @@ -459,9 +548,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -476,50 +565,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cxx" -version = "1.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 1.0.109", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling" version = "0.10.2" @@ -565,6 +610,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -609,9 +663,9 @@ checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -667,9 +721,9 @@ checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading", ] @@ -680,17 +734,11 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" @@ -776,24 +824,19 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.2.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "errno" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "cc", "libc", + "windows-sys 0.48.0", ] [[package]] @@ -807,12 +850,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "filedescriptor" @@ -827,21 +867,21 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.45.0", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", ] [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -853,12 +893,12 @@ version = "0.1.23" dependencies = [ "anyhow", "chrono", - "clap 4.1.6", + "clap 4.4.6", "content_inspector", "copypasta", "flate2", "fs_extra", - "futures 0.3.27", + "futures 0.3.28", "gag", "indicatif", "lazy_static", @@ -871,7 +911,7 @@ dependencies = [ "regex", "rust-lzma", "sanitize-filename", - "serde_yaml 0.9.17", + "serde_yaml 0.9.25", "shellexpand", "skim-qkzk", "strfmt", @@ -909,9 +949,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -924,9 +964,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -934,15 +974,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -951,38 +991,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures 0.1.31", "futures-channel", @@ -1019,9 +1059,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1050,21 +1090,33 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + [[package]] name = "heck" version = "0.4.1" @@ -1082,18 +1134,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hmac" @@ -1104,6 +1147,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1112,26 +1164,25 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows-core", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -1142,21 +1193,32 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", ] [[package]] name = "indicatif" -version = "0.17.3" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", "unicode-width", @@ -1181,16 +1243,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-lifetimes" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys 0.45.0", -] - [[package]] name = "iovec" version = "0.1.4" @@ -1200,44 +1252,26 @@ dependencies = [ "libc", ] -[[package]] -name = "is-terminal" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1256,18 +1290,18 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1279,15 +1313,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - [[package]] name = "linked-hash-map" version = "0.5.3" @@ -1296,15 +1321,15 @@ checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1312,11 +1337,10 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ - "cfg-if", "serde", ] @@ -1354,27 +1378,20 @@ dependencies = [ [[package]] name = "lopdf" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49a0272112719d0037ab63d4bb67f73ba659e1e90bc38f235f163a457ac16f3" +checksum = "de0f69c40d6dbc68ebac4bf5aec3d9978e094e22e29fcabd045acd9cec74a9dc" dependencies = [ - "dtoa", "encoding", "flate2", - "itoa 0.4.8", + "itoa", "linked-hash-map", "log", - "lzw", - "pom 3.2.0", + "pom 3.3.0", "time 0.2.27", + "weezl", ] -[[package]] -name = "lzw" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1410,9 +1427,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -1425,23 +1442,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1450,7 +1466,7 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cfg-if", "libc", @@ -1463,7 +1479,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.6.5", @@ -1481,29 +1497,29 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.3", "libc", ] @@ -1520,7 +1536,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "970500bd39aa62735edf3080e75edd2b297fec806c8c4a0940180fa13a6d3f7a" dependencies = [ "async-trait", - "futures 0.3.27", + "futures 0.3.28", "log", "parity-tokio-ipc", "rmp", @@ -1558,11 +1574,20 @@ dependencies = [ "objc", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "onig" @@ -1570,7 +1595,7 @@ version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "once_cell", "onig_sys", @@ -1588,26 +1613,20 @@ dependencies = [ [[package]] name = "ordered-float" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "parity-tokio-ipc" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.27", + "futures 0.3.28", "libc", "log", "rand 0.7.3", @@ -1627,15 +1646,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.5", ] [[package]] @@ -1651,9 +1670,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pathdiff" @@ -1675,9 +1694,9 @@ dependencies = [ [[package]] name = "pdf-extract" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7875466ea3ecc4b763c4946993d5dcdf4f6e3a67e2f293e506a4a9ec551759" +checksum = "f0f21fc45e1b40af7e6c7ca32af35464c1ea7a92e5d2e1465d08c8389e033240" dependencies = [ "adobe-cmap-parser", "encoding", @@ -1697,9 +1716,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1709,22 +1728,22 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9469799ca90293a376f68f6fcb8f11990d9cff55602cfba0ba83893c973a7f46" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ "base64", - "indexmap", + "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", - "time 0.3.19", + "time 0.3.30", ] [[package]] @@ -1735,15 +1754,18 @@ checksum = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6" [[package]] name = "pom" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e2192780e9f8e282049ff9bffcaa28171e1cb0844f49ed5374e518ae6024ec" +checksum = "5c2d73a5fe10d458e77534589512104e5aa8ac480aa9ac30b74563274235cce4" +dependencies = [ + "bstr", +] [[package]] name = "portable-atomic" -version = "0.3.19" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] name = "postscript" @@ -1752,34 +1774,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78451badbdaebaf17f053fd9152b3ffb33b516104eacb45e7864aaa9c712f306" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "ppv-lite86" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-hack" @@ -1789,18 +1793,18 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] @@ -1873,7 +1877,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -1887,9 +1891,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -1897,14 +1901,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -1913,7 +1915,25 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", ] [[package]] @@ -1922,40 +1942,34 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", - "redox_syscall", + "getrandom 0.2.10", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.9.6" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", ] [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", ] -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - [[package]] name = "regex-syntax" version = "0.7.5" @@ -1963,19 +1977,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rmp" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" dependencies = [ "byteorder", "num-traits", @@ -1984,9 +1995,9 @@ dependencies = [ [[package]] name = "rmpv" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de8813b3a2f95c5138fe5925bfb8784175d88d6bff059ba8ce090aa891319754" +checksum = "2e0e0214a4a2b444ecce41a4025792fc31f77c7bb89c46d253953ea8c65701ec" dependencies = [ "num-traits", "rmp", @@ -2001,6 +2012,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2012,29 +2029,28 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.8" +version = "0.38.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safemem" @@ -2069,15 +2085,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" @@ -2096,9 +2106,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] @@ -2115,22 +2125,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ - "itoa 1.0.5", + "itoa", "ryu", "serde", ] @@ -2141,7 +2151,7 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ - "indexmap", + "indexmap 1.9.3", "ryu", "serde", "yaml-rust", @@ -2149,12 +2159,12 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.17" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap", - "itoa 1.0.5", + "indexmap 2.0.2", + "itoa", "ryu", "serde", "unsafe-libyaml", @@ -2171,9 +2181,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -2188,9 +2198,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2229,7 +2239,7 @@ checksum = "ca4c3bcd3a72b5925fa30dae1e8ac51f8cf02f94c3dcb63e7f6136023c403374" dependencies = [ "atty", "beef", - "bitflags", + "bitflags 1.3.2", "chrono", "clap 2.34.0", "crossbeam", @@ -2252,26 +2262,26 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smithay-client-toolkit" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" dependencies = [ - "bitflags", + "bitflags 1.3.2", "dlib", "lazy_static", "log", @@ -2295,12 +2305,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -2409,9 +2419,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -2426,9 +2436,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -2437,21 +2447,19 @@ dependencies = [ [[package]] name = "syntect" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c454c27d9d7d9a84c7803aaa3c50cd088d2906fe3c6e42da3209aa623576a8" +checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91" dependencies = [ "bincode", - "bitflags", + "bitflags 1.3.2", "flate2", "fnv", - "lazy_static", "once_cell", "onig", "plist", - "regex-syntax 0.6.28", + "regex-syntax 0.7.5", "serde", - "serde_derive", "serde_json", "thiserror", "walkdir", @@ -2460,9 +2468,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.29.0" +version = "0.29.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f1dc6930a439cc5d154221b5387d153f8183529b07c19aca24ea31e0a167e1" +checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" dependencies = [ "cfg-if", "core-foundation-sys", @@ -2475,9 +2483,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -2486,16 +2494,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -2511,9 +2518,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] @@ -2544,17 +2551,16 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "thread-id" -version = "4.0.0" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" +checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" dependencies = [ "libc", - "redox_syscall", "winapi", ] @@ -2585,21 +2591,23 @@ dependencies = [ [[package]] name = "time" -version = "0.3.19" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ - "itoa 1.0.5", + "deranged", + "itoa", + "powerfmt", "serde", "time-core", - "time-macros 0.2.7", + "time-macros 0.2.15", ] [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" @@ -2613,9 +2621,9 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.7" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a460aeb8de6dcb0f381e1ee05f1cd56fcf5a5f6eb8187ff3d8f0b11078d38b7c" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -2659,14 +2667,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ - "autocfg", - "bytes 1.4.0", + "backtrace", + "bytes 1.5.0", "libc", - "memchr", "mio", "num_cpus", "parking_lot", @@ -2674,7 +2681,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2690,13 +2697,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -2705,7 +2712,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "futures-core", "futures-io", "futures-sink", @@ -2720,7 +2727,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e19c6ab038babee3d50c8c12ff8b910bdb2196f62278776422f50390d8e53d8" dependencies = [ - "bitflags", + "bitflags 1.3.2", "lazy_static", "log", "nix 0.24.3", @@ -2748,9 +2755,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ueberzug" @@ -2760,9 +2767,9 @@ checksum = "655adff33a85280c95b4ab171144209748895a334f953f5a75030f3b20dfaaea" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2775,9 +2782,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unsafe-any-ors" @@ -2790,9 +2797,9 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "url-escape" @@ -2815,9 +2822,9 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vec_map" @@ -2837,10 +2844,10 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" dependencies = [ - "itoa 1.0.5", + "itoa", "log", "unicode-width", - "vte 0.11.0", + "vte 0.11.1", ] [[package]] @@ -2856,11 +2863,11 @@ dependencies = [ [[package]] name = "vte" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aae21c12ad2ec2d168c236f369c38ff332bc1134f7246350dca641437365045" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.4", "utf8parse", "vte_generate_state_changes", ] @@ -2877,12 +2884,11 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -2900,9 +2906,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2910,24 +2916,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2935,22 +2941,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wayland-client" @@ -2958,7 +2964,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" dependencies = [ - "bitflags", + "bitflags 1.3.2", "downcast-rs", "libc", "nix 0.24.3", @@ -2997,7 +3003,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "wayland-client", "wayland-commons", "wayland-scanner", @@ -3025,15 +3031,22 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -3054,9 +3067,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -3077,18 +3090,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", + "windows-targets 0.48.5", ] [[package]] @@ -3097,22 +3104,31 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.42.1", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -3132,9 +3148,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -3144,9 +3160,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -3156,9 +3172,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -3168,9 +3184,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -3180,9 +3196,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -3192,9 +3208,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -3204,9 +3220,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -3216,9 +3232,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "x11-clipboard" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0827f86aa910c4e73329a4f619deabe88ebb4b042370bf023c2d5d8b4eb54695" +checksum = "980b9aa9226c3b7de8e2adb11bf20124327c054e0e5812d2aac0b5b5a87e7464" dependencies = [ "x11rb", ] @@ -3247,9 +3263,9 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] @@ -3293,8 +3309,8 @@ dependencies = [ "flate2", "hmac", "pbkdf2", - "sha1 0.10.5", - "time 0.3.19", + "sha1 0.10.6", + "time 0.3.30", "zstd", ] @@ -3319,11 +3335,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.7+zstd.1.5.4" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/development.md b/development.md index 703f0efa..06033fdd 100644 --- a/development.md +++ b/development.md @@ -575,7 +575,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] open/file pick flagged files if there are, use selected file instead - [x] regroup openers when opening multiple files. - [x] refresh every 10 seconds. If no file in current dir has changed, nothing happens. -- [ ] scroll in preview second screen +- [x] scroll in preview second screen +- [x] FIX sending `Event::User(())` events from refresher hangs skim. Use `Event(Key::AltPageUp)` which is now reserved. - [ ] avoid multiple refreshs if we edit files ourself ## TODO diff --git a/src/copy_move.rs b/src/copy_move.rs index 0d2427c2..9d9ee2ac 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result}; use fs_extra; use indicatif::{InMemoryTerm, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle}; use log::info; -use tuikit::prelude::{Attr, Color, Effect, Event, Term}; +use tuikit::prelude::{Attr, Color, Effect, Event, Key, Term}; use crate::constant_strings_paths::NOTIFY_EXECUTABLE; use crate::fileinfo::human_size; @@ -164,7 +164,7 @@ pub fn copy_move( } }; - let _ = c_term.send_event(Event::User(())); + let _ = c_term.send_event(Event::Key(Key::AltPageUp)); if let Err(e) = conflict_handler.solve_conflicts() { info!("Conflict Handler error: {e}"); diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index 8331a9fd..196a8143 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -52,7 +52,10 @@ impl EventDispatcher { status.click(row, col, current_height, colors)?; LeaveMode::right_click(status, colors)?; } - Event::User(_) => status.selected().refresh_if_needed()?, + // reserved keybind which can't be bound to anything. + // using `Key::User(())` conflicts with skim internal which + // interpret this event as a signal(1) + Event::Key(Key::AltPageUp) => status.selected().refresh_if_needed()?, Event::Resize { width, height } => status.resize(width, height)?, Event::Key(Key::Char(c)) => self.char(status, c, colors)?, diff --git a/src/keybindings.rs b/src/keybindings.rs index 5562a513..c3c53be9 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -5,6 +5,7 @@ use std::string::ToString; use tuikit::prelude::{from_keyname, Key}; use crate::action_map::ActionMap; +use crate::constant_strings_paths::CONFIG_PATH; /// Holds an hashmap between keys and actions. #[derive(Clone, Debug)] @@ -150,24 +151,36 @@ impl Bindings { }; for yaml_key in mappings.keys() { let Some(key_string) = yaml_key.as_str() else { - log::info!("~/.config/fm/config.yaml: Keybinding {yaml_key:?} is unreadable"); + log::info!("{CONFIG_PATH}: Keybinding {yaml_key:?} is unreadable"); continue; }; let Some(keymap) = from_keyname(key_string) else { - log::info!("~/.config/fm/config.yaml: Keybinding {key_string} is unknown"); + log::info!("{CONFIG_PATH}: Keybinding {key_string} is unknown"); continue; }; + if self.keymap_is_reserved(&keymap) { + continue; + } let Some(action_str) = yaml[yaml_key].as_str() else { continue; }; let Ok(action) = ActionMap::from_str(action_str) else { - log::info!("~/.config/fm/config.yaml: Action {action_str} is unknown"); + log::info!("{CONFIG_PATH}: Action {action_str} is unknown"); continue; }; self.binds.insert(keymap, action); } } + /// List of keymap used internally which can't be bound to anything. + fn keymap_is_reserved(&self, keymap: &Key) -> bool { + match *keymap { + // used to send refresh requests. + Key::AltPageUp => true, + _ => false, + } + } + pub fn update_custom(&mut self, yaml: &serde_yaml::value::Value) { let Some(mappings) = yaml.as_mapping() else { return; diff --git a/src/main.rs b/src/main.rs index f0a2381a..738d3595 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,10 +33,10 @@ struct FM { /// Since most are generated the first time an extension is met, /// we need to hold this. colors: Colors, - // /// Refresher is used to force a refresh when a file has been modified externally. - // /// It send `Event::User(())` every 10 seconds. - // /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. - // refresher: Refresher, + /// Refresher is used to force a refresh when a file has been modified externally. + /// It send `Event::Key(Key::AltPageUp)` every 10 seconds. + /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. + refresher: Refresher, } impl FM { @@ -71,7 +71,7 @@ impl FM { &config.settings, )?; let colors = config.colors.clone(); - // let refresher = Refresher::spawn(term); + let refresher = Refresher::spawn(term); drop(config); Ok(Self { event_reader, @@ -79,7 +79,7 @@ impl FM { status, display, colors, - // refresher, + refresher, }) } @@ -126,7 +126,7 @@ impl FM { fn quit(self) -> Result<()> { self.display.show_cursor()?; let final_path = self.status.selected_path_str().to_owned(); - // self.refresher.quit()?; + self.refresher.quit()?; print_on_quit(&final_path); info!("fm is shutting down"); Ok(()) @@ -144,6 +144,10 @@ struct Refresher { impl Refresher { /// Spawn a constantly thread sending refresh event to the terminal. /// It also listen to a receiver for quit messages. + /// This will send periodically an `Key::AltPageUp` event to the terminal which requires a refresh. + /// This keybind is reserved and can't be bound to anything. + /// Using Event::User(()) conflicts with skim internal which interpret this + /// event as a signal(1) and hangs the terminal. fn spawn(mut term: Arc) -> Self { let (tx, rx) = mpsc::channel(); let mut counter: u8 = 0; @@ -160,7 +164,7 @@ impl Refresher { thread::sleep(Duration::from_millis(100)); if counter >= 10 * 10 { counter = 0; - let event = tuikit::prelude::Event::User(()); + let event = tuikit::prelude::Event::Key(tuikit::prelude::Key::AltPageUp); if term.borrow_mut().send_event(event).is_err() { break; } From b74d60dfe8b54d5ee7a236fb8a8646dfac1fdba1 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 18 Oct 2023 21:12:29 +0200 Subject: [PATCH 032/168] refresher documentation --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 738d3595..5283f418 100644 --- a/src/main.rs +++ b/src/main.rs @@ -142,10 +142,13 @@ struct Refresher { } impl Refresher { - /// Spawn a constantly thread sending refresh event to the terminal. + /// Spawn a thread which sends events to the terminal. + /// Those events are interpreted as refresh requests. /// It also listen to a receiver for quit messages. + /// /// This will send periodically an `Key::AltPageUp` event to the terminal which requires a refresh. /// This keybind is reserved and can't be bound to anything. + /// /// Using Event::User(()) conflicts with skim internal which interpret this /// event as a signal(1) and hangs the terminal. fn spawn(mut term: Arc) -> Self { From 707b3067caa45ffb1b7c187fbfaa6c128c68c70d Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 19 Oct 2023 21:09:16 +0200 Subject: [PATCH 033/168] more args : dual pane, preview second, display full, show hidden. Refactor tab & status instanciation --- development.md | 3 ++ readme.md | 10 +++---- src/args.rs | 18 +++++++++++- src/shell_menu.rs | 17 ++++++------ src/status.rs | 70 +++++++++++++++++++++++++++-------------------- src/tab.rs | 26 +++++++++++++----- 6 files changed, 94 insertions(+), 50 deletions(-) diff --git a/development.md b/development.md index 06033fdd..8ef539a8 100644 --- a/development.md +++ b/development.md @@ -577,6 +577,9 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] refresh every 10 seconds. If no file in current dir has changed, nothing happens. - [x] scroll in preview second screen - [x] FIX sending `Event::User(())` events from refresher hangs skim. Use `Event(Key::AltPageUp)` which is now reserved. +- [x] allow file selection from args : -f filename selects a file +- [x] more args : dual pane, preview second, display full, show hidden +- [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself ## TODO diff --git a/readme.md b/readme.md index 8fa797ad..974e060f 100644 --- a/readme.md +++ b/readme.md @@ -11,16 +11,16 @@ Written in rust. [docrs]: https://docs.rs/fm-tui/0.1.0 ``` -FM : dired like file manager - +A TUI file manager inspired by dired and ranger Usage: fm [OPTIONS] Options: -p, --path Starting path [default: .] -s, --server Nvim server [default: ] - -h, --help Print help information - -V, --version Print version information + -f, --file Selected file [default: .] + -h, --help Print help + -V, --version Print version ``` ## Platform @@ -100,7 +100,7 @@ Many ways to jump somewhere : - Toggle the tree view with t. Fold selected folder with z. Unfold every folder with Z, fold every folder with Alt+z. - Enter preview mode with Alt+P. Every file is previewed in the second pane. - Filter the view (by extension, name, directory only, all files) with F -- Find files with / (with completion: Tab, enter to search), +- Find files with / (with completion: Tab, enter to search), - flag files matching a regex with w ### Fuzzy finder diff --git a/src/args.rs b/src/args.rs index 4034d44a..f49a6977 100644 --- a/src/args.rs +++ b/src/args.rs @@ -4,11 +4,27 @@ use clap::Parser; #[clap(author, version, about)] /// FM : dired like file manager{n} pub struct Args { - /// Starting path + /// Starting path. directory or file #[arg(short, long, default_value_t = String::from("."))] pub path: String, /// Nvim server #[arg(short, long, default_value_t = String::from(""))] pub server: String, + + /// Dual pane ? default to true + #[arg(short, long, default_value_t = false)] + pub dual: bool, + + /// Display file metadata ? default to true + #[arg(short, long, default_value_t = false)] + pub metadata: bool, + + /// Use second pane as preview ? default to false + #[arg(short = 'P', long, default_value_t = false)] + pub preview: bool, + + /// Display all files (hidden) + #[arg(short, long, default_value_t = false)] + pub all: bool, } diff --git a/src/shell_menu.rs b/src/shell_menu.rs index fb54abd3..05fed469 100644 --- a/src/shell_menu.rs +++ b/src/shell_menu.rs @@ -21,6 +21,15 @@ impl Default for ShellMenu { } impl ShellMenu { + pub fn new(path: &str) -> Result { + let mut shell_menu = Self::default(); + let file = + std::fs::File::open(std::path::Path::new(&shellexpand::tilde(path).to_string()))?; + let yaml = serde_yaml::from_reader(file)?; + shell_menu.update_from_file(&yaml)?; + Ok(shell_menu) + } + fn update_from_file(&mut self, yaml: &serde_yaml::mapping::Mapping) -> Result<()> { for (key, mapping) in yaml.into_iter() { let Some(command) = key.as_str() else { @@ -86,11 +95,3 @@ impl ShellMenu { type SBool = (String, bool); impl_selectable_content!(SBool, ShellMenu); - -pub fn load_shell_menu(path: &str) -> Result { - let mut shell_menu = ShellMenu::default(); - let file = std::fs::File::open(std::path::Path::new(&shellexpand::tilde(path).to_string()))?; - let yaml = serde_yaml::from_reader(file)?; - shell_menu.update_from_file(&yaml)?; - Ok(shell_menu) -} diff --git a/src/status.rs b/src/status.rs index d2cf265b..4ae9f5a6 100644 --- a/src/status.rs +++ b/src/status.rs @@ -35,7 +35,7 @@ use crate::password::{ }; use crate::preview::{Directory, Preview}; use crate::selectable_content::SelectableContent; -use crate::shell_menu::{load_shell_menu, ShellMenu}; +use crate::shell_menu::ShellMenu; use crate::shell_parser::ShellCommandParser; use crate::skim::Skimer; use crate::tab::Tab; @@ -109,50 +109,52 @@ impl Status { settings: &Settings, ) -> Result { let args = Args::parse(); - let Ok(shell_menu) = load_shell_menu(TUIS_PATH) else { - eprintln!("Couldn't load the TUIs config file at {TUIS_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/tuis.yaml for an example"); - info!("Couldn't read tuis file at {TUIS_PATH}. Exiting"); - std::process::exit(1); + let preview_second = args.preview; + let start_folder = std::fs::canonicalize(std::path::PathBuf::from(&args.path))?; + let nvim_server = args.server.clone(); + let display_full = args.metadata || settings.full; + let dual_pane = (args.dual || settings.dual) && Self::display_wide_enough(&term)?; + + let Ok(shell_menu) = ShellMenu::new(TUIS_PATH) else { + Self::quit() }; let cli_info = CliInfo::default(); - let sys = System::new_with_specifics(RefreshKind::new().with_disks()); - let nvim_server = args.server.clone(); let encrypted_devices = CryptoDeviceOpener::default(); let trash = Trash::new()?; let compression = Compresser::default(); let force_clear = false; let bulk = Bulk::default(); - let start_folder = std::fs::canonicalize(std::path::PathBuf::from(&args.path))?; - let dual_pane = settings.dual && term.term_size()?.0 >= MIN_WIDTH_FOR_DUAL_PANE; + let iso_device = None; + let password_holder = PasswordHolder::default(); + let sudo_command = None; + let flagged = Flagged::default(); + let marks = Marks::read_from_config_file(); + let skimer = Skimer::new(term.clone()); + let index = 0; + // unsafe because of UsersCache::with_all_users let users_cache = unsafe { UsersCache::with_all_users() }; - let mut right_tab = Tab::new(args.clone(), height, users_cache)?; - right_tab - .shortcut - .extend_with_mount_points(&Self::disks_mounts(sys.disks())); - // unsafe because of UsersCache::with_all_users let users_cache2 = unsafe { UsersCache::with_all_users() }; - let mut left_tab = Tab::new(args, height, users_cache2)?; - left_tab - .shortcut - .extend_with_mount_points(&Self::disks_mounts(sys.disks())); - let iso_mounter = None; - let password_holder = PasswordHolder::default(); - let sudo_command = None; + let mount_points = Self::disks_mounts(sys.disks()); + + let tabs = [ + Tab::new(&args, height, users_cache, &mount_points)?, + Tab::new(&args, height, users_cache2, &mount_points)?, + ]; Ok(Self { - tabs: [left_tab, right_tab], - index: 0, - flagged: Flagged::default(), - marks: Marks::read_from_config_file(), - skimer: Skimer::new(term.clone()), + tabs, + index, + flagged, + marks, + skimer, term, dual_pane, - preview_second: false, + preview_second, system_info: sys, - display_full: settings.full, + display_full, opener, help, trash, @@ -162,7 +164,7 @@ impl Status { force_clear, bulk, shell_menu, - iso_device: iso_mounter, + iso_device, cli_info, start_folder, password_holder, @@ -170,6 +172,16 @@ impl Status { }) } + fn display_wide_enough(term: &Arc) -> Result { + Ok(term.term_size()?.0 >= MIN_WIDTH_FOR_DUAL_PANE) + } + + fn quit() -> ! { + eprintln!("Couldn't load the TUIs config file at {TUIS_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/tuis.yaml for an example"); + info!("Couldn't read tuis file at {TUIS_PATH}. Exiting"); + std::process::exit(1); + } + /// Select the other tab if two are displayed. Does nother otherwise. pub fn next(&mut self) { if !self.dual_pane { diff --git a/src/tab.rs b/src/tab.rs index a0ec2f30..d0c6cafd 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -58,24 +58,36 @@ pub struct Tab { impl Tab { /// Creates a new tab from args and height. - pub fn new(args: Args, height: usize, users_cache: UsersCache) -> Result { + pub fn new( + args: &Args, + height: usize, + users_cache: UsersCache, + mount_points: &[&path::Path], + ) -> Result { let path = std::fs::canonicalize(path::Path::new(&args.path))?; - let directory = Directory::empty(&path, &users_cache)?; + let start_dir = if path.is_dir() { + &path + } else { + path.parent().context("")? + }; + let directory = Directory::empty(start_dir, &users_cache)?; let filter = FilterKind::All; - let show_hidden = false; - let path_content = PathContent::new(&path, users_cache, &filter, show_hidden)?; - let show_hidden = false; + let show_hidden = args.all; + let mut path_content = PathContent::new(start_dir, users_cache, &filter, show_hidden)?; let mode = Mode::Normal; let previous_mode = Mode::Normal; - let window = ContentWindow::new(path_content.content.len(), height); + let mut window = ContentWindow::new(path_content.content.len(), height); let input = Input::default(); let completion = Completion::default(); let must_quit = false; let preview = Preview::Empty; let mut history = History::default(); history.push(&path); - let shortcut = Shortcut::new(&path); + let mut shortcut = Shortcut::new(&path); + shortcut.extend_with_mount_points(mount_points); let searched = None; + let index = path_content.select_file(&path); + window.scroll_to(index); Ok(Self { mode, previous_mode, From eef896c21734079eacbdb8f0e10f7a5055514eb9 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 20 Oct 2023 13:06:18 +0200 Subject: [PATCH 034/168] improve settings & args --- src/args.rs | 12 ++++++++---- src/config.rs | 5 +++++ src/status.rs | 20 +++++++++++++++++--- src/tab.rs | 5 +++-- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/args.rs b/src/args.rs index f49a6977..0c3bbc38 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,7 +2,7 @@ use clap::Parser; #[derive(Parser, Debug, Clone)] #[clap(author, version, about)] -/// FM : dired like file manager{n} +/// FM : dired / ranger like file manager{n} pub struct Args { /// Starting path. directory or file #[arg(short, long, default_value_t = String::from("."))] @@ -17,14 +17,18 @@ pub struct Args { pub dual: bool, /// Display file metadata ? default to true - #[arg(short, long, default_value_t = false)] - pub metadata: bool, + #[arg(group = "full", conflicts_with = "metadata", short = 'S', long)] + pub simple: Option, + + /// Display file metadata ? default to true + #[arg(group = "full", conflicts_with = "simple", short = 'M', long)] + pub metadata: Option, /// Use second pane as preview ? default to false #[arg(short = 'P', long, default_value_t = false)] pub preview: bool, /// Display all files (hidden) - #[arg(short, long, default_value_t = false)] + #[arg(short = 'A', long, default_value_t = false)] pub all: bool, } diff --git a/src/config.rs b/src/config.rs index 1e4a588d..00b8e41f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ use crate::utils::is_program_in_path; pub struct Settings { pub dual: bool, pub full: bool, + pub all: bool, } impl Settings { @@ -27,6 +28,10 @@ impl Settings { serde_yaml::Value::Bool(false) => self.full = false, _ => self.full = true, } + match yaml["all"] { + serde_yaml::Value::Bool(false) => self.all = false, + _ => self.all = true, + } } } diff --git a/src/status.rs b/src/status.rs index 4ae9f5a6..3dae017c 100644 --- a/src/status.rs +++ b/src/status.rs @@ -112,7 +112,7 @@ impl Status { let preview_second = args.preview; let start_folder = std::fs::canonicalize(std::path::PathBuf::from(&args.path))?; let nvim_server = args.server.clone(); - let display_full = args.metadata || settings.full; + let display_full = Self::parse_display_full(args.simple, args.metadata, settings.full); let dual_pane = (args.dual || settings.dual) && Self::display_wide_enough(&term)?; let Ok(shell_menu) = ShellMenu::new(TUIS_PATH) else { @@ -141,8 +141,8 @@ impl Status { let mount_points = Self::disks_mounts(sys.disks()); let tabs = [ - Tab::new(&args, height, users_cache, &mount_points)?, - Tab::new(&args, height, users_cache2, &mount_points)?, + Tab::new(&args, height, users_cache, settings, &mount_points)?, + Tab::new(&args, height, users_cache2, settings, &mount_points)?, ]; Ok(Self { tabs, @@ -182,6 +182,20 @@ impl Status { std::process::exit(1); } + fn parse_display_full( + simple_args: Option, + metadata_args: Option, + full_config: bool, + ) -> bool { + if let Some(simple_args) = simple_args { + return !simple_args; + } + if let Some(metadata_args) = metadata_args { + return metadata_args; + } + full_config + } + /// Select the other tab if two are displayed. Does nother otherwise. pub fn next(&mut self) { if !self.dual_pane { diff --git a/src/tab.rs b/src/tab.rs index d0c6cafd..b46f6533 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -6,7 +6,7 @@ use users::UsersCache; use crate::args::Args; use crate::completion::{Completion, InputCompleted}; -use crate::config::Colors; +use crate::config::{Colors, Settings}; use crate::content_window::ContentWindow; use crate::fileinfo::{FileInfo, FileKind, PathContent}; use crate::filter::FilterKind; @@ -62,6 +62,7 @@ impl Tab { args: &Args, height: usize, users_cache: UsersCache, + settings: &Settings, mount_points: &[&path::Path], ) -> Result { let path = std::fs::canonicalize(path::Path::new(&args.path))?; @@ -72,7 +73,7 @@ impl Tab { }; let directory = Directory::empty(start_dir, &users_cache)?; let filter = FilterKind::All; - let show_hidden = args.all; + let show_hidden = args.all || settings.all; let mut path_content = PathContent::new(start_dir, users_cache, &filter, show_hidden)?; let mode = Mode::Normal; let previous_mode = Mode::Normal; From 1ba111f594824bbeb2c7cb8cf759b1d0594bb29e Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 21 Oct 2023 13:51:48 +0200 Subject: [PATCH 035/168] refactor config & args --- config_files/fm/config.yaml | 34 +++++++++++++++++++++------------- src/args.rs | 14 +++++--------- src/config.rs | 5 +++++ src/status.rs | 26 ++++++++++++++++---------- 4 files changed, 47 insertions(+), 32 deletions(-) diff --git a/config_files/fm/config.yaml b/config_files/fm/config.yaml index a78026cd..8095b109 100644 --- a/config_files/fm/config.yaml +++ b/config_files/fm/config.yaml @@ -3,12 +3,13 @@ terminal: st colors: # white, black, red, green, blue, yellow, cyan, magenta # light_white, light_black, light_red, light_green, light_blue, light_yellow, light_cyan, light_magenta - directory: red block: yellow char: green + directory: red fifo: blue socket: cyan symlink: magenta +# keybindings keys: 'esc': ResetMode 'up': MoveUp @@ -51,6 +52,18 @@ keys: 'v': ReverseFlags 'w': RegexMatch 'x': DeleteFile + 'alt-d': DragNDrop + 'alt-e': ToggleDisplayFull + 'alt-f': ToggleDualPane + 'alt-g': Goto + 'alt-p': TogglePreviewSecond + 'ctrl-c': CopyFilename + 'ctrl-e': EncryptedDrive + 'ctrl-f': FuzzyFind + 'ctrl-g': Shortcut + 'ctrl-p': CopyFilepath + 'ctrl-q': ResetMode + 'ctrl-r': RefreshView 'shift-b': Bulk 'shift-f': Filter 'shift-g': End @@ -60,21 +73,16 @@ keys: 'shift-o': Sort 'shift-p': Preview 'shift-t': MediaInfo - 'ctrl-c': CopyFilename - 'ctrl-e': EncryptedDrive - 'ctrl-f': FuzzyFind - 'ctrl-g': Shortcut - 'ctrl-p': CopyFilepath - 'ctrl-q': ResetMode - 'ctrl-r': RefreshView - 'alt-d': DragNDrop - 'alt-e': ToggleDisplayFull - 'alt-g': Goto - 'alt-p': TogglePreviewSecond - 'alt-f': ToggleDualPane +# display settings settings: + # display all files ? + all: false + # use a second pane (if the terminal is wide enough) ? dual: true + # display metadata ? full: true + # use second pane for preview ? + preview: false # Custom command : # * Use an unused keybind diff --git a/src/args.rs b/src/args.rs index 0c3bbc38..209460dd 100644 --- a/src/args.rs +++ b/src/args.rs @@ -12,18 +12,14 @@ pub struct Args { #[arg(short, long, default_value_t = String::from(""))] pub server: String, - /// Dual pane ? default to true - #[arg(short, long, default_value_t = false)] - pub dual: bool, + /// Dual pane ? + #[arg(short = 'D', long)] + pub dual: Option, - /// Display file metadata ? default to true - #[arg(group = "full", conflicts_with = "metadata", short = 'S', long)] + /// Display files metadata ? + #[arg(short = 'S', long)] pub simple: Option, - /// Display file metadata ? default to true - #[arg(group = "full", conflicts_with = "simple", short = 'M', long)] - pub metadata: Option, - /// Use second pane as preview ? default to false #[arg(short = 'P', long, default_value_t = false)] pub preview: bool, diff --git a/src/config.rs b/src/config.rs index 00b8e41f..e986cd39 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,7 @@ pub struct Settings { pub dual: bool, pub full: bool, pub all: bool, + pub preview: bool, } impl Settings { @@ -32,6 +33,10 @@ impl Settings { serde_yaml::Value::Bool(false) => self.all = false, _ => self.all = true, } + match yaml["preview"] { + serde_yaml::Value::Bool(true) => self.all = true, + _ => self.all = false, + } } } diff --git a/src/status.rs b/src/status.rs index 3dae017c..bc699841 100644 --- a/src/status.rs +++ b/src/status.rs @@ -112,8 +112,8 @@ impl Status { let preview_second = args.preview; let start_folder = std::fs::canonicalize(std::path::PathBuf::from(&args.path))?; let nvim_server = args.server.clone(); - let display_full = Self::parse_display_full(args.simple, args.metadata, settings.full); - let dual_pane = (args.dual || settings.dual) && Self::display_wide_enough(&term)?; + let display_full = Self::parse_display_full(args.simple, settings.full); + let dual_pane = Self::parse_dual_pane(args.dual, settings.dual, &term)?; let Ok(shell_menu) = ShellMenu::new(TUIS_PATH) else { Self::quit() @@ -182,20 +182,26 @@ impl Status { std::process::exit(1); } - fn parse_display_full( - simple_args: Option, - metadata_args: Option, - full_config: bool, - ) -> bool { + fn parse_display_full(simple_args: Option, full_config: bool) -> bool { if let Some(simple_args) = simple_args { return !simple_args; } - if let Some(metadata_args) = metadata_args { - return metadata_args; - } full_config } + fn parse_dual_pane( + args_dual: Option, + dual_config: bool, + term: &Arc, + ) -> Result { + if !Self::display_wide_enough(term)? { + return Ok(false); + } + if let Some(args_dual) = args_dual { + return Ok(args_dual); + } + Ok(dual_config) + } /// Select the other tab if two are displayed. Does nother otherwise. pub fn next(&mut self) { if !self.dual_pane { From 1f8bbbe534eda26c68980589ca5b59e767045a5c Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 21 Oct 2023 22:07:32 +0200 Subject: [PATCH 036/168] history: when moving back select back the file we were at --- development.md | 1 + src/event_exec.rs | 27 +++++++++-------------- src/history.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- src/status.rs | 2 -- src/tab.rs | 40 ++++++++++++++++++++++++--------- src/term_manager.rs | 24 +++++++++++++++++++- src/visited.rs | 45 ------------------------------------- 8 files changed, 119 insertions(+), 76 deletions(-) create mode 100644 src/history.rs delete mode 100644 src/visited.rs diff --git a/development.md b/development.md index 8ef539a8..1cc17301 100644 --- a/development.md +++ b/development.md @@ -579,6 +579,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX sending `Event::User(())` events from refresher hangs skim. Use `Event(Key::AltPageUp)` which is now reserved. - [x] allow file selection from args : -f filename selects a file - [x] more args : dual pane, preview second, display full, show hidden +- [x] history: when moving back select back the file we were at - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/event_exec.rs b/src/event_exec.rs index db48e950..91d1fce0 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -479,15 +479,7 @@ impl EventAction { return Ok(()); } let tab = status.selected(); - tab.history.content.pop(); - let last_index = tab.history.len() - 1; - let last = tab.history.content[last_index].clone(); - tab.set_pathcontent(&last)?; - if let Mode::Tree = tab.mode { - tab.make_tree(colors)? - } - - Ok(()) + tab.back(colors) } /// Move to $HOME aka ~. @@ -598,7 +590,7 @@ impl EventAction { pub fn move_left(status: &mut Status, colors: &Colors) -> Result<()> { let tab = status.selected(); match tab.mode { - Mode::Normal => tab.move_to_parent()?, + Mode::Normal => tab.move_to_parent(colors)?, Mode::Tree => tab.tree_select_parent(colors)?, Mode::InputSimple(_) | Mode::InputCompleted(_) => { tab.input.cursor_left(); @@ -744,7 +736,10 @@ impl EventAction { must_refresh = false; LeaveMode::jump(status, colors)? } - Mode::Navigate(Navigate::History) => LeaveMode::history(status, colors)?, + Mode::Navigate(Navigate::History) => { + must_refresh = false; + LeaveMode::history(status, colors)? + } Mode::Navigate(Navigate::Shortcut) => LeaveMode::shortcut(status, colors)?, Mode::Navigate(Navigate::Trash) => LeaveMode::trash(status, colors)?, Mode::Navigate(Navigate::Bulk) => LeaveMode::bulk(status, colors)?, @@ -1217,7 +1212,6 @@ impl LeaveMode { let marks = status.marks.clone(); let tab = status.selected(); if let Some((_, path)) = marks.selected() { - tab.history.push(path); tab.set_pathcontent(path)?; tab.window.reset(tab.path_content.content.len()); tab.input.reset(); @@ -1465,7 +1459,6 @@ impl LeaveMode { let completed = tab.completion.current_proposition(); let path = string_to_path(completed)?; tab.input.reset(); - tab.history.push(&path); tab.set_pathcontent(&path)?; tab.window.reset(tab.path_content.content.len()); status.update_second_pane_for_preview(colors) @@ -1481,7 +1474,6 @@ impl LeaveMode { .selected() .context("exec shortcut: empty shortcuts")? .clone(); - tab.history.push(&path); tab.set_pathcontent(&path)?; tab.refresh_view()?; status.update_second_pane_for_preview(colors) @@ -1491,15 +1483,16 @@ impl LeaveMode { /// It may fail if the user has no permission to visit the path pub fn history(status: &mut Status, colors: &Colors) -> Result<()> { let tab = status.selected(); - tab.input.reset(); - let path = tab + let (path, file) = tab .history .selected() .context("exec history: path unreachable")? .clone(); tab.set_pathcontent(&path)?; tab.history.drop_queue(); - tab.refresh_view()?; + let index = tab.path_content.select_file(&file); + tab.scroll_to(index); + log::info!("leave history {path:?} {file:?} {index}"); status.update_second_pane_for_preview(colors) } diff --git a/src/history.rs b/src/history.rs new file mode 100644 index 00000000..9ebdcc43 --- /dev/null +++ b/src/history.rs @@ -0,0 +1,54 @@ +use std::path::{Path, PathBuf}; + +use crate::impl_selectable_content; + +type DoublePB = (PathBuf, PathBuf); + +/// A stack of visited paths. +/// We save the last folder and the selected file every time a `PatchContent` is updated. +/// We also ensure not to save the same pair multiple times. +#[derive(Default, Clone)] +pub struct History { + pub content: Vec, + pub index: usize, +} + +impl History { + /// Add a new path and a selected file in the stack, without duplicates, and select the last + /// one. + pub fn push(&mut self, path: &Path, file: &Path) { + let pair = (path.to_owned(), file.to_owned()); + if !self.content.contains(&pair) { + self.content.push(pair); + self.index = self.len() - 1 + } + } + + /// Drop the last visited paths from the stack, after the selected one. + /// Used to go back a few steps in time. + pub fn drop_queue(&mut self) { + if self.is_empty() { + return; + } + let final_length = self.len() - self.index + 1; + self.content.truncate(final_length); + if self.is_empty() { + self.index = 0 + } else { + self.index = self.len() - 1 + } + } + + /// True iff the last element of the stack has the same + /// path as the one given. + /// Doesn't check the associated file. + /// false if the stack is empty. + pub fn is_this_the_last(&self, path: &Path) -> bool { + if self.is_empty() { + return false; + } + self.content[self.len() - 1].0 == path + } +} + +impl_selectable_content!(DoublePB, History); diff --git a/src/lib.rs b/src/lib.rs index 3e33b705..c56c2ab7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ pub mod filter; pub mod flagged; pub mod git; pub mod help; +pub mod history; pub mod input; pub mod iso; pub mod keybindings; @@ -42,4 +43,3 @@ pub mod term_manager; pub mod trash; pub mod tree; pub mod utils; -pub mod visited; diff --git a/src/status.rs b/src/status.rs index bc699841..a1dd734b 100644 --- a/src/status.rs +++ b/src/status.rs @@ -673,7 +673,6 @@ impl Status { }; let tab = self.selected(); let path = std::path::PathBuf::from(mount_point); - tab.history.push(&path); tab.set_pathcontent(&path)?; tab.refresh_view() } @@ -728,7 +727,6 @@ impl Status { pub fn marks_jump_char(&mut self, c: char, colors: &Colors) -> Result<()> { if let Some(path) = self.marks.get(c) { self.selected().set_pathcontent(&path)?; - self.selected().history.push(&path); } self.selected().refresh_view()?; self.selected().reset_mode(); diff --git a/src/tab.rs b/src/tab.rs index b46f6533..1a906a9c 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -10,6 +10,7 @@ use crate::config::{Colors, Settings}; use crate::content_window::ContentWindow; use crate::fileinfo::{FileInfo, FileKind, PathContent}; use crate::filter::FilterKind; +use crate::history::History; use crate::input::Input; use crate::mode::Mode; use crate::opener::execute_in_child; @@ -17,7 +18,6 @@ use crate::preview::{Directory, Preview}; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; use crate::utils::{row_to_window_index, set_clipboard}; -use crate::visited::History; /// Holds every thing about the current tab of the application. /// Most of the mutation is done externally. @@ -44,8 +44,6 @@ pub struct Tab { /// Lines of the previewed files. /// Empty if not in preview mode. pub preview: Preview, - /// Visited directories - pub history: History, /// Predefined shortcuts pub shortcut: Shortcut, /// Last searched string @@ -54,6 +52,8 @@ pub struct Tab { pub directory: Directory, /// The filter use before displaying files pub filter: FilterKind, + /// Visited directories + pub history: History, } impl Tab { @@ -82,8 +82,7 @@ impl Tab { let completion = Completion::default(); let must_quit = false; let preview = Preview::Empty; - let mut history = History::default(); - history.push(&path); + let history = History::default(); let mut shortcut = Shortcut::new(&path); shortcut.extend_with_mount_points(mount_points); let searched = None; @@ -99,12 +98,12 @@ impl Tab { completion, must_quit, preview, - history, shortcut, searched, directory, filter, show_hidden, + history, }) } @@ -197,7 +196,6 @@ impl Tab { .context("Empty directory")? .path .clone(); - self.history.push(childpath); self.set_pathcontent(childpath)?; self.window.reset(self.path_content.content.len()); self.input.cursor_start(); @@ -226,7 +224,10 @@ impl Tab { /// Reset the window. /// Add the last path to the history of visited paths. pub fn set_pathcontent(&mut self, path: &path::Path) -> Result<()> { - self.history.push(path); + self.history.push( + &self.path_content.path, + &self.path_content.selected().context("")?.path, + ); self.path_content .change_directory(path, &self.filter, self.show_hidden)?; self.window.reset(self.path_content.content.len()); @@ -310,20 +311,39 @@ impl Tab { } /// Move to the parent of current path - pub fn move_to_parent(&mut self) -> Result<()> { + pub fn move_to_parent(&mut self, colors: &Colors) -> Result<()> { let path = self.path_content.path.clone(); let Some(parent) = path.parent() else { return Ok(()); }; + if self.history.is_this_the_last(parent) { + self.back(colors)?; + return Ok(()); + } self.set_pathcontent(parent) } + pub fn back(&mut self, colors: &Colors) -> Result<()> { + let Some((path, file)) = self.history.content.pop() else { + return Ok(()); + }; + self.set_pathcontent(&path)?; + let index = self.path_content.select_file(&file); + self.scroll_to(index); + self.history.content.pop(); + if let Mode::Tree = self.mode { + self.make_tree(colors)? + } + + Ok(()) + } + /// Select the parent of current node. /// If we were at the root node, move to the parent and make a new tree. pub fn tree_select_parent(&mut self, colors: &Colors) -> Result<()> { self.directory.unselect_children(); if self.directory.tree.position.len() <= 1 { - self.move_to_parent()?; + self.move_to_parent(colors)?; self.make_tree(colors)? } self.directory.select_parent(colors) diff --git a/src/term_manager.rs b/src/term_manager.rs index f9775c2e..62b89356 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -650,7 +650,7 @@ impl<'a> WinSecondary<'a> { Navigate::CliInfo => self.cli_info(self.status, canvas), Navigate::Compress => self.compress(canvas, &self.status.compression), Navigate::EncryptedDrive => self.encrypt(self.status, self.tab, canvas), - Navigate::History => self.destination(canvas, &self.tab.history), + Navigate::History => self.history(canvas, &self.tab.history), Navigate::Jump => self.destination(canvas, &self.status.flagged), Navigate::Marks(_) => self.marks(self.status, canvas), Navigate::ShellMenu => self.shell_menu(self.status, canvas), @@ -682,6 +682,28 @@ impl<'a> WinSecondary<'a> { Ok(()) } + fn history( + &self, + canvas: &mut dyn Canvas, + selectable: &impl SelectableContent<(PathBuf, PathBuf)>, + ) -> Result<()> { + canvas.print(0, 0, "Go to...")?; + let content = &selectable.content(); + for (row, pair, attr) in enumerated_colored_iter!(content) { + let mut attr = *attr; + if row == selectable.index() { + attr.effect |= Effect::REVERSE; + } + let _ = canvas.print_with_attr( + row + ContentWindow::WINDOW_MARGIN_TOP, + 4, + pair.0.to_str().context("Unreadable filename")?, + attr, + ); + } + Ok(()) + } + fn bulk( &self, canvas: &mut dyn Canvas, diff --git a/src/visited.rs b/src/visited.rs deleted file mode 100644 index a42099bf..00000000 --- a/src/visited.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::path::PathBuf; - -use crate::impl_selectable_content; - -/// A Vec of pathbuf of visited files. -/// It's mostly used as a stack but we want to avoid multiple instances of the -/// same path and visit in a certain order. -/// A `BTreeSet` may be used instead. -/// We also need to drop the queue, which isn't easy with a BTreeSet... -#[derive(Default, Clone)] -pub struct History { - /// The visited paths - pub content: Vec, - /// The currently selected index. By default it's the last one. - pub index: usize, -} - -impl History { - /// Add a new path in the stack, without duplicates, and select the last - /// one. - pub fn push(&mut self, path: &std::path::Path) { - let path = path.to_path_buf(); - if !self.content.contains(&path) { - self.content.push(path); - self.index = self.len() - 1 - } - } - - /// Drop the last visited paths from the stack, after the selected one. - /// Used to go back a few steps in time. - pub fn drop_queue(&mut self) { - if self.is_empty() { - return; - } - let final_length = self.len() - self.index; - self.content.truncate(final_length); - if self.is_empty() { - self.index = 0 - } else { - self.index = self.len() - 1 - } - } -} - -impl_selectable_content!(PathBuf, History); From 38d2155a297d57c82bf15fbf45cf19531ac1be77 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 22 Oct 2023 21:36:50 +0200 Subject: [PATCH 037/168] remove useless logging --- src/tab.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tab.rs b/src/tab.rs index 1a906a9c..064412ef 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -452,7 +452,6 @@ impl Tab { /// Set a new mode and save the last one pub fn set_mode(&mut self, new_mode: Mode) { - log::info!("mode {new_mode}"); self.previous_mode = self.mode; self.mode = new_mode; } From 22ee632f9da1cd7a3f535abafe09256f4bd401bf Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 22 Oct 2023 21:44:17 +0200 Subject: [PATCH 038/168] use yellow block char to make flagged files more visibles. --- development.md | 1 + src/term_manager.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index 1cc17301..eafe2dc6 100644 --- a/development.md +++ b/development.md @@ -580,6 +580,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] allow file selection from args : -f filename selects a file - [x] more args : dual pane, preview second, display full, show hidden - [x] history: when moving back select back the file we were at +- [x] use yellow block char to make flagged files more visibles. - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/term_manager.rs b/src/term_manager.rs index 62b89356..399d1ab5 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -364,7 +364,8 @@ impl<'a> WinMain<'a> { file.format_simple()? }; if status.flagged.contains(&file.path) { - attr.effect |= Effect::BOLD | Effect::UNDERLINE; + attr.effect |= Effect::BOLD; + canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; } canvas.print_with_attr(row, 1, &string, attr)?; } @@ -1101,7 +1102,7 @@ fn draw_colored_strings( canvas: &mut dyn Canvas, reverse: bool, ) -> Result<()> { - let mut col = 0; + let mut col = 1; for (text, attr) in std::iter::zip(strings.iter(), FIRST_LINE_COLORS.iter().cycle()) { let mut attr = *attr; if reverse { From 920adf620455274bee7b7e39c46223c44749a825 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 22 Oct 2023 23:15:31 +0200 Subject: [PATCH 039/168] Move cursor 1 char right in input. Use unicode segmentation when replacing input --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + development.md | 2 ++ src/event_exec.rs | 25 ++++--------------------- src/input.rs | 27 ++++++++++++++++++++++----- src/keybindings.rs | 1 + src/tab.rs | 23 +++++++++++++++++++++-- src/term_manager.rs | 6 +++--- 8 files changed, 61 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa1312a4..74f85f3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -923,6 +923,7 @@ dependencies = [ "tokio", "tuikit", "ueberzug", + "unicode-segmentation", "url-escape", "users", "which", @@ -2780,6 +2781,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index 47372a2a..b9cffbd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,4 +64,5 @@ users = "0.11.0" zip = "0.6.4" tokio = "1" ueberzug = "0.1.0" +unicode-segmentation = "1.10.1" which = "4.4.0" diff --git a/development.md b/development.md index eafe2dc6..df793366 100644 --- a/development.md +++ b/development.md @@ -581,6 +581,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] more args : dual pane, preview second, display full, show hidden - [x] history: when moving back select back the file we were at - [x] use yellow block char to make flagged files more visibles. +- [x] move input 1 char right since we inserted a space +- [ ] BUG: when encrypted drive is already mounted don't let user mount it again - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/event_exec.rs b/src/event_exec.rs index 91d1fce0..eff4b91c 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -38,8 +38,8 @@ use crate::status::Status; use crate::tab::Tab; use crate::utils::is_program_in_path; use crate::utils::{ - args_is_empty, disk_used_by_path, filename_from_path, is_sudo_command, open_in_current_neovim, - opt_mount_point, string_to_path, + args_is_empty, disk_used_by_path, is_sudo_command, open_in_current_neovim, opt_mount_point, + string_to_path, }; /// Links events from tuikit to custom actions. @@ -358,21 +358,7 @@ impl EventAction { /// When we enter rename from a "tree" mode, we'll need to rename the selected file in the tree, /// not the selected file in the pathcontent. pub fn rename(tab: &mut Tab) -> Result<()> { - if tab.selected().is_some() { - let old_name = match tab.mode { - Mode::Tree => tab.directory.tree.current_node.filename(), - _ => filename_from_path( - &tab.path_content - .selected() - .context("Event rename: no file in current directory")? - .path, - )? - .to_owned(), - }; - tab.input.replace(&old_name); - tab.set_mode(Mode::InputSimple(InputSimple::Rename)); - } - Ok(()) + tab.rename() } /// Enter the goto mode where an user can type a path to jump to. @@ -635,10 +621,7 @@ impl EventAction { pub fn delete(status: &mut Status) -> Result<()> { match status.selected().mode { Mode::InputSimple(_) | Mode::InputCompleted(_) => { - { - let tab: &mut Tab = status.selected(); - tab.input.delete_chars_right() - }; + status.selected().input.delete_chars_right(); Ok(()) } _ => Ok(()), diff --git a/src/input.rs b/src/input.rs index 00f78382..d70eb6dd 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,9 +1,11 @@ +use unicode_segmentation::UnicodeSegmentation; + /// Holds the chars typed by the user and the cursor position. /// Methods allow mutation of this content and movement of the cursor. #[derive(Clone, Default)] pub struct Input { /// The input typed by the user - chars: Vec, + chars: Vec, /// The index of the cursor in that string pub cursor_index: usize, } @@ -57,12 +59,17 @@ impl Input { /// Delete all chars right to the cursor pub fn delete_chars_right(&mut self) { - self.chars = self.chars.iter().copied().take(self.cursor_index).collect(); + self.chars = self + .chars + .iter() + .take(self.cursor_index) + .map(|s| s.to_string()) + .collect(); } /// Returns the content typed by the user as a String. pub fn string(&self) -> String { - self.chars.iter().collect() + self.chars.join("") } /// Returns a string of * for every char typed. @@ -72,7 +79,7 @@ impl Input { /// Insert an utf-8 char into the input at cursor index. pub fn insert(&mut self, c: char) { - self.chars.insert(self.cursor_index, c); + self.chars.insert(self.cursor_index, String::from(c)); self.cursor_index += 1 } @@ -84,8 +91,18 @@ impl Input { /// Replace the content with the new content. /// Put the cursor at the end. + /// + /// To avoid splitting graphemes at wrong place, the new content is read + /// as Unicode Graphemes with + /// ```rust + /// unicode_segmentation::UnicodeSegmentation::graphemes(content, true) + /// ``` pub fn replace(&mut self, content: &str) { - self.chars = content.chars().collect(); + self.chars = UnicodeSegmentation::graphemes(content, true) + .collect::>() + .iter() + .map(|s| s.to_string()) + .collect(); self.cursor_index = self.len() } } diff --git a/src/keybindings.rs b/src/keybindings.rs index c3c53be9..8fc46621 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -112,6 +112,7 @@ impl Bindings { (Key::Ctrl('f'), ActionMap::FuzzyFind), (Key::Ctrl('g'), ActionMap::Shortcut), (Key::Ctrl('h'), ActionMap::History), + (Key::Ctrl('k'), ActionMap::Delete), (Key::Ctrl('s'), ActionMap::FuzzyFindLine), (Key::Ctrl('u'), ActionMap::PageUp), (Key::Ctrl('p'), ActionMap::CopyFilepath), diff --git a/src/tab.rs b/src/tab.rs index 064412ef..0fe3eb32 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -12,12 +12,12 @@ use crate::fileinfo::{FileInfo, FileKind, PathContent}; use crate::filter::FilterKind; use crate::history::History; use crate::input::Input; -use crate::mode::Mode; +use crate::mode::{InputSimple, Mode}; use crate::opener::execute_in_child; use crate::preview::{Directory, Preview}; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; -use crate::utils::{row_to_window_index, set_clipboard}; +use crate::utils::{filename_from_path, row_to_window_index, set_clipboard}; /// Holds every thing about the current tab of the application. /// Most of the mutation is done externally. @@ -707,4 +707,23 @@ impl Tab { execute_in_child(command, &args)?; Ok(true) } + + pub fn rename(&mut self) -> Result<()> { + if self.selected().is_some() { + let old_name = match self.mode { + Mode::Tree => self.directory.tree.current_node.filename(), + _ => filename_from_path( + &self + .path_content + .selected() + .context("Event rename: no file in current directory")? + .path, + )? + .to_owned(), + }; + self.input.replace(&old_name); + self.set_mode(Mode::InputSimple(InputSimple::Rename)); + } + Ok(()) + } } diff --git a/src/term_manager.rs b/src/term_manager.rs index 399d1ab5..4dd2fa9b 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -546,10 +546,10 @@ impl<'a> Draw for WinSecondary<'a> { } impl<'a> WinSecondary<'a> { - const EDIT_BOX_OFFSET: usize = 9; const ATTR_YELLOW: Attr = color_to_attr(Color::YELLOW); - const SORT_CURSOR_OFFSET: usize = 37; - const PASSWORD_CURSOR_OFFSET: usize = 7; + const EDIT_BOX_OFFSET: usize = 10; + const SORT_CURSOR_OFFSET: usize = 38; + const PASSWORD_CURSOR_OFFSET: usize = 8; fn new(status: &'a Status, index: usize) -> Self { Self { From 9a671b32bc7aa37731b27fa79247b53b4177a229 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 22 Oct 2023 23:38:23 +0200 Subject: [PATCH 040/168] limit pdf extraction to 20MB files --- src/preview.rs | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/preview.rs b/src/preview.rs index f76e0b83..36a54022 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -715,24 +715,30 @@ pub struct PdfContent { } impl PdfContent { - const SIZE_LIMIT: usize = 1048576; + // 20 MB + const FILESIZE_LIMIT: u64 = 20 * 1024 * 1024; + const CONTENT_LIMIT: usize = 1_048_576; + const ERROR_MSG: &str = "Couldn't extract text from pdf"; fn new(path: &Path) -> Self { - let result = catch_unwind_silent(|| { - // TODO! remove this when pdf_extract replaces println! whith dlog. - let _print_gag = gag::Gag::stdout().unwrap(); - if let Ok(content_string) = pdf_extract::extract_text(path) { - content_string - .split_whitespace() - .take(Self::SIZE_LIMIT) - .map(|s| s.to_owned()) - .collect() - } else { - vec!["Coudln't parse the pdf".to_owned()] - } - }); - let content = result.unwrap_or_else(|_| vec!["Couldn't read the pdf".to_owned()]); - + let content = if path.metadata().unwrap().len() > Self::FILESIZE_LIMIT { + vec![Self::ERROR_MSG.to_owned()] + } else { + let result = catch_unwind_silent(|| { + // TODO! remove this when pdf_extract replaces println! whith dlog. + let _print_gag = gag::Gag::stdout().unwrap(); + if let Ok(content_string) = pdf_extract::extract_text(path) { + content_string + .split_whitespace() + .take(Self::CONTENT_LIMIT) + .map(|s| s.to_owned()) + .collect() + } else { + vec![Self::ERROR_MSG.to_owned()] + } + }); + result.unwrap_or_else(|_| vec![Self::ERROR_MSG.to_owned()]) + }; Self { length: content.len(), content, From 9729b3f50d9456cb5cfefad880f3da0520e274af Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 23 Oct 2023 00:38:46 +0200 Subject: [PATCH 041/168] preview pdf with ueberzug. First page extracted with poppler -> cairo -> thumbnail -> ueberzug --- Cargo.lock | 400 +++++++++++++++++++--------------- Cargo.toml | 3 +- development.md | 1 + src/constant_strings_paths.rs | 2 +- src/preview.rs | 87 +++----- src/term_manager.rs | 3 - 6 files changed, 249 insertions(+), 247 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74f85f3c..ee79ee83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,15 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "adobe-cmap-parser" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3da9d617508ab8102c22f05bd772fc225ecb4fde431e38a45284e5c129a4bc" -dependencies = [ - "pom 1.1.0", -] - [[package]] name = "aes" version = "0.8.3" @@ -245,17 +236,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bstr" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" version = "3.14.0" @@ -305,6 +285,30 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cairo-rs" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "cc" version = "1.0.83" @@ -315,6 +319,16 @@ dependencies = [ "libc", ] +[[package]] +name = "cfg-expr" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -746,70 +760,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encoding" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" -dependencies = [ - "encoding-index-japanese", - "encoding-index-korean", - "encoding-index-simpchinese", - "encoding-index-singlebyte", - "encoding-index-tradchinese", -] - -[[package]] -name = "encoding-index-japanese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-korean" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-simpchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-singlebyte" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-tradchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding_index_tests" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" - [[package]] name = "env_logger" version = "0.8.4" @@ -839,15 +789,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "euclid" -version = "0.20.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb7ef65b3777a325d1eeefefab5b6d4959da54747e33bd6258e789640f307ad" -dependencies = [ - "num-traits", -] - [[package]] name = "fastrand" version = "2.0.1" @@ -892,6 +833,7 @@ name = "fm-tui" version = "0.1.23" dependencies = [ "anyhow", + "cairo-rs", "chrono", "clap 4.4.6", "content_inspector", @@ -906,7 +848,7 @@ dependencies = [ "log4rs", "nvim-rs", "pathdiff", - "pdf-extract", + "poppler", "rand 0.8.5", "regex", "rust-lzma", @@ -1106,6 +1048,62 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "glib" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.15.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" +dependencies = [ + "anyhow", + "heck", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1377,22 +1375,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "lopdf" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0f69c40d6dbc68ebac4bf5aec3d9978e094e22e29fcabd045acd9cec74a9dc" -dependencies = [ - "encoding", - "flate2", - "itoa", - "linked-hash-map", - "log", - "pom 3.3.0", - "time 0.2.27", - "weezl", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1693,22 +1675,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "pdf-extract" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f21fc45e1b40af7e6c7ca32af35464c1ea7a92e5d2e1465d08c8389e033240" -dependencies = [ - "adobe-cmap-parser", - "encoding", - "euclid", - "linked-hash-map", - "lopdf", - "postscript", - "type1-encoding-parser", - "unicode-normalization", -] - [[package]] name = "percent-encoding" version = "2.3.0" @@ -1748,18 +1714,13 @@ dependencies = [ ] [[package]] -name = "pom" -version = "1.1.0" +name = "poppler" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6" - -[[package]] -name = "pom" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d73a5fe10d458e77534589512104e5aa8ac480aa9ac30b74563274235cce4" +checksum = "8e4d5fb5148fbfe436811ca4de5e10176c4ae953f5592982a1c3e74a88e7c20e" dependencies = [ - "bstr", + "cairo-rs", + "glib", ] [[package]] @@ -1768,12 +1729,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" -[[package]] -name = "postscript" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78451badbdaebaf17f053fd9152b3ffb33b516104eacb45e7864aaa9c712f306" - [[package]] name = "powerfmt" version = "0.2.0" @@ -1786,6 +1741,40 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -2146,6 +2135,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_yaml" version = "0.8.26" @@ -2482,6 +2480,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "system-deps" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af52f9402f94aac4948a2518b43359be8d9ce6cd9efc1c4de3b2f7b7e897d6" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "tar" version = "0.4.40" @@ -2493,6 +2504,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" + [[package]] name = "tempfile" version = "3.8.0" @@ -2651,21 +2668,6 @@ dependencies = [ "chrono", ] -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.33.0" @@ -2722,6 +2724,51 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.0.2", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.0.2", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tuikit" version = "0.5.0" @@ -2736,15 +2783,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "type1-encoding-parser" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d6cc09e1a99c7e01f2afe4953789311a1c50baebbdac5b477ecf78e2e92a5b" -dependencies = [ - "pom 1.1.0", -] - [[package]] name = "typemap-ors" version = "1.0.0" @@ -2772,15 +2810,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.10.1" @@ -2839,6 +2868,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + [[package]] name = "version_check" version = "0.9.4" @@ -3038,12 +3073,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - [[package]] name = "which" version = "4.4.2" @@ -3237,6 +3266,15 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "winnow" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" +dependencies = [ + "memchr", +] + [[package]] name = "x11-clipboard" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index b9cffbd1..ae6f99d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ fs_extra = "1.2.0" [dependencies] anyhow = "1.0.28" +cairo-rs = { version = "0.15", features = ["png", "pdf"] } chrono = "0.4.31" clap = { version = "4.0.2", features = ["derive"] } content_inspector = "0.2.4" @@ -44,7 +45,7 @@ log = { version = "0.4.0", features = ["std"] } log4rs = { version = "1.2.0", features = ["rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } nvim-rs = { version = "0.3", features = ["use_tokio"] } pathdiff = "0.2.1" -pdf-extract = "0.6.4" +poppler = "0.3.2" rand = "0.8.5" regex = "1.6.0" rust-lzma = "0.5.1" diff --git a/development.md b/development.md index df793366..d6cbe170 100644 --- a/development.md +++ b/development.md @@ -582,6 +582,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] history: when moving back select back the file we were at - [x] use yellow block char to make flagged files more visibles. - [x] move input 1 char right since we inserted a space +- [x] preview pdf with ueberzug. First page extracted with poppler -> cairo -> thumbnail -> ueberzug - [ ] BUG: when encrypted drive is already mounted don't let user mount it again - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/constant_strings_paths.rs b/src/constant_strings_paths.rs index 49bd5988..54c68c28 100644 --- a/src/constant_strings_paths.rs +++ b/src/constant_strings_paths.rs @@ -52,7 +52,7 @@ pub const LOG_FIRST_SENTENCE: &str = " Logs: "; /// Description of the content below, aka what is logged there. pub const LOG_SECOND_SENTENCE: &str = " Last actions affecting the file tree"; /// Video thumbnails -pub const THUMBNAIL_PATH: &str = "/tmp/thumbnail.png"; +pub const THUMBNAIL_PATH: &str = "/tmp/fm_thumbnail.png"; /// Array of hardcoded shortcuts with standard *nix paths. pub const HARDCODED_SHORTCUTS: [&str; 9] = [ "/", diff --git a/src/preview.rs b/src/preview.rs index 36a54022..9b1fba9f 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -4,14 +4,12 @@ use std::fs::metadata; use std::io::Cursor; use std::io::{BufRead, BufReader, Read}; use std::iter::{Enumerate, Skip, Take}; -use std::panic; use std::path::{Path, PathBuf}; use std::slice::Iter; use anyhow::{anyhow, Context, Result}; use content_inspector::{inspect, ContentType}; use log::info; -use pdf_extract; use syntect::easy::HighlightLines; use syntect::highlighting::{Style, ThemeSet}; use syntect::parsing::{SyntaxReference, SyntaxSet}; @@ -92,7 +90,6 @@ pub enum Preview { Syntaxed(HLContent), Text(TextContent), Binary(BinaryContent), - Pdf(PdfContent), Archive(ArchiveContent), Ueberzug(Ueberzug), Media(MediaContent), @@ -136,7 +133,10 @@ impl Preview { &file_info.path, extension, )?)), - ExtensionKind::Pdf => Ok(Self::Pdf(PdfContent::new(&file_info.path))), + // ExtensionKind::Pdf => Ok(Self::Pdf(PdfContent::new(&file_info.path))), + ExtensionKind::Pdf => { + Ok(Self::Ueberzug(Ueberzug::pdf_thumbnail(&file_info.path)?)) + } ExtensionKind::Image if is_program_in_path(UEBERZUG) => { Ok(Self::Ueberzug(Ueberzug::image(&file_info.path)?)) } @@ -302,7 +302,6 @@ impl Preview { Self::Syntaxed(syntaxed) => syntaxed.len(), Self::Text(text) => text.len(), Self::Binary(binary) => binary.len(), - Self::Pdf(pdf) => pdf.len(), Self::Archive(zip) => zip.len(), Self::Ueberzug(_) => 0, Self::Media(media) => media.len(), @@ -703,53 +702,6 @@ impl Line { } } -/// Holds a preview of a pdffile as outputed by `pdf_extract` crate. -/// If the pdf file content can't be extracted, it doesn't fail but simply hold -/// an error message to be displayed. -/// Afterall, it's just a TUI filemanager, the user shouldn't expect to display -/// any kind of graphical pdf... -#[derive(Clone)] -pub struct PdfContent { - length: usize, - content: Vec, -} - -impl PdfContent { - // 20 MB - const FILESIZE_LIMIT: u64 = 20 * 1024 * 1024; - const CONTENT_LIMIT: usize = 1_048_576; - const ERROR_MSG: &str = "Couldn't extract text from pdf"; - - fn new(path: &Path) -> Self { - let content = if path.metadata().unwrap().len() > Self::FILESIZE_LIMIT { - vec![Self::ERROR_MSG.to_owned()] - } else { - let result = catch_unwind_silent(|| { - // TODO! remove this when pdf_extract replaces println! whith dlog. - let _print_gag = gag::Gag::stdout().unwrap(); - if let Ok(content_string) = pdf_extract::extract_text(path) { - content_string - .split_whitespace() - .take(Self::CONTENT_LIMIT) - .map(|s| s.to_owned()) - .collect() - } else { - vec![Self::ERROR_MSG.to_owned()] - } - }); - result.unwrap_or_else(|_| vec![Self::ERROR_MSG.to_owned()]) - }; - Self { - length: content.len(), - content, - } - } - - fn len(&self) -> usize { - self.length - } -} - /// Holds a list of file of an archive as returned by /// `ZipArchive::file_names` or from a `tar tvf` command. /// A generic error message prevent it from returning an error. @@ -857,6 +809,11 @@ impl Ueberzug { Ok(Self::thumbnail()) } + fn pdf_thumbnail(pdf_path: &Path) -> Result { + Self::make_pdf_thumbnail(pdf_path)?; + Ok(Self::thumbnail()) + } + fn make_thumbnail(exe: &str, args: &[&str]) -> Result<()> { let output = std::process::Command::new(exe).args(args).output()?; if !output.stderr.is_empty() { @@ -869,6 +826,23 @@ impl Ueberzug { Ok(()) } + fn make_pdf_thumbnail(pdf_path: &Path) -> Result<()> { + let doc: poppler::PopplerDocument = + poppler::PopplerDocument::new_from_file(pdf_path, "upw")?; + let page: poppler::PopplerPage = + doc.get_page(0).context("poppler couldn't extract page")?; + let (w, h) = page.get_size(); + let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, w as i32, h as i32)?; + let ctx = cairo::Context::new(&surface)?; + ctx.save()?; + page.render(&ctx); + ctx.restore()?; + ctx.show_page()?; + let mut file = std::fs::File::create(THUMBNAIL_PATH)?; + surface.write_to_png(&mut file)?; + Ok(()) + } + fn make_video_thumbnail(video_path: &Path) -> Result<()> { let path_str = video_path .to_str() @@ -1235,7 +1209,6 @@ pub type VecSyntaxedString = Vec; impl_window!(HLContent, VecSyntaxedString); impl_window!(TextContent, String); impl_window!(BinaryContent, Line); -impl_window!(PdfContent, String); impl_window!(ArchiveContent, String); impl_window!(MediaContent, String); impl_window!(Directory, ColoredTriplet); @@ -1245,11 +1218,3 @@ impl_window!(ColoredText, String); impl_window!(Socket, String); impl_window!(BlockDevice, String); impl_window!(FifoCharDevice, String); - -fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> std::thread::Result { - let prev_hook = panic::take_hook(); - panic::set_hook(Box::new(|_| {})); - let result = panic::catch_unwind(f); - panic::set_hook(prev_hook); - result -} diff --git a/src/term_manager.rs b/src/term_manager.rs index 4dd2fa9b..9de0361d 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -497,9 +497,6 @@ impl<'a> WinMain<'a> { Preview::Media(text) => { impl_preview!(text, tab, length, canvas, line_number_width, window) } - Preview::Pdf(text) => { - impl_preview!(text, tab, length, canvas, line_number_width, window) - } Preview::Text(text) => { impl_preview!(text, tab, length, canvas, line_number_width, window) } From 3c5dab0992f42d5f3b08956394edfd24216dc9fa Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 24 Oct 2023 18:46:30 +0200 Subject: [PATCH 042/168] FIX: when encrypted drive is already mounted don't let user mount it again --- development.md | 2 +- src/constant_strings_paths.rs | 2 ++ src/status.rs | 15 +++++++++++++++ src/term_manager.rs | 9 +++++---- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/development.md b/development.md index d6cbe170..afdf790f 100644 --- a/development.md +++ b/development.md @@ -583,7 +583,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] use yellow block char to make flagged files more visibles. - [x] move input 1 char right since we inserted a space - [x] preview pdf with ueberzug. First page extracted with poppler -> cairo -> thumbnail -> ueberzug -- [ ] BUG: when encrypted drive is already mounted don't let user mount it again +- [x] FIX: when encrypted drive is already mounted don't let user mount it again - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/constant_strings_paths.rs b/src/constant_strings_paths.rs index 54c68c28..0564d749 100644 --- a/src/constant_strings_paths.rs +++ b/src/constant_strings_paths.rs @@ -71,6 +71,8 @@ pub const RG_EXECUTABLE: &str = "rg --line-number \"{}\""; pub const GREP_EXECUTABLE: &str = "grep -rI --line-number \"{}\""; pub const SSHFS_EXECUTABLE: &str = "sshfs"; pub const NOTIFY_EXECUTABLE: &str = "notity-send"; +/// Encrypted devices bind description +pub const ENCRYPTED_DEVICE_BINDS: &str = "m: mount -- u: unmount -- g: go to mount point"; /// Sort presentation for the second window pub const SORT_LINES: [&str; 7] = [ "k: by kind (default)", diff --git a/src/status.rs b/src/status.rs index a1dd734b..c6b4391c 100644 --- a/src/status.rs +++ b/src/status.rs @@ -643,6 +643,12 @@ impl Status { /// passphrase. /// Those passwords are always dropped immediatly after the commands are run. pub fn mount_encrypted_drive(&mut self) -> Result<()> { + let Some(device) = self.encrypted_devices.selected() else { + return Ok(()); + }; + if device.is_mounted() { + return Ok(()); + } if !self.password_holder.has_sudo() { Self::ask_password( self, @@ -668,6 +674,9 @@ impl Status { let Some(device) = self.encrypted_devices.selected() else { return Ok(()); }; + if !device.is_mounted() { + return Ok(()); + } let Some(mount_point) = device.mount_point() else { return Ok(()); }; @@ -680,6 +689,12 @@ impl Status { /// Unmount the selected device. /// Will ask first for a sudo password which is immediatly forgotten. pub fn umount_encrypted_drive(&mut self) -> Result<()> { + let Some(device) = self.encrypted_devices.selected() else { + return Ok(()); + }; + if !device.is_mounted() { + return Ok(()); + } if !self.password_holder.has_sudo() { Self::ask_password( self, diff --git a/src/term_manager.rs b/src/term_manager.rs index 9de0361d..2563aade 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -13,7 +13,8 @@ use crate::completion::InputCompleted; use crate::compress::CompressionMethod; use crate::config::Colors; use crate::constant_strings_paths::{ - HELP_FIRST_SENTENCE, HELP_SECOND_SENTENCE, LOG_FIRST_SENTENCE, LOG_SECOND_SENTENCE, + ENCRYPTED_DEVICE_BINDS, HELP_FIRST_SENTENCE, HELP_SECOND_SENTENCE, LOG_FIRST_SENTENCE, + LOG_SECOND_SENTENCE, }; use crate::content_window::ContentWindow; use crate::fileinfo::{fileinfo_attr, shorten_path, FileInfo}; @@ -647,7 +648,7 @@ impl<'a> WinSecondary<'a> { Navigate::Bulk => self.bulk(canvas, &self.status.bulk), Navigate::CliInfo => self.cli_info(self.status, canvas), Navigate::Compress => self.compress(canvas, &self.status.compression), - Navigate::EncryptedDrive => self.encrypt(self.status, self.tab, canvas), + Navigate::EncryptedDrive => self.encrypted_drive(self.status, self.tab, canvas), Navigate::History => self.history(canvas, &self.tab.history), Navigate::Jump => self.destination(canvas, &self.status.flagged), Navigate::Marks(_) => self.marks(self.status, canvas), @@ -837,8 +838,8 @@ impl<'a> WinSecondary<'a> { Ok(()) } - fn encrypt(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { - canvas.print_with_attr(2, 3, "m: mount -- u: unmount", Self::ATTR_YELLOW)?; + fn encrypted_drive(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + canvas.print_with_attr(2, 3, ENCRYPTED_DEVICE_BINDS, Self::ATTR_YELLOW)?; for (i, device) in status.encrypted_devices.content.iter().enumerate() { let row = calc_line_row(i, &tab.window) + 2; let mut not_mounted_attr = Attr::default(); From cd4e066f23cfe8f514a7038c8cebd35ef5764873 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 24 Oct 2023 19:15:23 +0200 Subject: [PATCH 043/168] FIX: group & owner metadata alignement in tree mode --- development.md | 1 + src/fileinfo.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index afdf790f..9115ea55 100644 --- a/development.md +++ b/development.md @@ -584,6 +584,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] move input 1 char right since we inserted a space - [x] preview pdf with ueberzug. First page extracted with poppler -> cairo -> thumbnail -> ueberzug - [x] FIX: when encrypted drive is already mounted don't let user mount it again +- [x] FIX: group & owner metadata alignement in tree mode - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/fileinfo.rs b/src/fileinfo.rs index dd6927ae..8ee48aab 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -245,14 +245,24 @@ impl FileInfo { } fn format_base(&self, owner_col_width: usize, group_col_width: usize) -> Result { + let owner = format!( + "{owner:.owner_col_width$}", + owner = self.owner, + owner_col_width = owner_col_width + ); + let group = format!( + "{group:.group_col_width$}", + group = self.group, + group_col_width = group_col_width + ); let repr = format!( "{dir_symbol}{permissions} {file_size} {owner: Date: Tue, 24 Oct 2023 19:29:38 +0200 Subject: [PATCH 044/168] Tree mode: file & directory are created in selected dir --- development.md | 1 + src/event_exec.rs | 18 ++++++++++-------- src/tab.rs | 11 +---------- src/tree.rs | 14 +++++++++++++- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/development.md b/development.md index 9115ea55..9ccb8d43 100644 --- a/development.md +++ b/development.md @@ -585,6 +585,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] preview pdf with ueberzug. First page extracted with poppler -> cairo -> thumbnail -> ueberzug - [x] FIX: when encrypted drive is already mounted don't let user mount it again - [x] FIX: group & owner metadata alignement in tree mode +- [ ] Tree mode Copy / Move / New should copy in selected directory not root of tree - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/event_exec.rs b/src/event_exec.rs index eff4b91c..6fce1a5c 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -696,8 +696,8 @@ impl EventAction { let mut must_reset_mode = true; match status.selected_non_mut().mode { Mode::InputSimple(InputSimple::Rename) => LeaveMode::rename(status.selected())?, - Mode::InputSimple(InputSimple::Newfile) => LeaveMode::newfile(status.selected())?, - Mode::InputSimple(InputSimple::Newdir) => LeaveMode::newdir(status.selected())?, + Mode::InputSimple(InputSimple::Newfile) => LeaveMode::new_file(status.selected())?, + Mode::InputSimple(InputSimple::Newdir) => LeaveMode::new_dir(status.selected())?, Mode::InputSimple(InputSimple::Chmod) => LeaveMode::chmod(status)?, Mode::InputSimple(InputSimple::RegexMatch) => LeaveMode::regex(status)?, Mode::InputSimple(InputSimple::SetNvimAddr) => LeaveMode::set_nvim_addr(status)?, @@ -1139,10 +1139,12 @@ impl Display for NodeCreation { impl NodeCreation { fn create(&self, tab: &mut Tab) -> Result<()> { - let path = tab - .path_content - .path - .join(sanitize_filename::sanitize(tab.input.string())); + let root_path = match tab.previous_mode { + Mode::Tree => tab.directory.tree.directory_of_selected()?.to_owned(), + _ => tab.path_content.path.clone(), + }; + log::info!("root_path: {root_path:?}"); + let path = root_path.join(sanitize_filename::sanitize(tab.input.string())); if path.exists() { write_log_line(format!( "{self} {path} already exists", @@ -1365,7 +1367,7 @@ impl LeaveMode { /// Creates a new file with input string as name. /// Nothing is done if the file already exists. /// Filename is sanitized before processing. - pub fn newfile(tab: &mut Tab) -> Result<()> { + pub fn new_file(tab: &mut Tab) -> Result<()> { NodeCreation::Newfile.create(tab) } @@ -1374,7 +1376,7 @@ impl LeaveMode { /// We use `fs::create_dir` internally so it will fail if the input string /// ie. the user can create `newdir` or `newdir/newfolder`. /// Directory name is sanitized before processing. - pub fn newdir(tab: &mut Tab) -> Result<()> { + pub fn new_dir(tab: &mut Tab) -> Result<()> { NodeCreation::Newdir.create(tab) } diff --git a/src/tab.rs b/src/tab.rs index 0fe3eb32..8d7c4d2d 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -413,16 +413,7 @@ impl Tab { /// In normal mode it's the current working directory. pub fn directory_of_selected(&self) -> Result<&path::Path> { match self.mode { - Mode::Tree => { - let fileinfo = &self.directory.tree.current_node.fileinfo; - match fileinfo.file_kind { - FileKind::Directory => Ok(&self.directory.tree.current_node.fileinfo.path), - _ => Ok(fileinfo - .path - .parent() - .context("selected file should have a parent")?), - } - } + Mode::Tree => self.directory.tree.directory_of_selected(), _ => Ok(&self.path_content.path), } } diff --git a/src/tree.rs b/src/tree.rs index cc7605d9..57609b65 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,6 +1,6 @@ use std::path::Path; -use anyhow::Result; +use anyhow::{Context, Result}; use tuikit::attr::Attr; use users::UsersCache; @@ -574,6 +574,18 @@ impl Tree { visited.node.position.clone() } + + pub fn directory_of_selected(&self) -> Result<&std::path::Path> { + let fileinfo = &self.current_node.fileinfo; + + match fileinfo.file_kind { + FileKind::Directory => Ok(&self.current_node.fileinfo.path), + _ => Ok(fileinfo + .path + .parent() + .context("selected file should have a parent")?), + } + } } fn first_prefix(mut prefix: String) -> String { From ec39a6850c26a98902cad82c5b2ae0e0971fb9e7 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 24 Oct 2023 19:38:39 +0200 Subject: [PATCH 045/168] Tree mode Copy / Move happens in selected directory not root of tree --- development.md | 2 +- src/status.rs | 22 +++++++++++++++++----- src/tab.rs | 2 +- src/term_manager.rs | 11 ++++++++++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/development.md b/development.md index 9ccb8d43..9036922a 100644 --- a/development.md +++ b/development.md @@ -585,7 +585,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] preview pdf with ueberzug. First page extracted with poppler -> cairo -> thumbnail -> ueberzug - [x] FIX: when encrypted drive is already mounted don't let user mount it again - [x] FIX: group & owner metadata alignement in tree mode -- [ ] Tree mode Copy / Move / New should copy in selected directory not root of tree +- [x] Tree mode Copy / Move / New should copy in selected directory not root of tree - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/status.rs b/src/status.rs index c6b4391c..63c1c991 100644 --- a/src/status.rs +++ b/src/status.rs @@ -336,11 +336,23 @@ impl Status { /// is sent every time, even for 0 bytes files... pub fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CopyMove) -> Result<()> { let sources = self.flagged.content.clone(); - let dest = self - .selected_non_mut() - .path_content_str() - .context("cut or copy: unreadable path")?; - copy_move(cut_or_copy, sources, dest, self.term.clone())?; + + let dest = match self.selected_non_mut().previous_mode { + Mode::Tree => self + .selected_non_mut() + .directory + .tree + .directory_of_selected()? + .display() + .to_string(), + _ => self + .selected_non_mut() + .path_content_str() + .context("cut or copy: unreadable path")? + .to_owned(), + }; + + copy_move(cut_or_copy, sources, &dest, self.term.clone())?; self.clear_flags_and_reset_view() } diff --git a/src/tab.rs b/src/tab.rs index 8d7c4d2d..79ba3f15 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -8,7 +8,7 @@ use crate::args::Args; use crate::completion::{Completion, InputCompleted}; use crate::config::{Colors, Settings}; use crate::content_window::ContentWindow; -use crate::fileinfo::{FileInfo, FileKind, PathContent}; +use crate::fileinfo::{FileInfo, PathContent}; use crate::filter::FilterKind; use crate::history::History; use crate::input::Input; diff --git a/src/term_manager.rs b/src/term_manager.rs index 2563aade..e42dfcb8 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -888,10 +888,19 @@ impl<'a> WinSecondary<'a> { } } } + let dest = match tab.previous_mode { + Mode::Tree => tab + .directory + .tree + .directory_of_selected()? + .display() + .to_string(), + _ => tab.path_content.path_to_str(), + }; canvas.print_with_attr( 2, 3, - &confirmed_mode.confirmation_string(&tab.path_content.path_to_str()), + &confirmed_mode.confirmation_string(&dest), ATTR_YELLOW_BOLD, )?; From 64fabdfeb7cb414168233c035bdcadb58f728602 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 24 Oct 2023 19:41:22 +0200 Subject: [PATCH 046/168] dev --- development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/development.md b/development.md index 9036922a..d60d40da 100644 --- a/development.md +++ b/development.md @@ -577,7 +577,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] refresh every 10 seconds. If no file in current dir has changed, nothing happens. - [x] scroll in preview second screen - [x] FIX sending `Event::User(())` events from refresher hangs skim. Use `Event(Key::AltPageUp)` which is now reserved. -- [x] allow file selection from args : -f filename selects a file +- [x] allow file selection from args : -p filename selects the file from parent dir - [x] more args : dual pane, preview second, display full, show hidden - [x] history: when moving back select back the file we were at - [x] use yellow block char to make flagged files more visibles. From 780b566f129038179a9a3358414bd5a2786b02c1 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 24 Oct 2023 21:39:48 +0200 Subject: [PATCH 047/168] Allow scrolling in preview pdf. Required a lot of change in Preview::ueberzug. Update thumbnail when required. --- development.md | 1 + src/preview.rs | 146 +++++++++++++++++++++++++++++++++++--------- src/tab.rs | 26 +++++--- src/term_manager.rs | 7 ++- 4 files changed, 143 insertions(+), 37 deletions(-) diff --git a/development.md b/development.md index d60d40da..6bb02f10 100644 --- a/development.md +++ b/development.md @@ -586,6 +586,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: when encrypted drive is already mounted don't let user mount it again - [x] FIX: group & owner metadata alignement in tree mode - [x] Tree mode Copy / Move / New should copy in selected directory not root of tree +- [x] Allow scrolling in preview pdf. Required a lot of change in Preview::ueberzug. Update thumbnail when required. - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/preview.rs b/src/preview.rs index 9b1fba9f..efabb6fd 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -134,29 +134,39 @@ impl Preview { extension, )?)), // ExtensionKind::Pdf => Ok(Self::Pdf(PdfContent::new(&file_info.path))), - ExtensionKind::Pdf => { - Ok(Self::Ueberzug(Ueberzug::pdf_thumbnail(&file_info.path)?)) - } - ExtensionKind::Image if is_program_in_path(UEBERZUG) => { - Ok(Self::Ueberzug(Ueberzug::image(&file_info.path)?)) - } + ExtensionKind::Pdf => Ok(Self::Ueberzug(Ueberzug::make( + &file_info.path, + UeberzugKind::Pdf, + )?)), + ExtensionKind::Image if is_program_in_path(UEBERZUG) => Ok(Self::Ueberzug( + Ueberzug::make(&file_info.path, UeberzugKind::Image)?, + )), ExtensionKind::Audio if is_program_in_path(MEDIAINFO) => { Ok(Self::Media(MediaContent::new(&file_info.path)?)) } ExtensionKind::Video if is_program_in_path(UEBERZUG) && is_program_in_path(FFMPEG) => { - Ok(Self::Ueberzug(Ueberzug::video_thumbnail(&file_info.path)?)) + Ok(Self::Ueberzug(Ueberzug::make( + &file_info.path, + UeberzugKind::Video, + )?)) } ExtensionKind::Font if is_program_in_path(UEBERZUG) && is_program_in_path(FONTIMAGE) => { - Ok(Self::Ueberzug(Ueberzug::font_thumbnail(&file_info.path)?)) + Ok(Self::Ueberzug(Ueberzug::make( + &file_info.path, + UeberzugKind::Font, + )?)) } ExtensionKind::Svg if is_program_in_path(UEBERZUG) && is_program_in_path(RSVG_CONVERT) => { - Ok(Self::Ueberzug(Ueberzug::svg_thumbnail(&file_info.path)?)) + Ok(Self::Ueberzug(Ueberzug::make( + &file_info.path, + UeberzugKind::Svg, + )?)) } ExtensionKind::Iso if is_program_in_path(ISOINFO) => { Ok(Self::Iso(Iso::new(&file_info.path)?)) @@ -303,7 +313,7 @@ impl Preview { Self::Text(text) => text.len(), Self::Binary(binary) => binary.len(), Self::Archive(zip) => zip.len(), - Self::Ueberzug(_) => 0, + Self::Ueberzug(ueberzug) => ueberzug.len(), Self::Media(media) => media.len(), Self::Directory(directory) => directory.len(), Self::Diff(diff) => diff.len(), @@ -761,57 +771,89 @@ impl MediaContent { } } +pub enum UeberzugKind { + Font, + Image, + Pdf, + Svg, + Video, +} + /// Holds a path, a filename and an instance of ueberzug::Ueberzug. /// The ueberzug instance is held as long as the preview is displayed. /// When the preview is reset, the instance is dropped and the image is erased. /// Positonning the image is tricky since tuikit doesn't know where it's drawed in the terminal: /// the preview can't be placed correctly in embeded terminals. pub struct Ueberzug { + original: PathBuf, path: String, filename: String, + kind: UeberzugKind, ueberzug: ueberzug::Ueberzug, + length: usize, + pub index: usize, } impl Ueberzug { - fn image(img_path: &Path) -> Result { + fn thumbnail(original: PathBuf, kind: UeberzugKind) -> Self { + Self { + original, + path: THUMBNAIL_PATH.to_owned(), + filename: "thumbnail".to_owned(), + kind, + ueberzug: ueberzug::Ueberzug::new(), + length: 0, + index: 0, + } + } + + fn make(filepath: &Path, kind: UeberzugKind) -> Result { + match kind { + UeberzugKind::Font => Self::font_thumbnail(filepath), + UeberzugKind::Image => Self::image_thumbnail(filepath), + UeberzugKind::Pdf => Self::pdf_thumbnail(filepath), + UeberzugKind::Svg => Self::svg_thumbnail(filepath), + UeberzugKind::Video => Self::video_thumbnail(filepath), + } + } + + fn image_thumbnail(img_path: &Path) -> Result { let filename = filename_from_path(img_path)?.to_owned(); let path = img_path .to_str() .context("ueberzug: couldn't parse the path into a string")? .to_owned(); Ok(Self { + original: img_path.to_owned(), path, filename, + kind: UeberzugKind::Image, ueberzug: ueberzug::Ueberzug::new(), + length: 0, + index: 0, }) } - fn thumbnail() -> Self { - Self { - path: THUMBNAIL_PATH.to_owned(), - filename: "thumbnail".to_owned(), - ueberzug: ueberzug::Ueberzug::new(), - } - } - fn video_thumbnail(video_path: &Path) -> Result { Self::make_video_thumbnail(video_path)?; - Ok(Self::thumbnail()) + Ok(Self::thumbnail(video_path.to_owned(), UeberzugKind::Video)) } fn font_thumbnail(font_path: &Path) -> Result { Self::make_font_thumbnail(font_path)?; - Ok(Self::thumbnail()) + Ok(Self::thumbnail(font_path.to_owned(), UeberzugKind::Font)) } fn svg_thumbnail(svg_path: &Path) -> Result { Self::make_svg_thumbnail(svg_path)?; - Ok(Self::thumbnail()) + Ok(Self::thumbnail(svg_path.to_owned(), UeberzugKind::Svg)) } fn pdf_thumbnail(pdf_path: &Path) -> Result { - Self::make_pdf_thumbnail(pdf_path)?; - Ok(Self::thumbnail()) + let length = Self::make_pdf_thumbnail(pdf_path, 0)?; + let mut thumbnail = Self::thumbnail(pdf_path.to_owned(), UeberzugKind::Pdf); + thumbnail.length = length; + Ok(thumbnail) } fn make_thumbnail(exe: &str, args: &[&str]) -> Result<()> { @@ -826,11 +868,23 @@ impl Ueberzug { Ok(()) } - fn make_pdf_thumbnail(pdf_path: &Path) -> Result<()> { + /// Creates the thumbnail for the `index` page. + /// Returns the number of pages of the pdf. + /// + /// It may fail (and surelly crash the app) if the pdf is password protected. + /// We pass a generic password which is hardcoded. + fn make_pdf_thumbnail(pdf_path: &Path, index: usize) -> Result { let doc: poppler::PopplerDocument = poppler::PopplerDocument::new_from_file(pdf_path, "upw")?; - let page: poppler::PopplerPage = - doc.get_page(0).context("poppler couldn't extract page")?; + let length = doc.get_n_pages(); + if index >= length { + return Err(anyhow!( + "Poppler: index {index} >= number of page {length}." + )); + } + let page: poppler::PopplerPage = doc + .get_page(index) + .context("poppler couldn't extract page")?; let (w, h) = page.get_size(); let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, w as i32, h as i32)?; let ctx = cairo::Context::new(&surface)?; @@ -840,7 +894,7 @@ impl Ueberzug { ctx.show_page()?; let mut file = std::fs::File::create(THUMBNAIL_PATH)?; surface.write_to_png(&mut file)?; - Ok(()) + Ok(length) } fn make_video_thumbnail(video_path: &Path) -> Result<()> { @@ -879,6 +933,42 @@ impl Ueberzug { ) } + /// Only affect pdf thumbnail. Will decrease the index if possible. + pub fn up_one_row(&mut self) { + if let UeberzugKind::Pdf = self.kind { + if self.index > 0 { + self.index -= 1; + } + } + } + + /// Only affect pdf thumbnail. Will increase the index if possible. + pub fn down_one_row(&mut self) { + if let UeberzugKind::Pdf = self.kind { + if self.index + 1 < self.len() { + self.index += 1; + } + } + } + + /// 0 for every kind except pdf where it's the number of pages. + pub fn len(&self) -> usize { + self.length + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Update the thumbnail of the pdf to match its own index. + /// Does nothing for other kinds. + pub fn match_index(&self) -> Result<()> { + if let UeberzugKind::Pdf = self.kind { + Self::make_pdf_thumbnail(&self.original, self.index)?; + } + Ok(()) + } + /// Draw the image with ueberzug in the current window. /// The position is absolute, which is problematic when the app is embeded into a floating terminal. /// The whole struct instance is dropped when the preview is reset and the image is deleted. diff --git a/src/tab.rs b/src/tab.rs index 79ba3f15..8873311d 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -586,10 +586,15 @@ impl Tab { } fn preview_page_up(&mut self) { - if self.window.top > 0 { - let skip = min(self.window.top, 30); - self.window.bottom -= skip; - self.window.top -= skip; + match &mut self.preview { + Preview::Ueberzug(ref mut image) => image.up_one_row(), + _ => { + if self.window.top > 0 { + let skip = min(self.window.top, 30); + self.window.bottom -= skip; + self.window.top -= skip; + } + } } } @@ -614,10 +619,15 @@ impl Tab { } fn preview_page_down(&mut self) { - if self.window.bottom < self.preview.len() { - let skip = min(self.preview.len() - self.window.bottom, 30); - self.window.bottom += skip; - self.window.top += skip; + match &mut self.preview { + Preview::Ueberzug(ref mut image) => image.down_one_row(), + _ => { + if self.window.bottom < self.preview.len() { + let skip = min(self.preview.len() - self.window.bottom, 30); + self.window.bottom += skip; + self.window.top += skip; + } + } } } diff --git a/src/term_manager.rs b/src/term_manager.rs index e42dfcb8..a23935c1 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -301,7 +301,11 @@ impl<'a> WinMain<'a> { if let Some(fileinfo) = self.pick_previewed_fileinfo() { let mut strings = vec![" Preview ".to_owned()]; if !tab.preview.is_empty() { - strings.push(format!(" {} / {} ", tab.window.bottom, tab.preview.len())); + let index = match &tab.preview { + Preview::Ueberzug(image) => image.index + 1, + _ => tab.window.bottom, + }; + strings.push(format!(" {index} / {len} ", len = tab.preview.len())); }; strings.push(format!(" {} ", fileinfo.path.display())); strings @@ -462,6 +466,7 @@ impl<'a> WinMain<'a> { } Preview::Ueberzug(image) => { let (width, height) = canvas.size()?; + image.match_index()?; image.ueberzug( self.attributes.x_position as u16 + 2, 3, From 055a45599e8340e5893c8c06835990151ac934e4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 25 Oct 2023 18:17:40 +0200 Subject: [PATCH 048/168] use yellow block to highlight flagged files in tree mode --- src/term_manager.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/term_manager.rs b/src/term_manager.rs index a23935c1..bd59ffb0 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -388,7 +388,7 @@ impl<'a> WinMain<'a> { } fn tree(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { - let left_margin = if status.display_full { 0 } else { 3 }; + let left_margin = if status.display_full { 1 } else { 3 }; let (_, height) = canvas.size()?; let (top, bottom, len) = tab.directory.calculate_tree_window(height); @@ -396,8 +396,10 @@ impl<'a> WinMain<'a> { let row = i + ContentWindow::WINDOW_MARGIN_TOP - top; let mut attr = colored_string.attr; if status.flagged.contains(&colored_string.path) { - attr.effect |= Effect::BOLD | Effect::UNDERLINE; + attr.effect |= Effect::BOLD; + canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; } + let col_metadata = if status.display_full { canvas.print_with_attr(row, left_margin, &metadata.text, attr)? } else { @@ -406,7 +408,7 @@ impl<'a> WinMain<'a> { let col_tree_prefix = canvas.print(row, left_margin + col_metadata, prefix)?; canvas.print_with_attr( row, - left_margin + col_metadata + col_tree_prefix + 1, + left_margin + col_metadata + col_tree_prefix, &colored_string.text, attr, )?; From 1c3cc930f3db8f483a739766317958bdf47e3e29 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 25 Oct 2023 18:26:52 +0200 Subject: [PATCH 049/168] Flag the selected file if no file is flagged before entering delete mode or trashing a file. --- development.md | 2 ++ src/event_exec.rs | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/development.md b/development.md index 6bb02f10..e158cc4f 100644 --- a/development.md +++ b/development.md @@ -587,6 +587,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: group & owner metadata alignement in tree mode - [x] Tree mode Copy / Move / New should copy in selected directory not root of tree - [x] Allow scrolling in preview pdf. Required a lot of change in Preview::ueberzug. Update thumbnail when required. +- [x] Flag the selected file if no file is flagged before entering delete mode or trashing a file. +- [ ] fuzzy finder should do nothing if escape (quit?) is inputed - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/event_exec.rs b/src/event_exec.rs index 6fce1a5c..8f7bc38b 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -62,7 +62,7 @@ impl EventAction { .for_each(|file| { status.flagged.push(file.path.clone()); }); - status.reset_tabs_view() + Ok(()) } /// Reverse every flag in _current_ directory. Flagged files in other @@ -73,7 +73,7 @@ impl EventAction { .content .iter() .for_each(|file| status.flagged.toggle(&file.path)); - status.reset_tabs_view() + Ok(()) } /// Toggle a single flag and move down one row. @@ -259,10 +259,10 @@ impl EventAction { /// Enter the delete mode. /// A confirmation is then asked before deleting all the flagged files. - /// Does nothing is no file is flagged. + /// If no file is flagged, flag the selected one before entering the mode. pub fn delete_file(status: &mut Status) -> Result<()> { if status.flagged.is_empty() { - return Ok(()); + Self::toggle_flag(status)?; } status .selected() @@ -887,12 +887,17 @@ impl EventAction { status.select_tab(0)?; Ok(()) } + /// Move flagged files to the trash directory. + /// If no file is flagged, flag the selected file. /// More information in the trash crate itself. /// If the file is mounted on the $topdir of the trash (aka the $HOME mount point), /// it is moved there. /// Else, nothing is done. pub fn trash_move_file(status: &mut Status) -> Result<()> { + if status.flagged.is_empty() { + Self::toggle_flag(status)?; + } let trash_mount_point = opt_mount_point(disk_used_by_path( status.system_info.disks(), &std::path::PathBuf::from(&status.trash.trash_folder_files), From a9f71c3a5e326cdd5fa033cc4b16061c346b83cc Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 25 Oct 2023 18:57:45 +0200 Subject: [PATCH 050/168] FIX: fuzzy finder should do nothing if escape (quit?) is inputed --- development.md | 2 +- src/skim.rs | 50 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/development.md b/development.md index e158cc4f..66efa259 100644 --- a/development.md +++ b/development.md @@ -588,7 +588,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Tree mode Copy / Move / New should copy in selected directory not root of tree - [x] Allow scrolling in preview pdf. Required a lot of change in Preview::ueberzug. Update thumbnail when required. - [x] Flag the selected file if no file is flagged before entering delete mode or trashing a file. -- [ ] fuzzy finder should do nothing if escape (quit?) is inputed +- [x] FIX: fuzzy finder should do nothing if escape (quit?) is inputed - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/skim.rs b/src/skim.rs index b78041e0..5a0c0121 100644 --- a/src/skim.rs +++ b/src/skim.rs @@ -36,24 +36,35 @@ impl Skimer { /// The preview is enabled by default and we assume the previewer won't be uninstalled during the lifetime /// of the application. pub fn search_filename(&self, path_str: &str) -> Vec> { - self.skim - .run_internal(None, path_str.to_owned(), Some(&self.previewer), None) - .map(|out| out.selected_items) - .unwrap_or_else(Vec::new) + let Some(output) = + self.skim + .run_internal(None, path_str.to_owned(), Some(&self.previewer), None) + else { + return vec![]; + }; + if output.is_abort { + vec![] + } else { + output.selected_items + } } /// Call skim on its term. /// Returns the file whose line match a pattern from current folder using ripgrep or grep. pub fn search_line_in_file(&self, path_str: &str) -> Vec> { - self.skim - .run_internal( - None, - path_str.to_owned(), - None, - Some(self.file_matcher.to_owned()), - ) - .map(|out| out.selected_items) - .unwrap_or_else(Vec::new) + let Some(output) = self.skim.run_internal( + None, + path_str.to_owned(), + None, + Some(self.file_matcher.to_owned()), + ) else { + return vec![]; + }; + if output.is_abort { + vec![] + } else { + output.selected_items + } } /// Search in a text content, splitted by line. @@ -66,10 +77,17 @@ impl Skimer { })); } drop(tx_item); // so that skim could know when to stop waiting for more items. - self.skim + let Some(output) = self + .skim .run_internal(Some(rx_item), "".to_owned(), None, None) - .map(|out| out.selected_items) - .unwrap_or_else(Vec::new) + else { + return vec![]; + }; + if output.is_abort { + vec![] + } else { + output.selected_items + } } } From 943df0a67d24c6140ebf41cf43c07b41e486e884 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 25 Oct 2023 18:59:37 +0200 Subject: [PATCH 051/168] dev --- development.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/development.md b/development.md index 66efa259..7712da10 100644 --- a/development.md +++ b/development.md @@ -658,8 +658,6 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. ## BUGS -- [ ] creates $ENV{HOME} folders everywhere - - a new version of log4rs seems to solve this, it's not deplayed to crates.io yet - [ ] tree mode : index are offset by one ## Won't do From 787fee90d3cfbf065ccec1bfd163e39ec08857f2 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 25 Oct 2023 22:08:58 +0200 Subject: [PATCH 052/168] improve previewing: drop tmp files, preview office files as images, empty preview when not showing --- development.md | 1 + src/bulkrename.rs | 8 ++++- src/constant_strings_paths.rs | 6 +++- src/copy_move.rs | 6 ++++ src/main.rs | 3 +- src/preview.rs | 66 +++++++++++++++++++++++------------ src/status.rs | 5 +++ src/utils.rs | 7 ++++ 8 files changed, 76 insertions(+), 26 deletions(-) diff --git a/development.md b/development.md index 7712da10..c1b68737 100644 --- a/development.md +++ b/development.md @@ -589,6 +589,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Allow scrolling in preview pdf. Required a lot of change in Preview::ueberzug. Update thumbnail when required. - [x] Flag the selected file if no file is flagged before entering delete mode or trashing a file. - [x] FIX: fuzzy finder should do nothing if escape (quit?) is inputed +- [x] preview openoffice / office documents as images. Don't use pandoc for .doc .odb etc. previews - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/bulkrename.rs b/src/bulkrename.rs index 441147f5..1c4e12fa 100644 --- a/src/bulkrename.rs +++ b/src/bulkrename.rs @@ -146,7 +146,7 @@ impl<'a> Bulkrename<'a> { Ok(new_names) } - fn delete_temp_file(&self) -> Result<()> { + pub fn delete_temp_file(&self) -> Result<()> { std::fs::remove_file(&self.temp_file)?; Ok(()) } @@ -211,6 +211,12 @@ impl<'a> Bulkrename<'a> { } } +impl<'a> Drop for Bulkrename<'a> { + fn drop(&mut self) { + let _ = self.delete_temp_file(); + } +} + pub struct Bulk { pub content: Vec, index: usize, diff --git a/src/constant_strings_paths.rs b/src/constant_strings_paths.rs index 0564d749..771c324e 100644 --- a/src/constant_strings_paths.rs +++ b/src/constant_strings_paths.rs @@ -51,8 +51,10 @@ pub const HELP_SECOND_SENTENCE: &str = " Keybindings "; pub const LOG_FIRST_SENTENCE: &str = " Logs: "; /// Description of the content below, aka what is logged there. pub const LOG_SECOND_SENTENCE: &str = " Last actions affecting the file tree"; -/// Video thumbnails +/// Ueberzug image thumbnails pub const THUMBNAIL_PATH: &str = "/tmp/fm_thumbnail.png"; +/// Libreoffice pdf output +pub const CALC_PDF_PATH: &str = "/tmp/fm_calc.pdf"; /// Array of hardcoded shortcuts with standard *nix paths. pub const HARDCODED_SHORTCUTS: [&str; 9] = [ "/", @@ -188,3 +190,5 @@ pub const LSOF: &str = "lsof"; pub const NVIM: &str = "nvim"; /// tar executable pub const TAR: &str = "tar"; +/// libreoffice executable +pub const LIBREOFFICE: &str = "libreoffice"; diff --git a/src/copy_move.rs b/src/copy_move.rs index 9d9ee2ac..cd846675 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -296,6 +296,12 @@ impl ConflictHandler { } } +impl Drop for ConflictHandler { + fn drop(&mut self) { + let _ = self.delete_temp_dest(); + } +} + /// Send a notification to the desktop. /// Does nothing if "notify-send" isn't installed. fn notify(text: &str) -> Result<()> { diff --git a/src/main.rs b/src/main.rs index 5283f418..486dea8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use fm::log::set_loggers; use fm::opener::{load_opener, Opener}; use fm::status::Status; use fm::term_manager::{Display, EventReader}; -use fm::utils::{init_term, print_on_quit}; +use fm::utils::{clear_tmp_file, init_term, print_on_quit}; /// Holds everything about the application itself. /// Most attributes holds an `Arc`. @@ -124,6 +124,7 @@ impl FM { /// drop itself, which allow us to print normally afterward /// print the final path fn quit(self) -> Result<()> { + clear_tmp_file(); self.display.show_cursor()?; let final_path = self.status.selected_path_str().to_owned(); self.refresher.quit()?; diff --git a/src/preview.rs b/src/preview.rs index efabb6fd..1f8a858b 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -18,8 +18,8 @@ use users::UsersCache; use crate::config::Colors; use crate::constant_strings_paths::{ - DIFF, FFMPEG, FONTIMAGE, ISOINFO, JUPYTER, LSBLK, LSOF, MEDIAINFO, PANDOC, RSVG_CONVERT, SS, - THUMBNAIL_PATH, UEBERZUG, + CALC_PDF_PATH, DIFF, FFMPEG, FONTIMAGE, ISOINFO, JUPYTER, LIBREOFFICE, LSBLK, LSOF, MEDIAINFO, + PANDOC, RSVG_CONVERT, SS, THUMBNAIL_PATH, UEBERZUG, }; use crate::content_window::ContentWindow; use crate::decompress::{list_files_tar, list_files_zip}; @@ -28,7 +28,7 @@ use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; use crate::status::Status; use crate::tree::{ColoredString, Tree}; -use crate::utils::{filename_from_path, is_program_in_path}; +use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path}; /// Different kind of extension for grouped by previewers. /// Any extension we can preview should be matched here. @@ -43,7 +43,7 @@ pub enum ExtensionKind { Pdf, Iso, Notebook, - Doc, + Office, Epub, #[default] Unknown, @@ -66,7 +66,7 @@ impl ExtensionKind { "pdf" => Self::Pdf, "iso" => Self::Iso, "ipynb" => Self::Notebook, - "doc" | "docx" | "odt" | "sxw" => Self::Doc, + "doc" | "docx" | "odt" | "sxw" | "xlsx" | "xls" => Self::Office, "epub" => Self::Epub, _ => Self::Unknown, } @@ -107,6 +107,12 @@ pub enum Preview { impl Preview { const CONTENT_INSPECTOR_MIN_SIZE: usize = 1024; + /// Empty preview, holding nothing. + pub fn new_empty() -> Self { + clear_tmp_file(); + Self::Empty + } + /// Creates a new preview instance based on the filekind and the extension of /// the file. /// Sometimes it reads the content of the file, sometimes it delegates @@ -117,6 +123,7 @@ impl Preview { status: &Status, colors: &Colors, ) -> Result { + clear_tmp_file(); match file_info.file_kind { FileKind::Directory => Ok(Self::Directory(Directory::new( &file_info.path, @@ -175,9 +182,9 @@ impl Preview { Ok(Self::notebook(&file_info.path) .context("Preview: Couldn't parse notebook")?) } - ExtensionKind::Doc if is_program_in_path(PANDOC) => { - Ok(Self::doc(&file_info.path).context("Preview: Couldn't parse doc")?) - } + ExtensionKind::Office if is_program_in_path(LIBREOFFICE) => Ok(Self::Ueberzug( + Ueberzug::make(&file_info.path, UeberzugKind::Office)?, + )), ExtensionKind::Epub if is_program_in_path(PANDOC) => { Ok(Self::epub(&file_info.path).context("Preview: Couldn't parse epub")?) } @@ -238,16 +245,6 @@ impl Preview { Self::syntaxed_from_str(output, "md") } - fn doc(path: &Path) -> Option { - let path_str = path.to_str()?; - let output = execute_and_capture_output_without_check( - PANDOC, - &["-s", "-t", "markdown", "--", path_str], - ) - .ok()?; - Self::syntaxed_from_str(output, "md") - } - fn syntaxed_from_str(output: String, ext: &str) -> Option { let ss = SyntaxSet::load_defaults_nonewlines(); ss.find_syntax_by_extension(ext).map(|syntax| { @@ -299,11 +296,6 @@ impl Preview { )) } - /// Empty preview, holding nothing. - pub fn new_empty() -> Self { - Self::Empty - } - /// The size (most of the time the number of lines) of the preview. /// Some preview (thumbnail, empty) can't be scrolled and their size is always 0. pub fn len(&self) -> usize { @@ -774,6 +766,7 @@ impl MediaContent { pub enum UeberzugKind { Font, Image, + Office, Pdf, Svg, Video, @@ -811,6 +804,7 @@ impl Ueberzug { match kind { UeberzugKind::Font => Self::font_thumbnail(filepath), UeberzugKind::Image => Self::image_thumbnail(filepath), + UeberzugKind::Office => Self::office_thumbnail(filepath), UeberzugKind::Pdf => Self::pdf_thumbnail(filepath), UeberzugKind::Svg => Self::svg_thumbnail(filepath), UeberzugKind::Video => Self::video_thumbnail(filepath), @@ -849,6 +843,32 @@ impl Ueberzug { Ok(Self::thumbnail(svg_path.to_owned(), UeberzugKind::Svg)) } + fn office_thumbnail(calc_path: &Path) -> Result { + let calc_str = calc_path.display().to_string(); + let args = vec!["--convert-to", "pdf", "--outdir", "/tmp", &calc_str]; + let output = std::process::Command::new(LIBREOFFICE) + .args(args) + .output()?; + if !output.stderr.is_empty() { + info!( + "libreoffice conversion output: {} {}", + String::from_utf8(output.stdout).unwrap_or_default(), + String::from_utf8(output.stderr).unwrap_or_default() + ); + return Err(anyhow!("{LIBREOFFICE} couldn't convert {calc_str} to pdf")); + } + let mut pdf_path = std::path::PathBuf::from("/tmp"); + let filename = calc_path.file_name().context("")?; + pdf_path.push(filename); + pdf_path.set_extension("pdf"); + std::fs::rename(pdf_path, CALC_PDF_PATH)?; + let calc_pdf_path = PathBuf::from(CALC_PDF_PATH); + let length = Self::make_pdf_thumbnail(&calc_pdf_path, 0)?; + let mut thumbnail = Self::thumbnail(calc_pdf_path.to_owned(), UeberzugKind::Pdf); + thumbnail.length = length; + Ok(thumbnail) + } + fn pdf_thumbnail(pdf_path: &Path) -> Result { let length = Self::make_pdf_thumbnail(pdf_path, 0)?; let mut thumbnail = Self::thumbnail(pdf_path.to_owned(), UeberzugKind::Pdf); diff --git a/src/status.rs b/src/status.rs index 63c1c991..43ca9258 100644 --- a/src/status.rs +++ b/src/status.rs @@ -535,6 +535,11 @@ impl Status { pub fn set_second_pane_for_preview(&mut self, colors: &Colors) -> Result<()> { self.tabs[1].set_mode(Mode::Preview); + if !Self::display_wide_enough(&self.term)? { + self.tabs[1].preview = Preview::new_empty(); + return Ok(()); + } + let fileinfo = self.tabs[0] .selected() .context("force preview: No file to select")?; diff --git a/src/utils.rs b/src/utils.rs index 23d59cb0..6cc6dba9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,6 +9,7 @@ use sysinfo::{Disk, DiskExt}; use tuikit::term::Term; use users::{get_current_uid, get_user_by_uid}; +use crate::constant_strings_paths::{CALC_PDF_PATH, THUMBNAIL_PATH}; use crate::content_window::ContentWindow; use crate::fileinfo::human_size; use crate::nvim::nvim; @@ -163,3 +164,9 @@ pub fn random_name() -> String { rand_str.push_str(".txt"); rand_str } + +/// Clear the temporary file used by fm for previewing. +pub fn clear_tmp_file() { + let _ = std::fs::remove_file(THUMBNAIL_PATH); + let _ = std::fs::remove_file(CALC_PDF_PATH); +} From c98329a769567050989d5e29dba43d16b3c5b48d Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 16:30:20 +0200 Subject: [PATCH 053/168] add mtp mounted devices to shortcuts --- src/shortcut.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/shortcut.rs b/src/shortcut.rs index e265e31b..8129f19e 100644 --- a/src/shortcut.rs +++ b/src/shortcut.rs @@ -90,6 +90,31 @@ impl Shortcut { pub fn extend_with_mount_points(&mut self, mount_points: &[&Path]) { self.content .extend(mount_points.iter().map(|p| p.to_path_buf())); + self.extend_with_mtp() + } + + /// Update the shortcuts with MTP mount points + fn extend_with_mtp(&mut self) { + let uid = users::get_current_uid(); + let mtp_mount_point = PathBuf::from(format!("/run/user/{uid}/gvfs/")); + if !mtp_mount_point.exists() || !mtp_mount_point.is_dir() { + return; + } + let mount_points: Vec = match std::fs::read_dir(&mtp_mount_point) { + Ok(read_dir) => read_dir + .filter_map(|direntry| direntry.ok()) + .filter(|direntry| direntry.path().is_dir()) + .map(|direntry| direntry.path()) + .collect(), + Err(error) => { + log::info!( + "unreadable gvfs {mtp_mount_point}: {error:?} ", + mtp_mount_point = mtp_mount_point.display(), + ); + return; + } + }; + self.content.extend(mount_points) } /// Refresh the shortcuts. It drops non "hardcoded" shortcuts and From 4ec37f1c1b895469b59984bcd31e7ab7d3dd6cc4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 16:30:46 +0200 Subject: [PATCH 054/168] struct for removable devices --- development.md | 3 ++ src/lib.rs | 1 + src/removable_devices.rs | 59 ++++++++++++++++++++++++++++++++++++++++ src/status.rs | 4 +++ 4 files changed, 67 insertions(+) create mode 100644 src/removable_devices.rs diff --git a/development.md b/development.md index c1b68737..6fba5cdb 100644 --- a/development.md +++ b/development.md @@ -590,6 +590,9 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Flag the selected file if no file is flagged before entering delete mode or trashing a file. - [x] FIX: fuzzy finder should do nothing if escape (quit?) is inputed - [x] preview openoffice / office documents as images. Don't use pandoc for .doc .odb etc. previews +- [ ] mtp mount with gio [nnn plugin](https://github.com/jarun/nnn/blob/master/plugins/mtpmount) + - [ ] add MTP mount points to shortcuts + - [ ] list, mount, unmount mtp mount points - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/lib.rs b/src/lib.rs index c56c2ab7..5fd6ff0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod nvim; pub mod opener; pub mod password; pub mod preview; +pub mod removable_devices; pub mod selectable_content; pub mod shell_menu; pub mod shell_parser; diff --git a/src/removable_devices.rs b/src/removable_devices.rs new file mode 100644 index 00000000..763178d9 --- /dev/null +++ b/src/removable_devices.rs @@ -0,0 +1,59 @@ +use anyhow::{anyhow, Result}; +use log::info; + +use crate::impl_selectable_content; + +#[derive(Debug, Clone, Default)] +pub struct RemovableDevices { + pub content: Vec, + pub index: usize, +} + +impl RemovableDevices { + fn from_gio() -> Option { + let Ok(output) = std::process::Command::new("gio").args(["-li"]).output() else { + return None; + }; + let Ok(stdout) = String::from_utf8(output.stdout) else { + return None; + }; + + let content: Vec<_> = stdout + .lines() + .filter(|line| line.contains("activation_root")) + .map(|line| line.to_owned()) + .map(|line| Removable::from_gio(line)) + .filter_map(|removable| removable.ok()) + .collect(); + + if content.is_empty() { + None + } else { + Some(Self { content, index: 0 }) + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct Removable { + pub device_name: String, + pub path: String, + is_mounted: bool, +} + +impl Removable { + fn from_gio(line: String) -> Result { + let device_name = line.replace("mtp:://", ""); + let uid = users::get_current_uid(); + let path = format!("/run/user/{uid}/gvfs/{line}"); + let pb_path = std::path::Path::new(&path); + let is_mounted = pb_path.exists() && pb_path.read_dir()?.next().is_some(); + Ok(Self { + device_name, + path, + is_mounted, + }) + } +} + +impl_selectable_content!(Removable, RemovableDevices); diff --git a/src/status.rs b/src/status.rs index 43ca9258..eab4312a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -34,6 +34,7 @@ use crate::password::{ PasswordKind, PasswordUsage, }; use crate::preview::{Directory, Preview}; +use crate::removable_devices::RemovableDevices; use crate::selectable_content::SelectableContent; use crate::shell_menu::ShellMenu; use crate::shell_parser::ShellCommandParser; @@ -92,6 +93,7 @@ pub struct Status { pub start_folder: std::path::PathBuf, pub password_holder: PasswordHolder, pub sudo_command: Option, + pub removable_devices: Option, } impl Status { @@ -144,6 +146,7 @@ impl Status { Tab::new(&args, height, users_cache, settings, &mount_points)?, Tab::new(&args, height, users_cache2, settings, &mount_points)?, ]; + let removable_devices = None; Ok(Self { tabs, index, @@ -169,6 +172,7 @@ impl Status { start_folder, password_holder, sudo_command, + removable_devices, }) } From 7d46af336f214d1acc03afd8b7a13dfb1f655fa1 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 16:48:27 +0200 Subject: [PATCH 055/168] display removable mtp devices - no action yet --- src/action_map.rs | 2 ++ src/constant_strings_paths.rs | 2 ++ src/event_exec.rs | 15 +++++++++++++++ src/keybindings.rs | 1 + src/mode.rs | 5 +++++ src/removable_devices.rs | 14 ++++++++++---- src/status.rs | 2 +- src/term_manager.rs | 21 +++++++++++++++++++++ 8 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/action_map.rs b/src/action_map.rs index 3fa3d1ac..c12cf9dc 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -74,6 +74,7 @@ pub enum ActionMap { RefreshView, RegexMatch, RemoteMount, + RemovableDevices, Rename, ResetMode, ReverseFlags, @@ -171,6 +172,7 @@ impl ActionMap { ActionMap::RefreshView => EventAction::refreshview(status, colors), ActionMap::RegexMatch => EventAction::regex_match(current_tab), ActionMap::RemoteMount => EventAction::remote_mount(current_tab), + ActionMap::RemovableDevices => EventAction::removable_devices(status), ActionMap::Rename => EventAction::rename(current_tab), ActionMap::ResetMode => EventAction::reset_mode(current_tab), ActionMap::ReverseFlags => EventAction::reverse_flags(status), diff --git a/src/constant_strings_paths.rs b/src/constant_strings_paths.rs index 771c324e..3bb16314 100644 --- a/src/constant_strings_paths.rs +++ b/src/constant_strings_paths.rs @@ -184,6 +184,8 @@ pub const SS: &str = "ss"; pub const LSBLK: &str = "lsblk"; /// cryptsetup is used to mount encrypted drives pub const CRYPTSETUP: &str = "cryptsetup"; +/// gio is used to mount removable devices +pub const GIO: &str = "gio"; /// used to get information about fifo files pub const LSOF: &str = "lsof"; /// neovim executable diff --git a/src/event_exec.rs b/src/event_exec.rs index 8f7bc38b..3bbdf307 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -13,6 +13,7 @@ use crate::action_map::ActionMap; use crate::completion::InputCompleted; use crate::config::Colors; use crate::constant_strings_paths::DIFF; +use crate::constant_strings_paths::GIO; use crate::constant_strings_paths::MEDIAINFO; use crate::constant_strings_paths::NITROGEN; use crate::constant_strings_paths::SSHFS_EXECUTABLE; @@ -32,6 +33,7 @@ use crate::opener::{ use crate::password::{PasswordKind, PasswordUsage}; use crate::preview::ExtensionKind; use crate::preview::Preview; +use crate::removable_devices::RemovableDevices; use crate::selectable_content::SelectableContent; use crate::shell_parser::ShellCommandParser; use crate::status::Status; @@ -738,6 +740,7 @@ impl EventAction { LeaveMode::marks_jump(status, colors)? } Mode::Navigate(Navigate::Compress) => LeaveMode::compress(status)?, + Mode::Navigate(Navigate::RemovableDevices) => (), Mode::InputCompleted(InputCompleted::Exec) => LeaveMode::exec(status.selected())?, Mode::InputCompleted(InputCompleted::Search) => { must_refresh = false; @@ -995,6 +998,18 @@ impl EventAction { Ok(()) } + pub fn removable_devices(status: &mut Status) -> Result<()> { + if !is_program_in_path(GIO) { + write_log_line("gio must be installed.".to_owned()); + return Ok(()); + } + status.removable_devices = RemovableDevices::from_gio(); + status + .selected() + .set_mode(Mode::Navigate(Navigate::RemovableDevices)); + Ok(()) + } + /// Open the config file. pub fn open_config(status: &mut Status) -> Result<()> { match status.opener.open(&path::PathBuf::from( diff --git a/src/keybindings.rs b/src/keybindings.rs index 8fc46621..c5f7bddd 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -105,6 +105,7 @@ impl Bindings { (Key::Alt('o'), ActionMap::TrashOpen), (Key::Alt('p'), ActionMap::TogglePreviewSecond), (Key::Alt('r'), ActionMap::RemoteMount), + (Key::Alt('R'), ActionMap::RemovableDevices), (Key::Alt('x'), ActionMap::TrashEmpty), (Key::Alt('z'), ActionMap::TreeFoldAll), (Key::Ctrl('c'), ActionMap::CopyFilename), diff --git a/src/mode.rs b/src/mode.rs index 429f5b77..e302f1f4 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -134,6 +134,8 @@ pub enum Navigate { Trash, /// Manipulate an encrypted device EncryptedDrive, + /// Removable devices + RemovableDevices, /// Manipulate an iso file to mount it Marks(MarkAction), /// Pick a compression method @@ -221,6 +223,9 @@ impl fmt::Display for Mode { Mode::Navigate(Navigate::EncryptedDrive) => { write!(f, "Encrypted devices :") } + Mode::Navigate(Navigate::RemovableDevices) => { + write!(f, "Removable devices :") + } Mode::Navigate(Navigate::CliInfo) => write!(f, "Display infos :"), Mode::NeedConfirmation(_) => write!(f, "Y/N :"), Mode::Preview => write!(f, "Preview : "), diff --git a/src/removable_devices.rs b/src/removable_devices.rs index 763178d9..a67e8a9f 100644 --- a/src/removable_devices.rs +++ b/src/removable_devices.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use log::info; -use crate::impl_selectable_content; +use crate::{constant_strings_paths::GIO, impl_selectable_content}; #[derive(Debug, Clone, Default)] pub struct RemovableDevices { @@ -10,13 +10,17 @@ pub struct RemovableDevices { } impl RemovableDevices { - fn from_gio() -> Option { - let Ok(output) = std::process::Command::new("gio").args(["-li"]).output() else { + pub fn from_gio() -> Option { + let Ok(output) = std::process::Command::new(GIO) + .args(["mount", "-li"]) + .output() + else { return None; }; let Ok(stdout) = String::from_utf8(output.stdout) else { return None; }; + log::info!("gio {stdout}"); let content: Vec<_> = stdout .lines() @@ -38,16 +42,18 @@ impl RemovableDevices { pub struct Removable { pub device_name: String, pub path: String, - is_mounted: bool, + pub is_mounted: bool, } impl Removable { fn from_gio(line: String) -> Result { + let line = line.replace("activation_root=", ""); let device_name = line.replace("mtp:://", ""); let uid = users::get_current_uid(); let path = format!("/run/user/{uid}/gvfs/{line}"); let pb_path = std::path::Path::new(&path); let is_mounted = pb_path.exists() && pb_path.read_dir()?.next().is_some(); + log::info!("gio {line} - is_mounted {is_mounted}"); Ok(Self { device_name, path, diff --git a/src/status.rs b/src/status.rs index eab4312a..c448958d 100644 --- a/src/status.rs +++ b/src/status.rs @@ -146,7 +146,7 @@ impl Status { Tab::new(&args, height, users_cache, settings, &mount_points)?, Tab::new(&args, height, users_cache2, settings, &mount_points)?, ]; - let removable_devices = None; + let removable_devices = RemovableDevices::from_gio(); Ok(Self { tabs, index, diff --git a/src/term_manager.rs b/src/term_manager.rs index bd59ffb0..ed890a5a 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -656,6 +656,7 @@ impl<'a> WinSecondary<'a> { Navigate::CliInfo => self.cli_info(self.status, canvas), Navigate::Compress => self.compress(canvas, &self.status.compression), Navigate::EncryptedDrive => self.encrypted_drive(self.status, self.tab, canvas), + Navigate::RemovableDevices => self.removable_devices(self.status, self.tab, canvas), Navigate::History => self.history(canvas, &self.tab.history), Navigate::Jump => self.destination(canvas, &self.status.flagged), Navigate::Marks(_) => self.marks(self.status, canvas), @@ -864,6 +865,26 @@ impl<'a> WinSecondary<'a> { Ok(()) } + fn removable_devices(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + canvas.print_with_attr(2, 3, ENCRYPTED_DEVICE_BINDS, Self::ATTR_YELLOW)?; + if let Some(removable) = &status.removable_devices { + for (i, removable) in removable.content.iter().enumerate() { + let row = calc_line_row(i, &tab.window) + 2; + let mut not_mounted_attr = Attr::default(); + let mut mounted_attr = Attr::from(Color::BLUE); + if i == status.encrypted_devices.index() { + not_mounted_attr.effect |= Effect::REVERSE; + mounted_attr.effect |= Effect::REVERSE; + } + if removable.is_mounted { + canvas.print_with_attr(row, 3, &removable.device_name, mounted_attr)?; + } else { + canvas.print_with_attr(row, 3, &removable.device_name, not_mounted_attr)?; + } + } + } + Ok(()) + } /// Display a list of edited (deleted, copied, moved) files for confirmation fn confirm( &self, From 37a36500a368d8e150534f09fde4efe557f858c7 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 17:13:33 +0200 Subject: [PATCH 056/168] removable not working, wrong command passed to gio, everything else looks good --- src/event_dispatch.rs | 9 ++++++++ src/removable_devices.rs | 48 +++++++++++++++++++++++++++++++++++----- src/status.rs | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index 196a8143..8dfbe952 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -95,6 +95,15 @@ impl EventDispatcher { Mode::Navigate(Navigate::EncryptedDrive) if c == 'm' => status.mount_encrypted_drive(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'g' => status.go_to_encrypted_drive(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'u' => status.umount_encrypted_drive(), + Mode::Navigate(Navigate::RemovableDevices) if c == 'm' => { + status.mount_removable_device() + } + Mode::Navigate(Navigate::RemovableDevices) if c == 'g' => { + status.go_to_removable_device() + } + Mode::Navigate(Navigate::RemovableDevices) if c == 'u' => { + status.umount_removable_device() + } Mode::Navigate(Navigate::Jump) if c == ' ' => status.jump_remove_selected_flagged(), Mode::Navigate(Navigate::Jump) if c == 'u' => status.clear_flags_and_reset_view(), Mode::Navigate(Navigate::Jump) if c == 'x' => status.delete_single_flagged(), diff --git a/src/removable_devices.rs b/src/removable_devices.rs index a67e8a9f..6f793f84 100644 --- a/src/removable_devices.rs +++ b/src/removable_devices.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; -use log::info; use crate::{constant_strings_paths::GIO, impl_selectable_content}; @@ -36,6 +35,14 @@ impl RemovableDevices { Some(Self { content, index: 0 }) } } + + pub fn current(&mut self) -> Option<&mut Removable> { + if self.content.is_empty() { + None + } else { + Some(&mut self.content[self.index]) + } + } } #[derive(Debug, Clone, Default)] @@ -47,19 +54,50 @@ pub struct Removable { impl Removable { fn from_gio(line: String) -> Result { - let line = line.replace("activation_root=", ""); - let device_name = line.replace("mtp:://", ""); + let line = line.replace("activation_root=mtp://", ""); + let device_name = line; let uid = users::get_current_uid(); - let path = format!("/run/user/{uid}/gvfs/{line}"); + let path = format!("/run/user/{uid}/gvfs/mtp:host={device_name}"); let pb_path = std::path::Path::new(&path); let is_mounted = pb_path.exists() && pb_path.read_dir()?.next().is_some(); - log::info!("gio {line} - is_mounted {is_mounted}"); + log::info!("gio {device_name} - is_mounted {is_mounted}"); Ok(Self { device_name, path, is_mounted, }) } + + pub fn mount(&mut self) -> Result<()> { + if self.is_mounted { + return Err(anyhow!("Already mounted {name}", name = self.device_name)); + } + self.is_mounted = std::process::Command::new(GIO) + .args(vec![ + "mount", + &format!("mtp://{name}", name = self.device_name), + ]) + .spawn()? + .wait()? + .success(); + Ok(()) + } + + pub fn umount(&mut self) -> Result<()> { + if !self.is_mounted { + return Err(anyhow!("Not mounted {name}", name = self.device_name)); + } + self.is_mounted = std::process::Command::new(GIO) + .args(vec![ + "mount", + &format!("mtp://{name}", name = self.device_name), + "-u", + ]) + .spawn()? + .wait()? + .success(); + Ok(()) + } } impl_selectable_content!(Removable, RemovableDevices); diff --git a/src/status.rs b/src/status.rs index c448958d..03f209db 100644 --- a/src/status.rs +++ b/src/status.rs @@ -729,6 +729,49 @@ impl Status { } } + pub fn mount_removable_device(&mut self) -> Result<()> { + let Some(devices) = &mut self.removable_devices else { + return Ok(()); + }; + let Some(device) = devices.current() else { + return Ok(()); + }; + if device.is_mounted { + return Ok(()); + } + device.mount()?; + Ok(()) + } + + pub fn umount_removable_device(&mut self) -> Result<()> { + let Some(devices) = &mut self.removable_devices else { + return Ok(()); + }; + let Some(device) = devices.current() else { + return Ok(()); + }; + if !device.is_mounted { + return Ok(()); + } + device.umount()?; + Ok(()) + } + + pub fn go_to_removable_device(&mut self) -> Result<()> { + let Some(devices) = &self.removable_devices else { + return Ok(()); + }; + let Some(device) = devices.selected() else { + return Ok(()); + }; + if !device.is_mounted { + return Ok(()); + } + let path = std::path::PathBuf::from(&device.path); + self.selected().set_pathcontent(&path)?; + self.selected().refresh_view() + } + /// Ask for a password of some kind (sudo or device passphrase). pub fn ask_password( &mut self, From 11c6fc48215162b72bb7faff90d5048eba9727dc Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 21:22:31 +0200 Subject: [PATCH 057/168] mtp mount with gio --- build.rs | 2 +- development.md | 6 ++-- src/filter.rs | 4 ++- src/help.rs | 6 ++-- src/removable_devices.rs | 77 ++++++++++++++++++++++++++++++---------- src/status.rs | 4 +-- src/utils.rs | 8 +++++ 7 files changed, 79 insertions(+), 28 deletions(-) diff --git a/build.rs b/build.rs index 842ea2cd..3157f934 100644 --- a/build.rs +++ b/build.rs @@ -7,7 +7,7 @@ use std::borrow::Borrow; fn main() { let Ok(mut default_config_files) = std::env::current_dir() else { eprintln!("Environment variable $PWD should be set. Couldn't find the source folder."); - return + return; }; default_config_files.push("config_files/fm"); diff --git a/development.md b/development.md index 6fba5cdb..aae20645 100644 --- a/development.md +++ b/development.md @@ -590,9 +590,9 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Flag the selected file if no file is flagged before entering delete mode or trashing a file. - [x] FIX: fuzzy finder should do nothing if escape (quit?) is inputed - [x] preview openoffice / office documents as images. Don't use pandoc for .doc .odb etc. previews -- [ ] mtp mount with gio [nnn plugin](https://github.com/jarun/nnn/blob/master/plugins/mtpmount) - - [ ] add MTP mount points to shortcuts - - [ ] list, mount, unmount mtp mount points +- [x] mtp mount with gio [nnn plugin](https://github.com/jarun/nnn/blob/master/plugins/mtpmount) + - [x] add MTP mount points to shortcuts + - [x] list, mount, unmount mtp mount points - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/filter.rs b/src/filter.rs index 8678f689..8e7c1878 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -50,7 +50,9 @@ impl FilterKind { if keep_dirs && fileinfo.is_dir() { true } else { - let Ok(re) = Regex::new(filename) else { return false }; + let Ok(re) = Regex::new(filename) else { + return false; + }; re.is_match(&fileinfo.filename) } } diff --git a/src/help.rs b/src/help.rs index 9efff9fc..c626f0b6 100644 --- a/src/help.rs +++ b/src/help.rs @@ -50,7 +50,7 @@ static HELP_TO_FORMAT: &str = " {MarksNew:<10}: mark current path {MarksJump:<10}: jump to a mark {SearchNext:<10}: search next matching element -{FuzzyFind:<10}: fuzzy finder +{FuzzyFind:<10}: fuzzy finder for file {FuzzyFindLine:<10}: fuzzy finder for line {FuzzyFindHelp:<10}: fuzzy finder from help {RefreshView:<10}: refresh view @@ -98,7 +98,9 @@ Navigate as usual. Most actions works as in 'normal' view. {History:<10}: HISTORY {Shortcut:<10}: SHORTCUT {EncryptedDrive:<10}: ENCRYPTED DRIVE - (m: open & mount, u: unmount & close) + (m: open & mount, u: unmount & close, g: go there) +{RemovableDevices:<10}: REMOVABLE DEVICES + (m: mount, u: unmount, g: go there) {Search:<10}: SEARCH {Command:<10}: COMMAND {Bulk:<10}: BULK diff --git a/src/removable_devices.rs b/src/removable_devices.rs index 6f793f84..27bd270e 100644 --- a/src/removable_devices.rs +++ b/src/removable_devices.rs @@ -1,7 +1,12 @@ use anyhow::{anyhow, Result}; -use crate::{constant_strings_paths::GIO, impl_selectable_content}; +use crate::{constant_strings_paths::GIO, impl_selectable_content, utils::is_dir_empty}; +/// Holds info about removable devices. +/// We can navigate this struct. +/// It requires a special method, oustide of `SelectableContent` trait, +/// allowing the mutate the inner element. +/// It can create itself from a `gio` command output. #[derive(Debug, Clone, Default)] pub struct RemovableDevices { pub content: Vec, @@ -9,6 +14,11 @@ pub struct RemovableDevices { } impl RemovableDevices { + /// Creates itself from a `gio mount -l` command output. + /// + /// Output lines are filtered, looking for `activation_root`. + /// Then we create a `Removable` instance for every line. + /// If no line match or if any error happens, we return `None`. pub fn from_gio() -> Option { let Ok(output) = std::process::Command::new(GIO) .args(["mount", "-li"]) @@ -19,13 +29,11 @@ impl RemovableDevices { let Ok(stdout) = String::from_utf8(output.stdout) else { return None; }; - log::info!("gio {stdout}"); let content: Vec<_> = stdout .lines() .filter(|line| line.contains("activation_root")) - .map(|line| line.to_owned()) - .map(|line| Removable::from_gio(line)) + .map(Removable::from_gio) .filter_map(|removable| removable.ok()) .collect(); @@ -36,7 +44,9 @@ impl RemovableDevices { } } - pub fn current(&mut self) -> Option<&mut Removable> { + /// Mutable reference to the selected element. + /// None if the content is empty (aka no removable device detected) + pub fn selected_mut(&mut self) -> Option<&mut Removable> { if self.content.is_empty() { None } else { @@ -45,6 +55,8 @@ impl RemovableDevices { } } +/// Holds a MTP device name, a path and a flag set to true +/// if the device is mounted. #[derive(Debug, Clone, Default)] pub struct Removable { pub device_name: String, @@ -53,13 +65,21 @@ pub struct Removable { } impl Removable { - fn from_gio(line: String) -> Result { - let line = line.replace("activation_root=mtp://", ""); - let device_name = line; + /// Creates a `Removable` instance from a filtered `gio` command output. + /// + /// `gio mount -l` will return a lot of information about mount points, + /// including MTP (aka Android) devices. + /// We don't check if the device actually exists, we just create the instance. + fn from_gio(line: &str) -> Result { + let device_name = line + .replace("activation_root=mtp://", "") + .replace('/', "") + .trim() + .to_owned(); let uid = users::get_current_uid(); let path = format!("/run/user/{uid}/gvfs/mtp:host={device_name}"); let pb_path = std::path::Path::new(&path); - let is_mounted = pb_path.exists() && pb_path.read_dir()?.next().is_some(); + let is_mounted = pb_path.exists() && !is_dir_empty(pb_path)?; log::info!("gio {device_name} - is_mounted {is_mounted}"); Ok(Self { device_name, @@ -68,36 +88,55 @@ impl Removable { }) } + /// Mount a non mounted removable device. + /// `Err` if the device is already mounted. + /// Runs a `gio mount $device_name` command and check + /// the result. + /// The `is_mounted` flag is updated accordingly to the result. pub fn mount(&mut self) -> Result<()> { if self.is_mounted { return Err(anyhow!("Already mounted {name}", name = self.device_name)); } self.is_mounted = std::process::Command::new(GIO) - .args(vec![ - "mount", - &format!("mtp://{name}", name = self.device_name), - ]) + .args(vec!["mount", &self.format_for_gio()]) .spawn()? .wait()? .success(); + log::info!( + target: "special", + "Mounted {device}. Success ? {success}", + device=self.device_name, success=self.is_mounted + ); Ok(()) } + /// Unount a mounted removable device. + /// `Err` if the device isnt mounted. + /// Runs a `gio mount $device_name` command and check + /// the result. + /// The `is_mounted` flag is updated accordingly to the result. pub fn umount(&mut self) -> Result<()> { if !self.is_mounted { return Err(anyhow!("Not mounted {name}", name = self.device_name)); } - self.is_mounted = std::process::Command::new(GIO) - .args(vec![ - "mount", - &format!("mtp://{name}", name = self.device_name), - "-u", - ]) + self.is_mounted = !std::process::Command::new(GIO) + .args(vec!["mount", &self.format_for_gio(), "-u"]) .spawn()? .wait()? .success(); + log::info!( + target: "special", + "Unmounted {device}. Success ? {success}", + device=self.device_name, + success=!self.is_mounted + ); Ok(()) } + + /// Format itself as a valid `gio mount $device` argument. + fn format_for_gio(&self) -> String { + format!("mtp://{name}", name = self.device_name) + } } impl_selectable_content!(Removable, RemovableDevices); diff --git a/src/status.rs b/src/status.rs index 03f209db..6176a750 100644 --- a/src/status.rs +++ b/src/status.rs @@ -733,7 +733,7 @@ impl Status { let Some(devices) = &mut self.removable_devices else { return Ok(()); }; - let Some(device) = devices.current() else { + let Some(device) = devices.selected_mut() else { return Ok(()); }; if device.is_mounted { @@ -747,7 +747,7 @@ impl Status { let Some(devices) = &mut self.removable_devices else { return Ok(()); }; - let Some(device) = devices.current() else { + let Some(device) = devices.selected_mut() else { return Ok(()); }; if !device.is_mounted { diff --git a/src/utils.rs b/src/utils.rs index 6cc6dba9..13bf17fa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -170,3 +170,11 @@ pub fn clear_tmp_file() { let _ = std::fs::remove_file(THUMBNAIL_PATH); let _ = std::fs::remove_file(CALC_PDF_PATH); } + +/// True if the directory is empty, +/// False if it's not. +/// Err if the path doesn't exists or isn't accessible by +/// the user. +pub fn is_dir_empty(path: &std::path::Path) -> Result { + Ok(path.read_dir()?.next().is_none()) +} From 57e952b4be4482384a4dbb3ade9c9fd7ac7908e1 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 21:31:08 +0200 Subject: [PATCH 058/168] FIX: back (in history) does nothing --- development.md | 14 +++----------- src/action_map.rs | 2 +- src/event_exec.rs | 6 +----- src/tab.rs | 3 +++ 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/development.md b/development.md index aae20645..ed6ab3fe 100644 --- a/development.md +++ b/development.md @@ -670,17 +670,9 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. All of this stuff can be done easily through a shell command or automatically. I'm not sure I wan't to bloat fm with it. -- [ ] auto mount usb keys ??? [rusb](https://github.com/a1ien/rusb) -- just use udiskie (started automatically) and udiskie-umount /mount/point - just use udiskie -- [ ] mtp... but fast [libmtp.rs](https://docs.rs/libmtp-rs/0.7.7/libmtp_rs/) -- [ ] connexion to remote servers [removefs](https://crates.io/crates/remotefs) [termscp](https://crates.io/crates/termscp) - - - ssh - - sftp - - ftp - - google drive - - or just use sshfs... +- [ ] auto mount usb keys ??? just use udiskie (started automatically) and udiskie-umount /mount/point +- [ ] cloud services (apple, microsoft, google, dropbox etc.) +- [ ] ftp ## Sources diff --git a/src/action_map.rs b/src/action_map.rs index c12cf9dc..d20bf896 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -110,7 +110,7 @@ impl ActionMap { pub fn matcher(&self, status: &mut Status, colors: &Colors) -> Result<()> { let current_tab = status.selected(); match self { - ActionMap::Back => EventAction::back(status, colors), + ActionMap::Back => EventAction::back(current_tab, colors), ActionMap::BackTab => EventAction::backtab(status), ActionMap::Backspace => EventAction::backspace(current_tab), ActionMap::Bulk => EventAction::bulk(status), diff --git a/src/event_exec.rs b/src/event_exec.rs index 3bbdf307..7742f133 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -462,11 +462,7 @@ impl EventAction { } /// Move back in history to the last visited directory. - pub fn back(status: &mut Status, colors: &Colors) -> Result<()> { - if status.selected_non_mut().history.content.len() <= 1 { - return Ok(()); - } - let tab = status.selected(); + pub fn back(tab: &mut Tab, colors: &Colors) -> Result<()> { tab.back(colors) } diff --git a/src/tab.rs b/src/tab.rs index 8873311d..902a4b62 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -324,6 +324,9 @@ impl Tab { } pub fn back(&mut self, colors: &Colors) -> Result<()> { + if self.history.content.is_empty() { + return Ok(()); + } let Some((path, file)) = self.history.content.pop() else { return Ok(()); }; From 4e488629735b1e5161e87d4452a69e1197488fd5 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 21:36:01 +0200 Subject: [PATCH 059/168] replace status by tab if possible --- src/action_map.rs | 2 +- src/event_exec.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/action_map.rs b/src/action_map.rs index d20bf896..8e725a5a 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -161,7 +161,7 @@ impl ActionMap { ActionMap::NewDir => EventAction::new_dir(current_tab), ActionMap::NewFile => EventAction::new_file(current_tab), ActionMap::NvimFilepicker => EventAction::nvim_filepicker(status), - ActionMap::NvimSetAddress => EventAction::set_nvim_server(status), + ActionMap::NvimSetAddress => EventAction::set_nvim_server(current_tab), ActionMap::OpenConfig => EventAction::open_config(status), ActionMap::OpenFile => EventAction::open_file(status), ActionMap::PageDown => EventAction::page_down(status, colors), diff --git a/src/event_exec.rs b/src/event_exec.rs index 7742f133..b9f1e42f 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -447,10 +447,8 @@ impl EventAction { Ok(()) } - pub fn set_nvim_server(status: &mut Status) -> Result<()> { - status - .selected() - .set_mode(Mode::InputSimple(InputSimple::SetNvimAddr)); + pub fn set_nvim_server(tab: &mut Tab) -> Result<()> { + tab.set_mode(Mode::InputSimple(InputSimple::SetNvimAddr)); Ok(()) } From d2290a1e36dc3e4ee11ecd56372f6f2e017debf4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 21:36:06 +0200 Subject: [PATCH 060/168] dev --- development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/development.md b/development.md index ed6ab3fe..1a44ebd3 100644 --- a/development.md +++ b/development.md @@ -593,6 +593,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] mtp mount with gio [nnn plugin](https://github.com/jarun/nnn/blob/master/plugins/mtpmount) - [x] add MTP mount points to shortcuts - [x] list, mount, unmount mtp mount points +- [ ] make every Navigable thing `None` until first use and then `Some(default)` - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself From c801ba97f631f89c7b17f3aab9021e2854440310 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 21:37:08 +0200 Subject: [PATCH 061/168] add MTP in removable help line --- src/help.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/help.rs b/src/help.rs index c626f0b6..217a9635 100644 --- a/src/help.rs +++ b/src/help.rs @@ -99,7 +99,7 @@ Navigate as usual. Most actions works as in 'normal' view. {Shortcut:<10}: SHORTCUT {EncryptedDrive:<10}: ENCRYPTED DRIVE (m: open & mount, u: unmount & close, g: go there) -{RemovableDevices:<10}: REMOVABLE DEVICES +{RemovableDevices:<10}: REMOVABLE MTP DEVICES (m: mount, u: unmount, g: go there) {Search:<10}: SEARCH {Command:<10}: COMMAND From 189943723257a15f1a21fad66f45e7cf57a3147e Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 26 Oct 2023 21:46:11 +0200 Subject: [PATCH 062/168] improve gio parsing --- src/removable_devices.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/removable_devices.rs b/src/removable_devices.rs index 27bd270e..323bac12 100644 --- a/src/removable_devices.rs +++ b/src/removable_devices.rs @@ -1,6 +1,10 @@ use anyhow::{anyhow, Result}; -use crate::{constant_strings_paths::GIO, impl_selectable_content, utils::is_dir_empty}; +use crate::{ + constant_strings_paths::GIO, + impl_selectable_content, + utils::{is_dir_empty, is_program_in_path}, +}; /// Holds info about removable devices. /// We can navigate this struct. @@ -20,6 +24,9 @@ impl RemovableDevices { /// Then we create a `Removable` instance for every line. /// If no line match or if any error happens, we return `None`. pub fn from_gio() -> Option { + if !is_program_in_path(GIO) { + return None; + } let Ok(output) = std::process::Command::new(GIO) .args(["mount", "-li"]) .output() @@ -99,6 +106,8 @@ impl Removable { } self.is_mounted = std::process::Command::new(GIO) .args(vec!["mount", &self.format_for_gio()]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) .spawn()? .wait()? .success(); @@ -121,6 +130,8 @@ impl Removable { } self.is_mounted = !std::process::Command::new(GIO) .args(vec!["mount", &self.format_for_gio(), "-u"]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) .spawn()? .wait()? .success(); From cdc6952e20f67db4fba3a79e49fa7230e1da2e23 Mon Sep 17 00:00:00 2001 From: quentin konieczko Date: Fri, 27 Oct 2023 10:51:49 +0200 Subject: [PATCH 063/168] make removable none at startup --- development.md | 1 + src/status.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/development.md b/development.md index 1a44ebd3..3481184f 100644 --- a/development.md +++ b/development.md @@ -593,6 +593,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] mtp mount with gio [nnn plugin](https://github.com/jarun/nnn/blob/master/plugins/mtpmount) - [x] add MTP mount points to shortcuts - [x] list, mount, unmount mtp mount points +- [ ] remove dependencies - [ ] make every Navigable thing `None` until first use and then `Some(default)` - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/status.rs b/src/status.rs index 6176a750..4d03549f 100644 --- a/src/status.rs +++ b/src/status.rs @@ -146,7 +146,7 @@ impl Status { Tab::new(&args, height, users_cache, settings, &mount_points)?, Tab::new(&args, height, users_cache2, settings, &mount_points)?, ]; - let removable_devices = RemovableDevices::from_gio(); + let removable_devices = None; Ok(Self { tabs, index, From 66fdd73adc234b55377280d76ac78add7f4982d8 Mon Sep 17 00:00:00 2001 From: quentin konieczko Date: Fri, 27 Oct 2023 11:13:07 +0200 Subject: [PATCH 064/168] make bulk menu optional --- src/bulkrename.rs | 3 +-- src/event_exec.rs | 7 ++++--- src/status.rs | 32 ++++++++++++++++++++++++++++++-- src/term_manager.rs | 19 +++++++++++-------- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/bulkrename.rs b/src/bulkrename.rs index 1c4e12fa..9729ac75 100644 --- a/src/bulkrename.rs +++ b/src/bulkrename.rs @@ -237,8 +237,7 @@ impl Default for Bulk { impl Bulk { /// Execute the selected bulk method depending on the index. /// First method is a rename of selected files, - /// Second is the creation of files, - /// Third is the creation of folders. + /// Second is the creation of files or folders, pub fn execute_bulk(&self, status: &Status) -> Result<()> { match self.index { 0 => Bulkrename::renamer(status.filtered_flagged_files())?.rename(&status.opener), diff --git a/src/event_exec.rs b/src/event_exec.rs index b9f1e42f..9ab46882 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -168,6 +168,7 @@ impl EventAction { /// can edit the selected filenames. /// Once the temp file is saved, those file names are changed. pub fn bulk(status: &mut Status) -> Result<()> { + status.init_bulk(); status.selected().set_mode(Mode::Navigate(Navigate::Bulk)); Ok(()) } @@ -534,7 +535,7 @@ impl EventAction { Mode::Navigate(Navigate::Shortcut) => tab.shortcut.prev(), Mode::Navigate(Navigate::Marks(_)) => status.marks.prev(), Mode::Navigate(Navigate::Compress) => status.compression.prev(), - Mode::Navigate(Navigate::Bulk) => status.bulk.prev(), + Mode::Navigate(Navigate::Bulk) => status.bulk_prev(), Mode::Navigate(Navigate::ShellMenu) => status.shell_menu.prev(), Mode::Navigate(Navigate::CliInfo) => status.cli_info.prev(), Mode::Navigate(Navigate::EncryptedDrive) => status.encrypted_devices.prev(), @@ -556,7 +557,7 @@ impl EventAction { Mode::Navigate(Navigate::Shortcut) => status.selected().shortcut.next(), Mode::Navigate(Navigate::Marks(_)) => status.marks.next(), Mode::Navigate(Navigate::Compress) => status.compression.next(), - Mode::Navigate(Navigate::Bulk) => status.bulk.next(), + Mode::Navigate(Navigate::Bulk) => status.bulk_next(), Mode::Navigate(Navigate::ShellMenu) => status.shell_menu.next(), Mode::Navigate(Navigate::CliInfo) => status.cli_info.next(), Mode::Navigate(Navigate::EncryptedDrive) => status.encrypted_devices.next(), @@ -1238,7 +1239,7 @@ impl LeaveMode { } pub fn bulk(status: &mut Status, colors: &Colors) -> Result<()> { - status.bulk.execute_bulk(status)?; + status.execute_bulk()?; status.update_second_pane_for_preview(colors) } diff --git a/src/status.rs b/src/status.rs index 4d03549f..2d60dcc3 100644 --- a/src/status.rs +++ b/src/status.rs @@ -87,7 +87,7 @@ pub struct Status { /// NVIM RPC server address pub nvim_server: String, pub force_clear: bool, - pub bulk: Bulk, + pub bulk: Option, pub shell_menu: ShellMenu, pub cli_info: CliInfo, pub start_folder: std::path::PathBuf, @@ -126,7 +126,7 @@ impl Status { let trash = Trash::new()?; let compression = Compresser::default(); let force_clear = false; - let bulk = Bulk::default(); + let bulk = None; let iso_device = None; let password_holder = PasswordHolder::default(); let sudo_command = None; @@ -176,6 +176,34 @@ impl Status { }) } + /// Creats a new bulk instance if needed + pub fn init_bulk(&mut self) { + if self.bulk.is_none() { + self.bulk = Some(Bulk::default()); + } + } + + pub fn bulk_prev(&mut self) { + self.init_bulk(); + if let Some(bulk) = &mut self.bulk { + bulk.prev(); + } + } + + pub fn bulk_next(&mut self) { + self.init_bulk(); + if let Some(bulk) = &mut self.bulk { + bulk.next(); + } + } + + pub fn execute_bulk(&self) -> Result<()> { + if let Some(bulk) = &self.bulk { + bulk.execute_bulk(&self)?; + } + Ok(()) + } + fn display_wide_enough(term: &Arc) -> Result { Ok(term.term_size()?.0 >= MIN_WIDTH_FOR_DUAL_PANE) } diff --git a/src/term_manager.rs b/src/term_manager.rs index ed890a5a..9c7f30ce 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -714,16 +714,19 @@ impl<'a> WinSecondary<'a> { fn bulk( &self, canvas: &mut dyn Canvas, - selectable: &impl SelectableContent, + selectable: &Option>, ) -> Result<()> { - canvas.print(0, 0, "Action...")?; - let content = &selectable.content(); - for (row, text, attr) in enumerated_colored_iter!(content) { - let mut attr = *attr; - if row == selectable.index() { - attr.effect |= Effect::REVERSE; + if let Some(selectable) = selectable { + canvas.print(0, 0, "Action...")?; + let content = &selectable.content(); + for (row, text, attr) in enumerated_colored_iter!(content) { + let mut attr = *attr; + if row == selectable.index() { + attr.effect |= Effect::REVERSE; + } + let _ = + canvas.print_with_attr(row + ContentWindow::WINDOW_MARGIN_TOP, 4, text, attr); } - let _ = canvas.print_with_attr(row + ContentWindow::WINDOW_MARGIN_TOP, 4, text, attr); } Ok(()) } From 96120045444c0c3eaf42d5ad6d99c5229fbe20fc Mon Sep 17 00:00:00 2001 From: quentin konieczko Date: Fri, 27 Oct 2023 11:15:02 +0200 Subject: [PATCH 065/168] drop status bulk asap --- src/event_exec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event_exec.rs b/src/event_exec.rs index 9ab46882..3bb6ba18 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -1240,6 +1240,7 @@ impl LeaveMode { pub fn bulk(status: &mut Status, colors: &Colors) -> Result<()> { status.execute_bulk()?; + status.bulk = None; status.update_second_pane_for_preview(colors) } From 2dfd74a2630ca7ef22238c45a8d33657cdbf4e85 Mon Sep 17 00:00:00 2001 From: quentin konieczko Date: Fri, 27 Oct 2023 12:44:40 +0200 Subject: [PATCH 066/168] make skim an option for status --- src/status.rs | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/status.rs b/src/status.rs index 2d60dcc3..029c0394 100644 --- a/src/status.rs +++ b/src/status.rs @@ -64,7 +64,7 @@ pub struct Status { pub marks: Marks, /// terminal pub term: Arc, - skimer: Skimer, + skimer: Option, /// do we display one or two tabs ? pub dual_pane: bool, pub system_info: System, @@ -132,7 +132,7 @@ impl Status { let sudo_command = None; let flagged = Flagged::default(); let marks = Marks::read_from_config_file(); - let skimer = Skimer::new(term.clone()); + let skimer = None; //Skimer::new(term.clone()); let index = 0; // unsafe because of UsersCache::with_all_users @@ -270,10 +270,18 @@ impl Status { self.flagged.toggle(path) } + fn skim_init(&mut self) { + self.skimer = Some(Skimer::new(self.term.clone())); + } + /// Replace the tab content with the first result of skim. /// It calls skim, reads its output, then update the tab content. pub fn skim_output_to_tab(&mut self) -> Result<()> { - let skim = self.skimer.search_filename( + self.skim_init(); + let Some(skimer) = &self.skimer else { + return Ok(()); + }; + let skim = skimer.search_filename( self.selected_non_mut() .path_content_str() .context("Couldn't parse current directory")?, @@ -281,14 +289,20 @@ impl Status { let Some(output) = skim.first() else { return Ok(()); }; - self._update_tab_from_skim_output(output) + self._update_tab_from_skim_output(output)?; + self.skimer = None; + Ok(()) } /// Replace the tab content with the first result of skim. /// It calls skim, reads its output, then update the tab content. /// The output is splited at `:` since we only care about the path, not the line number. pub fn skim_line_output_to_tab(&mut self) -> Result<()> { - let skim = self.skimer.search_line_in_file( + self.skim_init(); + let Some(skimer) = &self.skimer else { + return Ok(()); + }; + let skim = skimer.search_line_in_file( self.selected_non_mut() .path_content_str() .context("Couldn't parse current directory")?, @@ -296,14 +310,20 @@ impl Status { let Some(output) = skim.first() else { return Ok(()); }; - self._update_tab_from_skim_line_output(output) + self._update_tab_from_skim_line_output(output)?; + self.skimer = None; + Ok(()) } /// Run a command directly from help. /// Search a command in skim, if it's a keybinding, run it directly. /// If the result can't be parsed, nothing is done. pub fn skim_find_keybinding(&mut self) -> Result<()> { - let skim = self.skimer.search_in_text(self.help.clone()); + self.skim_init(); + let Some(skimer) = &mut self.skimer else { + return Ok(()); + }; + let skim = skimer.search_in_text(self.help.clone()); let Some(output) = skim.first() else { return Ok(()); }; @@ -319,6 +339,7 @@ impl Status { }; let event = Event::Key(key); let _ = self.term.borrow_mut().send_event(event); + self.skimer = None; Ok(()) } From 524ae158be8b302656eeed4bf0ac54555a868242 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 27 Oct 2023 19:14:50 +0200 Subject: [PATCH 067/168] track memory leak: unsuccessful --- src/tab.rs | 3 +++ src/tree.rs | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 902a4b62..8720c56e 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -455,6 +455,9 @@ impl Tab { /// Returns True if the last mode requires a refresh afterwards. pub fn reset_mode(&mut self) -> bool { let must_refresh = self.mode.refresh_required(); + if matches!(self.mode, Mode::InputCompleted(_)) { + self.completion.reset(); + } self.mode = self.previous_mode; self.previous_mode = Mode::Normal; must_refresh diff --git a/src/tree.rs b/src/tree.rs index 57609b65..c8425702 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -73,12 +73,7 @@ impl Node { } fn attr(&self, colors: &Colors) -> Attr { - let mut attr = fileinfo_attr(&self.fileinfo, colors); - if self.fileinfo.is_selected { - attr.effect |= tuikit::attr::Effect::REVERSE - }; - - attr + fileinfo_attr(&self.fileinfo, colors) } fn select(&mut self) { From c2b1fe180dbde2b724d039861eeb3e8179eeb7c4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 27 Oct 2023 21:02:00 +0200 Subject: [PATCH 068/168] use arc clone when cloning term --- src/copy_move.rs | 2 +- src/main.rs | 6 +++--- src/status.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/copy_move.rs b/src/copy_move.rs index cd846675..35c903e1 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -143,7 +143,7 @@ pub fn copy_move( dest: &str, term: Arc, ) -> Result<()> { - let c_term = term.clone(); + 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| { handle_progress_display(&in_mem, &pb, &term, process_info) diff --git a/src/main.rs b/src/main.rs index 486dea8d..d4490301 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ impl FM { exit_wrong_config() }; let term = Arc::new(init_term()?); - let event_reader = EventReader::new(term.clone()); + let event_reader = EventReader::new(Arc::clone(&term)); let event_dispatcher = EventDispatcher::new(config.binds.clone()); let opener = load_opener(OPENER_PATH, &config.terminal).unwrap_or_else(|_| { eprintln!("Couldn't read the opener config file at {OPENER_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/opener.yaml for an example. Using default."); @@ -62,10 +62,10 @@ impl FM { Opener::new(&config.terminal) }); let help = Help::from_keybindings(&config.binds, &opener)?.help; - let display = Display::new(term.clone()); + let display = Display::new(Arc::clone(&term)); let status = Status::new( display.height()?, - term.clone(), + Arc::clone(&term), help, opener, &config.settings, diff --git a/src/status.rs b/src/status.rs index 029c0394..7b432584 100644 --- a/src/status.rs +++ b/src/status.rs @@ -132,7 +132,7 @@ impl Status { let sudo_command = None; let flagged = Flagged::default(); let marks = Marks::read_from_config_file(); - let skimer = None; //Skimer::new(term.clone()); + let skimer = None; let index = 0; // unsafe because of UsersCache::with_all_users @@ -271,7 +271,7 @@ impl Status { } fn skim_init(&mut self) { - self.skimer = Some(Skimer::new(self.term.clone())); + self.skimer = Some(Skimer::new(Arc::clone(&self.term))); } /// Replace the tab content with the first result of skim. @@ -405,7 +405,7 @@ impl Status { .to_owned(), }; - copy_move(cut_or_copy, sources, &dest, self.term.clone())?; + copy_move(cut_or_copy, sources, &dest, Arc::clone(&self.term))?; self.clear_flags_and_reset_view() } From 840789072f0671cb5bf0456d7b2e1308f3383278 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 27 Oct 2023 22:07:40 +0200 Subject: [PATCH 069/168] removed some dependencies, little refactoring --- Cargo.lock | 143 +++++++--------------------------------------- Cargo.toml | 3 - development.md | 2 +- src/event_exec.rs | 31 +++++----- src/opener.rs | 2 +- src/status.rs | 2 +- 6 files changed, 38 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee79ee83..f01590fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,12 +215,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" - [[package]] name = "block" version = "0.1.6" @@ -291,7 +285,7 @@ version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cairo-sys-rs", "glib", "libc", @@ -367,7 +361,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags 1.3.2", + "bitflags", "strsim 0.8.0", "textwrap", "unicode-width", @@ -779,33 +773,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "filedescriptor" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" -dependencies = [ - "libc", - "thiserror", - "winapi", -] - [[package]] name = "filetime" version = "0.2.22" @@ -840,8 +807,6 @@ dependencies = [ "copypasta", "flate2", "fs_extra", - "futures 0.3.28", - "gag", "indicatif", "lazy_static", "log", @@ -853,7 +818,7 @@ dependencies = [ "regex", "rust-lzma", "sanitize-filename", - "serde_yaml 0.9.25", + "serde_yaml 0.9.27", "shellexpand", "skim-qkzk", "strfmt", @@ -868,7 +833,6 @@ dependencies = [ "unicode-segmentation", "url-escape", "users", - "which", "zip", ] @@ -990,16 +954,6 @@ dependencies = [ "thread_local", ] -[[package]] -name = "gag" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a713bee13966e9fbffdf7193af71d54a6b35a0bb34997cd6c9519ebeb5005972" -dependencies = [ - "filedescriptor", - "tempfile", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1054,7 +1008,7 @@ version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" dependencies = [ - "bitflags 1.3.2", + "bitflags", "futures-channel", "futures-core", "futures-executor", @@ -1146,15 +1100,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "humantime" version = "2.1.0" @@ -1318,12 +1263,6 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" - [[package]] name = "lock_api" version = "0.4.11" @@ -1449,7 +1388,7 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cc", "cfg-if", "libc", @@ -1462,7 +1401,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", "libc", "memoffset 0.6.5", @@ -1578,7 +1517,7 @@ version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" dependencies = [ - "bitflags 1.3.2", + "bitflags", "libc", "once_cell", "onig_sys", @@ -1905,7 +1844,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -1914,7 +1853,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -1923,7 +1862,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -2017,19 +1956,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" -dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - [[package]] name = "rustversion" version = "1.0.14" @@ -2096,9 +2022,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] @@ -2115,9 +2041,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -2158,9 +2084,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.25" +version = "0.9.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" dependencies = [ "indexmap 2.0.2", "itoa", @@ -2238,7 +2164,7 @@ checksum = "ca4c3bcd3a72b5925fa30dae1e8ac51f8cf02f94c3dcb63e7f6136023c403374" dependencies = [ "atty", "beef", - "bitflags 1.3.2", + "bitflags", "chrono", "clap 2.34.0", "crossbeam", @@ -2280,7 +2206,7 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" dependencies = [ - "bitflags 1.3.2", + "bitflags", "dlib", "lazy_static", "log", @@ -2451,7 +2377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91" dependencies = [ "bincode", - "bitflags 1.3.2", + "bitflags", "flate2", "fnv", "once_cell", @@ -2510,19 +2436,6 @@ version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" -[[package]] -name = "tempfile" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "term" version = "0.7.0" @@ -2775,7 +2688,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e19c6ab038babee3d50c8c12ff8b910bdb2196f62278776422f50390d8e53d8" dependencies = [ - "bitflags 1.3.2", + "bitflags", "lazy_static", "log", "nix 0.24.3", @@ -3006,7 +2919,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" dependencies = [ - "bitflags 1.3.2", + "bitflags", "downcast-rs", "libc", "nix 0.24.3", @@ -3045,7 +2958,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ - "bitflags 1.3.2", + "bitflags", "wayland-client", "wayland-commons", "wayland-scanner", @@ -3073,18 +2986,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index ae6f99d2..26246ab3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,8 +37,6 @@ content_inspector = "0.2.4" copypasta = "0.8.1" flate2 = "1.0" fs_extra = "1.2.0" -futures = "0.3" -gag = "1.0.0" indicatif = { version = "0.17.1", features= ["in_memory"] } lazy_static = "1.4.0" log = { version = "0.4.0", features = ["std"] } @@ -66,4 +64,3 @@ zip = "0.6.4" tokio = "1" ueberzug = "0.1.0" unicode-segmentation = "1.10.1" -which = "4.4.0" diff --git a/development.md b/development.md index 3481184f..dc087cdc 100644 --- a/development.md +++ b/development.md @@ -593,8 +593,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] mtp mount with gio [nnn plugin](https://github.com/jarun/nnn/blob/master/plugins/mtpmount) - [x] add MTP mount points to shortcuts - [x] list, mount, unmount mtp mount points +- [x] bulk, skim & removable are `None` until first use. - [ ] remove dependencies -- [ ] make every Navigable thing `None` until first use and then `Some(default)` - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/event_exec.rs b/src/event_exec.rs index 3bb6ba18..69501468 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -7,41 +7,33 @@ use std::str::FromStr; use anyhow::{anyhow, Context, Result}; use log::info; use sysinfo::SystemExt; -use which::which; use crate::action_map::ActionMap; use crate::completion::InputCompleted; use crate::config::Colors; -use crate::constant_strings_paths::DIFF; -use crate::constant_strings_paths::GIO; -use crate::constant_strings_paths::MEDIAINFO; -use crate::constant_strings_paths::NITROGEN; -use crate::constant_strings_paths::SSHFS_EXECUTABLE; -use crate::constant_strings_paths::{CONFIG_PATH, DEFAULT_DRAGNDROP}; +use crate::constant_strings_paths::{ + CONFIG_PATH, DEFAULT_DRAGNDROP, DIFF, GIO, MEDIAINFO, NITROGEN, SSHFS_EXECUTABLE, +}; use crate::cryptsetup::{lsblk_and_cryptsetup_installed, BlockDeviceAction}; use crate::fileinfo::FileKind; use crate::filter::FilterKind; -use crate::log::read_log; -use crate::log::write_log_line; -use crate::mocp::is_mocp_installed; -use crate::mocp::Mocp; +use crate::log::{read_log, write_log_line}; +use crate::mocp::{is_mocp_installed, Mocp}; use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation}; use crate::opener::{ execute_and_capture_output, execute_and_capture_output_without_check, execute_in_child, execute_in_child_without_output_with_path, }; use crate::password::{PasswordKind, PasswordUsage}; -use crate::preview::ExtensionKind; -use crate::preview::Preview; +use crate::preview::{ExtensionKind, Preview}; use crate::removable_devices::RemovableDevices; use crate::selectable_content::SelectableContent; use crate::shell_parser::ShellCommandParser; use crate::status::Status; use crate::tab::Tab; -use crate::utils::is_program_in_path; use crate::utils::{ - args_is_empty, disk_used_by_path, is_sudo_command, open_in_current_neovim, opt_mount_point, - string_to_path, + args_is_empty, disk_used_by_path, is_program_in_path, is_sudo_command, open_in_current_neovim, + opt_mount_point, string_to_path, }; /// Links events from tuikit to custom actions. @@ -1314,6 +1306,9 @@ impl LeaveMode { /// Execute a shell command typed by the user. /// pipes and redirections aren't supported /// but expansions are supported + /// Returns `Ok(true)` if a refresh is required, + /// `Ok(false)` if we should stay in the current mode (aka, a password is required) + /// It won't return an `Err` if the command fail. pub fn shell(status: &mut Status) -> Result { let shell_command = status.selected_non_mut().input.string(); let mut args = ShellCommandParser::new(&shell_command).compute(status)?; @@ -1328,9 +1323,9 @@ impl LeaveMode { status.ask_password(PasswordKind::SUDO, None, PasswordUsage::SUDOCOMMAND)?; Ok(false) } else { - let Ok(executable) = which(executable) else { + if !is_program_in_path(&executable) { return Ok(true); - }; + } let current_directory = status .selected_non_mut() .directory_of_selected()? diff --git a/src/opener.rs b/src/opener.rs index f6d5760f..1f360014 100644 --- a/src/opener.rs +++ b/src/opener.rs @@ -480,7 +480,7 @@ where P: AsRef, { info!("execute_in_child_without_output_with_path. executable: {exe:?}, arguments: {args:?}"); - let params = if let Some(args) = args { args } else { &[] }; + let params = args.unwrap_or(&[]); Ok(Command::new(exe) .stdin(Stdio::null()) .stdout(Stdio::null()) diff --git a/src/status.rs b/src/status.rs index 7b432584..128a76ca 100644 --- a/src/status.rs +++ b/src/status.rs @@ -199,7 +199,7 @@ impl Status { pub fn execute_bulk(&self) -> Result<()> { if let Some(bulk) = &self.bulk { - bulk.execute_bulk(&self)?; + bulk.execute_bulk(self)?; } Ok(()) } From 62b7792ea488680928ba3a927a95d0d784c7a84b Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 29 Oct 2023 21:16:06 +0100 Subject: [PATCH 070/168] dissociate preview for files and preview for directories --- src/event_exec.rs | 8 +------- src/preview.rs | 35 ++++++++++++++++++++++------------- src/status.rs | 12 +++++++++--- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index 69501468..4679df5f 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -234,13 +234,7 @@ impl EventAction { }; match file_info.file_kind { FileKind::NormalFile => { - let preview = Preview::new( - file_info, - &unmutable_tab.path_content.users_cache, - status, - colors, - ) - .unwrap_or_default(); + let preview = Preview::new(file_info).unwrap_or_default(); status.selected().set_mode(Mode::Preview); status.selected().window.reset(preview.len()); status.selected().preview = preview; diff --git a/src/preview.rs b/src/preview.rs index 1f8a858b..bc00d829 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -113,26 +113,35 @@ impl Preview { Self::Empty } - /// Creates a new preview instance based on the filekind and the extension of - /// the file. - /// Sometimes it reads the content of the file, sometimes it delegates - /// it to the display method. - pub fn new( + /// Creates a new `Directory` from the file_info + /// It explores recursivelly the directory and creates a tree. + /// The recursive exploration is limited to depth 2. + pub fn directory( file_info: &FileInfo, users_cache: &UsersCache, status: &Status, colors: &Colors, ) -> Result { + Ok(Self::Directory(Directory::new( + &file_info.path, + users_cache, + colors, + &status.selected_non_mut().filter, + status.selected_non_mut().show_hidden, + Some(2), + )?)) + } + /// Creates a new preview instance based on the filekind and the extension of + /// the file. + /// Sometimes it reads the content of the file, sometimes it delegates + /// it to the display method. + pub fn new(file_info: &FileInfo) -> Result { clear_tmp_file(); match file_info.file_kind { - FileKind::Directory => Ok(Self::Directory(Directory::new( - &file_info.path, - users_cache, - colors, - &status.selected_non_mut().filter, - status.selected_non_mut().show_hidden, - Some(2), - )?)), + FileKind::Directory => Err(anyhow!( + "{path} is a directory", + path = file_info.path.display() + )), FileKind::NormalFile => { let extension = &file_info.extension.to_lowercase(); match ExtensionKind::matcher(extension) { diff --git a/src/status.rs b/src/status.rs index 128a76ca..6e8c2aa5 100644 --- a/src/status.rs +++ b/src/status.rs @@ -22,6 +22,7 @@ use crate::config::{Colors, Settings}; use crate::constant_strings_paths::{NVIM, SS, TUIS_PATH}; use crate::copy_move::{copy_move, CopyMove}; use crate::cryptsetup::{BlockDeviceAction, CryptoDeviceOpener}; +use crate::fileinfo::FileKind; use crate::flagged::Flagged; use crate::iso::IsoDevice; use crate::log::write_log_line; @@ -596,9 +597,14 @@ impl Status { let fileinfo = self.tabs[0] .selected() .context("force preview: No file to select")?; - let users_cache = &self.tabs[0].path_content.users_cache; - self.tabs[1].preview = - Preview::new(fileinfo, users_cache, self, colors).unwrap_or_default(); + let preview = match fileinfo.file_kind { + FileKind::Directory => { + let users_cache = &self.tabs[0].path_content.users_cache; + Preview::directory(fileinfo, users_cache, self, colors) + } + _ => Preview::new(fileinfo), + }; + self.tabs[1].preview = preview.unwrap_or_default(); self.tabs[1].window.reset(self.tabs[1].preview.len()); Ok(()) From ce5150ae5bbe11c0be0953e1eac12d9f8f7431bc Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 29 Oct 2023 22:10:01 +0100 Subject: [PATCH 071/168] preview instancation refactoring. Separate dirs for files --- src/event_exec.rs | 36 ++--------------------------- src/preview.rs | 16 +++++++------ src/status.rs | 56 ++++++++++++++++++++++++++++++++++++++------- src/tab.rs | 2 +- src/term_manager.rs | 28 ++++++++++++++++++----- 5 files changed, 82 insertions(+), 56 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index 4679df5f..6be910b7 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -15,7 +15,6 @@ use crate::constant_strings_paths::{ CONFIG_PATH, DEFAULT_DRAGNDROP, DIFF, GIO, MEDIAINFO, NITROGEN, SSHFS_EXECUTABLE, }; use crate::cryptsetup::{lsblk_and_cryptsetup_installed, BlockDeviceAction}; -use crate::fileinfo::FileKind; use crate::filter::FilterKind; use crate::log::{read_log, write_log_line}; use crate::mocp::{is_mocp_installed, Mocp}; @@ -225,25 +224,7 @@ impl EventAction { /// more details on previewinga file. /// Does nothing if the directory is empty. pub fn preview(status: &mut Status, colors: &Colors) -> Result<()> { - if status.selected_non_mut().path_content.is_empty() { - return Ok(()); - } - let unmutable_tab = status.selected_non_mut(); - let Some(file_info) = unmutable_tab.selected() else { - return Ok(()); - }; - match file_info.file_kind { - FileKind::NormalFile => { - let preview = Preview::new(file_info).unwrap_or_default(); - status.selected().set_mode(Mode::Preview); - status.selected().window.reset(preview.len()); - status.selected().preview = preview; - } - FileKind::Directory => Self::tree(status, colors)?, - _ => (), - } - - Ok(()) + status.make_preview(colors) } /// Enter the delete mode. @@ -920,20 +901,7 @@ impl EventAction { /// Creates a tree in every mode but "Tree". /// It tree mode it will exit this view. pub fn tree(status: &mut Status, colors: &Colors) -> Result<()> { - if let Mode::Tree = status.selected_non_mut().mode { - { - let tab: &mut Tab = status.selected(); - tab.refresh_view() - }?; - status.selected().set_mode(Mode::Normal) - } else { - status.display_full = true; - status.selected().make_tree(colors)?; - status.selected().set_mode(Mode::Tree); - let len = status.selected_non_mut().directory.len(); - status.selected().window.reset(len); - } - Ok(()) + status.tree(colors) } /// Fold the current node of the tree. diff --git a/src/preview.rs b/src/preview.rs index bc00d829..013e1ba9 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -26,7 +26,6 @@ use crate::decompress::{list_files_tar, list_files_zip}; use crate::fileinfo::{FileInfo, FileKind}; use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; -use crate::status::Status; use crate::tree::{ColoredString, Tree}; use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path}; @@ -108,7 +107,7 @@ impl Preview { const CONTENT_INSPECTOR_MIN_SIZE: usize = 1024; /// Empty preview, holding nothing. - pub fn new_empty() -> Self { + pub fn empty() -> Self { clear_tmp_file(); Self::Empty } @@ -119,23 +118,27 @@ impl Preview { pub fn directory( file_info: &FileInfo, users_cache: &UsersCache, - status: &Status, + filter: &FilterKind, + show_hidden: bool, colors: &Colors, ) -> Result { Ok(Self::Directory(Directory::new( &file_info.path, users_cache, colors, - &status.selected_non_mut().filter, - status.selected_non_mut().show_hidden, + filter, + show_hidden, Some(2), )?)) } + /// Creates a new preview instance based on the filekind and the extension of /// the file. /// Sometimes it reads the content of the file, sometimes it delegates /// it to the display method. - pub fn new(file_info: &FileInfo) -> Result { + /// Directories aren't handled there since we need more arguments to create + /// their previews. + pub fn file(file_info: &FileInfo) -> Result { clear_tmp_file(); match file_info.file_kind { FileKind::Directory => Err(anyhow!( @@ -149,7 +152,6 @@ impl Preview { &file_info.path, extension, )?)), - // ExtensionKind::Pdf => Ok(Self::Pdf(PdfContent::new(&file_info.path))), ExtensionKind::Pdf => Ok(Self::Ueberzug(Ueberzug::make( &file_info.path, UeberzugKind::Pdf, diff --git a/src/status.rs b/src/status.rs index 6e8c2aa5..becb3e72 100644 --- a/src/status.rs +++ b/src/status.rs @@ -576,6 +576,44 @@ impl Status { Ok(()) } + pub fn make_preview(&mut self, colors: &Colors) -> Result<()> { + if self.selected_non_mut().path_content.is_empty() { + return Ok(()); + } + let Some(file_info) = self.selected_non_mut().selected() else { + return Ok(()); + }; + match file_info.file_kind { + FileKind::NormalFile => { + let preview = Preview::file(file_info).unwrap_or_default(); + self.selected().set_mode(Mode::Preview); + self.selected().window.reset(preview.len()); + self.selected().preview = preview; + } + FileKind::Directory => self.tree(colors)?, + _ => (), + } + + Ok(()) + } + + pub fn tree(&mut self, colors: &Colors) -> Result<()> { + if let Mode::Tree = self.selected_non_mut().mode { + { + let tab: &mut Tab = self.selected(); + tab.refresh_view() + }?; + self.selected().set_mode(Mode::Normal) + } else { + self.display_full = true; + self.selected().make_tree(colors)?; + self.selected().set_mode(Mode::Tree); + let len = self.selected_non_mut().directory.len(); + self.selected().window.reset(len); + } + Ok(()) + } + /// Check if the second pane should display a preview and force it. pub fn update_second_pane_for_preview(&mut self, colors: &Colors) -> Result<()> { if self.index == 0 && self.preview_second { @@ -587,22 +625,24 @@ impl Status { /// Force preview the selected file of the first pane in the second pane. /// Doesn't check if it has do. pub fn set_second_pane_for_preview(&mut self, colors: &Colors) -> Result<()> { - self.tabs[1].set_mode(Mode::Preview); - if !Self::display_wide_enough(&self.term)? { - self.tabs[1].preview = Preview::new_empty(); + self.tabs[1].preview = Preview::empty(); return Ok(()); } + self.tabs[1].set_mode(Mode::Preview); let fileinfo = self.tabs[0] .selected() .context("force preview: No file to select")?; let preview = match fileinfo.file_kind { - FileKind::Directory => { - let users_cache = &self.tabs[0].path_content.users_cache; - Preview::directory(fileinfo, users_cache, self, colors) - } - _ => Preview::new(fileinfo), + FileKind::Directory => Preview::directory( + fileinfo, + &self.tabs[0].path_content.users_cache, + &self.tabs[0].filter, + self.tabs[0].show_hidden, + colors, + ), + _ => Preview::file(fileinfo), }; self.tabs[1].preview = preview.unwrap_or_default(); diff --git a/src/tab.rs b/src/tab.rs index 8720c56e..f644bf71 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -140,7 +140,7 @@ impl Tab { pub fn refresh_params(&mut self) -> Result<()> { self.filter = FilterKind::All; self.input.reset(); - self.preview = Preview::new_empty(); + self.preview = Preview::empty(); self.completion.reset(); self.directory.clear(); Ok(()) diff --git a/src/term_manager.rs b/src/term_manager.rs index 9c7f30ce..01277be9 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -67,6 +67,12 @@ const ATTR_YELLOW_BOLD: Attr = Attr { effect: Effect::BOLD, }; +const ATTR_CYAN_BOLD: Attr = Attr { + fg: Color::CYAN, + bg: Color::Default, + effect: Effect::BOLD, +}; + /// Simple struct to read the events. pub struct EventReader { term: Arc, @@ -204,7 +210,7 @@ impl<'a> WinMain<'a> { let Some(file) = tab.selected() else { return Ok(0); }; - self.second_line_detailed(file, canvas) + self.second_line_detailed(file, status, canvas) } else { self.second_line_simple(status, canvas) } @@ -213,18 +219,28 @@ impl<'a> WinMain<'a> { } } - fn second_line_detailed(&self, file: &FileInfo, canvas: &mut dyn Canvas) -> Result { + fn second_line_detailed( + &self, + file: &FileInfo, + status: &Status, + canvas: &mut dyn Canvas, + ) -> Result { let owner_size = file.owner.len(); let group_size = file.group.len(); let mut attr = fileinfo_attr(file, self.colors); attr.effect ^= Effect::REVERSE; - Ok(canvas.print_with_attr(1, 0, &file.format(owner_size, group_size)?, attr)?) + + if status.flagged.contains(&file.path) { + canvas.print_with_attr(1, 0, "█", ATTR_CYAN_BOLD)?; + attr.effect |= Effect::BOLD; + } + Ok(canvas.print_with_attr(1, 1, &file.format(owner_size, group_size)?, attr)?) } fn second_line_simple(&self, status: &Status, canvas: &mut dyn Canvas) -> Result { Ok(canvas.print_with_attr( 1, - 0, + 1, &format!("{}", &status.selected_non_mut().filter), ATTR_YELLOW_BOLD, )?) @@ -370,7 +386,7 @@ impl<'a> WinMain<'a> { }; if status.flagged.contains(&file.path) { attr.effect |= Effect::BOLD; - canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; + canvas.print_with_attr(row, 0, "█", ATTR_CYAN_BOLD)?; } canvas.print_with_attr(row, 1, &string, attr)?; } @@ -397,7 +413,7 @@ impl<'a> WinMain<'a> { let mut attr = colored_string.attr; if status.flagged.contains(&colored_string.path) { attr.effect |= Effect::BOLD; - canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; + canvas.print_with_attr(row, 0, "█", ATTR_CYAN_BOLD)?; } let col_metadata = if status.display_full { From 045151ccbbf661025f591e42f18d2a5622734d9b Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 29 Oct 2023 22:36:27 +0100 Subject: [PATCH 072/168] refactor sort display --- src/sort.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/sort.rs b/src/sort.rs index 2245c9f4..8d215881 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -18,6 +18,19 @@ enum SortBy { Exte, } +impl std::fmt::Display for SortBy { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let sort_by = match &self { + SortBy::Exte => "Exte", + SortBy::Date => "Date", + SortBy::File => "File", + SortBy::Size => "Size", + SortBy::Kind => "Kind", + }; + write!(f, "{sort_by}") + } +} + /// Ascending or descending sort #[derive(Debug, Clone, Default)] enum Order { @@ -155,18 +168,10 @@ impl SortKind { impl std::fmt::Display for SortKind { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let s = match (&self.sort_by, &self.order) { - (SortBy::Exte, Order::Ascending) => "Exte ↑", - (SortBy::Exte, Order::Descending) => "Exte ↓", - (SortBy::Date, Order::Ascending) => "Date ↑", - (SortBy::Date, Order::Descending) => "Date ↓", - (SortBy::File, Order::Ascending) => "Name ↑", - (SortBy::File, Order::Descending) => "Name ↓", - (SortBy::Size, Order::Ascending) => "Size ↑", - (SortBy::Size, Order::Descending) => "Size ↓", - (SortBy::Kind, Order::Ascending) => "Kind ↑", - (SortBy::Kind, Order::Descending) => "Kind ↓", + let sort_order = match &self.order { + Order::Ascending => "↓", + Order::Descending => "↑", }; - write!(f, "{}", s) + write!(f, "{sort_by} {sort_order}", sort_by = &self.sort_by) } } From b9b83fc24df5d14753d67dbfaa7ac693946d67f8 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 29 Oct 2023 23:03:10 +0100 Subject: [PATCH 073/168] simplify fileinfo formating --- src/fileinfo.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 8ee48aab..b16d493d 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -245,25 +245,13 @@ impl FileInfo { } fn format_base(&self, owner_col_width: usize, group_col_width: usize) -> Result { - let owner = format!( - "{owner:.owner_col_width$}", - owner = self.owner, - owner_col_width = owner_col_width - ); - let group = format!( - "{group:.group_col_width$}", - group = self.group, - group_col_width = group_col_width - ); + let owner = format!("{owner:.owner_col_width$}", owner = self.owner,); + let group = format!("{group:.group_col_width$}", group = self.group,); let repr = format!( "{dir_symbol}{permissions} {file_size} {owner: Date: Mon, 30 Oct 2023 09:38:05 +0100 Subject: [PATCH 074/168] remove configurable colors, calc them every time --- src/action_map.rs | 55 ++++++----- src/color_cache.rs | 34 +++---- src/config.rs | 177 ++++++++++++++++++------------------ src/event_dispatch.rs | 33 ++++--- src/event_exec.rs | 206 +++++++++++++++++++++--------------------- src/fileinfo.rs | 39 +++++--- src/main.rs | 16 ++-- src/preview.rs | 58 ++++++------ src/status.rs | 67 +++++--------- src/tab.rs | 87 ++++++++---------- src/term_manager.rs | 28 ++---- src/tree.rs | 19 ++-- 12 files changed, 391 insertions(+), 428 deletions(-) diff --git a/src/action_map.rs b/src/action_map.rs index 8e725a5a..daefabd0 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -1,7 +1,6 @@ use anyhow::Result; use strum_macros::{Display, EnumIter, EnumString}; -use crate::config::Colors; use crate::event_exec::{EventAction, LeaveMode}; use crate::status::Status; @@ -107,10 +106,10 @@ pub enum ActionMap { impl ActionMap { /// Makes the junction between `Actions` and `Events`. /// Every Action links to a different `EventExec` method. - pub fn matcher(&self, status: &mut Status, colors: &Colors) -> Result<()> { + pub fn matcher(&self, status: &mut Status) -> Result<()> { let current_tab = status.selected(); match self { - ActionMap::Back => EventAction::back(current_tab, colors), + ActionMap::Back => EventAction::back(current_tab), ActionMap::BackTab => EventAction::backtab(status), ActionMap::Backspace => EventAction::backspace(current_tab), ActionMap::Bulk => EventAction::bulk(status), @@ -128,48 +127,48 @@ impl ActionMap { ActionMap::Diff => EventAction::diff(status), ActionMap::DragNDrop => EventAction::drag_n_drop(status), ActionMap::EncryptedDrive => EventAction::encrypted_drive(status), - ActionMap::End => EventAction::end(status, colors), - ActionMap::Enter => EventAction::enter(status, colors), + ActionMap::End => EventAction::end(status), + ActionMap::Enter => EventAction::enter(status), ActionMap::Exec => EventAction::exec(current_tab), ActionMap::Filter => EventAction::filter(current_tab), ActionMap::FlagAll => EventAction::flag_all(status), - ActionMap::FuzzyFind => EventAction::fuzzyfind(status, colors), + ActionMap::FuzzyFind => EventAction::fuzzyfind(status), ActionMap::FuzzyFindHelp => EventAction::fuzzyfind_help(status), - ActionMap::FuzzyFindLine => EventAction::fuzzyfind_line(status, colors), - ActionMap::GoRoot => EventAction::go_root(status, colors), - ActionMap::GoStart => EventAction::go_start(status, colors), + ActionMap::FuzzyFindLine => EventAction::fuzzyfind_line(status), + ActionMap::GoRoot => EventAction::go_root(status), + ActionMap::GoStart => EventAction::go_start(status), ActionMap::Goto => EventAction::goto(current_tab), ActionMap::Help => EventAction::help(status), ActionMap::History => EventAction::history(current_tab), - ActionMap::Home => EventAction::home(status, colors), + ActionMap::Home => EventAction::home(status), ActionMap::Jump => EventAction::jump(status), - ActionMap::KeyHome => EventAction::key_home(status, colors), + ActionMap::KeyHome => EventAction::key_home(status), ActionMap::Log => EventAction::log(current_tab), ActionMap::MarksJump => EventAction::marks_jump(status), ActionMap::MarksNew => EventAction::marks_new(current_tab), ActionMap::MediaInfo => EventAction::mediainfo(current_tab), ActionMap::MocpAddToPlayList => EventAction::mocp_add_to_playlist(current_tab), ActionMap::MocpClearPlaylist => EventAction::mocp_clear_playlist(), - ActionMap::MocpGoToSong => EventAction::mocp_go_to_song(status, colors), + ActionMap::MocpGoToSong => EventAction::mocp_go_to_song(status), ActionMap::MocpNext => EventAction::mocp_next(), ActionMap::MocpPrevious => EventAction::mocp_previous(), ActionMap::MocpTogglePause => EventAction::mocp_toggle_pause(status), - ActionMap::MoveDown => EventAction::move_down(status, colors), - ActionMap::MoveLeft => EventAction::move_left(status, colors), - ActionMap::MoveRight => EventAction::move_right(status, colors), - ActionMap::MoveUp => EventAction::move_up(status, colors), + ActionMap::MoveDown => EventAction::move_down(status), + ActionMap::MoveLeft => EventAction::move_left(status), + ActionMap::MoveRight => EventAction::move_right(status), + ActionMap::MoveUp => EventAction::move_up(status), ActionMap::NewDir => EventAction::new_dir(current_tab), ActionMap::NewFile => EventAction::new_file(current_tab), ActionMap::NvimFilepicker => EventAction::nvim_filepicker(status), ActionMap::NvimSetAddress => EventAction::set_nvim_server(current_tab), ActionMap::OpenConfig => EventAction::open_config(status), ActionMap::OpenFile => EventAction::open_file(status), - ActionMap::PageDown => EventAction::page_down(status, colors), - ActionMap::PageUp => EventAction::page_up(status, colors), - ActionMap::Preview => EventAction::preview(status, colors), + ActionMap::PageDown => EventAction::page_down(status), + ActionMap::PageUp => EventAction::page_up(status), + ActionMap::Preview => EventAction::preview(status), ActionMap::Quit => EventAction::quit(current_tab), ActionMap::RefreshIfNeeded => EventAction::refresh_if_needed(current_tab), - ActionMap::RefreshView => EventAction::refreshview(status, colors), + ActionMap::RefreshView => EventAction::refreshview(status), ActionMap::RegexMatch => EventAction::regex_match(current_tab), ActionMap::RemoteMount => EventAction::remote_mount(current_tab), ActionMap::RemovableDevices => EventAction::removable_devices(status), @@ -177,7 +176,7 @@ impl ActionMap { ActionMap::ResetMode => EventAction::reset_mode(current_tab), ActionMap::ReverseFlags => EventAction::reverse_flags(status), ActionMap::Search => EventAction::search(current_tab), - ActionMap::SearchNext => EventAction::search_next(status, colors), + ActionMap::SearchNext => EventAction::search_next(status), ActionMap::SetWallpaper => EventAction::set_wallpaper(current_tab), ActionMap::Shell => EventAction::shell(status), ActionMap::ShellCommand => EventAction::shell_command(current_tab), @@ -189,16 +188,16 @@ impl ActionMap { ActionMap::ToggleDisplayFull => EventAction::toggle_display_full(status), ActionMap::ToggleDualPane => EventAction::toggle_dualpane(status), ActionMap::ToggleFlag => EventAction::toggle_flag(status), - ActionMap::ToggleHidden => EventAction::toggle_hidden(status, colors), - ActionMap::TogglePreviewSecond => EventAction::toggle_preview_second(status, colors), + ActionMap::ToggleHidden => EventAction::toggle_hidden(status), + ActionMap::TogglePreviewSecond => EventAction::toggle_preview_second(status), ActionMap::TrashEmpty => EventAction::trash_empty(status), ActionMap::TrashMoveFile => EventAction::trash_move_file(status), ActionMap::TrashOpen => EventAction::trash_open(status), - ActionMap::TrashRestoreFile => LeaveMode::trash(status, colors), - ActionMap::Tree => EventAction::tree(status, colors), - ActionMap::TreeFold => EventAction::tree_fold(current_tab, colors), - ActionMap::TreeFoldAll => EventAction::tree_fold_all(current_tab, colors), - ActionMap::TreeUnFoldAll => EventAction::tree_unfold_all(current_tab, colors), + ActionMap::TrashRestoreFile => LeaveMode::trash(status), + ActionMap::Tree => EventAction::tree(status), + ActionMap::TreeFold => EventAction::tree_fold(current_tab), + ActionMap::TreeFoldAll => EventAction::tree_fold_all(current_tab), + ActionMap::TreeUnFoldAll => EventAction::tree_unfold_all(current_tab), ActionMap::Custom(string) => EventAction::custom(status, string), ActionMap::Nothing => Ok(()), diff --git a/src/color_cache.rs b/src/color_cache.rs index 3052ff33..e8af4cd7 100644 --- a/src/color_cache.rs +++ b/src/color_cache.rs @@ -1,5 +1,5 @@ -use std::cell::RefCell; -use std::collections::HashMap; +// use std::cell::RefCell; +// use std::collections::HashMap; use std::hash::Hasher; use tuikit::attr::Color; @@ -9,23 +9,23 @@ use tuikit::attr::Color; /// per run. This trades a bit of memory for a bit of CPU. #[derive(Default, Clone, Debug)] pub struct ColorCache { - cache: RefCell>, + // cache: RefCell>, } -impl ColorCache { - /// Returns a color for any possible extension. - /// The color is cached within the struct, avoiding multiple calculations. - pub fn extension_color(&self, extension: &str) -> Color { - let mut cache = self.cache.borrow_mut(); - if let Some(color) = cache.get(extension) { - color.to_owned() - } else { - let color = extension_color(extension); - cache.insert(extension.to_owned(), color); - color - } - } -} +// impl ColorCache { +// /// Returns a color for any possible extension. +// /// The color is cached within the struct, avoiding multiple calculations. +// pub fn extension_color(&self, extension: &str) -> Color { +// let mut cache = self.cache.borrow_mut(); +// if let Some(color) = cache.get(extension) { +// color.to_owned() +// } else { +// let color = extension_color(extension); +// cache.insert(extension.to_owned(), color); +// color +// } +// } +// } /// Picks a blueish/greenish color on color picker hexagon's perimeter. fn color(coords: usize) -> Color { diff --git a/src/config.rs b/src/config.rs index e986cd39..5a87c646 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,9 +2,8 @@ use std::{fs::File, path}; use anyhow::Result; use serde_yaml; -use tuikit::attr::Color; +// use tuikit::attr::Color; -use crate::color_cache::ColorCache; use crate::constant_strings_paths::DEFAULT_TERMINAL_APPLICATION; use crate::keybindings::Bindings; use crate::utils::is_program_in_path; @@ -40,21 +39,19 @@ impl Settings { } } -macro_rules! update_attribute { - ($self_attr:expr, $yaml:ident, $key:expr) => { - if let Some(attr) = read_yaml_value($yaml, $key) { - $self_attr = attr; - } - }; -} +// macro_rules! update_attribute { +// ($self_attr:expr, $yaml:ident, $key:expr) => { +// if let Some(attr) = read_yaml_value($yaml, $key) { +// $self_attr = attr; +// } +// }; +// } /// Holds every configurable aspect of the application. /// All attributes are hardcoded then updated from optional values /// of the config file. /// The config file is a YAML file in `~/.config/fm/config.yaml` #[derive(Debug, Clone)] pub struct Config { - /// Color of every kind of file - pub colors: Colors, /// The name of the terminal application. It should be installed properly. pub terminal: String, /// Configurable keybindings. @@ -67,7 +64,6 @@ impl Config { /// Returns a default config with hardcoded values. fn new() -> Result { Ok(Self { - colors: Colors::default(), terminal: DEFAULT_TERMINAL_APPLICATION.to_owned(), binds: Bindings::default(), settings: Settings::default(), @@ -75,7 +71,6 @@ impl Config { } /// Updates the config from a configuration content. fn update_from_config(&mut self, yaml: &serde_yaml::value::Value) -> Result<()> { - self.colors.update_from_config(&yaml["colors"]); self.binds.update_normal(&yaml["keys"]); self.binds.update_custom(&yaml["custom"]); self.update_terminal(&yaml["terminal"]); @@ -101,85 +96,85 @@ impl Config { } } -fn read_yaml_value(yaml: &serde_yaml::value::Value, key: &str) -> Option { - yaml[key].as_str().map(|s| s.to_string()) -} - -/// Holds configurable colors for every kind of file. -/// "Normal" files are displayed with a different color by extension. -#[derive(Debug, Clone)] -pub struct Colors { - /// Color for `directory` files. - pub directory: String, - /// Color for `block` files. - pub block: String, - /// Color for `char` files. - pub char: String, - /// Color for `fifo` files. - pub fifo: String, - /// Color for `socket` files. - pub socket: String, - /// Color for `symlink` files. - pub symlink: String, - /// Color for broken `symlink` files. - pub broken: String, - /// Colors for normal files, depending of extension - pub color_cache: ColorCache, -} - -impl Colors { - fn update_from_config(&mut self, yaml: &serde_yaml::value::Value) { - update_attribute!(self.directory, yaml, "directory"); - update_attribute!(self.block, yaml, "block"); - update_attribute!(self.char, yaml, "char"); - update_attribute!(self.fifo, yaml, "fifo"); - update_attribute!(self.socket, yaml, "socket"); - update_attribute!(self.symlink, yaml, "symlink"); - update_attribute!(self.broken, yaml, "broken"); - } +// fn read_yaml_value(yaml: &serde_yaml::value::Value, key: &str) -> Option { +// yaml[key].as_str().map(|s| s.to_string()) +// } - fn new() -> Self { - Self { - directory: "red".to_owned(), - block: "yellow".to_owned(), - char: "green".to_owned(), - fifo: "blue".to_owned(), - socket: "cyan".to_owned(), - symlink: "magenta".to_owned(), - broken: "white".to_owned(), - color_cache: ColorCache::default(), - } - } -} - -impl Default for Colors { - fn default() -> Self { - Self::new() - } -} - -/// Convert a string color into a `tuikit::Color` instance. -pub fn str_to_tuikit(color: &str) -> Color { - match color { - "white" => Color::WHITE, - "red" => Color::RED, - "green" => Color::GREEN, - "blue" => Color::BLUE, - "yellow" => Color::YELLOW, - "cyan" => Color::CYAN, - "magenta" => Color::MAGENTA, - "black" => Color::BLACK, - "light_white" => Color::LIGHT_WHITE, - "light_red" => Color::LIGHT_RED, - "light_green" => Color::LIGHT_GREEN, - "light_blue" => Color::LIGHT_BLUE, - "light_yellow" => Color::LIGHT_YELLOW, - "light_cyan" => Color::LIGHT_CYAN, - "light_magenta" => Color::LIGHT_MAGENTA, - "light_black" => Color::LIGHT_BLACK, - _ => Color::default(), - } -} +// /// Holds configurable colors for every kind of file. +// /// "Normal" files are displayed with a different color by extension. +// #[derive(Debug, Clone)] +// pub struct Colors { +// /// Color for `directory` files. +// pub directory: String, +// /// Color for `block` files. +// pub block: String, +// /// Color for `char` files. +// pub char: String, +// /// Color for `fifo` files. +// pub fifo: String, +// /// Color for `socket` files. +// pub socket: String, +// /// Color for `symlink` files. +// pub symlink: String, +// /// Color for broken `symlink` files. +// pub broken: String, +// // /// Colors for normal files, depending of extension +// // pub color_cache: ColorCache, +// } +// +// impl Colors { +// fn update_from_config(&mut self, yaml: &serde_yaml::value::Value) { +// update_attribute!(self.directory, yaml, "directory"); +// update_attribute!(self.block, yaml, "block"); +// update_attribute!(self.char, yaml, "char"); +// update_attribute!(self.fifo, yaml, "fifo"); +// update_attribute!(self.socket, yaml, "socket"); +// update_attribute!(self.symlink, yaml, "symlink"); +// update_attribute!(self.broken, yaml, "broken"); +// } +// +// fn new() -> Self { +// Self { +// directory: "red".to_owned(), +// block: "yellow".to_owned(), +// char: "green".to_owned(), +// fifo: "blue".to_owned(), +// socket: "cyan".to_owned(), +// symlink: "magenta".to_owned(), +// broken: "white".to_owned(), +// // color_cache: ColorCache::default(), +// } +// } +// } +// +// impl Default for Colors { +// fn default() -> Self { +// Self::new() +// } +// } +// +// /// Convert a string color into a `tuikit::Color` instance. +// pub fn str_to_tuikit(color: &str) -> Color { +// match color { +// "white" => Color::WHITE, +// "red" => Color::RED, +// "green" => Color::GREEN, +// "blue" => Color::BLUE, +// "yellow" => Color::YELLOW, +// "cyan" => Color::CYAN, +// "magenta" => Color::MAGENTA, +// "black" => Color::BLACK, +// "light_white" => Color::LIGHT_WHITE, +// "light_red" => Color::LIGHT_RED, +// "light_green" => Color::LIGHT_GREEN, +// "light_blue" => Color::LIGHT_BLUE, +// "light_yellow" => Color::LIGHT_YELLOW, +// "light_cyan" => Color::LIGHT_CYAN, +// "light_magenta" => Color::LIGHT_MAGENTA, +// "light_black" => Color::LIGHT_BLACK, +// _ => Color::default(), +// } +// } /// Returns a config with values from : /// diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index 8dfbe952..c98a894f 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -1,7 +1,6 @@ use anyhow::Result; use tuikit::prelude::{Event, Key, MouseButton}; -use crate::config::Colors; use crate::event_exec::{EventAction, LeaveMode}; use crate::keybindings::Bindings; use crate::mode::{InputSimple, MarkAction, Mode, Navigate}; @@ -30,27 +29,27 @@ impl EventDispatcher { &self, status: &mut Status, ev: Event, - colors: &Colors, + // colors: &Colors, current_height: usize, ) -> Result<()> { match ev { Event::Key(Key::WheelUp(_, col, _)) => { status.select_pane(col)?; - EventAction::move_up(status, colors)?; + EventAction::move_up(status)?; } Event::Key(Key::WheelDown(_, col, _)) => { status.select_pane(col)?; - EventAction::move_down(status, colors)?; + EventAction::move_down(status)?; } Event::Key(Key::SingleClick(MouseButton::Left, row, col)) => { - status.click(row, col, current_height, colors)?; + status.click(row, col, current_height)?; } Event::Key( Key::SingleClick(MouseButton::Right, row, col) | Key::DoubleClick(MouseButton::Left, row, col), ) => { - status.click(row, col, current_height, colors)?; - LeaveMode::right_click(status, colors)?; + status.click(row, col, current_height)?; + LeaveMode::right_click(status)?; } // reserved keybind which can't be bound to anything. // using `Key::User(())` conflicts with skim internal which @@ -58,24 +57,24 @@ impl EventDispatcher { Event::Key(Key::AltPageUp) => status.selected().refresh_if_needed()?, Event::Resize { width, height } => status.resize(width, height)?, - Event::Key(Key::Char(c)) => self.char(status, c, colors)?, - Event::Key(key) => self.key_matcher(status, key, colors)?, + Event::Key(Key::Char(c)) => self.char(status, c)?, + Event::Key(key) => self.key_matcher(status, key)?, _ => (), }; Ok(()) } - fn key_matcher(&self, status: &mut Status, key: Key, colors: &Colors) -> Result<()> { + fn key_matcher(&self, status: &mut Status, key: Key) -> Result<()> { match self.binds.get(&key) { - Some(action) => action.matcher(status, colors), + Some(action) => action.matcher(status), None => Ok(()), } } - fn char(&self, status: &mut Status, c: char, colors: &Colors) -> Result<()> { + fn char(&self, status: &mut Status, c: char) -> Result<()> { let tab = status.selected(); match tab.mode { - Mode::InputSimple(InputSimple::Sort) => tab.sort(c, colors), + Mode::InputSimple(InputSimple::Sort) => tab.sort(c), Mode::InputSimple(InputSimple::RegexMatch) => { tab.input.insert(c); status.select_from_regex()?; @@ -87,10 +86,10 @@ impl EventDispatcher { } Mode::InputCompleted(_) => tab.text_insert_and_complete(c), Mode::Normal | Mode::Tree => match self.binds.get(&Key::Char(c)) { - Some(action) => action.matcher(status, colors), + Some(action) => action.matcher(status), None => Ok(()), }, - Mode::NeedConfirmation(confirmed_action) => status.confirm(c, confirmed_action, colors), + Mode::NeedConfirmation(confirmed_action) => status.confirm(c, confirmed_action), Mode::Navigate(Navigate::Trash) if c == 'x' => status.trash.remove(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'm' => status.mount_encrypted_drive(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'g' => status.go_to_encrypted_drive(), @@ -108,8 +107,8 @@ impl EventDispatcher { Mode::Navigate(Navigate::Jump) if c == 'u' => status.clear_flags_and_reset_view(), Mode::Navigate(Navigate::Jump) if c == 'x' => status.delete_single_flagged(), Mode::Navigate(Navigate::Jump) if c == 'X' => status.trash_single_flagged(), - Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => status.marks_jump_char(c, colors), - Mode::Navigate(Navigate::Marks(MarkAction::New)) => status.marks_new(c, colors), + Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => status.marks_jump_char(c), + Mode::Navigate(Navigate::Marks(MarkAction::New)) => status.marks_new(c), Mode::Preview | Mode::Navigate(_) => { if tab.reset_mode() { tab.refresh_view()?; diff --git a/src/event_exec.rs b/src/event_exec.rs index 6be910b7..07eaa892 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -10,7 +10,6 @@ use sysinfo::SystemExt; use crate::action_map::ActionMap; use crate::completion::InputCompleted; -use crate::config::Colors; use crate::constant_strings_paths::{ CONFIG_PATH, DEFAULT_DRAGNDROP, DIFF, GIO, MEDIAINFO, NITROGEN, SSHFS_EXECUTABLE, }; @@ -223,8 +222,8 @@ impl EventAction { /// Every file can be previewed. See the `crate::enum::Preview` for /// more details on previewinga file. /// Does nothing if the directory is empty. - pub fn preview(status: &mut Status, colors: &Colors) -> Result<()> { - status.make_preview(colors) + pub fn preview(status: &mut Status) -> Result<()> { + status.make_preview() } /// Enter the delete mode. @@ -297,13 +296,13 @@ impl EventAction { Ok(()) } /// Toggle the display of hidden files. - pub fn toggle_hidden(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn toggle_hidden(status: &mut Status) -> Result<()> { let tab = status.selected(); tab.show_hidden = !tab.show_hidden; tab.path_content.reset_files(&tab.filter, tab.show_hidden)?; tab.window.reset(tab.path_content.content.len()); if let Mode::Tree = tab.mode { - tab.make_tree(colors)? + tab.make_tree()? } Ok(()) } @@ -428,31 +427,31 @@ impl EventAction { } /// Move back in history to the last visited directory. - pub fn back(tab: &mut Tab, colors: &Colors) -> Result<()> { - tab.back(colors) + pub fn back(tab: &mut Tab) -> Result<()> { + tab.back() } /// Move to $HOME aka ~. - pub fn home(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn home(status: &mut Status) -> Result<()> { let tab = status.selected(); let home_cow = shellexpand::tilde("~"); let home: &str = home_cow.borrow(); let path = std::fs::canonicalize(home)?; tab.set_pathcontent(&path)?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } - pub fn go_root(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn go_root(status: &mut Status) -> Result<()> { let tab = status.selected(); let root_path = std::path::PathBuf::from("/"); tab.set_pathcontent(&root_path)?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } - pub fn go_start(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn go_start(status: &mut Status) -> Result<()> { let start_folder = status.start_folder.clone(); status.selected().set_pathcontent(&start_folder)?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Executes a `dragon-drop` command on the selected file. @@ -474,7 +473,7 @@ impl EventAction { Ok(()) } - pub fn search_next(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn search_next(status: &mut Status) -> Result<()> { let tab = status.selected(); match tab.mode { Mode::Tree => (), @@ -484,7 +483,7 @@ impl EventAction { }; let next_index = (tab.path_content.index + 1) % tab.path_content.content.len(); tab.search_from(&searched, next_index); - status.update_second_pane_for_preview(colors)?; + status.update_second_pane_for_preview()?; } } Ok(()) @@ -492,7 +491,7 @@ impl EventAction { /// Move up one row in modes allowing movement. /// Does nothing if the selected item is already the first in list. - pub fn move_up(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn move_up(status: &mut Status) -> Result<()> { let tab = status.selected(); match tab.mode { Mode::Normal | Mode::Preview => tab.up_one_row(), @@ -507,15 +506,15 @@ impl EventAction { Mode::Navigate(Navigate::CliInfo) => status.cli_info.prev(), Mode::Navigate(Navigate::EncryptedDrive) => status.encrypted_devices.prev(), Mode::InputCompleted(_) => tab.completion.prev(), - Mode::Tree => tab.tree_select_prev(colors)?, + Mode::Tree => tab.tree_select_prev()?, _ => (), }; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move down one row in modes allowing movements. /// Does nothing if the user is already at the bottom. - pub fn move_down(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn move_down(status: &mut Status) -> Result<()> { match status.selected().mode { Mode::Normal | Mode::Preview => status.selected().down_one_row(), Mode::Navigate(Navigate::Jump) => status.flagged.next(), @@ -529,37 +528,37 @@ impl EventAction { Mode::Navigate(Navigate::CliInfo) => status.cli_info.next(), Mode::Navigate(Navigate::EncryptedDrive) => status.encrypted_devices.next(), Mode::InputCompleted(_) => status.selected().completion.next(), - Mode::Tree => status.selected().select_next(colors)?, + Mode::Tree => status.selected().select_next()?, _ => (), }; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move to parent in normal mode, /// move left one char in mode requiring text input. - pub fn move_left(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn move_left(status: &mut Status) -> Result<()> { let tab = status.selected(); match tab.mode { - Mode::Normal => tab.move_to_parent(colors)?, - Mode::Tree => tab.tree_select_parent(colors)?, + Mode::Normal => tab.move_to_parent()?, + Mode::Tree => tab.tree_select_parent()?, Mode::InputSimple(_) | Mode::InputCompleted(_) => { tab.input.cursor_left(); } _ => (), } - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move to child if any or open a regular file in normal mode. /// Move the cursor one char to right in mode requiring text input. - pub fn move_right(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn move_right(status: &mut Status) -> Result<()> { let tab: &mut Tab = status.selected(); match tab.mode { Mode::Normal => LeaveMode::open_file(status), Mode::Tree => { - tab.select_first_child(colors)?; - status.update_second_pane_for_preview(colors) + tab.select_first_child()?; + status.update_second_pane_for_preview() } Mode::InputSimple(_) | Mode::InputCompleted(_) => { tab.input.cursor_right(); @@ -593,39 +592,39 @@ impl EventAction { } /// Move to leftmost char in mode allowing edition. - pub fn key_home(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn key_home(status: &mut Status) -> Result<()> { let tab = status.selected(); match tab.mode { Mode::Normal | Mode::Preview => tab.go_top(), - Mode::Tree => tab.tree_go_to_root(colors)?, + Mode::Tree => tab.tree_go_to_root()?, _ => tab.input.cursor_start(), }; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move to the bottom in any mode. - pub fn end(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn end(status: &mut Status) -> Result<()> { let tab = status.selected(); match tab.mode { Mode::Normal | Mode::Preview => tab.go_bottom(), - Mode::Tree => tab.tree_go_to_bottom_leaf(colors)?, + Mode::Tree => tab.tree_go_to_bottom_leaf()?, _ => tab.input.cursor_end(), }; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move up 10 lines in normal mode and preview. - pub fn page_up(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn page_up(status: &mut Status) -> Result<()> { let tab = status.selected(); match tab.mode { Mode::Normal => { tab.page_up(); - status.update_second_pane_for_preview(colors)?; + status.update_second_pane_for_preview()?; } Mode::Preview => tab.page_up(), Mode::Tree => { - tab.tree_page_up(colors)?; - status.update_second_pane_for_preview(colors)?; + tab.tree_page_up()?; + status.update_second_pane_for_preview()?; } _ => (), }; @@ -633,17 +632,17 @@ impl EventAction { } /// Move down 10 lines in normal & preview mode. - pub fn page_down(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn page_down(status: &mut Status) -> Result<()> { let tab = status.selected(); match tab.mode { Mode::Normal => { tab.page_down(); - status.update_second_pane_for_preview(colors)?; + status.update_second_pane_for_preview()?; } Mode::Preview => tab.page_down(), Mode::Tree => { - tab.tree_page_down(colors)?; - status.update_second_pane_for_preview(colors)?; + tab.tree_page_down()?; + status.update_second_pane_for_preview()?; } _ => (), }; @@ -655,7 +654,7 @@ impl EventAction { /// related action. /// In normal mode, it will open the file. /// Reset to normal mode afterwards. - pub fn enter(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn enter(status: &mut Status) -> Result<()> { let mut must_refresh = true; let mut must_reset_mode = true; match status.selected_non_mut().mode { @@ -671,25 +670,25 @@ impl EventAction { } Mode::InputSimple(InputSimple::Filter) => { must_refresh = false; - LeaveMode::filter(status.selected(), colors)? + LeaveMode::filter(status.selected())? } Mode::InputSimple(InputSimple::Password(kind, action, dest)) => { must_refresh = false; must_reset_mode = false; - LeaveMode::password(status, kind, colors, dest, action)? + LeaveMode::password(status, kind, dest, action)? } Mode::InputSimple(InputSimple::Remote) => LeaveMode::remote(status.selected())?, Mode::Navigate(Navigate::Jump) => { must_refresh = false; - LeaveMode::jump(status, colors)? + LeaveMode::jump(status)? } Mode::Navigate(Navigate::History) => { must_refresh = false; - LeaveMode::history(status, colors)? + LeaveMode::history(status)? } - Mode::Navigate(Navigate::Shortcut) => LeaveMode::shortcut(status, colors)?, - Mode::Navigate(Navigate::Trash) => LeaveMode::trash(status, colors)?, - Mode::Navigate(Navigate::Bulk) => LeaveMode::bulk(status, colors)?, + Mode::Navigate(Navigate::Shortcut) => LeaveMode::shortcut(status)?, + Mode::Navigate(Navigate::Trash) => LeaveMode::trash(status)?, + Mode::Navigate(Navigate::Bulk) => LeaveMode::bulk(status)?, Mode::Navigate(Navigate::ShellMenu) => LeaveMode::shellmenu(status)?, Mode::Navigate(Navigate::CliInfo) => { must_refresh = false; @@ -698,20 +697,18 @@ impl EventAction { } Mode::Navigate(Navigate::EncryptedDrive) => (), Mode::Navigate(Navigate::Marks(MarkAction::New)) => LeaveMode::marks_update(status)?, - Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => { - LeaveMode::marks_jump(status, colors)? - } + Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => LeaveMode::marks_jump(status)?, Mode::Navigate(Navigate::Compress) => LeaveMode::compress(status)?, Mode::Navigate(Navigate::RemovableDevices) => (), Mode::InputCompleted(InputCompleted::Exec) => LeaveMode::exec(status.selected())?, Mode::InputCompleted(InputCompleted::Search) => { must_refresh = false; - LeaveMode::search(status, colors)? + LeaveMode::search(status)? } - Mode::InputCompleted(InputCompleted::Goto) => LeaveMode::goto(status, colors)?, - Mode::InputCompleted(InputCompleted::Command) => LeaveMode::command(status, colors)?, + Mode::InputCompleted(InputCompleted::Goto) => LeaveMode::goto(status)?, + Mode::InputCompleted(InputCompleted::Command) => LeaveMode::command(status)?, Mode::Normal => LeaveMode::open_file(status)?, - Mode::Tree => LeaveMode::tree(status, colors)?, + Mode::Tree => LeaveMode::tree(status)?, Mode::NeedConfirmation(_) | Mode::Preview | Mode::InputCompleted(InputCompleted::Nothing) @@ -723,7 +720,7 @@ impl EventAction { status.selected().reset_mode(); } if must_refresh { - status.refresh_status(colors)?; + status.refresh_status()?; } Ok(()) } @@ -752,15 +749,15 @@ impl EventAction { } /// Start a fuzzy find with skim. - pub fn fuzzyfind(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn fuzzyfind(status: &mut Status) -> Result<()> { status.skim_output_to_tab()?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Start a fuzzy find for a specific line with skim. - pub fn fuzzyfind_line(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn fuzzyfind_line(status: &mut Status) -> Result<()> { status.skim_line_output_to_tab()?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Start a fuzzy find for a keybinding with skim. @@ -785,10 +782,10 @@ impl EventAction { } /// Refresh the current view, reloading the files. Move the selection to top. - pub fn refreshview(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn refreshview(status: &mut Status) -> Result<()> { status.encrypted_devices.update()?; - status.refresh_status(colors)?; - status.update_second_pane_for_preview(colors) + status.refresh_status()?; + status.update_second_pane_for_preview() } /// Refresh the view if files were modified in current directory. @@ -900,34 +897,34 @@ impl EventAction { /// Creates a tree in every mode but "Tree". /// It tree mode it will exit this view. - pub fn tree(status: &mut Status, colors: &Colors) -> Result<()> { - status.tree(colors) + pub fn tree(status: &mut Status) -> Result<()> { + status.tree() } /// Fold the current node of the tree. /// Has no effect on "file" nodes. - pub fn tree_fold(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn tree_fold(tab: &mut Tab) -> Result<()> { let (tree, _, _) = tab.directory.tree.explore_position(false); tree.node.toggle_fold(); - tab.directory.make_preview(colors); - tab.select_next(colors) + tab.directory.make_preview(); + tab.select_next() } /// Unfold every child node in the tree. /// Recursively explore the tree and unfold every node. /// Reset the display. - pub fn tree_unfold_all(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn tree_unfold_all(tab: &mut Tab) -> Result<()> { tab.directory.tree.unfold_children(); - tab.directory.make_preview(colors); + tab.directory.make_preview(); Ok(()) } /// Fold every child node in the tree. /// Recursively explore the tree and fold every node. /// Reset the display. - pub fn tree_fold_all(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn tree_fold_all(tab: &mut Tab) -> Result<()> { tab.directory.tree.fold_children(); - tab.directory.make_preview(colors); + tab.directory.make_preview(); Ok(()) } @@ -988,9 +985,9 @@ impl EventAction { } /// Toggle the second pane between preview & normal mode (files). - pub fn toggle_preview_second(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn toggle_preview_second(status: &mut Status) -> Result<()> { status.preview_second = !status.preview_second; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Set the current selected file as wallpaper with `nitrogen`. @@ -1034,7 +1031,7 @@ impl EventAction { } /// Add a song or a folder to MOC playlist. Start it first... - pub fn mocp_go_to_song(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn mocp_go_to_song(status: &mut Status) -> Result<()> { let tab = status.selected(); if !is_mocp_installed() { write_log_line("mocp isn't installed".to_owned()); @@ -1042,7 +1039,7 @@ impl EventAction { } Mocp::go_to_song(tab)?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Toggle play/pause on MOC. @@ -1141,11 +1138,11 @@ pub struct LeaveMode {} impl LeaveMode { /// Restore a file from the trash if possible. /// Parent folders are created if needed. - pub fn trash(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn trash(status: &mut Status) -> Result<()> { status.trash.restore()?; status.selected().reset_mode(); status.selected().refresh_view()?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Open the file with configured opener or enter the directory. @@ -1162,7 +1159,7 @@ impl LeaveMode { } /// Jump to the current mark. - pub fn marks_jump(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn marks_jump(status: &mut Status) -> Result<()> { let marks = status.marks.clone(); let tab = status.selected(); if let Some((_, path)) = marks.selected() { @@ -1170,7 +1167,7 @@ impl LeaveMode { tab.window.reset(tab.path_content.content.len()); tab.input.reset(); } - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Update the selected mark with the current path. @@ -1192,10 +1189,10 @@ impl LeaveMode { Ok(()) } - pub fn bulk(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn bulk(status: &mut Status) -> Result<()> { status.execute_bulk()?; status.bulk = None; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } pub fn shellmenu(status: &mut Status) -> Result<()> { @@ -1243,7 +1240,7 @@ impl LeaveMode { /// Execute a jump to the selected flagged file. /// If the user selected a directory, we jump inside it. /// Otherwise, we jump to the parent and select the file. - pub fn jump(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn jump(status: &mut Status) -> Result<()> { let Some(jump_target) = status.flagged.selected() else { return Ok(()); }; @@ -1255,7 +1252,7 @@ impl LeaveMode { status.selected().set_pathcontent(target_dir)?; let index = status.selected().path_content.select_file(&jump_target); status.selected().scroll_to(index); - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Select the first file matching the typed regex in current dir. @@ -1376,7 +1373,7 @@ impl LeaveMode { /// ie. If you typed `"jpg"` before, it will move to the first file /// whose filename contains `"jpg"`. /// The current order of files is used. - pub fn search(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn search(status: &mut Status) -> Result<()> { let tab = status.selected(); let searched = tab.input.string(); tab.input.reset(); @@ -1395,21 +1392,21 @@ impl LeaveMode { } else { tab.directory.tree.select_root() }; - tab.directory.make_preview(colors); + tab.directory.make_preview(); } _ => { let next_index = tab.path_content.index; tab.search_from(&searched, next_index); } }; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move to the folder typed by the user. /// The first completion proposition is used, `~` expansion is done. /// If no result were found, no cd is done and we go back to normal mode /// silently. - pub fn goto(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn goto(status: &mut Status) -> Result<()> { let tab = status.selected(); if tab.completion.is_empty() { return Ok(()); @@ -1419,12 +1416,12 @@ impl LeaveMode { tab.input.reset(); tab.set_pathcontent(&path)?; tab.window.reset(tab.path_content.content.len()); - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move to the selected shortcut. /// It may fail if the user has no permission to visit the path. - pub fn shortcut(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn shortcut(status: &mut Status) -> Result<()> { let tab = status.selected(); tab.input.reset(); let path = tab @@ -1434,12 +1431,12 @@ impl LeaveMode { .clone(); tab.set_pathcontent(&path)?; tab.refresh_view()?; - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Move back to a previously visited path. /// It may fail if the user has no permission to visit the path - pub fn history(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn history(status: &mut Status) -> Result<()> { let tab = status.selected(); let (path, file) = tab .history @@ -1451,18 +1448,18 @@ impl LeaveMode { let index = tab.path_content.select_file(&file); tab.scroll_to(index); log::info!("leave history {path:?} {file:?} {index}"); - status.update_second_pane_for_preview(colors) + status.update_second_pane_for_preview() } /// Execute the selected node if it's a file else enter the directory. - pub fn tree(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn tree(status: &mut Status) -> Result<()> { let tab = status.selected(); let node = tab.directory.tree.current_node.clone(); if !node.is_dir { EventAction::open_file(status) } else { tab.set_pathcontent(&node.filepath())?; - tab.make_tree(colors)?; + tab.make_tree()?; Ok(()) } } @@ -1471,7 +1468,6 @@ impl LeaveMode { fn password( status: &mut Status, password_kind: PasswordKind, - colors: &Colors, dest: PasswordUsage, action: Option, ) -> Result<()> { @@ -1481,7 +1477,7 @@ impl LeaveMode { PasswordKind::CRYPTSETUP => status.password_holder.set_cryptsetup(password), } status.selected().reset_mode(); - status.dispatch_password(dest, action, colors) + status.dispatch_password(dest, action) } /// Compress the flagged files into an archive. @@ -1508,32 +1504,32 @@ impl LeaveMode { /// Execute the selected command. /// Some commands does nothing as they require to be executed from a specific /// context. - pub fn command(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn command(status: &mut Status) -> Result<()> { let command_str = status.selected_non_mut().completion.current_proposition(); let Ok(command) = ActionMap::from_str(command_str) else { return Ok(()); }; - command.matcher(status, colors) + command.matcher(status) } /// A right click opens a file or a directory. - pub fn right_click(status: &mut Status, colors: &Colors) -> Result<()> { + pub fn right_click(status: &mut Status) -> Result<()> { match status.selected().mode { Mode::Normal => LeaveMode::open_file(status), - Mode::Tree => LeaveMode::tree(status, colors), + Mode::Tree => LeaveMode::tree(status), _ => Ok(()), } } /// Apply a filter to the displayed files. /// See `crate::filter` for more details. - pub fn filter(tab: &mut Tab, colors: &Colors) -> Result<()> { + pub fn filter(tab: &mut Tab) -> Result<()> { let filter = FilterKind::from_input(&tab.input.string()); tab.set_filter(filter); tab.input.reset(); tab.path_content.reset_files(&tab.filter, tab.show_hidden)?; if let Mode::Tree = tab.previous_mode { - tab.make_tree(colors)?; + tab.make_tree()?; } tab.window.reset(tab.path_content.content.len()); Ok(()) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index b16d493d..3468c8f7 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -10,7 +10,7 @@ use log::info; use tuikit::prelude::{Attr, Color, Effect}; use users::{Groups, Users, UsersCache}; -use crate::config::{str_to_tuikit, Colors}; +use crate::color_cache::extension_color; use crate::constant_strings_paths::PERMISSIONS_STR; use crate::filter::FilterKind; use crate::git::git; @@ -549,20 +549,35 @@ impl PathContent { impl_selectable_content!(FileInfo, PathContent); +fn fileinfo_color(fileinfo: &FileInfo) -> Color { + match fileinfo.file_kind { + FileKind::Directory => Color::RED, + FileKind::BlockDevice => Color::YELLOW, + FileKind::CharDevice => Color::GREEN, + FileKind::Fifo => Color::BLUE, + FileKind::Socket => Color::CYAN, + FileKind::SymbolicLink(true) => Color::MAGENTA, + FileKind::SymbolicLink(false) => Color::WHITE, + _ => extension_color(&fileinfo.extension), + } +} + /// Associates a filetype to `tuikit::prelude::Attr` : fg color, bg color and /// effect. /// Selected file is reversed. -pub fn fileinfo_attr(fileinfo: &FileInfo, colors: &Colors) -> Attr { - let fg = match fileinfo.file_kind { - FileKind::Directory => str_to_tuikit(&colors.directory), - FileKind::BlockDevice => str_to_tuikit(&colors.block), - FileKind::CharDevice => str_to_tuikit(&colors.char), - FileKind::Fifo => str_to_tuikit(&colors.fifo), - FileKind::Socket => str_to_tuikit(&colors.socket), - FileKind::SymbolicLink(true) => str_to_tuikit(&colors.symlink), - FileKind::SymbolicLink(false) => str_to_tuikit(&colors.broken), - _ => colors.color_cache.extension_color(&fileinfo.extension), - }; +pub fn fileinfo_attr(fileinfo: &FileInfo) -> Attr { + // let fg = match fileinfo.file_kind { + // FileKind::Directory => str_to_tuikit(&colors.directory), + // FileKind::BlockDevice => str_to_tuikit(&colors.block), + // FileKind::CharDevice => str_to_tuikit(&colors.char), + // FileKind::Fifo => str_to_tuikit(&colors.fifo), + // FileKind::Socket => str_to_tuikit(&colors.socket), + // FileKind::SymbolicLink(true) => str_to_tuikit(&colors.symlink), + // FileKind::SymbolicLink(false) => str_to_tuikit(&colors.broken), + // _ => extension_color(&fileinfo.extension), + // // _ => colors.color_cache.extension_color(&fileinfo.extension), + // }; + let fg = fileinfo_color(fileinfo); let effect = if fileinfo.is_selected { Effect::REVERSE diff --git a/src/main.rs b/src/main.rs index d4490301..45e9cebf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use std::time::Duration; use anyhow::Result; use log::info; -use fm::config::{load_config, Colors}; +use fm::config::load_config; use fm::constant_strings_paths::{CONFIG_PATH, OPENER_PATH}; use fm::event_dispatch::EventDispatcher; use fm::help::Help; @@ -29,10 +29,10 @@ struct FM { status: Status, /// Responsible for the display on screen. display: Display, - /// Colors used by different kind of files. - /// Since most are generated the first time an extension is met, - /// we need to hold this. - colors: Colors, + // /// Colors used by different kind of files. + // /// Since most are generated the first time an extension is met, + // /// we need to hold this. + // colors: Colors, /// Refresher is used to force a refresh when a file has been modified externally. /// It send `Event::Key(Key::AltPageUp)` every 10 seconds. /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. @@ -70,7 +70,6 @@ impl FM { opener, &config.settings, )?; - let colors = config.colors.clone(); let refresher = Refresher::spawn(term); drop(config); Ok(Self { @@ -78,7 +77,6 @@ impl FM { event_dispatcher, status, display, - colors, refresher, }) } @@ -102,7 +100,7 @@ impl FM { self.event_dispatcher.dispatch( &mut self.status, event, - &self.colors, + // &self.colors, self.event_reader.term_height()?, )?; self.status.refresh_disks(); @@ -112,7 +110,7 @@ impl FM { /// Display itself using its `display` attribute. fn display(&mut self) -> Result<()> { self.force_clear_if_needed()?; - self.display.display_all(&self.status, &self.colors) + self.display.display_all(&self.status) } /// True iff the application must quit. diff --git a/src/preview.rs b/src/preview.rs index 013e1ba9..29b07c28 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -16,7 +16,6 @@ use syntect::parsing::{SyntaxReference, SyntaxSet}; use tuikit::attr::{Attr, Color}; use users::UsersCache; -use crate::config::Colors; use crate::constant_strings_paths::{ CALC_PDF_PATH, DIFF, FFMPEG, FONTIMAGE, ISOINFO, JUPYTER, LIBREOFFICE, LSBLK, LSOF, MEDIAINFO, PANDOC, RSVG_CONVERT, SS, THUMBNAIL_PATH, UEBERZUG, @@ -120,12 +119,10 @@ impl Preview { users_cache: &UsersCache, filter: &FilterKind, show_hidden: bool, - colors: &Colors, ) -> Result { Ok(Self::Directory(Directory::new( &file_info.path, users_cache, - colors, filter, show_hidden, Some(2), @@ -568,8 +565,8 @@ impl HLContent { } } -/// Holds a string to be displayed with given colors. -/// We have to read the colors from Syntect and parse it into tuikit attr +/// Holds a string to be displayed with given . +/// We have to read the from Syntect and parse it into tuikit attr /// This struct does the parsing. #[derive(Clone)] pub struct SyntaxedString { @@ -1063,7 +1060,6 @@ impl Directory { pub fn new( path: &Path, users_cache: &UsersCache, - colors: &Colors, filter_kind: &FilterKind, show_hidden: bool, max_depth: Option, @@ -1082,7 +1078,7 @@ impl Directory { vec![0], )?; tree.select_root(); - let (selected_index, content) = tree.into_navigable_content(colors); + let (selected_index, content) = tree.into_navigable_content(); Ok(Self { tree, len: content.len(), @@ -1120,10 +1116,10 @@ impl Directory { } /// Select the root node and reset the view. - pub fn select_root(&mut self, colors: &Colors) -> Result<()> { + pub fn select_root(&mut self) -> Result<()> { self.tree.select_root(); - (self.selected_index, self.content) = self.tree.into_navigable_content(colors); - self.update_tree_position_from_index(colors)?; + (self.selected_index, self.content) = self.tree.into_navigable_content(); + self.update_tree_position_from_index()?; Ok(()) } @@ -1134,40 +1130,40 @@ impl Directory { /// Select the "next" element of the tree if any. /// This is the element immediatly below the current one. - pub fn select_next(&mut self, colors: &Colors) -> Result<()> { + pub fn select_next(&mut self) -> Result<()> { if self.selected_index < self.content.len() { self.tree.increase_required_height(); self.unselect_children(); self.selected_index += 1; - self.update_tree_position_from_index(colors)?; + self.update_tree_position_from_index()?; } Ok(()) } /// Select the previous sibling if any. /// This is the element immediatly below the current one. - pub fn select_prev(&mut self, colors: &Colors) -> Result<()> { + pub fn select_prev(&mut self) -> Result<()> { if self.selected_index > 0 { self.tree.decrease_required_height(); self.unselect_children(); self.selected_index -= 1; - self.update_tree_position_from_index(colors)?; + self.update_tree_position_from_index()?; } Ok(()) } /// Move up 10 times. - pub fn page_up(&mut self, colors: &Colors) -> Result<()> { + pub fn page_up(&mut self) -> Result<()> { if self.selected_index > 10 { self.selected_index -= 10; } else { self.selected_index = 1; } - self.update_tree_position_from_index(colors) + self.update_tree_position_from_index() } /// Move down 10 times - pub fn page_down(&mut self, colors: &Colors) -> Result<()> { + pub fn page_down(&mut self) -> Result<()> { self.selected_index += 10; if self.selected_index > self.content.len() { if !self.content.is_empty() { @@ -1176,45 +1172,45 @@ impl Directory { self.selected_index = 1; } } - self.update_tree_position_from_index(colors) + self.update_tree_position_from_index() } /// Update the position of the selected element from its index. - pub fn update_tree_position_from_index(&mut self, colors: &Colors) -> Result<()> { + pub fn update_tree_position_from_index(&mut self) -> Result<()> { self.tree.position = self.tree.position_from_index(self.selected_index); let (_, _, node) = self.tree.select_from_position()?; self.tree.current_node = node; - (_, self.content) = self.tree.into_navigable_content(colors); + (_, self.content) = self.tree.into_navigable_content(); Ok(()) } /// Select the first child, if any. - pub fn select_first_child(&mut self, colors: &Colors) -> Result<()> { + pub fn select_first_child(&mut self) -> Result<()> { self.tree.select_first_child()?; - (self.selected_index, self.content) = self.tree.into_navigable_content(colors); - self.update_tree_position_from_index(colors)?; + (self.selected_index, self.content) = self.tree.into_navigable_content(); + self.update_tree_position_from_index()?; Ok(()) } /// Select the parent of current node. - pub fn select_parent(&mut self, colors: &Colors) -> Result<()> { + pub fn select_parent(&mut self) -> Result<()> { self.tree.select_parent()?; - (self.selected_index, self.content) = self.tree.into_navigable_content(colors); - self.update_tree_position_from_index(colors)?; + (self.selected_index, self.content) = self.tree.into_navigable_content(); + self.update_tree_position_from_index()?; Ok(()) } /// Select the last leaf of the tree (ie the last line.) - pub fn go_to_bottom_leaf(&mut self, colors: &Colors) -> Result<()> { + pub fn go_to_bottom_leaf(&mut self) -> Result<()> { self.tree.go_to_bottom_leaf()?; - (self.selected_index, self.content) = self.tree.into_navigable_content(colors); - self.update_tree_position_from_index(colors)?; + (self.selected_index, self.content) = self.tree.into_navigable_content(); + self.update_tree_position_from_index()?; Ok(()) } /// Make a preview of the tree. - pub fn make_preview(&mut self, colors: &Colors) { - (self.selected_index, self.content) = self.tree.into_navigable_content(colors); + pub fn make_preview(&mut self) { + (self.selected_index, self.content) = self.tree.into_navigable_content(); } /// Calculates the top, bottom and lenght of the view, depending on which element diff --git a/src/status.rs b/src/status.rs index becb3e72..db78725e 100644 --- a/src/status.rs +++ b/src/status.rs @@ -18,7 +18,7 @@ use crate::args::Args; use crate::bulkrename::Bulk; use crate::cli_info::CliInfo; use crate::compress::Compresser; -use crate::config::{Colors, Settings}; +use crate::config::Settings; use crate::constant_strings_paths::{NVIM, SS, TUIS_PATH}; use crate::copy_move::{copy_move, CopyMove}; use crate::cryptsetup::{BlockDeviceAction, CryptoDeviceOpener}; @@ -450,15 +450,9 @@ impl Status { Ok(()) } - pub fn click( - &mut self, - row: u16, - col: u16, - current_height: usize, - colors: &Colors, - ) -> Result<()> { + pub fn click(&mut self, row: u16, col: u16, current_height: usize) -> Result<()> { self.select_pane(col)?; - self.selected().select_row(row, colors, current_height) + self.selected().select_row(row, current_height) } /// Set the permissions of the flagged files according to a given permission. @@ -576,7 +570,7 @@ impl Status { Ok(()) } - pub fn make_preview(&mut self, colors: &Colors) -> Result<()> { + pub fn make_preview(&mut self) -> Result<()> { if self.selected_non_mut().path_content.is_empty() { return Ok(()); } @@ -590,14 +584,14 @@ impl Status { self.selected().window.reset(preview.len()); self.selected().preview = preview; } - FileKind::Directory => self.tree(colors)?, + FileKind::Directory => self.tree()?, _ => (), } Ok(()) } - pub fn tree(&mut self, colors: &Colors) -> Result<()> { + pub fn tree(&mut self) -> Result<()> { if let Mode::Tree = self.selected_non_mut().mode { { let tab: &mut Tab = self.selected(); @@ -606,7 +600,7 @@ impl Status { self.selected().set_mode(Mode::Normal) } else { self.display_full = true; - self.selected().make_tree(colors)?; + self.selected().make_tree()?; self.selected().set_mode(Mode::Tree); let len = self.selected_non_mut().directory.len(); self.selected().window.reset(len); @@ -615,16 +609,16 @@ impl Status { } /// Check if the second pane should display a preview and force it. - pub fn update_second_pane_for_preview(&mut self, colors: &Colors) -> Result<()> { + pub fn update_second_pane_for_preview(&mut self) -> Result<()> { if self.index == 0 && self.preview_second { - self.set_second_pane_for_preview(colors)?; + self.set_second_pane_for_preview()?; }; Ok(()) } /// Force preview the selected file of the first pane in the second pane. /// Doesn't check if it has do. - pub fn set_second_pane_for_preview(&mut self, colors: &Colors) -> Result<()> { + pub fn set_second_pane_for_preview(&mut self) -> Result<()> { if !Self::display_wide_enough(&self.term)? { self.tabs[1].preview = Preview::empty(); return Ok(()); @@ -640,7 +634,6 @@ impl Status { &self.tabs[0].path_content.users_cache, &self.tabs[0].filter, self.tabs[0].show_hidden, - colors, ), _ => Preview::file(fileinfo), }; @@ -885,7 +878,7 @@ impl Status { } /// Execute a new mark, saving it to a config file for futher use. - pub fn marks_new(&mut self, c: char, colors: &Colors) -> Result<()> { + pub fn marks_new(&mut self, c: char) -> Result<()> { let path = self.selected().path_content.path.clone(); self.marks.new_mark(c, &path)?; { @@ -893,27 +886,27 @@ impl Status { tab.refresh_view() }?; self.selected().reset_mode(); - self.refresh_status(colors) + self.refresh_status() } /// Execute a jump to a mark, moving to a valid path. /// If the saved path is invalid, it does nothing but reset the view. - pub fn marks_jump_char(&mut self, c: char, colors: &Colors) -> Result<()> { + pub fn marks_jump_char(&mut self, c: char) -> Result<()> { if let Some(path) = self.marks.get(c) { self.selected().set_pathcontent(&path)?; } self.selected().refresh_view()?; self.selected().reset_mode(); - self.refresh_status(colors) + self.refresh_status() } /// Reset the selected tab view to the default. - pub fn refresh_status(&mut self, colors: &Colors) -> Result<()> { + pub fn refresh_status(&mut self) -> Result<()> { self.force_clear(); self.refresh_users()?; self.selected().refresh_view()?; if let Mode::Tree = self.selected_non_mut().mode { - self.selected().make_tree(colors)? + self.selected().make_tree()? } Ok(()) } @@ -931,7 +924,7 @@ impl Status { } /// Recursively delete all flagged files. - pub fn confirm_delete_files(&mut self, colors: &Colors) -> Result<()> { + pub fn confirm_delete_files(&mut self) -> Result<()> { let nb = self.flagged.len(); for pathbuf in self.flagged.content.iter() { if pathbuf.is_dir() { @@ -944,7 +937,7 @@ impl Status { write_log_line(log_line); self.selected().reset_mode(); self.clear_flags_and_reset_view()?; - self.refresh_status(colors) + self.refresh_status() } /// Empty the trash folder permanently. @@ -955,7 +948,7 @@ impl Status { Ok(()) } - fn run_sudo_command(&mut self, colors: &Colors) -> Result<()> { + fn run_sudo_command(&mut self) -> Result<()> { self.selected().set_mode(Mode::Normal); reset_sudo_faillock()?; let Some(sudo_command) = &self.sudo_command else { @@ -972,14 +965,13 @@ impl Status { )?; self.password_holder.reset(); drop_sudo_privileges()?; - self.refresh_status(colors) + self.refresh_status() } pub fn dispatch_password( &mut self, dest: PasswordUsage, action: Option, - colors: &Colors, ) -> Result<()> { match dest { PasswordUsage::ISO => match action { @@ -992,7 +984,7 @@ impl Status { Some(BlockDeviceAction::UMOUNT) => self.umount_encrypted_drive(), None => Ok(()), }, - PasswordUsage::SUDOCOMMAND => Self::run_sudo_command(self, colors), + PasswordUsage::SUDOCOMMAND => Self::run_sudo_command(self), } } @@ -1028,14 +1020,9 @@ impl Status { /// Execute a command requiring a confirmation (Delete, Move or Copy). /// The action is only executed if the user typed the char `y` - pub fn confirm( - &mut self, - c: char, - confirmed_action: NeedConfirmation, - colors: &Colors, - ) -> Result<()> { + pub fn confirm(&mut self, c: char, confirmed_action: NeedConfirmation) -> Result<()> { if c == 'y' { - let _ = self.match_confirmed_mode(confirmed_action, colors); + let _ = self.match_confirmed_mode(confirmed_action); } if self.selected().reset_mode() { self.selected().refresh_view()?; @@ -1043,13 +1030,9 @@ impl Status { Ok(()) } - fn match_confirmed_mode( - &mut self, - confirmed_action: NeedConfirmation, - colors: &Colors, - ) -> Result<()> { + fn match_confirmed_mode(&mut self, confirmed_action: NeedConfirmation) -> Result<()> { match confirmed_action { - NeedConfirmation::Delete => self.confirm_delete_files(colors), + NeedConfirmation::Delete => self.confirm_delete_files(), NeedConfirmation::Move => self.cut_or_copy_flagged_files(CopyMove::Move), NeedConfirmation::Copy => self.cut_or_copy_flagged_files(CopyMove::Copy), NeedConfirmation::EmptyTrash => self.confirm_trash_empty(), diff --git a/src/tab.rs b/src/tab.rs index f644bf71..6e1a6e69 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -6,7 +6,7 @@ use users::UsersCache; use crate::args::Args; use crate::completion::{Completion, InputCompleted}; -use crate::config::{Colors, Settings}; +use crate::config::Settings; use crate::content_window::ContentWindow; use crate::fileinfo::{FileInfo, PathContent}; use crate::filter::FilterKind; @@ -305,25 +305,25 @@ impl Tab { } /// Select the root node of the tree. - pub fn tree_select_root(&mut self, colors: &Colors) -> Result<()> { + pub fn tree_select_root(&mut self) -> Result<()> { self.directory.unselect_children(); - self.directory.select_root(colors) + self.directory.select_root() } /// Move to the parent of current path - pub fn move_to_parent(&mut self, colors: &Colors) -> Result<()> { + pub fn move_to_parent(&mut self) -> Result<()> { let path = self.path_content.path.clone(); let Some(parent) = path.parent() else { return Ok(()); }; if self.history.is_this_the_last(parent) { - self.back(colors)?; + self.back()?; return Ok(()); } self.set_pathcontent(parent) } - pub fn back(&mut self, colors: &Colors) -> Result<()> { + pub fn back(&mut self) -> Result<()> { if self.history.content.is_empty() { return Ok(()); } @@ -335,7 +335,7 @@ impl Tab { self.scroll_to(index); self.history.content.pop(); if let Mode::Tree = self.mode { - self.make_tree(colors)? + self.make_tree()? } Ok(()) @@ -343,50 +343,50 @@ impl Tab { /// Select the parent of current node. /// If we were at the root node, move to the parent and make a new tree. - pub fn tree_select_parent(&mut self, colors: &Colors) -> Result<()> { + pub fn tree_select_parent(&mut self) -> Result<()> { self.directory.unselect_children(); if self.directory.tree.position.len() <= 1 { - self.move_to_parent(colors)?; - self.make_tree(colors)? + self.move_to_parent()?; + self.make_tree()? } - self.directory.select_parent(colors) + self.directory.select_parent() } /// Move down 10 times in the tree - pub fn tree_page_down(&mut self, colors: &Colors) -> Result<()> { + pub fn tree_page_down(&mut self) -> Result<()> { self.directory.tree.increase_required_height_by_ten(); self.directory.unselect_children(); - self.directory.page_down(colors) + self.directory.page_down() } /// Move up 10 times in the tree - pub fn tree_page_up(&mut self, colors: &Colors) -> Result<()> { + pub fn tree_page_up(&mut self) -> Result<()> { self.directory.tree.decrease_required_height_by_ten(); self.directory.unselect_children(); - self.directory.page_up(colors) + self.directory.page_up() } /// Select the next sibling. - pub fn tree_select_next(&mut self, colors: &Colors) -> Result<()> { - self.directory.select_next(colors) + pub fn tree_select_next(&mut self) -> Result<()> { + self.directory.select_next() } /// Select the previous siblging - pub fn tree_select_prev(&mut self, colors: &Colors) -> Result<()> { - self.directory.select_prev(colors) + pub fn tree_select_prev(&mut self) -> Result<()> { + self.directory.select_prev() } /// Select the first child if any. - pub fn tree_select_first_child(&mut self, colors: &Colors) -> Result<()> { + pub fn tree_select_first_child(&mut self) -> Result<()> { self.directory.unselect_children(); - self.directory.select_first_child(colors) + self.directory.select_first_child() } /// Go to the last leaf. - pub fn tree_go_to_bottom_leaf(&mut self, colors: &Colors) -> Result<()> { + pub fn tree_go_to_bottom_leaf(&mut self) -> Result<()> { self.directory.tree.set_required_height_to_max(); self.directory.unselect_children(); - self.directory.go_to_bottom_leaf(colors) + self.directory.go_to_bottom_leaf() } /// Returns the current path. @@ -430,17 +430,10 @@ impl Tab { } /// Makes a new tree of the current path. - pub fn make_tree(&mut self, colors: &Colors) -> Result<()> { + pub fn make_tree(&mut self) -> Result<()> { let path = self.path_content.path.clone(); let users_cache = &self.path_content.users_cache; - self.directory = Directory::new( - &path, - users_cache, - colors, - &self.filter, - self.show_hidden, - None, - )?; + self.directory = Directory::new(&path, users_cache, &self.filter, self.show_hidden, None)?; Ok(()) } @@ -517,24 +510,24 @@ impl Tab { /// Fold every child node in the tree. /// Recursively explore the tree and fold every node. Reset the display. - pub fn tree_go_to_root(&mut self, colors: &Colors) -> Result<()> { + pub fn tree_go_to_root(&mut self) -> Result<()> { self.directory.tree.reset_required_height(); - self.tree_select_root(colors) + self.tree_select_root() } /// Select the first child of the current node and reset the display. - pub fn select_first_child(&mut self, colors: &Colors) -> Result<()> { - self.tree_select_first_child(colors) + pub fn select_first_child(&mut self) -> Result<()> { + self.tree_select_first_child() } /// Select the next sibling of the current node. - pub fn select_next(&mut self, colors: &Colors) -> Result<()> { - self.tree_select_next(colors) + pub fn select_next(&mut self) -> Result<()> { + self.tree_select_next() } /// Select the previous sibling of the current node. - pub fn select_prev(&mut self, colors: &Colors) -> Result<()> { - self.tree_select_prev(colors) + pub fn select_prev(&mut self) -> Result<()> { + self.tree_select_prev() } /// Copy the selected filename to the clipboard. Only the filename. @@ -638,10 +631,10 @@ impl Tab { } /// Select a given row, if there's something in it. - pub fn select_row(&mut self, row: u16, colors: &Colors, term_height: usize) -> Result<()> { + pub fn select_row(&mut self, row: u16, term_height: usize) -> Result<()> { match self.mode { Mode::Normal => self.normal_select_row(row), - Mode::Tree => self.tree_select_row(row, colors, term_height)?, + Mode::Tree => self.tree_select_row(row, term_height)?, _ => (), } Ok(()) @@ -654,7 +647,7 @@ impl Tab { self.window.scroll_to(index); } - fn tree_select_row(&mut self, row: u16, colors: &Colors, term_height: usize) -> Result<()> { + fn tree_select_row(&mut self, row: u16, term_height: usize) -> Result<()> { let screen_index = row_to_window_index(row) + 1; // term.height = canvas.height + 2 rows for the canvas border let (top, _, _) = self.directory.calculate_tree_window(term_height - 2); @@ -662,7 +655,7 @@ impl Tab { self.directory.tree.unselect_children(); self.directory.tree.position = self.directory.tree.position_from_index(index); let (_, _, node) = self.directory.tree.select_from_position()?; - self.directory.make_preview(colors); + self.directory.make_preview(); self.directory.tree.current_node = node; Ok(()) } @@ -676,7 +669,7 @@ impl Tab { /// by extension. /// The first letter is used to identify the method. /// If the user types an uppercase char, the sort is reverse. - pub fn sort(&mut self, c: char, colors: &Colors) -> Result<()> { + pub fn sort(&mut self, c: char) -> Result<()> { if self.path_content.content.is_empty() { return Ok(()); } @@ -692,8 +685,8 @@ impl Tab { Mode::Tree => { self.directory.tree.update_sort_from_char(c); self.directory.tree.sort(); - self.tree_select_root(colors)?; - self.directory.tree.into_navigable_content(colors); + self.tree_select_root()?; + self.directory.tree.into_navigable_content(); } _ => (), } diff --git a/src/term_manager.rs b/src/term_manager.rs index 01277be9..ef7d915f 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -11,7 +11,6 @@ use tuikit::term::Term; use crate::completion::InputCompleted; use crate::compress::CompressionMethod; -use crate::config::Colors; use crate::constant_strings_paths::{ ENCRYPTED_DEVICE_BINDS, HELP_FIRST_SENTENCE, HELP_SECOND_SENTENCE, LOG_FIRST_SENTENCE, LOG_SECOND_SENTENCE, @@ -133,7 +132,6 @@ struct WinMain<'a> { status: &'a Status, tab: &'a Tab, disk_space: &'a str, - colors: &'a Colors, attributes: WinMainAttributes, } @@ -167,14 +165,12 @@ impl<'a> WinMain<'a> { status: &'a Status, index: usize, disk_space: &'a str, - colors: &'a Colors, attributes: WinMainAttributes, ) -> Self { Self { status, tab: &status.tabs[index], disk_space, - colors, attributes, } } @@ -227,7 +223,7 @@ impl<'a> WinMain<'a> { ) -> Result { let owner_size = file.owner.len(); let group_size = file.group.len(); - let mut attr = fileinfo_attr(file, self.colors); + let mut attr = fileinfo_attr(file); attr.effect ^= Effect::REVERSE; if status.flagged.contains(&file.path) { @@ -378,7 +374,7 @@ impl<'a> WinMain<'a> { .skip(tab.window.top) { let row = i + ContentWindow::WINDOW_MARGIN_TOP - tab.window.top; - let mut attr = fileinfo_attr(file, self.colors); + let mut attr = fileinfo_attr(file); let string = if status.display_full { file.format(owner_size, group_size)? } else { @@ -1003,16 +999,16 @@ impl Display { /// The preview in preview mode. /// Displays one pane or two panes, depending of the width and current /// status of the application. - pub fn display_all(&mut self, status: &Status, colors: &Colors) -> Result<()> { + pub fn display_all(&mut self, status: &Status) -> Result<()> { self.hide_cursor()?; self.term.clear()?; let (width, _) = self.term.term_size()?; let disk_spaces = status.disk_spaces_per_tab(); if status.dual_pane && width > MIN_WIDTH_FOR_DUAL_PANE { - self.draw_dual_pane(status, &disk_spaces.0, &disk_spaces.1, colors)? + self.draw_dual_pane(status, &disk_spaces.0, &disk_spaces.1)? } else { - self.draw_single_pane(status, &disk_spaces.0, colors)? + self.draw_single_pane(status, &disk_spaces.0)? } Ok(self.term.present()?) @@ -1071,7 +1067,6 @@ impl Display { status: &Status, disk_space_tab_0: &str, disk_space_tab_1: &str, - colors: &Colors, ) -> Result<()> { let (width, _) = self.term.term_size()?; let (first_selected, second_selected) = (status.index == 0, status.index == 1); @@ -1081,14 +1076,14 @@ impl Display { first_selected, status.tabs[0].need_second_window(), ); - let win_main_left = WinMain::new(status, 0, disk_space_tab_0, colors, attributes_left); + let win_main_left = WinMain::new(status, 0, disk_space_tab_0, attributes_left); let attributes_right = WinMainAttributes::new( width / 2, true, second_selected, status.tabs[1].need_second_window(), ); - let win_main_right = WinMain::new(status, 1, disk_space_tab_1, colors, attributes_right); + let win_main_right = WinMain::new(status, 1, disk_space_tab_1, attributes_right); let win_second_left = WinSecondary::new(status, 0); let win_second_right = WinSecondary::new(status, 1); let (border_left, border_right) = self.borders(status); @@ -1110,15 +1105,10 @@ impl Display { Ok(self.term.draw(&hsplit)?) } - fn draw_single_pane( - &mut self, - status: &Status, - disk_space_tab_0: &str, - colors: &Colors, - ) -> Result<()> { + fn draw_single_pane(&mut self, status: &Status, disk_space_tab_0: &str) -> Result<()> { let attributes_left = WinMainAttributes::new(0, false, true, status.tabs[0].need_second_window()); - let win_main_left = WinMain::new(status, 0, disk_space_tab_0, colors, attributes_left); + let win_main_left = WinMain::new(status, 0, disk_space_tab_0, attributes_left); let win_second_left = WinSecondary::new(status, 0); let percent_left = self.size_for_second_window(&status.tabs[0])?; let win = self.vertical_split( diff --git a/src/tree.rs b/src/tree.rs index c8425702..bd9f80d4 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -4,7 +4,6 @@ use anyhow::{Context, Result}; use tuikit::attr::Attr; use users::UsersCache; -use crate::config::Colors; use crate::fileinfo::{fileinfo_attr, files_collection, FileInfo, FileKind}; use crate::filter::FilterKind; use crate::preview::ColoredTriplet; @@ -27,7 +26,7 @@ impl ColoredString { Self { text, attr, path } } - fn from_node(current_node: &Node, colors: &Colors) -> Self { + fn from_node(current_node: &Node) -> Self { let text = if current_node.is_dir { if current_node.folded { format!("▸ {}", ¤t_node.fileinfo.filename) @@ -37,13 +36,13 @@ impl ColoredString { } else { current_node.filename() }; - Self::new(text, current_node.attr(colors), current_node.filepath()) + Self::new(text, current_node.attr(), current_node.filepath()) } - fn from_metadata_line(current_node: &Node, colors: &Colors) -> Self { + fn from_metadata_line(current_node: &Node) -> Self { Self::new( current_node.metadata_line.to_owned(), - current_node.attr(colors), + current_node.attr(), current_node.filepath(), ) } @@ -72,8 +71,8 @@ impl Node { self.fileinfo.path.to_owned() } - fn attr(&self, colors: &Colors) -> Attr { - fileinfo_attr(&self.fileinfo, colors) + fn attr(&self) -> Attr { + fileinfo_attr(&self.fileinfo) } fn select(&mut self) { @@ -459,7 +458,7 @@ impl Tree { /// is reached. There's no way atm to avoid parsing the first lines /// since the "prefix" (straight lines at left of screen) can reach /// the whole screen. - pub fn into_navigable_content(&mut self, colors: &Colors) -> (usize, Vec) { + pub fn into_navigable_content(&mut self) -> (usize, Vec) { let required_height = self.required_height; let mut stack = vec![("".to_owned(), self)]; let mut content = vec![]; @@ -471,9 +470,9 @@ impl Tree { } content.push(( - ColoredString::from_metadata_line(¤t.node, colors), + ColoredString::from_metadata_line(¤t.node), prefix.to_owned(), - ColoredString::from_node(¤t.node, colors), + ColoredString::from_node(¤t.node), )); if !current.node.folded { From 3f2a118b19e421a040e3e5334796d48e6ee67731 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 30 Oct 2023 20:00:48 +0100 Subject: [PATCH 075/168] remove color configuration & caching. All colors are calculated every time --- config_files/fm/config.yaml | 9 ---- development.md | 3 +- readme.md | 8 ++-- src/color_cache.rs | 45 ------------------- src/colors.rs | 18 ++++++++ src/config.rs | 87 ------------------------------------- src/event_dispatch.rs | 8 +--- src/fileinfo.rs | 13 +----- src/lib.rs | 2 +- src/main.rs | 5 --- src/term_manager.rs | 12 ++--- 11 files changed, 31 insertions(+), 179 deletions(-) delete mode 100644 src/color_cache.rs create mode 100644 src/colors.rs diff --git a/config_files/fm/config.yaml b/config_files/fm/config.yaml index 8095b109..b5719f8e 100644 --- a/config_files/fm/config.yaml +++ b/config_files/fm/config.yaml @@ -1,14 +1,5 @@ # the terminal must be installed terminal: st -colors: - # white, black, red, green, blue, yellow, cyan, magenta - # light_white, light_black, light_red, light_green, light_blue, light_yellow, light_cyan, light_magenta - block: yellow - char: green - directory: red - fifo: blue - socket: cyan - symlink: magenta # keybindings keys: 'esc': ResetMode diff --git a/development.md b/development.md index dc087cdc..ec290089 100644 --- a/development.md +++ b/development.md @@ -594,7 +594,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] add MTP mount points to shortcuts - [x] list, mount, unmount mtp mount points - [x] bulk, skim & removable are `None` until first use. -- [ ] remove dependencies +- [x] remove dependencies +- [x] remove colors configuration. Calculate every color for every extension - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/readme.md b/readme.md index 974e060f..b9e5fd97 100644 --- a/readme.md +++ b/readme.md @@ -289,7 +289,6 @@ Every configuration file is saved in `~/.config/fm/` You can configure : -- **Colors** for non standard file types (directory, socket, char device, block device) - **Keybindings**. Some should be left as they are, but all keybindings can be configured. use the provided config file as a default. Multiple keys can be bound the the same action. @@ -310,11 +309,13 @@ You can configure : Open the menu with `S` and pick the desired one. It will only work with a TUI application like HTOP, not a CLI application like bat. +Before version 0.1.23, colors used to be configurable. Configuration wasn't a problem but passing it everywhere was a burden. + ## External dependencies Most of the openers and tui applications are configurable from config files. Some are hardcoded if their command is quite specific or if I couldn't find a workaround. -- [lsblk](https://linux.die.net/man/8/lsblk): list encroytped devices +- [lsblk](https://linux.die.net/man/8/lsblk): list encrytped devices - [faillock](https://linux.die.net/man/8/faillock): reset failed sudo attempts - [Cryptsetup](https://gitlab.com/cryptsetup/cryptsetup): decrypt & mount encrypted devices - [Nitrogen](https://github.com/l3ib/nitrogen/): set up a wallpaper @@ -323,9 +324,10 @@ Most of the openers and tui applications are configurable from config files. Som - [Ueberzug](https://github.com/LalleSX/ueberzug) display images in your terminal. Used to preview images. This one may be tricky to install from source since the original maintener nuked his project. It's still available in many package managers. - [isoinfo](https://command-not-found.com/isoinfo) allow the content preview of an iso file - [jupyter](https://jupyter.org/) preview jupyter notebooks by converting them to markdown -- [pandoc](https://pandoc.org) preview open documents by converting them to markdown with pandoc +- [pandoc](https://pandoc.org) preview epub by converting them to markdown with pandoc - [fontimage](https://fontforge.org/docs/fontutils/fontimage.html) preview fonts by creating a thumbnail - [rsvg-convert](https://github.com/brion/librsvg) preview svg by creating a thumbnail +- [libreoffice](https://www.libreoffice.org) preview open & MS-office documents ## Contribution diff --git a/src/color_cache.rs b/src/color_cache.rs deleted file mode 100644 index e8af4cd7..00000000 --- a/src/color_cache.rs +++ /dev/null @@ -1,45 +0,0 @@ -// use std::cell::RefCell; -// use std::collections::HashMap; -use std::hash::Hasher; - -use tuikit::attr::Color; - -/// Holds a map of extension name to color. -/// Every extension is associated to a color which is only computed once -/// per run. This trades a bit of memory for a bit of CPU. -#[derive(Default, Clone, Debug)] -pub struct ColorCache { - // cache: RefCell>, -} - -// impl ColorCache { -// /// Returns a color for any possible extension. -// /// The color is cached within the struct, avoiding multiple calculations. -// pub fn extension_color(&self, extension: &str) -> Color { -// let mut cache = self.cache.borrow_mut(); -// if let Some(color) = cache.get(extension) { -// color.to_owned() -// } else { -// let color = extension_color(extension); -// cache.insert(extension.to_owned(), color); -// color -// } -// } -// } - -/// Picks a blueish/greenish color on color picker hexagon's perimeter. -fn color(coords: usize) -> Color { - (128..255) - .map(|b| Color::Rgb(0, 255, b)) - .chain((128..255).map(|g| Color::Rgb(0, g, 255))) - .chain((128..255).rev().map(|b| Color::Rgb(0, 255, b))) - .chain((128..255).rev().map(|g| Color::Rgb(0, g, 255))) - .nth(coords % 508) - .unwrap() -} - -pub fn extension_color(extension: &str) -> Color { - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - hasher.write(extension.as_bytes()); - color(hasher.finish() as usize) -} diff --git a/src/colors.rs b/src/colors.rs new file mode 100644 index 00000000..f7d20ac7 --- /dev/null +++ b/src/colors.rs @@ -0,0 +1,18 @@ +use std::hash::Hasher; + +use tuikit::attr::Color; + +/// Picks a blueish/greenish color on color picker hexagon's perimeter. +fn color(hash: usize) -> Color { + (128..255) + .map(|b| Color::Rgb(0, 255, b)) + .chain((128..255).map(|g| Color::Rgb(0, g, 255))) + .nth(hash % 254) + .unwrap() +} + +pub fn extension_color(extension: &str) -> Color { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + hasher.write(extension.as_bytes()); + color(hasher.finish() as usize) +} diff --git a/src/config.rs b/src/config.rs index 5a87c646..3909cb91 100644 --- a/src/config.rs +++ b/src/config.rs @@ -39,13 +39,6 @@ impl Settings { } } -// macro_rules! update_attribute { -// ($self_attr:expr, $yaml:ident, $key:expr) => { -// if let Some(attr) = read_yaml_value($yaml, $key) { -// $self_attr = attr; -// } -// }; -// } /// Holds every configurable aspect of the application. /// All attributes are hardcoded then updated from optional values /// of the config file. @@ -96,86 +89,6 @@ impl Config { } } -// fn read_yaml_value(yaml: &serde_yaml::value::Value, key: &str) -> Option { -// yaml[key].as_str().map(|s| s.to_string()) -// } - -// /// Holds configurable colors for every kind of file. -// /// "Normal" files are displayed with a different color by extension. -// #[derive(Debug, Clone)] -// pub struct Colors { -// /// Color for `directory` files. -// pub directory: String, -// /// Color for `block` files. -// pub block: String, -// /// Color for `char` files. -// pub char: String, -// /// Color for `fifo` files. -// pub fifo: String, -// /// Color for `socket` files. -// pub socket: String, -// /// Color for `symlink` files. -// pub symlink: String, -// /// Color for broken `symlink` files. -// pub broken: String, -// // /// Colors for normal files, depending of extension -// // pub color_cache: ColorCache, -// } -// -// impl Colors { -// fn update_from_config(&mut self, yaml: &serde_yaml::value::Value) { -// update_attribute!(self.directory, yaml, "directory"); -// update_attribute!(self.block, yaml, "block"); -// update_attribute!(self.char, yaml, "char"); -// update_attribute!(self.fifo, yaml, "fifo"); -// update_attribute!(self.socket, yaml, "socket"); -// update_attribute!(self.symlink, yaml, "symlink"); -// update_attribute!(self.broken, yaml, "broken"); -// } -// -// fn new() -> Self { -// Self { -// directory: "red".to_owned(), -// block: "yellow".to_owned(), -// char: "green".to_owned(), -// fifo: "blue".to_owned(), -// socket: "cyan".to_owned(), -// symlink: "magenta".to_owned(), -// broken: "white".to_owned(), -// // color_cache: ColorCache::default(), -// } -// } -// } -// -// impl Default for Colors { -// fn default() -> Self { -// Self::new() -// } -// } -// -// /// Convert a string color into a `tuikit::Color` instance. -// pub fn str_to_tuikit(color: &str) -> Color { -// match color { -// "white" => Color::WHITE, -// "red" => Color::RED, -// "green" => Color::GREEN, -// "blue" => Color::BLUE, -// "yellow" => Color::YELLOW, -// "cyan" => Color::CYAN, -// "magenta" => Color::MAGENTA, -// "black" => Color::BLACK, -// "light_white" => Color::LIGHT_WHITE, -// "light_red" => Color::LIGHT_RED, -// "light_green" => Color::LIGHT_GREEN, -// "light_blue" => Color::LIGHT_BLUE, -// "light_yellow" => Color::LIGHT_YELLOW, -// "light_cyan" => Color::LIGHT_CYAN, -// "light_magenta" => Color::LIGHT_MAGENTA, -// "light_black" => Color::LIGHT_BLACK, -// _ => Color::default(), -// } -// } - /// Returns a config with values from : /// /// 1. hardcoded values diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index c98a894f..1ae3aa6f 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -25,13 +25,7 @@ impl EventDispatcher { /// Only non keyboard events are dealt here directly. /// Keyboard events are configurable and are sent to specific functions /// which needs to know those keybindings. - pub fn dispatch( - &self, - status: &mut Status, - ev: Event, - // colors: &Colors, - current_height: usize, - ) -> Result<()> { + pub fn dispatch(&self, status: &mut Status, ev: Event, current_height: usize) -> Result<()> { match ev { Event::Key(Key::WheelUp(_, col, _)) => { status.select_pane(col)?; diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 3468c8f7..881dcb73 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -10,7 +10,7 @@ use log::info; use tuikit::prelude::{Attr, Color, Effect}; use users::{Groups, Users, UsersCache}; -use crate::color_cache::extension_color; +use crate::colors::extension_color; use crate::constant_strings_paths::PERMISSIONS_STR; use crate::filter::FilterKind; use crate::git::git; @@ -566,17 +566,6 @@ fn fileinfo_color(fileinfo: &FileInfo) -> Color { /// effect. /// Selected file is reversed. pub fn fileinfo_attr(fileinfo: &FileInfo) -> Attr { - // let fg = match fileinfo.file_kind { - // FileKind::Directory => str_to_tuikit(&colors.directory), - // FileKind::BlockDevice => str_to_tuikit(&colors.block), - // FileKind::CharDevice => str_to_tuikit(&colors.char), - // FileKind::Fifo => str_to_tuikit(&colors.fifo), - // FileKind::Socket => str_to_tuikit(&colors.socket), - // FileKind::SymbolicLink(true) => str_to_tuikit(&colors.symlink), - // FileKind::SymbolicLink(false) => str_to_tuikit(&colors.broken), - // _ => extension_color(&fileinfo.extension), - // // _ => colors.color_cache.extension_color(&fileinfo.extension), - // }; let fg = fileinfo_color(fileinfo); let effect = if fileinfo.is_selected { diff --git a/src/lib.rs b/src/lib.rs index 5fd6ff0b..ef36e709 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub mod action_map; pub mod args; pub mod bulkrename; pub mod cli_info; -pub mod color_cache; +pub mod colors; pub mod completion; pub mod compress; pub mod config; diff --git a/src/main.rs b/src/main.rs index 45e9cebf..113918cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,10 +29,6 @@ struct FM { status: Status, /// Responsible for the display on screen. display: Display, - // /// Colors used by different kind of files. - // /// Since most are generated the first time an extension is met, - // /// we need to hold this. - // colors: Colors, /// Refresher is used to force a refresh when a file has been modified externally. /// It send `Event::Key(Key::AltPageUp)` every 10 seconds. /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. @@ -45,7 +41,6 @@ impl FM { /// an `EventDispatcher`, /// a `Status`, /// a `Display`, - /// some `Colors`, /// a `Refresher`. /// It reads and drops the configuration from the config file. /// If the config can't be parsed, it exits with error code 1. diff --git a/src/term_manager.rs b/src/term_manager.rs index ef7d915f..0f63930a 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -66,12 +66,6 @@ const ATTR_YELLOW_BOLD: Attr = Attr { effect: Effect::BOLD, }; -const ATTR_CYAN_BOLD: Attr = Attr { - fg: Color::CYAN, - bg: Color::Default, - effect: Effect::BOLD, -}; - /// Simple struct to read the events. pub struct EventReader { term: Arc, @@ -227,7 +221,7 @@ impl<'a> WinMain<'a> { attr.effect ^= Effect::REVERSE; if status.flagged.contains(&file.path) { - canvas.print_with_attr(1, 0, "█", ATTR_CYAN_BOLD)?; + canvas.print_with_attr(1, 0, "█", ATTR_YELLOW_BOLD)?; attr.effect |= Effect::BOLD; } Ok(canvas.print_with_attr(1, 1, &file.format(owner_size, group_size)?, attr)?) @@ -382,7 +376,7 @@ impl<'a> WinMain<'a> { }; if status.flagged.contains(&file.path) { attr.effect |= Effect::BOLD; - canvas.print_with_attr(row, 0, "█", ATTR_CYAN_BOLD)?; + canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; } canvas.print_with_attr(row, 1, &string, attr)?; } @@ -409,7 +403,7 @@ impl<'a> WinMain<'a> { let mut attr = colored_string.attr; if status.flagged.contains(&colored_string.path) { attr.effect |= Effect::BOLD; - canvas.print_with_attr(row, 0, "█", ATTR_CYAN_BOLD)?; + canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; } let col_metadata = if status.display_full { From d3493b79579b59bf8ecad1ce5225b27aa910d848 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 30 Oct 2023 20:58:25 +0100 Subject: [PATCH 076/168] create simple struct for users & groups. 1st step into removing users --- Cargo.lock | 106 +++++++++++++++++++++++++++++---------------------- Cargo.toml | 1 + src/lib.rs | 1 + src/users.rs | 63 ++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 45 deletions(-) create mode 100644 src/users.rs diff --git a/Cargo.lock b/Cargo.lock index f01590fc..b296f14d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -151,7 +151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", - "libc", + "libc 0.2.149", "winapi", ] @@ -170,7 +170,7 @@ dependencies = [ "addr2line", "cc", "cfg-if", - "libc", + "libc 0.2.149", "miniz_oxide", "object", "rustc-demangle", @@ -265,7 +265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" dependencies = [ "bzip2-sys", - "libc", + "libc 0.2.149", ] [[package]] @@ -275,7 +275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" dependencies = [ "cc", - "libc", + "libc 0.2.149", "pkg-config", ] @@ -288,7 +288,7 @@ dependencies = [ "bitflags", "cairo-sys-rs", "glib", - "libc", + "libc 0.2.149", "thiserror", ] @@ -299,7 +299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", - "libc", + "libc 0.2.149", "system-deps", ] @@ -310,7 +310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", - "libc", + "libc 0.2.149", ] [[package]] @@ -432,7 +432,7 @@ checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", - "libc", + "libc 0.2.149", "unicode-width", "windows-sys 0.45.0", ] @@ -484,7 +484,7 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -705,7 +705,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ - "libc", + "libc 0.2.149", "redox_users", "winapi", ] @@ -716,7 +716,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ - "libc", + "libc 0.2.149", "redox_users", "winapi", ] @@ -780,7 +780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", - "libc", + "libc 0.2.149", "redox_syscall 0.3.5", "windows-sys 0.48.0", ] @@ -813,6 +813,7 @@ dependencies = [ "log4rs", "nvim-rs", "pathdiff", + "pgs-files", "poppler", "rand 0.8.5", "regex", @@ -970,7 +971,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" dependencies = [ - "libc", + "libc 0.2.149", "winapi", ] @@ -981,7 +982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", - "libc", + "libc 0.2.149", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -992,7 +993,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", - "libc", + "libc 0.2.149", "wasi 0.11.0+wasi-snapshot-preview1", ] @@ -1016,7 +1017,7 @@ dependencies = [ "glib-macros", "glib-sys", "gobject-sys", - "libc", + "libc 0.2.149", "once_cell", "smallvec", "thiserror", @@ -1043,7 +1044,7 @@ version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ - "libc", + "libc 0.2.149", "system-deps", ] @@ -1054,7 +1055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", - "libc", + "libc 0.2.149", "system-deps", ] @@ -1082,7 +1083,7 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -1193,7 +1194,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -1208,7 +1209,7 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -1232,6 +1233,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32a70cf75e5846d53a673923498228bbec6a8624708a9ea5645f075d6276122" + [[package]] name = "libc" version = "0.2.149" @@ -1300,7 +1307,7 @@ dependencies = [ "derivative", "fnv", "humantime", - "libc", + "libc 0.2.149", "log", "log-mdc", "parking_lot", @@ -1320,7 +1327,7 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -1335,7 +1342,7 @@ version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -1377,7 +1384,7 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ - "libc", + "libc 0.2.149", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -1391,7 +1398,7 @@ dependencies = [ "bitflags", "cc", "cfg-if", - "libc", + "libc 0.2.149", "memoffset 0.6.5", ] @@ -1403,7 +1410,7 @@ checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags", "cfg-if", - "libc", + "libc 0.2.149", "memoffset 0.6.5", ] @@ -1442,7 +1449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi 0.3.3", - "libc", + "libc 0.2.149", ] [[package]] @@ -1518,7 +1525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" dependencies = [ "bitflags", - "libc", + "libc 0.2.149", "once_cell", "onig_sys", ] @@ -1549,7 +1556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ "futures 0.3.28", - "libc", + "libc 0.2.149", "log", "rand 0.7.3", "tokio", @@ -1573,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", - "libc", + "libc 0.2.149", "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", @@ -1620,6 +1627,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pgs-files" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a7b7d377867a5a16859f9521ea3b884438f6e3eb096ffd18014243125faf715" +dependencies = [ + "libc 0.1.12", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1754,7 +1770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", - "libc", + "libc 0.2.149", "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", @@ -1766,7 +1782,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", + "libc 0.2.149", "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -2153,7 +2169,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -2234,7 +2250,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ - "libc", + "libc 0.2.149", "windows-sys 0.48.0", ] @@ -2399,7 +2415,7 @@ checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" dependencies = [ "cfg-if", "core-foundation-sys", - "libc", + "libc 0.2.149", "ntapi", "once_cell", "rayon", @@ -2426,7 +2442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", - "libc", + "libc 0.2.149", "xattr", ] @@ -2491,7 +2507,7 @@ version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" dependencies = [ - "libc", + "libc 0.2.149", "winapi", ] @@ -2512,7 +2528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" dependencies = [ "const_fn", - "libc", + "libc 0.2.149", "standback", "stdweb", "time-macros 0.1.1", @@ -2589,7 +2605,7 @@ checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes 1.5.0", - "libc", + "libc 0.2.149", "mio", "num_cpus", "parking_lot", @@ -2765,7 +2781,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" dependencies = [ - "libc", + "libc 0.2.149", "log", ] @@ -2921,7 +2937,7 @@ checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" dependencies = [ "bitflags", "downcast-rs", - "libc", + "libc 0.2.149", "nix 0.24.3", "scoped-tls", "wayland-commons", @@ -3213,7 +3229,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ - "libc", + "libc 0.2.149", ] [[package]] @@ -3275,7 +3291,7 @@ version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ - "libc", + "libc 0.2.149", "zstd-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 26246ab3..1f8ac779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ log = { version = "0.4.0", features = ["std"] } log4rs = { version = "1.2.0", features = ["rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } nvim-rs = { version = "0.3", features = ["use_tokio"] } pathdiff = "0.2.1" +pgs-files = "0.0.7" poppler = "0.3.2" rand = "0.8.5" regex = "1.6.0" diff --git a/src/lib.rs b/src/lib.rs index ef36e709..47dab289 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,4 +43,5 @@ pub mod tab; pub mod term_manager; pub mod trash; pub mod tree; +pub mod users; pub mod utils; diff --git a/src/users.rs b/src/users.rs new file mode 100644 index 00000000..6b2aa62a --- /dev/null +++ b/src/users.rs @@ -0,0 +1,63 @@ +#[derive(Clone, Debug, Default)] +pub struct Users { + users: Vec<(u32, String)>, + groups: Vec<(u32, String)>, +} + +impl Users { + pub fn get_user_by_uid(&self, uid: u32) -> Option { + if let Ok(index) = self + .users + .iter() + .map(|pair| pair.0) + .collect::>() + .binary_search(&uid) + { + return Some(self.users[index].1.to_owned()); + } + None + } + + pub fn get_group_by_gid(&self, gid: u32) -> Option { + if let Ok(index) = self + .groups + .iter() + .map(|pair| pair.0) + .collect::>() + .binary_search(&gid) + { + return Some(self.groups[index].1.to_owned()); + } + None + } + + fn update_users(mut self) -> Self { + let users = pgs_files::passwd::get_all_entries(); + let mut pairs: Vec<(u32, String)> = users + .iter() + .map(|entry| (entry.uid, entry.name.to_owned())) + .collect(); + pairs.sort_unstable_by_key(|pair| pair.0); + self.users = pairs; + self + } + + fn update_groups(mut self) -> Self { + let users = pgs_files::group::get_all_entries(); + let mut pairs: Vec<(u32, String)> = users + .iter() + .map(|entry| (entry.gid, entry.name.to_owned())) + .collect(); + pairs.sort_unstable_by_key(|pair| pair.0); + self.groups = pairs; + self + } + + pub fn new() -> Self { + Self::default().update_users().update_groups() + } + + pub fn update(mut self) { + self = Self::new(); + } +} From cd1f70191b6067e258328424bf5acf13dbfb5880 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 30 Oct 2023 21:58:43 +0100 Subject: [PATCH 077/168] replace `users` crate by `pgs` and a simple struct. --- Cargo.lock | 11 ------ Cargo.toml | 1 - src/event_exec.rs | 6 ++- src/fileinfo.rs | 85 ++++++++++++++++++---------------------- src/preview.rs | 21 ++++------ src/removable_devices.rs | 4 +- src/shortcut.rs | 6 ++- src/status.rs | 25 ++++++------ src/tab.rs | 28 ++++++------- src/tree.rs | 23 ++++++----- src/users.rs | 19 +++++++-- src/utils.rs | 21 ++++++---- 12 files changed, 120 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b296f14d..06c4b63b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -833,7 +833,6 @@ dependencies = [ "ueberzug", "unicode-segmentation", "url-escape", - "users", "zip", ] @@ -2775,16 +2774,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "users" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" -dependencies = [ - "libc 0.2.149", - "log", -] - [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 1f8ac779..53795c93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ sysinfo = "0.29.0" tar = "0.4.38" tuikit = "0.5.0" url-escape = "0.1.1" -users = "0.11.0" zip = "0.6.4" tokio = "1" ueberzug = "0.1.0" diff --git a/src/event_exec.rs b/src/event_exec.rs index 07eaa892..3b7e08dc 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -299,7 +299,8 @@ impl EventAction { pub fn toggle_hidden(status: &mut Status) -> Result<()> { let tab = status.selected(); tab.show_hidden = !tab.show_hidden; - tab.path_content.reset_files(&tab.filter, tab.show_hidden)?; + tab.path_content + .reset_files(&tab.filter, tab.show_hidden, &tab.users)?; tab.window.reset(tab.path_content.content.len()); if let Mode::Tree = tab.mode { tab.make_tree()? @@ -1527,7 +1528,8 @@ impl LeaveMode { let filter = FilterKind::from_input(&tab.input.string()); tab.set_filter(filter); tab.input.reset(); - tab.path_content.reset_files(&tab.filter, tab.show_hidden)?; + tab.path_content + .reset_files(&tab.filter, tab.show_hidden, &tab.users)?; if let Mode::Tree = tab.previous_mode { tab.make_tree()?; } diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 881dcb73..66c0b79b 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -8,7 +8,6 @@ use chrono::offset::Local; use chrono::DateTime; use log::info; use tuikit::prelude::{Attr, Color, Effect}; -use users::{Groups, Users, UsersCache}; use crate::colors::extension_color; use crate::constant_strings_paths::PERMISSIONS_STR; @@ -16,6 +15,7 @@ use crate::filter::FilterKind; use crate::git::git; use crate::impl_selectable_content; use crate::sort::SortKind; +use crate::users::Users; use crate::utils::filename_from_path; type Valid = bool; @@ -159,30 +159,26 @@ pub struct FileInfo { impl FileInfo { /// Reads every information about a file from its metadata and returs /// a new `FileInfo` object if we can create one. - pub fn new(direntry: &DirEntry, users_cache: &UsersCache) -> Result { + pub fn new(direntry: &DirEntry, users: &Users) -> Result { let path = direntry.path(); let filename = extract_filename(direntry)?; - Self::create_from_metadata_and_filename(&path, &filename, users_cache) + Self::create_from_metadata_and_filename(&path, &filename, users) } /// Creates a fileinfo from a path and a filename. /// The filename is used when we create the fileinfo for "." and ".." in every folder. - pub fn from_path_with_name( - path: &path::Path, - filename: &str, - users_cache: &UsersCache, - ) -> Result { - Self::create_from_metadata_and_filename(path, filename, users_cache) + pub fn from_path_with_name(path: &path::Path, filename: &str, users: &Users) -> Result { + Self::create_from_metadata_and_filename(path, filename, users) } - pub fn from_path(path: &path::Path, users_cache: &UsersCache) -> Result { + pub fn from_path(path: &path::Path, users: &Users) -> Result { let filename = path .file_name() .context("from path: couldn't read filenale")? .to_str() .context("from path: couldn't parse filenale")?; - Self::create_from_metadata_and_filename(path, filename, users_cache) + Self::create_from_metadata_and_filename(path, filename, users) } fn metadata(&self) -> Result { @@ -196,13 +192,13 @@ impl FileInfo { fn create_from_metadata_and_filename( path: &path::Path, filename: &str, - users_cache: &UsersCache, + users: &Users, ) -> Result { let filename = filename.to_owned(); let metadata = symlink_metadata(path)?; let path = path.to_owned(); - let owner = extract_owner(&metadata, users_cache)?; - let group = extract_group(&metadata, users_cache)?; + let owner = extract_owner(&metadata, users)?; + let group = extract_group(&metadata, users)?; let system_time = extract_datetime(&metadata)?; let is_selected = false; let true_size = extract_file_size(&metadata); @@ -313,7 +309,6 @@ pub struct PathContent { /// The kind of sort used to display the files. pub sort_kind: SortKind, used_space: u64, - pub users_cache: UsersCache, } impl PathContent { @@ -322,12 +317,12 @@ impl PathContent { /// Selects the first file if any. pub fn new( path: &path::Path, - users_cache: UsersCache, + users: &Users, filter: &FilterKind, show_hidden: bool, ) -> Result { let path = path.to_owned(); - let mut content = Self::files(&path, show_hidden, filter, &users_cache)?; + let mut content = Self::files(&path, show_hidden, filter, users)?; let sort_kind = SortKind::default(); sort_kind.sort(&mut content); let selected_index: usize = 0; @@ -342,7 +337,6 @@ impl PathContent { index: selected_index, sort_kind, used_space, - users_cache, }) } @@ -351,8 +345,9 @@ impl PathContent { path: &path::Path, filter: &FilterKind, show_hidden: bool, + users: &Users, ) -> Result<()> { - self.content = Self::files(path, show_hidden, filter, &self.users_cache)?; + self.content = Self::files(path, show_hidden, filter, users)?; self.sort_kind.sort(&mut self.content); self.index = 0; if !self.content.is_empty() { @@ -367,24 +362,24 @@ impl PathContent { path: &path::Path, show_hidden: bool, filter_kind: &FilterKind, - users_cache: &UsersCache, + users: &Users, ) -> Result> { - let mut files: Vec = Self::create_dot_dotdot(path, users_cache)?; + let mut files: Vec = Self::create_dot_dotdot(path, users)?; - let fileinfo = FileInfo::from_path_with_name(path, filename_from_path(path)?, users_cache)?; + let fileinfo = FileInfo::from_path_with_name(path, filename_from_path(path)?, users)?; if let Some(true_files) = - files_collection(&fileinfo, users_cache, show_hidden, filter_kind, false) + files_collection(&fileinfo, users, show_hidden, filter_kind, false) { files.extend(true_files); } Ok(files) } - fn create_dot_dotdot(path: &path::Path, users_cache: &UsersCache) -> Result> { - let current = FileInfo::from_path_with_name(path, ".", users_cache)?; + fn create_dot_dotdot(path: &path::Path, users: &Users) -> Result> { + let current = FileInfo::from_path_with_name(path, ".", users)?; match path.parent() { Some(parent) => { - let parent = FileInfo::from_path_with_name(parent, "..", users_cache)?; + let parent = FileInfo::from_path_with_name(parent, "..", users)?; Ok(vec![current, parent]) } None => Ok(vec![current]), @@ -428,8 +423,13 @@ impl PathContent { /// Reset the current file content. /// Reads and sort the content with current key. /// Select the first file if any. - pub fn reset_files(&mut self, filter: &FilterKind, show_hidden: bool) -> Result<()> { - self.content = Self::files(&self.path, show_hidden, filter, &self.users_cache)?; + pub fn reset_files( + &mut self, + filter: &FilterKind, + show_hidden: bool, + users: &Users, + ) -> Result<()> { + self.content = Self::files(&self.path, show_hidden, filter, users)?; self.sort_kind = SortKind::default(); self.sort(); self.index = 0; @@ -524,12 +524,11 @@ impl PathContent { /// Refresh the existing users. pub fn refresh_users( &mut self, - users_cache: UsersCache, + users: &Users, filter: &FilterKind, show_hidden: bool, ) -> Result<()> { - self.users_cache = users_cache; - self.reset_files(filter, show_hidden) + self.reset_files(filter, show_hidden, users) } /// Returns the correct index jump target to a flagged files. @@ -623,13 +622,9 @@ fn convert_octal_mode(mode: usize) -> &'static str { /// Reads the owner name and returns it as a string. /// If it's not possible to get the owner name (happens if the owner exists on a remote machine but not on host), /// it returns the uid as a `Result`. -fn extract_owner(metadata: &Metadata, users_cache: &UsersCache) -> Result { - match users_cache.get_user_by_uid(metadata.uid()) { - Some(uid) => Ok(uid - .name() - .to_str() - .context("extract owner: Couldn't parse owner name")? - .to_owned()), +fn extract_owner(metadata: &Metadata, users: &Users) -> Result { + match users.get_user_by_uid(metadata.uid()) { + Some(name) => Ok(name), None => Ok(format!("{}", metadata.uid())), } } @@ -637,13 +632,9 @@ fn extract_owner(metadata: &Metadata, users_cache: &UsersCache) -> Result`. -fn extract_group(metadata: &Metadata, users_cache: &UsersCache) -> Result { - match users_cache.get_group_by_gid(metadata.gid()) { - Some(gid) => Ok(gid - .name() - .to_str() - .context("extract group: Couldn't parse group name")? - .to_owned()), +fn extract_group(metadata: &Metadata, users: &Users) -> Result { + match users.get_group_by_gid(metadata.gid()) { + Some(name) => Ok(name), None => Ok(format!("{}", metadata.gid())), } } @@ -694,7 +685,7 @@ fn filekind_and_filename(filename: &str, file_kind: &FileKind) -> String /// Returns None if there's no file. pub fn files_collection( fileinfo: &FileInfo, - users_cache: &UsersCache, + users: &Users, show_hidden: bool, filter_kind: &FilterKind, keep_dir: bool, @@ -704,7 +695,7 @@ pub fn files_collection( read_dir .filter_map(|direntry| direntry.ok()) .filter(|direntry| show_hidden || is_not_hidden(direntry).unwrap_or(true)) - .map(|direntry| FileInfo::new(&direntry, users_cache)) + .map(|direntry| FileInfo::new(&direntry, users)) .filter_map(|fileinfo| fileinfo.ok()) .filter(|fileinfo| filter_kind.filter_by(fileinfo, keep_dir)) .collect(), diff --git a/src/preview.rs b/src/preview.rs index 29b07c28..c86ff61a 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -14,7 +14,6 @@ use syntect::easy::HighlightLines; use syntect::highlighting::{Style, ThemeSet}; use syntect::parsing::{SyntaxReference, SyntaxSet}; use tuikit::attr::{Attr, Color}; -use users::UsersCache; use crate::constant_strings_paths::{ CALC_PDF_PATH, DIFF, FFMPEG, FONTIMAGE, ISOINFO, JUPYTER, LIBREOFFICE, LSBLK, LSOF, MEDIAINFO, @@ -26,6 +25,7 @@ use crate::fileinfo::{FileInfo, FileKind}; use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; use crate::tree::{ColoredString, Tree}; +use crate::users::Users; use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path}; /// Different kind of extension for grouped by previewers. @@ -116,13 +116,13 @@ impl Preview { /// The recursive exploration is limited to depth 2. pub fn directory( file_info: &FileInfo, - users_cache: &UsersCache, + users: &Users, filter: &FilterKind, show_hidden: bool, ) -> Result { Ok(Self::Directory(Directory::new( &file_info.path, - users_cache, + users, filter, show_hidden, Some(2), @@ -1059,7 +1059,7 @@ impl Directory { /// We only hold the result here, since the tree itself has now usage atm. pub fn new( path: &Path, - users_cache: &UsersCache, + users: &Users, filter_kind: &FilterKind, show_hidden: bool, max_depth: Option, @@ -1069,14 +1069,7 @@ impl Directory { None => Tree::MAX_DEPTH, }; - let mut tree = Tree::from_path( - path, - max_depth, - users_cache, - filter_kind, - show_hidden, - vec![0], - )?; + let mut tree = Tree::from_path(path, max_depth, users, filter_kind, show_hidden, vec![0])?; tree.select_root(); let (selected_index, content) = tree.into_navigable_content(); Ok(Self { @@ -1088,9 +1081,9 @@ impl Directory { } /// Creates an empty directory preview. - pub fn empty(path: &Path, users_cache: &UsersCache) -> Result { + pub fn empty(path: &Path, users: &Users) -> Result { Ok(Self { - tree: Tree::empty(path, users_cache)?, + tree: Tree::empty(path, users)?, len: 0, content: vec![], selected_index: 0, diff --git a/src/removable_devices.rs b/src/removable_devices.rs index 323bac12..7759c830 100644 --- a/src/removable_devices.rs +++ b/src/removable_devices.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use crate::{ constant_strings_paths::GIO, impl_selectable_content, - utils::{is_dir_empty, is_program_in_path}, + utils::{current_uid, is_dir_empty, is_program_in_path}, }; /// Holds info about removable devices. @@ -83,7 +83,7 @@ impl Removable { .replace('/', "") .trim() .to_owned(); - let uid = users::get_current_uid(); + let uid = current_uid()?; let path = format!("/run/user/{uid}/gvfs/mtp:host={device_name}"); let pb_path = std::path::Path::new(&path); let is_mounted = pb_path.exists() && !is_dir_empty(pb_path)?; diff --git a/src/shortcut.rs b/src/shortcut.rs index 8129f19e..f7e45060 100644 --- a/src/shortcut.rs +++ b/src/shortcut.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use crate::constant_strings_paths::{CONFIG_FOLDER, HARDCODED_SHORTCUTS}; use crate::git::git_root; use crate::impl_selectable_content; +use crate::utils::current_uid; /// Holds the hardcoded and mountpoints shortcuts the user can jump to. /// Also know which shortcut is currently selected by the user. @@ -95,11 +96,14 @@ impl Shortcut { /// Update the shortcuts with MTP mount points fn extend_with_mtp(&mut self) { - let uid = users::get_current_uid(); + let Ok(uid) = current_uid() else { + return; + }; let mtp_mount_point = PathBuf::from(format!("/run/user/{uid}/gvfs/")); if !mtp_mount_point.exists() || !mtp_mount_point.is_dir() { return; } + let mount_points: Vec = match std::fs::read_dir(&mtp_mount_point) { Ok(read_dir) => read_dir .filter_map(|direntry| direntry.ok()) diff --git a/src/status.rs b/src/status.rs index db78725e..877c9015 100644 --- a/src/status.rs +++ b/src/status.rs @@ -12,7 +12,6 @@ use skim::SkimItem; use sysinfo::{Disk, DiskExt, RefreshKind, System, SystemExt}; use tuikit::prelude::{from_keyname, Event}; use tuikit::term::Term; -use users::UsersCache; use crate::args::Args; use crate::bulkrename::Bulk; @@ -43,6 +42,7 @@ use crate::skim::Skimer; use crate::tab::Tab; use crate::term_manager::MIN_WIDTH_FOR_DUAL_PANE; use crate::trash::Trash; +use crate::users::Users; use crate::utils::{current_username, disk_space, filename_from_path, is_program_in_path}; /// Holds every mutable parameter of the application itself, except for @@ -137,15 +137,15 @@ impl Status { let index = 0; // unsafe because of UsersCache::with_all_users - let users_cache = unsafe { UsersCache::with_all_users() }; + let users = Users::new(); // unsafe because of UsersCache::with_all_users - let users_cache2 = unsafe { UsersCache::with_all_users() }; + let users2 = users.clone(); let mount_points = Self::disks_mounts(sys.disks()); let tabs = [ - Tab::new(&args, height, users_cache, settings, &mount_points)?, - Tab::new(&args, height, users_cache2, settings, &mount_points)?, + Tab::new(&args, height, users, settings, &mount_points)?, + Tab::new(&args, height, users2, settings, &mount_points)?, ]; let removable_devices = None; Ok(Self { @@ -549,18 +549,19 @@ impl Status { /// Refresh the existing users. pub fn refresh_users(&mut self) -> Result<()> { - for tab in self.tabs.iter_mut() { - let users_cache = unsafe { UsersCache::with_all_users() }; - tab.refresh_users(users_cache)?; - } + let users = Users::new(); + self.tabs[0].users = users.clone(); + self.tabs[1].users = users; + self.tabs[0].refresh_view()?; + self.tabs[1].refresh_view()?; Ok(()) } /// Drop the current tree, replace it with an empty one. pub fn remove_tree(&mut self) -> Result<()> { let path = self.selected_non_mut().path_content.path.clone(); - let users_cache = &self.selected_non_mut().path_content.users_cache; - self.selected().directory = Directory::empty(&path, users_cache)?; + let users = &self.selected_non_mut().users; + self.selected().directory = Directory::empty(&path, users)?; Ok(()) } @@ -631,7 +632,7 @@ impl Status { let preview = match fileinfo.file_kind { FileKind::Directory => Preview::directory( fileinfo, - &self.tabs[0].path_content.users_cache, + &self.tabs[0].users, &self.tabs[0].filter, self.tabs[0].show_hidden, ), diff --git a/src/tab.rs b/src/tab.rs index 6e1a6e69..3349c847 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -2,7 +2,6 @@ use std::cmp::min; use std::path; use anyhow::{Context, Result}; -use users::UsersCache; use crate::args::Args; use crate::completion::{Completion, InputCompleted}; @@ -17,6 +16,7 @@ use crate::opener::execute_in_child; use crate::preview::{Directory, Preview}; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; +use crate::users::Users; use crate::utils::{filename_from_path, row_to_window_index, set_clipboard}; /// Holds every thing about the current tab of the application. @@ -54,6 +54,8 @@ pub struct Tab { pub filter: FilterKind, /// Visited directories pub history: History, + /// Users & groups + pub users: Users, } impl Tab { @@ -61,7 +63,7 @@ impl Tab { pub fn new( args: &Args, height: usize, - users_cache: UsersCache, + users: Users, settings: &Settings, mount_points: &[&path::Path], ) -> Result { @@ -71,10 +73,10 @@ impl Tab { } else { path.parent().context("")? }; - let directory = Directory::empty(start_dir, &users_cache)?; + let directory = Directory::empty(start_dir, &users)?; let filter = FilterKind::All; let show_hidden = args.all || settings.all; - let mut path_content = PathContent::new(start_dir, users_cache, &filter, show_hidden)?; + let mut path_content = PathContent::new(start_dir, &users, &filter, show_hidden)?; let mode = Mode::Normal; let previous_mode = Mode::Normal; let mut window = ContentWindow::new(path_content.content.len(), height); @@ -104,6 +106,7 @@ impl Tab { filter, show_hidden, history, + users, }) } @@ -153,7 +156,7 @@ impl Tab { pub fn refresh_view(&mut self) -> Result<()> { self.refresh_params()?; self.path_content - .reset_files(&self.filter, self.show_hidden)?; + .reset_files(&self.filter, self.show_hidden, &self.users)?; self.window.reset(self.path_content.content.len()); Ok(()) } @@ -229,7 +232,7 @@ impl Tab { &self.path_content.selected().context("")?.path, ); self.path_content - .change_directory(path, &self.filter, self.show_hidden)?; + .change_directory(path, &self.filter, self.show_hidden, &self.users)?; self.window.reset(self.path_content.content.len()); std::env::set_current_dir(path)?; Ok(()) @@ -262,15 +265,6 @@ impl Tab { self.window.scroll_to(index); } - /// Refresh the existing users. - pub fn refresh_users(&mut self, users_cache: UsersCache) -> Result<()> { - let last_pathcontent_index = self.path_content.index; - self.path_content - .refresh_users(users_cache, &self.filter, self.show_hidden)?; - self.path_content.select_index(last_pathcontent_index); - Ok(()) - } - /// Search in current directory for an file whose name contains `searched_name`, /// from a starting position `next_index`. /// We search forward from that position and start again from top if nothing is found. @@ -432,8 +426,8 @@ impl Tab { /// Makes a new tree of the current path. pub fn make_tree(&mut self) -> Result<()> { let path = self.path_content.path.clone(); - let users_cache = &self.path_content.users_cache; - self.directory = Directory::new(&path, users_cache, &self.filter, self.show_hidden, None)?; + let users = &self.users; + self.directory = Directory::new(&path, users, &self.filter, self.show_hidden, None)?; Ok(()) } diff --git a/src/tree.rs b/src/tree.rs index bd9f80d4..f5c83a58 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -2,12 +2,12 @@ use std::path::Path; use anyhow::{Context, Result}; use tuikit::attr::Attr; -use users::UsersCache; use crate::fileinfo::{fileinfo_attr, files_collection, FileInfo, FileKind}; use crate::filter::FilterKind; use crate::preview::ColoredTriplet; use crate::sort::SortKind; +use crate::users::Users; use crate::utils::filename_from_path; /// Holds a string and its display attributes. @@ -188,15 +188,15 @@ impl Tree { pub fn from_path( path: &Path, max_depth: usize, - users_cache: &UsersCache, + users: &Users, filter_kind: &FilterKind, show_hidden: bool, parent_position: Vec, ) -> Result { Self::create_tree_from_fileinfo( - FileInfo::from_path_with_name(path, filename_from_path(path)?, users_cache)?, + FileInfo::from_path_with_name(path, filename_from_path(path)?, users)?, max_depth, - users_cache, + users, filter_kind, show_hidden, parent_position, @@ -218,7 +218,7 @@ impl Tree { fn create_tree_from_fileinfo( fileinfo: FileInfo, max_depth: usize, - users_cache: &UsersCache, + users: &Users, filter_kind: &FilterKind, display_hidden: bool, parent_position: Vec, @@ -227,7 +227,7 @@ impl Tree { let leaves = Self::make_leaves( &fileinfo, max_depth, - users_cache, + users, display_hidden, filter_kind, &sort_kind, @@ -249,7 +249,7 @@ impl Tree { fn make_leaves( fileinfo: &FileInfo, max_depth: usize, - users_cache: &UsersCache, + users: &Users, display_hidden: bool, filter_kind: &FilterKind, sort_kind: &SortKind, @@ -261,8 +261,7 @@ impl Tree { let FileKind::Directory = fileinfo.file_kind else { return Ok(vec![]); }; - let Some(mut files) = - files_collection(fileinfo, users_cache, display_hidden, filter_kind, true) + let Some(mut files) = files_collection(fileinfo, users, display_hidden, filter_kind, true) else { return Ok(vec![]); }; @@ -276,7 +275,7 @@ impl Tree { Self::create_tree_from_fileinfo( fileinfo.to_owned(), max_depth - 1, - users_cache, + users, filter_kind, display_hidden, position, @@ -303,9 +302,9 @@ impl Tree { /// Creates an empty tree. Used when the user changes the CWD and hasn't displayed /// a tree yet. - pub fn empty(path: &Path, users_cache: &UsersCache) -> Result { + pub fn empty(path: &Path, users: &Users) -> Result { let filename = filename_from_path(path)?; - let fileinfo = FileInfo::from_path_with_name(path, filename, users_cache)?; + let fileinfo = FileInfo::from_path_with_name(path, filename, users)?; let node = Node::empty(fileinfo); let leaves = vec![]; let position = vec![0]; diff --git a/src/users.rs b/src/users.rs index 6b2aa62a..38beaae8 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,3 +1,14 @@ +/// Users and Groups of current Unix system. +/// It requires `/etc/passwd` and `/etc/group` to be at their usual place. +/// +/// Holds two vectors, one for users, one for group. +/// Each vector is a pair of `(u32, String)`, for uid, username and gid, groupname respectively. +/// Those vectors are read from `/etc/passwd` and from `/etc/group` directly. +/// It also provides two methods allowing to access the name from uid or gid. +/// +/// Both users and groups use vectors which are sorted by their first element (uid/gid). +/// It allows use to use bisection (binary search) to find the correct name. +/// Cloning should be easy. #[derive(Clone, Debug, Default)] pub struct Users { users: Vec<(u32, String)>, @@ -5,6 +16,7 @@ pub struct Users { } impl Users { + /// Name of the user from its uid. pub fn get_user_by_uid(&self, uid: u32) -> Option { if let Ok(index) = self .users @@ -18,6 +30,7 @@ impl Users { None } + /// Name of the group from its gid. pub fn get_group_by_gid(&self, gid: u32) -> Option { if let Ok(index) = self .groups @@ -53,11 +66,9 @@ impl Users { self } + /// Creates a default instance and update both users and groups from + /// `/etc/passwd` and `/etc/group` respectively. pub fn new() -> Self { Self::default().update_users().update_groups() } - - pub fn update(mut self) { - self = Self::new(); - } } diff --git a/src/utils.rs b/src/utils.rs index 13bf17fa..2563469a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ use std::borrow::Borrow; +use std::fs::metadata; use std::io::BufRead; +use std::os::unix::fs::MetadataExt; use std::path::Path; use anyhow::{Context, Result}; @@ -7,12 +9,12 @@ use copypasta::{ClipboardContext, ClipboardProvider}; use rand::Rng; use sysinfo::{Disk, DiskExt}; use tuikit::term::Term; -use users::{get_current_uid, get_user_by_uid}; use crate::constant_strings_paths::{CALC_PDF_PATH, THUMBNAIL_PATH}; use crate::content_window::ContentWindow; use crate::fileinfo::human_size; use crate::nvim::nvim; +use crate::users::Users; /// Returns a `Display` instance after `tuikit::term::Term` creation. pub fn init_term() -> Result { @@ -88,14 +90,19 @@ pub fn filename_from_path(path: &std::path::Path) -> Result<&str> { .context("couldn't parse the filename") } +/// Uid of the current user. +/// Read from `/proc/self`. +/// Should never fail. +pub fn current_uid() -> Result { + Ok(metadata("/proc/self").map(|m| m.uid())?) +} + /// Get the current username as a String. +/// Read from `/proc/self` and then `/etc/passwd` and should never fail. pub fn current_username() -> Result { - let user = get_user_by_uid(get_current_uid()).context("Couldn't read username")?; - Ok(user - .name() - .to_str() - .context("Couldn't read username")? - .to_owned()) + let uid = current_uid()?; + let user = Users::new().get_user_by_uid(uid); + user.context("Couldn't read my own name") } /// True iff the command is available in $PATH. From 1431f44b072a34af1495a7a1c184cfb6e64b65a3 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 30 Oct 2023 22:03:37 +0100 Subject: [PATCH 078/168] utils documentation --- src/utils.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 2563469a..502e9022 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -136,10 +136,14 @@ pub fn set_clipboard(content: String) -> Result<()> { Ok(()) } +/// Convert a row into a `crate::fm::ContentWindow` index. +/// Just remove the header rows. pub fn row_to_window_index(row: u16) -> usize { row as usize - ContentWindow::HEADER_ROWS } +/// Convert a string into a valid, expanded and canonicalized path. +/// Doesn't check if the path exists. pub fn string_to_path(path_string: &str) -> Result { let expanded_cow_path = shellexpand::tilde(&path_string); let expanded_target: &str = expanded_cow_path.borrow(); @@ -150,10 +154,12 @@ pub fn args_is_empty(args: &[String]) -> bool { args.is_empty() || args[0] == *"" } +/// True if the executable is "sudo" pub fn is_sudo_command(executable: &str) -> bool { matches!(executable, "sudo") } +/// Open the path in neovim. pub fn open_in_current_neovim(path_str: &str, nvim_server: &str) { let command = &format!(":e {path_str}:set number:close"); let _ = nvim(nvim_server, command); @@ -162,7 +168,7 @@ pub fn open_in_current_neovim(path_str: &str, nvim_server: &str) { /// Creates a random string. /// The string starts with `fm-` and contains 7 random alphanumeric characters. pub fn random_name() -> String { - let mut rand_str = String::with_capacity(14); + let mut rand_str = String::with_capacity(10); rand_str.push_str("fm-"); rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) From 3d985fb1f6405bb2003f6bd384648edd61fd6993 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 30 Oct 2023 22:14:56 +0100 Subject: [PATCH 079/168] remove useless borrowmut --- src/main.rs | 17 ++++++++++++----- src/status.rs | 3 +-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 113918cb..18b04cc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use std::borrow::BorrowMut; use std::sync::mpsc::{self, TryRecvError}; use std::sync::Arc; use std::thread; @@ -136,6 +135,15 @@ struct Refresher { } impl Refresher { + /// Between 2 refreshed + const TEN_SECONDS_IN_DECISECONDS: u8 = 10 * 10; + + /// 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: tuikit::prelude::Event = + tuikit::prelude::Event::Key(tuikit::prelude::Key::AltPageUp); + /// Spawn a thread which sends events to the terminal. /// Those events are interpreted as refresh requests. /// It also listen to a receiver for quit messages. @@ -145,7 +153,7 @@ impl Refresher { /// /// Using Event::User(()) conflicts with skim internal which interpret this /// event as a signal(1) and hangs the terminal. - fn spawn(mut term: Arc) -> Self { + fn spawn(term: Arc) -> Self { let (tx, rx) = mpsc::channel(); let mut counter: u8 = 0; let handle = thread::spawn(move || loop { @@ -159,10 +167,9 @@ impl Refresher { } counter += 1; thread::sleep(Duration::from_millis(100)); - if counter >= 10 * 10 { + if counter >= Self::TEN_SECONDS_IN_DECISECONDS { counter = 0; - let event = tuikit::prelude::Event::Key(tuikit::prelude::Key::AltPageUp); - if term.borrow_mut().send_event(event).is_err() { + if term.send_event(Self::REFRESH_EVENT).is_err() { break; } } diff --git a/src/status.rs b/src/status.rs index 877c9015..54af94e5 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,4 +1,3 @@ -use std::borrow::BorrowMut; use std::fs; use std::os::unix::fs::PermissionsExt; use std::path::Path; @@ -339,7 +338,7 @@ impl Status { return Ok(()); }; let event = Event::Key(key); - let _ = self.term.borrow_mut().send_event(event); + let _ = self.term.send_event(event); self.skimer = None; Ok(()) } From 21628abd3ad257af67ad943ea359a105b7945f61 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 30 Oct 2023 22:32:27 +0100 Subject: [PATCH 080/168] simplify util --- src/utils.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 502e9022..395ec734 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -94,15 +94,15 @@ pub fn filename_from_path(path: &std::path::Path) -> Result<&str> { /// Read from `/proc/self`. /// Should never fail. pub fn current_uid() -> Result { - Ok(metadata("/proc/self").map(|m| m.uid())?) + Ok(metadata("/proc/self").map(|metadata| metadata.uid())?) } /// Get the current username as a String. /// Read from `/proc/self` and then `/etc/passwd` and should never fail. pub fn current_username() -> Result { - let uid = current_uid()?; - let user = Users::new().get_user_by_uid(uid); - user.context("Couldn't read my own name") + Users::new() + .get_user_by_uid(current_uid()?) + .context("Couldn't read my own name") } /// True iff the command is available in $PATH. From bb6dfbbe8ad5f16b3590d9cdc18a65fc4fc8114a Mon Sep 17 00:00:00 2001 From: quentin konieczko Date: Tue, 31 Oct 2023 09:04:23 +0100 Subject: [PATCH 081/168] refactor users --- src/users.rs | 85 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/src/users.rs b/src/users.rs index 38beaae8..b169d4f1 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,3 +1,36 @@ +trait User { + fn id(&self) -> u32; + fn name(&self) -> &str; + fn from_id_and_name(id: u32, name: &str) -> Self; +} + +#[derive(Debug, Clone)] +struct Owner { + id: u32, + name: String, +} + +impl User for Owner { + fn id(&self) -> u32 { + self.id + } + + fn name(&self) -> &str { + self.name.as_ref() + } + + fn from_id_and_name(id: u32, name: &str) -> Self { + Self { + id, + name: name.to_owned(), + } + } +} + +type Group = Owner; + +// NOTE: should this be splitted in 2 ? + /// Users and Groups of current Unix system. /// It requires `/etc/passwd` and `/etc/group` to be at their usual place. /// @@ -11,58 +44,52 @@ /// Cloning should be easy. #[derive(Clone, Debug, Default)] pub struct Users { - users: Vec<(u32, String)>, - groups: Vec<(u32, String)>, + users: Vec, + groups: Vec, } impl Users { - /// Name of the user from its uid. - pub fn get_user_by_uid(&self, uid: u32) -> Option { - if let Ok(index) = self - .users + /// Search for an id in a _**sorted**_ collection of `Owner`, returns its name. + fn search(collection: &[Owner], id: u32) -> Option { + if let Ok(index) = collection .iter() - .map(|pair| pair.0) + .map(|pair| pair.id()) .collect::>() - .binary_search(&uid) + .binary_search(&id) { - return Some(self.users[index].1.to_owned()); + return Some(collection[index].name().into()); } None } + /// Name of the user from its uid. + pub fn get_user_by_uid(&self, uid: u32) -> Option { + Self::search(&self.users, uid) + } + /// Name of the group from its gid. pub fn get_group_by_gid(&self, gid: u32) -> Option { - if let Ok(index) = self - .groups - .iter() - .map(|pair| pair.0) - .collect::>() - .binary_search(&gid) - { - return Some(self.groups[index].1.to_owned()); - } - None + Self::search(&self.groups, gid) } + // NOTE: can't refactor further since GroupEntry and PasswdEntry don't share common trait fn update_users(mut self) -> Self { - let users = pgs_files::passwd::get_all_entries(); - let mut pairs: Vec<(u32, String)> = users + let mut users: Vec = pgs_files::passwd::get_all_entries() .iter() - .map(|entry| (entry.uid, entry.name.to_owned())) + .map(|entry| User::from_id_and_name(entry.uid, &entry.name)) .collect(); - pairs.sort_unstable_by_key(|pair| pair.0); - self.users = pairs; + users.sort_unstable_by_key(|pair| pair.id()); + self.users = users; self } fn update_groups(mut self) -> Self { - let users = pgs_files::group::get_all_entries(); - let mut pairs: Vec<(u32, String)> = users + let mut groups: Vec = pgs_files::group::get_all_entries() .iter() - .map(|entry| (entry.gid, entry.name.to_owned())) + .map(|entry| Group::from_id_and_name(entry.gid, &entry.name)) .collect(); - pairs.sort_unstable_by_key(|pair| pair.0); - self.groups = pairs; + groups.sort_unstable_by_key(|pair| pair.id()); + self.groups = groups; self } From a859239199a5bfacca351f339009739bc51bbda5 Mon Sep 17 00:00:00 2001 From: quentin konieczko Date: Tue, 31 Oct 2023 11:39:49 +0100 Subject: [PATCH 082/168] tree: don't parse metadata as a colored string since it shares attr --- src/preview.rs | 2 +- src/term_manager.rs | 2 +- src/tree.rs | 10 +--------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/preview.rs b/src/preview.rs index c86ff61a..89d656d3 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -1311,7 +1311,7 @@ macro_rules! impl_window { /// A tuple with `(ColoredString, String, ColoredString)`. /// Used to iter and impl window trait in tree mode. -pub type ColoredTriplet = (ColoredString, String, ColoredString); +pub type ColoredTriplet = (String, String, ColoredString); /// A vector of highlighted strings pub type VecSyntaxedString = Vec; diff --git a/src/term_manager.rs b/src/term_manager.rs index 0f63930a..4a427b84 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -407,7 +407,7 @@ impl<'a> WinMain<'a> { } let col_metadata = if status.display_full { - canvas.print_with_attr(row, left_margin, &metadata.text, attr)? + canvas.print_with_attr(row, left_margin, metadata, attr)? } else { 0 }; diff --git a/src/tree.rs b/src/tree.rs index f5c83a58..e6818b81 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -38,14 +38,6 @@ impl ColoredString { }; Self::new(text, current_node.attr(), current_node.filepath()) } - - fn from_metadata_line(current_node: &Node) -> Self { - Self::new( - current_node.metadata_line.to_owned(), - current_node.attr(), - current_node.filepath(), - ) - } } /// An element in a tree. @@ -469,7 +461,7 @@ impl Tree { } content.push(( - ColoredString::from_metadata_line(¤t.node), + current.node.metadata_line.to_owned(), prefix.to_owned(), ColoredString::from_node(¤t.node), )); From 50d448c93b13fcc47ba8ff197951694345b840e8 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 31 Oct 2023 17:17:43 +0100 Subject: [PATCH 083/168] move app and refresher to separate files --- src/app.rs | 135 +++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/main.rs | 201 +---------------------------------------------- src/refresher.rs | 66 ++++++++++++++++ 4 files changed, 205 insertions(+), 199 deletions(-) create mode 100644 src/app.rs create mode 100644 src/refresher.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 00000000..3e31b4c5 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,135 @@ +use std::sync::Arc; + +use anyhow::Result; +use log::info; +use tuikit::prelude::Event; + +use crate::config::load_config; +use crate::constant_strings_paths::{CONFIG_PATH, OPENER_PATH}; +use crate::event_dispatch::EventDispatcher; +use crate::help::Help; +use crate::log::set_loggers; +use crate::opener::{load_opener, Opener}; +use crate::refresher::Refresher; +use crate::status::Status; +use crate::term_manager::{Display, EventReader}; +use crate::utils::{clear_tmp_file, init_term, print_on_quit}; + +/// Holds everything about the application itself. +/// Most attributes holds an `Arc`. +/// Dropping the instance of FM allows to write again to stdout. +pub struct FM { + /// Poll the event sent to the terminal by the user or the OS + event_reader: EventReader, + /// Associate the event to a method, modifing the status. + event_dispatcher: EventDispatcher, + /// Current status of the application. Mostly the filetrees + status: Status, + /// Responsible for the display on screen. + display: Display, + /// Refresher is used to force a refresh when a file has been modified externally. + /// It send `Event::Key(Key::AltPageUp)` every 10 seconds. + /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. + refresher: Refresher, +} + +impl FM { + /// Setup everything the application needs in its main loop : + /// an `EventReader`, + /// an `EventDispatcher`, + /// a `Status`, + /// a `Display`, + /// a `Refresher`. + /// It reads and drops the configuration from the config file. + /// If the config can't be parsed, it exits with error code 1. + pub fn start() -> Result { + set_loggers()?; + let Ok(config) = load_config(CONFIG_PATH) else { + exit_wrong_config() + }; + let term = Arc::new(init_term()?); + let event_reader = EventReader::new(Arc::clone(&term)); + let event_dispatcher = EventDispatcher::new(config.binds.clone()); + let opener = load_opener(OPENER_PATH, &config.terminal).unwrap_or_else(|_| { + eprintln!("Couldn't read the opener config file at {OPENER_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/opener.yaml for an example. Using default."); + info!("Couldn't read opener file at {OPENER_PATH}. Using default."); + Opener::new(&config.terminal) + }); + let help = Help::from_keybindings(&config.binds, &opener)?.help; + let display = Display::new(Arc::clone(&term)); + let status = Status::new( + display.height()?, + Arc::clone(&term), + help, + opener, + &config.settings, + )?; + let refresher = Refresher::new(term); + drop(config); + Ok(Self { + event_reader, + event_dispatcher, + status, + display, + refresher, + }) + } + + /// Return the last event received by the terminal + pub fn poll_event(&self) -> Result { + self.event_reader.poll_event() + } + + /// Force clear the display if the status requires it, then reset it in status. + pub fn force_clear_if_needed(&mut self) -> Result<()> { + if self.status.force_clear { + self.display.force_clear()?; + self.status.force_clear = false; + } + Ok(()) + } + + /// Update itself, changing its status. + pub fn update(&mut self, event: Event) -> Result<()> { + self.event_dispatcher.dispatch( + &mut self.status, + event, + // &self.colors, + self.event_reader.term_height()?, + )?; + self.status.refresh_disks(); + Ok(()) + } + + /// Display itself using its `display` attribute. + pub fn display(&mut self) -> Result<()> { + self.force_clear_if_needed()?; + self.display.display_all(&self.status) + } + + /// True iff the application must quit. + pub fn must_quit(&self) -> bool { + self.status.must_quit() + } + + /// Display the cursor, + /// drop itself, which allow us to print normally afterward + /// print the final path + pub fn quit(self) -> Result<()> { + clear_tmp_file(); + self.display.show_cursor()?; + let final_path = self.status.selected_path_str().to_owned(); + self.refresher.quit()?; + print_on_quit(&final_path); + info!("fm is shutting down"); + Ok(()) + } +} + +/// Exit the application and log a message. +/// Used when the config can't be read. +fn exit_wrong_config() -> ! { + eprintln!("Couldn't load the config file at {CONFIG_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/config.yaml for an example."); + info!("Couldn't read the config file {CONFIG_PATH}"); + std::process::exit(1) +} diff --git a/src/lib.rs b/src/lib.rs index 47dab289..900c090d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod action_map; +pub mod app; pub mod args; pub mod bulkrename; pub mod cli_info; @@ -31,6 +32,7 @@ pub mod nvim; pub mod opener; pub mod password; pub mod preview; +pub mod refresher; pub mod removable_devices; pub mod selectable_content; pub mod shell_menu; diff --git a/src/main.rs b/src/main.rs index 18b04cc9..c9a0381b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,207 +1,10 @@ -use std::sync::mpsc::{self, TryRecvError}; -use std::sync::Arc; -use std::thread; -use std::time::Duration; - -use anyhow::Result; -use log::info; - -use fm::config::load_config; -use fm::constant_strings_paths::{CONFIG_PATH, OPENER_PATH}; -use fm::event_dispatch::EventDispatcher; -use fm::help::Help; -use fm::log::set_loggers; -use fm::opener::{load_opener, Opener}; -use fm::status::Status; -use fm::term_manager::{Display, EventReader}; -use fm::utils::{clear_tmp_file, init_term, print_on_quit}; - -/// Holds everything about the application itself. -/// Most attributes holds an `Arc`. -/// Dropping the instance of FM allows to write again to stdout. -struct FM { - /// Poll the event sent to the terminal by the user or the OS - event_reader: EventReader, - /// Associate the event to a method, modifing the status. - event_dispatcher: EventDispatcher, - /// Current status of the application. Mostly the filetrees - status: Status, - /// Responsible for the display on screen. - display: Display, - /// Refresher is used to force a refresh when a file has been modified externally. - /// It send `Event::Key(Key::AltPageUp)` every 10 seconds. - /// It also has a `mpsc::Sender` to send a quit message and reset the cursor. - refresher: Refresher, -} - -impl FM { - /// Setup everything the application needs in its main loop : - /// an `EventReader`, - /// an `EventDispatcher`, - /// a `Status`, - /// a `Display`, - /// a `Refresher`. - /// It reads and drops the configuration from the config file. - /// If the config can't be parsed, it exits with error code 1. - fn start() -> Result { - let Ok(config) = load_config(CONFIG_PATH) else { - exit_wrong_config() - }; - let term = Arc::new(init_term()?); - let event_reader = EventReader::new(Arc::clone(&term)); - let event_dispatcher = EventDispatcher::new(config.binds.clone()); - let opener = load_opener(OPENER_PATH, &config.terminal).unwrap_or_else(|_| { - eprintln!("Couldn't read the opener config file at {OPENER_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/opener.yaml for an example. Using default."); - info!("Couldn't read opener file at {OPENER_PATH}. Using default."); - Opener::new(&config.terminal) - }); - let help = Help::from_keybindings(&config.binds, &opener)?.help; - let display = Display::new(Arc::clone(&term)); - let status = Status::new( - display.height()?, - Arc::clone(&term), - help, - opener, - &config.settings, - )?; - let refresher = Refresher::spawn(term); - drop(config); - Ok(Self { - event_reader, - event_dispatcher, - status, - display, - refresher, - }) - } - - /// Return the last event received by the terminal - fn poll_event(&self) -> Result { - self.event_reader.poll_event() - } - - /// Force clear the display if the status requires it, then reset it in status. - fn force_clear_if_needed(&mut self) -> Result<()> { - if self.status.force_clear { - self.display.force_clear()?; - self.status.force_clear = false; - } - Ok(()) - } - - /// Update itself, changing its status. - fn update(&mut self, event: tuikit::prelude::Event) -> Result<()> { - self.event_dispatcher.dispatch( - &mut self.status, - event, - // &self.colors, - self.event_reader.term_height()?, - )?; - self.status.refresh_disks(); - Ok(()) - } - - /// Display itself using its `display` attribute. - fn display(&mut self) -> Result<()> { - self.force_clear_if_needed()?; - self.display.display_all(&self.status) - } - - /// True iff the application must quit. - fn must_quit(&self) -> bool { - self.status.must_quit() - } - - /// Display the cursor, - /// drop itself, which allow us to print normally afterward - /// print the final path - fn quit(self) -> Result<()> { - clear_tmp_file(); - self.display.show_cursor()?; - let final_path = self.status.selected_path_str().to_owned(); - self.refresher.quit()?; - print_on_quit(&final_path); - info!("fm is shutting down"); - Ok(()) - } -} - -/// Allows refresh if the current path has been modified externally. -struct Refresher { - /// Sender of messages, used to terminate the thread properly - tx: mpsc::Sender<()>, - /// Handle to the `term::Event` sender thread. - handle: thread::JoinHandle<()>, -} - -impl Refresher { - /// Between 2 refreshed - const TEN_SECONDS_IN_DECISECONDS: u8 = 10 * 10; - - /// 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: tuikit::prelude::Event = - tuikit::prelude::Event::Key(tuikit::prelude::Key::AltPageUp); - - /// Spawn a thread which sends events to the terminal. - /// Those events are interpreted as refresh requests. - /// It also listen to a receiver for quit messages. - /// - /// This will send periodically an `Key::AltPageUp` event to the terminal which requires a refresh. - /// This keybind is reserved and can't be bound to anything. - /// - /// Using Event::User(()) conflicts with skim internal which interpret this - /// event as a signal(1) and hangs the terminal. - fn spawn(term: Arc) -> Self { - let (tx, rx) = mpsc::channel(); - let mut counter: u8 = 0; - let handle = thread::spawn(move || loop { - match rx.try_recv() { - Ok(_) | Err(TryRecvError::Disconnected) => { - log::info!("terminating refresher"); - let _ = term.show_cursor(true); - return; - } - Err(TryRecvError::Empty) => {} - } - counter += 1; - thread::sleep(Duration::from_millis(100)); - if counter >= Self::TEN_SECONDS_IN_DECISECONDS { - counter = 0; - if term.send_event(Self::REFRESH_EVENT).is_err() { - break; - } - } - }); - Self { tx, handle } - } - - /// Send a quit message to the receiver, signaling it to quit. - /// Join the refreshing thread which should be terminated. - fn quit(self) -> Result<()> { - self.tx.send(())?; - let _ = self.handle.join(); - Ok(()) - } -} - -/// Exit the application and log a message. -/// Used when the config can't be read. -fn exit_wrong_config() -> ! { - eprintln!("Couldn't load the config file at {CONFIG_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/config.yaml for an example."); - info!("Couldn't read the config file {CONFIG_PATH}"); - std::process::exit(1) -} - /// Main function /// Init the status and display and listen to events (keyboard, mouse, resize, custom...). /// The application is redrawn after every event. /// When the user issues a quit event, the main loop is broken /// Then we reset the cursor, drop everything holding a terminal and print the last path. -fn main() -> Result<()> { - set_loggers()?; - let mut fm = FM::start()?; +fn main() -> anyhow::Result<()> { + let mut fm = fm::app::FM::start()?; while let Ok(event) = fm.poll_event() { fm.update(event)?; diff --git a/src/refresher.rs b/src/refresher.rs new file mode 100644 index 00000000..ce26ffb1 --- /dev/null +++ b/src/refresher.rs @@ -0,0 +1,66 @@ +use std::sync::mpsc::{self, TryRecvError}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +use anyhow::Result; +use tuikit::prelude::{Event, Key}; + +/// Allows refresh if the current path has been modified externally. +pub struct Refresher { + /// Sender of messages, used to terminate the thread properly + tx: mpsc::Sender<()>, + /// Handle to the `term::Event` sender thread. + handle: thread::JoinHandle<()>, +} + +impl Refresher { + /// Between 2 refreshed + const TEN_SECONDS_IN_DECISECONDS: u8 = 10 * 10; + + /// 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. + /// It also listen to a receiver for quit messages. + /// + /// This will send periodically an `Key::AltPageUp` event to the terminal which requires a refresh. + /// This keybind is reserved and can't be bound to anything. + /// + /// Using Event::User(()) conflicts with skim internal which interpret this + /// event as a signal(1) and hangs the terminal. + pub fn new(term: Arc) -> Self { + let (tx, rx) = mpsc::channel(); + let mut counter: u8 = 0; + let handle = thread::spawn(move || loop { + match rx.try_recv() { + Ok(_) | Err(TryRecvError::Disconnected) => { + log::info!("terminating refresher"); + let _ = term.show_cursor(true); + return; + } + Err(TryRecvError::Empty) => {} + } + counter += 1; + thread::sleep(Duration::from_millis(100)); + if counter >= Self::TEN_SECONDS_IN_DECISECONDS { + counter = 0; + if term.send_event(Self::REFRESH_EVENT).is_err() { + break; + } + } + }); + Self { tx, handle } + } + + /// Send a quit message to the receiver, signaling it to quit. + /// Join the refreshing thread which should be terminated. + pub fn quit(self) -> Result<()> { + self.tx.send(())?; + let _ = self.handle.join(); + Ok(()) + } +} From 5363d5ebd2984a1d09133ce9b62a2e0c0649e912 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 31 Oct 2023 19:21:33 +0100 Subject: [PATCH 084/168] refactor event dispatcher --- src/event_dispatch.rs | 37 ++++++++----------------------------- src/status.rs | 15 ++++++++++++--- src/tab.rs | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index 1ae3aa6f..ec870433 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -69,46 +69,25 @@ impl EventDispatcher { let tab = status.selected(); match tab.mode { Mode::InputSimple(InputSimple::Sort) => tab.sort(c), - Mode::InputSimple(InputSimple::RegexMatch) => { - tab.input.insert(c); - status.select_from_regex()?; - Ok(()) - } - Mode::InputSimple(_) => { - tab.input.insert(c); - Ok(()) - } + Mode::InputSimple(InputSimple::RegexMatch) => status.input_regex(c), + Mode::InputSimple(_) => tab.input_insert(c), Mode::InputCompleted(_) => tab.text_insert_and_complete(c), - Mode::Normal | Mode::Tree => match self.binds.get(&Key::Char(c)) { - Some(action) => action.matcher(status), - None => Ok(()), - }, + Mode::Normal | Mode::Tree => self.key_matcher(status, Key::Char(c)), Mode::NeedConfirmation(confirmed_action) => status.confirm(c, confirmed_action), - Mode::Navigate(Navigate::Trash) if c == 'x' => status.trash.remove(), + Mode::Navigate(Navigate::Trash) if c == 'x' => status.trash_remove(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'm' => status.mount_encrypted_drive(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'g' => status.go_to_encrypted_drive(), Mode::Navigate(Navigate::EncryptedDrive) if c == 'u' => status.umount_encrypted_drive(), - Mode::Navigate(Navigate::RemovableDevices) if c == 'm' => { - status.mount_removable_device() - } - Mode::Navigate(Navigate::RemovableDevices) if c == 'g' => { - status.go_to_removable_device() - } - Mode::Navigate(Navigate::RemovableDevices) if c == 'u' => { - status.umount_removable_device() - } + Mode::Navigate(Navigate::RemovableDevices) if c == 'm' => status.mount_removable(), + Mode::Navigate(Navigate::RemovableDevices) if c == 'g' => status.go_to_removable(), + Mode::Navigate(Navigate::RemovableDevices) if c == 'u' => status.umount_removable(), Mode::Navigate(Navigate::Jump) if c == ' ' => status.jump_remove_selected_flagged(), Mode::Navigate(Navigate::Jump) if c == 'u' => status.clear_flags_and_reset_view(), Mode::Navigate(Navigate::Jump) if c == 'x' => status.delete_single_flagged(), Mode::Navigate(Navigate::Jump) if c == 'X' => status.trash_single_flagged(), Mode::Navigate(Navigate::Marks(MarkAction::Jump)) => status.marks_jump_char(c), Mode::Navigate(Navigate::Marks(MarkAction::New)) => status.marks_new(c), - Mode::Preview | Mode::Navigate(_) => { - if tab.reset_mode() { - tab.refresh_view()?; - } - Ok(()) - } + Mode::Preview | Mode::Navigate(_) => tab.reset_mode_and_view(), } } } diff --git a/src/status.rs b/src/status.rs index 54af94e5..69f4bdae 100644 --- a/src/status.rs +++ b/src/status.rs @@ -421,6 +421,9 @@ impl Status { Ok(()) } + pub fn trash_remove(&mut self) -> Result<()> { + self.trash.remove() + } /// Move the selected flagged file to the trash. pub fn trash_single_flagged(&mut self) -> Result<()> { let filepath = self @@ -466,6 +469,12 @@ impl Status { )?) } + pub fn input_regex(&mut self, char: char) -> Result<()> { + self.selected().input.insert(char); + self.select_from_regex()?; + Ok(()) + } + /// Flag every file matching a typed regex. pub fn select_from_regex(&mut self) -> Result<(), regex::Error> { if self.selected_non_mut().input.string().is_empty() { @@ -817,7 +826,7 @@ impl Status { } } - pub fn mount_removable_device(&mut self) -> Result<()> { + pub fn mount_removable(&mut self) -> Result<()> { let Some(devices) = &mut self.removable_devices else { return Ok(()); }; @@ -831,7 +840,7 @@ impl Status { Ok(()) } - pub fn umount_removable_device(&mut self) -> Result<()> { + pub fn umount_removable(&mut self) -> Result<()> { let Some(devices) = &mut self.removable_devices else { return Ok(()); }; @@ -845,7 +854,7 @@ impl Status { Ok(()) } - pub fn go_to_removable_device(&mut self) -> Result<()> { + pub fn go_to_removable(&mut self) -> Result<()> { let Some(devices) = &self.removable_devices else { return Ok(()); }; diff --git a/src/tab.rs b/src/tab.rs index 3349c847..801c86e7 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -450,6 +450,14 @@ impl Tab { must_refresh } + pub fn reset_mode_and_view(&mut self) -> Result<()> { + if self.reset_mode() { + self.refresh_view() + } else { + Ok(()) + } + } + /// Returns true if the current mode requires 2 windows. /// Only Tree, Normal & Preview doesn't require 2 windows. pub fn need_second_window(&self) -> bool { @@ -496,6 +504,12 @@ impl Tab { self.window.scroll_to(0); } + /// Insert a char in the input string. + pub fn input_insert(&mut self, char: char) -> Result<()> { + self.input.insert(char); + Ok(()) + } + /// Add a char to input string, look for a possible completion. pub fn text_insert_and_complete(&mut self, c: char) -> Result<()> { self.input.insert(c); From ef6b7a1e3ca1ee232b4fe7a679f4684cf193941a Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 31 Oct 2023 19:48:58 +0100 Subject: [PATCH 085/168] documentation --- src/password.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/password.rs b/src/password.rs index 1cd01583..24ec0a7f 100644 --- a/src/password.rs +++ b/src/password.rs @@ -25,9 +25,10 @@ impl std::fmt::Display for PasswordKind { } /// What will this password be used for ? -/// ATM only 2 usages are supported: +/// ATM only 3 usages are supported: /// * mounting an ISO file, /// * opening an mounting an encrypted device. +/// * running a sudo command #[derive(Debug, Clone, Copy)] pub enum PasswordUsage { ISO, @@ -35,33 +36,35 @@ pub enum PasswordUsage { SUDOCOMMAND, } +type Password = String; + /// Holds passwords allowing to mount or unmount an encrypted drive. #[derive(Default, Clone, Debug)] pub struct PasswordHolder { - sudo: Option, - cryptsetup: Option, + sudo: Option, + cryptsetup: Option, } impl PasswordHolder { /// Set the sudo password. - pub fn set_sudo(&mut self, password: String) { + pub fn set_sudo(&mut self, password: Password) { self.sudo = Some(password) } /// Set the encrypted device passphrase - pub fn set_cryptsetup(&mut self, passphrase: String) { + pub fn set_cryptsetup(&mut self, passphrase: Password) { self.cryptsetup = Some(passphrase) } /// Reads the cryptsetup password - pub fn cryptsetup(&self) -> Result { + pub fn cryptsetup(&self) -> Result { self.cryptsetup .clone() .context("PasswordHolder: cryptsetup password isn't set") } /// Reads the sudo password - pub fn sudo(&self) -> Result { + pub fn sudo(&self) -> Result { self.sudo .clone() .context("PasswordHolder: sudo password isn't set") From 76a25089935f35e8a24c735b975c1896fcddbb07 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 31 Oct 2023 23:20:47 +0100 Subject: [PATCH 086/168] trash refactor --- src/constant_strings_paths.rs | 2 + src/event_exec.rs | 2 +- src/term_manager.rs | 23 +++- src/trash.rs | 249 ++++++++++++++++++++-------------- 4 files changed, 166 insertions(+), 110 deletions(-) diff --git a/src/constant_strings_paths.rs b/src/constant_strings_paths.rs index 3bb16314..b8469aca 100644 --- a/src/constant_strings_paths.rs +++ b/src/constant_strings_paths.rs @@ -14,6 +14,8 @@ pub const ACTION_LOG_PATH: &str = "~/.config/fm/log/action_logger.log"; pub const TRASH_FOLDER_FILES: &str = "~/.local/share/Trash/files"; /// Path to the trash folder info file pub const TRASH_FOLDER_INFO: &str = "~/.local/share/Trash/info"; +/// Trash info files extension. Watchout it includes the final '.' +pub const TRASH_INFO_EXTENSION: &str = ".trashinfo"; /// Log file path. Rotating file logs are created in the same directeroy pub const LOG_PATH: &str = "~/.config/fm/fm{}"; /// File where marks are stored. diff --git a/src/event_exec.rs b/src/event_exec.rs index 3b7e08dc..d29b4f98 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -863,7 +863,7 @@ impl EventAction { } let trash_mount_point = opt_mount_point(disk_used_by_path( status.system_info.disks(), - &std::path::PathBuf::from(&status.trash.trash_folder_files), + std::path::Path::new(&status.trash.trash_folder_files), )); for flagged in status.flagged.content.iter() { diff --git a/src/term_manager.rs b/src/term_manager.rs index 4a427b84..4b097d4f 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -894,7 +894,7 @@ impl<'a> WinSecondary<'a> { } Ok(()) } - /// Display a list of edited (deleted, copied, moved) files for confirmation + /// Display a list of edited (deleted, copied, moved, trashed) files for confirmation fn confirm( &self, status: &Status, @@ -905,13 +905,22 @@ impl<'a> WinSecondary<'a> { info!("confirmed action: {:?}", confirmed_mode); match confirmed_mode { NeedConfirmation::EmptyTrash => { - for (row, trashinfo) in status.trash.content.iter().enumerate() { - canvas.print_with_attr( - row + ContentWindow::WINDOW_MARGIN_TOP + 2, + if status.trash.is_empty() { + let _ = canvas.print_with_attr( + ContentWindow::WINDOW_MARGIN_TOP + 2, 4, - &format!("{trashinfo}"), - Attr::default(), - )?; + "Trash is empty", + ATTR_YELLOW_BOLD, + ); + } else { + for (row, trashinfo) in status.trash.content().iter().enumerate() { + canvas.print_with_attr( + row + ContentWindow::WINDOW_MARGIN_TOP + 2, + 4, + &format!("{trashinfo}"), + Attr::default(), + )?; + } } } _ => { diff --git a/src/trash.rs b/src/trash.rs index 75aa28db..d607bda1 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -8,7 +8,7 @@ use log::info; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use crate::constant_strings_paths::{TRASH_FOLDER_FILES, TRASH_FOLDER_INFO}; +use crate::constant_strings_paths::{TRASH_FOLDER_FILES, TRASH_FOLDER_INFO, TRASH_INFO_EXTENSION}; use crate::impl_selectable_content; use crate::log::write_log_line; use crate::utils::read_lines; @@ -72,73 +72,95 @@ DeletionDate={} /// Reads a .trashinfo file and parse it into a new instance. /// - /// Let say `Document.trashinfo` contains : + /// Let say `Documents.trashinfo` contains : /// + /// ```not_rust /// [TrashInfo] /// Path=/home/quentin/Documents /// DeletionDate=2022-12-31T22:45:55 + /// ``` /// /// It will be parsed into + /// ```rust /// TrashInfo { PathBuf::from("/home/quentin/Documents"), "Documents", "2022-12-31T22:45:55" } + /// ``` pub fn from_trash_info_file(trash_info_file: &Path) -> Result { - let mut option_path: Option = None; - let mut option_deleted_time: Option = None; - let mut found_trash_info_line: bool = false; - if let Some(dest_name) = trash_info_file.file_name() { - let dest_name = Self::remove_extension(dest_name.to_str().unwrap().to_owned())?; - if let Ok(lines) = read_lines(trash_info_file) { - for (index, line_result) in lines.enumerate() { - let Ok(line) = line_result.as_ref() else { - continue; - }; - if line.starts_with("[Trash Info]") { - if index == 0 { - found_trash_info_line = true; - continue; - } else { - return trashinfo_error("[TrashInfo] was found after first line"); - } - } - if line.starts_with("Path=") && option_path.is_none() { - if !found_trash_info_line { - return trashinfo_error("Found Path line before TrashInfo"); - } - let path_part = &line[5..]; - let cow_path_str = url_escape::decode(path_part); - let path_str = cow_path_str.as_ref(); - option_path = Some(PathBuf::from(path_str)); - } else if line.starts_with("DeletionDate=") && option_deleted_time.is_none() { - if !found_trash_info_line { - return trashinfo_error("Found DeletionDate line before TrashInfo"); - } - let deletion_date_str = &line[13..]; - match parsed_date_from_path_info(deletion_date_str) { - Ok(()) => (), - Err(e) => return Err(e), - } - option_deleted_time = Some(deletion_date_str.to_owned()) - } - } - } - match (option_path, option_deleted_time) { - (Some(origin), Some(deletion_date)) => Ok(Self { + let (option_path, option_deleted_time) = Self::parse_trash_info_file(trash_info_file)?; + + match (option_path, option_deleted_time) { + (Some(origin), Some(deletion_date)) => { + let dest_name = Self::get_dest_name(trash_info_file)?; + Ok(Self { dest_name, deletion_date, origin, - }), - _ => trashinfo_error("Couldn't parse the trash info file"), + }) } + _ => Err(anyhow!("Couldn't parse the trash info file")), + } + } + + fn get_dest_name(trash_info_file: &Path) -> Result { + if let Some(dest_name) = trash_info_file.file_name() { + let dest_name = Self::remove_extension(dest_name.to_str().unwrap().to_owned())?; + Ok(dest_name) } else { - trashinfo_error("Couldn't parse the trash info filename") + Err(anyhow!("Couldn't parse the trash info filename")) + } + } + + fn parse_trash_info_file(trash_info_file: &Path) -> Result<(Option, Option)> { + let mut option_path: Option = None; + let mut option_deleted_time: Option = None; + + if let Ok(mut lines) = read_lines(trash_info_file) { + let Some(Ok(first_line)) = lines.next() else { + return Err(anyhow!("Unreadable TrashInfo file")); + }; + if !first_line.starts_with("[Trash Info]") { + return Err(anyhow!("First line should start with [TrashInfo]")); + } + + for line in lines { + let Ok(line) = line else { + continue; + }; + if option_path.is_none() && line.starts_with("Path=") { + option_path = Some(Self::parse_option_path(&line)); + continue; + } + if option_deleted_time.is_none() && line.starts_with("DeletionDate=") { + option_deleted_time = Some(Self::parse_deletion_date(&line)?); + } + } + } + + Ok((option_path, option_deleted_time)) + } + + fn parse_option_path(line: &str) -> PathBuf { + let path_part = &line[5..]; + let cow_path_str = url_escape::decode(path_part); + let path_str = cow_path_str.as_ref(); + PathBuf::from(path_str) + } + + fn parse_deletion_date(line: &str) -> Result { + let deletion_date_str = &line[13..]; + match parsed_date_from_path_info(deletion_date_str) { + Ok(()) => Ok(deletion_date_str.to_owned()), + Err(e) => Err(e), } } fn remove_extension(mut destname: String) -> Result { - if destname.ends_with(".trashinfo") { + if destname.ends_with(TRASH_INFO_EXTENSION) { destname.truncate(destname.len() - 10); Ok(destname) } else { - Err(anyhow!("trahsinfo: filename doesn't contain .trashfino")) + Err(anyhow!( + "trahsinfo: filename doesn't contain {TRASH_INFO_EXTENSION}" + )) } } } @@ -159,7 +181,7 @@ impl std::fmt::Display for TrashInfo { #[derive(Clone)] pub struct Trash { /// Trashed files info. - pub content: Vec, + content: Vec, index: usize, /// The path to the trashed files pub trash_folder_files: String, @@ -171,7 +193,7 @@ impl Trash { if let Some(file_name) = origin.file_name() { let mut dest = file_name .to_str() - .context("pick_dest_name :Couldn't parse the origin filename into a string")? + .context("pick_dest_name: Couldn't parse the origin filename into a string")? .to_owned(); let mut dest_path = PathBuf::from(&self.trash_folder_files); dest_path.push(&dest); @@ -242,50 +264,67 @@ impl Trash { } let dest_file_name = self.pick_dest_name(origin)?; - let trash_info = TrashInfo::new(origin, &dest_file_name); - let mut trashfile_filename = PathBuf::from(&self.trash_folder_files); - trashfile_filename.push(&dest_file_name); - let mut dest_trashinfo_name = dest_file_name.clone(); - dest_trashinfo_name.push_str(".trashinfo"); - let mut trashinfo_filename = PathBuf::from(&self.trash_folder_info); - trashinfo_filename.push(dest_trashinfo_name); - trash_info.write_trash_info(&trashinfo_filename)?; + self.execute_trash(TrashInfo::new(origin, &dest_file_name), &dest_file_name) + } + + fn concat_path(root: &str, filename: &str) -> PathBuf { + let mut concatened_path = PathBuf::from(root); + concatened_path.push(filename); + concatened_path + } + + fn trashfile_path(&self, dest_file_name: &str) -> PathBuf { + Self::concat_path(&self.trash_folder_files, dest_file_name) + } + fn trashinfo_path(&self, dest_trashinfo_name: &str) -> PathBuf { + let mut dest_trashinfo_name = dest_trashinfo_name.to_owned(); + dest_trashinfo_name.push_str(TRASH_INFO_EXTENSION); + Self::concat_path(&self.trash_folder_info, &dest_trashinfo_name) + } + + fn execute_trash(&mut self, trash_info: TrashInfo, dest_file_name: &str) -> Result<()> { + let trashfile_filename = &self.trashfile_path(dest_file_name); + std::fs::rename(&trash_info.origin, trashfile_filename)?; + Self::log_trash_add(&trash_info.origin, dest_file_name); + trash_info.write_trash_info(&self.trashinfo_path(dest_file_name))?; self.content.push(trash_info); - std::fs::rename(origin, &trashfile_filename)?; + Ok(()) + } + fn log_trash_add(origin: &Path, dest_file_name: &str) { info!("moved to trash {:?} -> {:?}", origin, dest_file_name); let log_line = format!("moved to trash {:?} -> {:?}", origin, dest_file_name); write_log_line(log_line); - Ok(()) } /// Empty the trash, removing all the files and the trashinfo. /// This action requires a confirmation. /// Watchout, it may delete files that weren't parsed. pub fn empty_trash(&mut self) -> Result<()> { - remove_dir_all(&self.trash_folder_files)?; - create_dir(&self.trash_folder_files)?; - - remove_dir_all(&self.trash_folder_info)?; - create_dir(&self.trash_folder_info)?; + self.empty_trash_dirs()?; let number_of_elements = self.content.len(); - self.content = vec![]; + Self::log_trash_empty(number_of_elements); + Ok(()) + } - let log_line = format!( - "Emptied the trash: {} files permanently deleted", - number_of_elements - ); - write_log_line(log_line); - info!( - "Emptied the trash: {} files permanently deleted", - number_of_elements - ); + fn empty_trash_dirs(&self) -> Result<(), std::io::Error> { + Self::empty_dir(&self.trash_folder_files)?; + Self::empty_dir(&self.trash_folder_files) + } - Ok(()) + fn empty_dir(dir: &str) -> Result<(), std::io::Error> { + remove_dir_all(dir)?; + create_dir(dir) + } + + fn log_trash_empty(number_of_elements: usize) { + let log_line = format!("Emptied the trash: {number_of_elements} files permanently deleted"); + write_log_line(log_line); + info!("Emptied the trash: {number_of_elements} files permanently deleted"); } fn remove_selected_file(&mut self) -> Result<(PathBuf, PathBuf, PathBuf)> { @@ -294,37 +333,33 @@ impl Trash { "remove selected file: Can't restore from an empty trash", )); } - let trashinfo = self.content[self.index].to_owned(); - - let origin = trashinfo.origin; - let dest_name = trashinfo.dest_name; - let parent = find_parent(&origin)?; + let trashinfo = &self.content[self.index]; + let origin = trashinfo.origin.to_owned(); - let mut info_name = dest_name.clone(); - info_name.push_str(".trashinfo"); + let parent = find_parent(&trashinfo.origin)?; - let mut trashed_file_content = PathBuf::from(&self.trash_folder_files); - trashed_file_content.push(&dest_name); - - let mut trashed_file_info = PathBuf::from(&self.trash_folder_info); - trashed_file_info.push(&info_name); + let trashed_file_content = self.trashfile_path(&trashinfo.dest_name); + let trashed_file_info = self.trashinfo_path(&trashinfo.dest_name); if !trashed_file_content.exists() { return Err(anyhow!("trash restore: Couldn't find the trashed file",)); } if !trashed_file_info.exists() { - return Err(anyhow!( - "trash restore: Couldn't find the trashed file info", - )); + return Err(anyhow!("trash restore: Couldn't find the trashed info",)); } - self.content.remove(self.index); - std::fs::remove_file(&trashed_file_info)?; + self.remove_from_content_and_delete_trashinfo(&trashed_file_info)?; Ok((origin, trashed_file_content, parent)) } + fn remove_from_content_and_delete_trashinfo(&mut self, trashed_file_info: &Path) -> Result<()> { + self.content.remove(self.index); + std::fs::remove_file(trashed_file_info)?; + Ok(()) + } + /// Restores a file from the trash to its previous directory. /// If the parent (or ancestor) folder were deleted, it is recreated. pub fn restore(&mut self) -> Result<()> { @@ -332,13 +367,22 @@ impl Trash { return Ok(()); } let (origin, trashed_file_content, parent) = self.remove_selected_file()?; + Self::execute_restore(&origin, &trashed_file_content, &parent)?; + Self::log_trash_restore(&origin); + Ok(()) + } + + fn execute_restore(origin: &Path, trashed_file_content: &Path, parent: &Path) -> Result<()> { if !parent.exists() { - std::fs::create_dir_all(&parent)? + std::fs::create_dir_all(parent)? } - std::fs::rename(trashed_file_content, &origin)?; + std::fs::rename(trashed_file_content, origin)?; + Ok(()) + } + + fn log_trash_restore(origin: &Path) { let log_line = format!("Trash restored: {origin}", origin = origin.display()); write_log_line(log_line); - Ok(()) } /// Deletes a file permanently from the trash. @@ -350,15 +394,20 @@ impl Trash { let (_, trashed_file_content, _) = self.remove_selected_file()?; std::fs::remove_file(&trashed_file_content)?; + Self::log_trash_remove(&trashed_file_content); + if self.index > 0 { self.index -= 1 } + Ok(()) + } + + fn log_trash_remove(trashed_file_content: &Path) { let log_line = format!( - "Trash removed {trashed_file_content}", + "Trash removed: {trashed_file_content}", trashed_file_content = trashed_file_content.display() ); write_log_line(log_line); - Ok(()) } } @@ -382,10 +431,6 @@ fn rand_string() -> String { .collect() } -fn trashinfo_error(msg: &str) -> Result { - Err(anyhow!("trash {}", msg)) -} - fn find_parent(path: &Path) -> Result { Ok(path .parent() From 4a4eba0258d4f1cc9be26f978660737c3a0458bb Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 31 Oct 2023 23:37:30 +0100 Subject: [PATCH 087/168] refactor trash. Don't check mountpoints, let the OS handle the error --- src/event_exec.rs | 11 +---------- src/trash.rs | 23 ++++++++++++++++------- src/utils.rs | 10 ---------- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index d29b4f98..e22f2f88 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -30,8 +30,7 @@ use crate::shell_parser::ShellCommandParser; use crate::status::Status; use crate::tab::Tab; use crate::utils::{ - args_is_empty, disk_used_by_path, is_program_in_path, is_sudo_command, open_in_current_neovim, - opt_mount_point, string_to_path, + args_is_empty, is_program_in_path, is_sudo_command, open_in_current_neovim, string_to_path, }; /// Links events from tuikit to custom actions. @@ -861,16 +860,8 @@ impl EventAction { if status.flagged.is_empty() { Self::toggle_flag(status)?; } - let trash_mount_point = opt_mount_point(disk_used_by_path( - status.system_info.disks(), - std::path::Path::new(&status.trash.trash_folder_files), - )); for flagged in status.flagged.content.iter() { - let origin_mount_point = opt_mount_point(disk_used_by_path(status.disks(), flagged)); - if trash_mount_point != origin_mount_point { - continue; - } status.trash.trash(flagged)?; } status.flagged.clear(); diff --git a/src/trash.rs b/src/trash.rs index d607bda1..c68057f3 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -177,7 +177,10 @@ impl std::fmt::Display for TrashInfo { } /// Represent a view of the trash. -/// It's content is navigable so we use a Vector to hold the content. +/// Its content is navigable so we use a Vector to hold the content. +/// Only files that share the same mount point as the trash folder (generally ~/.local/share/Trash) +/// can be moved to trash. +/// Other files are unaffected. #[derive(Clone)] pub struct Trash { /// Trashed files info. @@ -286,12 +289,18 @@ impl Trash { fn execute_trash(&mut self, trash_info: TrashInfo, dest_file_name: &str) -> Result<()> { let trashfile_filename = &self.trashfile_path(dest_file_name); - std::fs::rename(&trash_info.origin, trashfile_filename)?; - Self::log_trash_add(&trash_info.origin, dest_file_name); - trash_info.write_trash_info(&self.trashinfo_path(dest_file_name))?; - self.content.push(trash_info); - - Ok(()) + match std::fs::rename(&trash_info.origin, trashfile_filename) { + Err(error) => { + log::info!("Couldn't trash {trash_info}. Error: {error:?}"); + Ok(()) + } + Ok(()) => { + Self::log_trash_add(&trash_info.origin, dest_file_name); + trash_info.write_trash_info(&self.trashinfo_path(dest_file_name))?; + self.content.push(trash_info); + Ok(()) + } + } } fn log_trash_add(origin: &Path, dest_file_name: &str) { diff --git a/src/utils.rs b/src/utils.rs index 395ec734..c5d8e5a8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -55,16 +55,6 @@ pub fn disk_space(disks: &[Disk], path: &Path) -> String { disk_space_used(disk_used_by_path(disks, path)) } -/// Takes a disk and returns its mount point. -/// Returns `None` if it received `None`. -/// It's a poor fix to support OSX where `sysinfo::Disk` doesn't implement `PartialEq`. -pub fn opt_mount_point(disk: Option<&Disk>) -> Option<&std::path::Path> { - let Some(disk) = disk else { - return None; - }; - Some(disk.mount_point()) -} - /// Print the path on the stdout. pub fn print_on_quit(path_string: &str) { println!("{path_string}") From 2abff6a9486dbd2b300729c7fe0a7c91c24d60da Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 16:39:13 +0100 Subject: [PATCH 088/168] use const instead of static --- src/trash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trash.rs b/src/trash.rs index c68057f3..33329012 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -13,7 +13,7 @@ use crate::impl_selectable_content; use crate::log::write_log_line; use crate::utils::read_lines; -static TRASHINFO_DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; +const TRASHINFO_DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; /// Holds the information about a trashed file. /// Follow the specifications of .trashinfo files as described in From 6b1d5d4d204f11204fca02b2678c96fa3aacbf91 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 16:39:32 +0100 Subject: [PATCH 089/168] FIX: couldn't reset view after exiting preview second window --- src/event_exec.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index e22f2f88..0c48fd80 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -979,7 +979,13 @@ impl EventAction { /// Toggle the second pane between preview & normal mode (files). pub fn toggle_preview_second(status: &mut Status) -> Result<()> { status.preview_second = !status.preview_second; - status.update_second_pane_for_preview() + if status.preview_second { + status.set_second_pane_for_preview()?; + } else { + status.tabs[1].reset_mode(); + status.tabs[1].refresh_view()?; + } + Ok(()) } /// Set the current selected file as wallpaper with `nitrogen`. From 2486f847db8a603b6da8a4ca892e80d498d40c6b Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 16:47:54 +0100 Subject: [PATCH 090/168] remove useless function --- src/event_exec.rs | 16 ++++++++-------- src/mocp.rs | 10 +--------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index 0c48fd80..bc2e5635 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -6,7 +6,6 @@ use std::str::FromStr; use anyhow::{anyhow, Context, Result}; use log::info; -use sysinfo::SystemExt; use crate::action_map::ActionMap; use crate::completion::InputCompleted; @@ -16,7 +15,8 @@ use crate::constant_strings_paths::{ use crate::cryptsetup::{lsblk_and_cryptsetup_installed, BlockDeviceAction}; use crate::filter::FilterKind; use crate::log::{read_log, write_log_line}; -use crate::mocp::{is_mocp_installed, Mocp}; +use crate::mocp::Mocp; +use crate::mocp::MOCP; use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation}; use crate::opener::{ execute_and_capture_output, execute_and_capture_output_without_check, execute_in_child, @@ -1013,7 +1013,7 @@ impl EventAction { /// Add a song or a folder to MOC playlist. Start it first... pub fn mocp_add_to_playlist(tab: &Tab) -> Result<()> { - if !is_mocp_installed() { + if !is_program_in_path(MOCP) { write_log_line("mocp isn't installed".to_owned()); return Ok(()); } @@ -1021,7 +1021,7 @@ impl EventAction { } pub fn mocp_clear_playlist() -> Result<()> { - if !is_mocp_installed() { + if !is_program_in_path(MOCP) { write_log_line("mocp isn't installed".to_owned()); return Ok(()); } @@ -1031,7 +1031,7 @@ impl EventAction { /// Add a song or a folder to MOC playlist. Start it first... pub fn mocp_go_to_song(status: &mut Status) -> Result<()> { let tab = status.selected(); - if !is_mocp_installed() { + if !is_program_in_path(MOCP) { write_log_line("mocp isn't installed".to_owned()); return Ok(()); } @@ -1044,7 +1044,7 @@ impl EventAction { /// Starts the server if needed, preventing the output to fill the screen. /// Then toggle play/pause pub fn mocp_toggle_pause(status: &mut Status) -> Result<()> { - if !is_mocp_installed() { + if !is_program_in_path(MOCP) { write_log_line("mocp isn't installed".to_owned()); return Ok(()); } @@ -1053,7 +1053,7 @@ impl EventAction { /// Skip to the next song in MOC pub fn mocp_next() -> Result<()> { - if !is_mocp_installed() { + if !is_program_in_path(MOCP) { write_log_line("mocp isn't installed".to_owned()); return Ok(()); } @@ -1062,7 +1062,7 @@ impl EventAction { /// Go to the previous song in MOC pub fn mocp_previous() -> Result<()> { - if !is_mocp_installed() { + if !is_program_in_path(MOCP) { write_log_line("mocp isn't installed".to_owned()); return Ok(()); } diff --git a/src/mocp.rs b/src/mocp.rs index 85d82ba4..d3bf7d50 100644 --- a/src/mocp.rs +++ b/src/mocp.rs @@ -7,16 +7,8 @@ use crate::opener::{ }; use crate::status::Status; use crate::tab::Tab; -use crate::utils::is_program_in_path; -static MOCP: &str = DEFAULT_AUDIO_OPENER.0; - -/// True iff `mocp` is in path. -/// Nothing can be done here without it, we shouldn't run commands -/// that will always fail. -pub fn is_mocp_installed() -> bool { - is_program_in_path(MOCP) -} +pub const MOCP: &str = DEFAULT_AUDIO_OPENER.0; /// A bunch of methods to control MOC. /// It relies on the application `mocp` itself to : From 422f48502badaf438b23797f71482a2a25170b0b Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 16:48:02 +0100 Subject: [PATCH 091/168] accept string or str --- src/log.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/log.rs b/src/log.rs index f917590b..081918d0 100644 --- a/src/log.rs +++ b/src/log.rs @@ -74,16 +74,22 @@ pub fn read_last_log_line() -> String { /// Write a new log line to the global variable `LAST_LOG_LINE`. /// It uses `lazy_static` to manipulate the global variable. /// Fail silently if the global variable can't be written. -fn write_last_log_line(log: &str) { +fn write_last_log_line(log: S) +where + S: Into + std::fmt::Display, +{ let Ok(mut new_log_line) = LAST_LOG_LINE.write() else { return; }; - *new_log_line = log.to_owned(); + *new_log_line = log.to_string(); } /// Write a line to both the global variable `LAST_LOG_LINE` and the special log /// which can be displayed with Alt+l -pub fn write_log_line(log_line: String) { +pub fn write_log_line(log_line: S) +where + S: Into + std::fmt::Display, +{ log::info!(target: "special", "{log_line}"); - write_last_log_line(&log_line); + write_last_log_line(log_line); } From 153eb1fbec5e14ad4b9a67c5bc26105e1f381dbc Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 16:53:13 +0100 Subject: [PATCH 092/168] remove useless variable --- src/event_exec.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index bc2e5635..881b043e 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -396,8 +396,7 @@ impl EventAction { }; let nvim_server = status.nvim_server.clone(); if status.flagged.is_empty() { - let tab = status.selected(); - let Some(fileinfo) = tab.selected() else { + let Some(fileinfo) = status.selected_non_mut().selected() else { return Ok(()); }; let Some(path_str) = fileinfo.path.to_str() else { From 021824fcf623bfe745c115d9983f95accaef9b5f Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 16:53:45 +0100 Subject: [PATCH 093/168] Fix J&K --- src/keybindings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keybindings.rs b/src/keybindings.rs index c5f7bddd..d2a4c6b5 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -57,8 +57,8 @@ impl Bindings { (Key::Char('F'), ActionMap::Filter), (Key::Char('G'), ActionMap::End), (Key::Char('H'), ActionMap::Help), - (Key::Char('J'), ActionMap::PageUp), - (Key::Char('K'), ActionMap::PageDown), + (Key::Char('J'), ActionMap::PageDown), + (Key::Char('K'), ActionMap::PageUp), (Key::Char('I'), ActionMap::NvimSetAddress), (Key::Char('L'), ActionMap::Symlink), (Key::Char('M'), ActionMap::MarksNew), From f8abdf9c0adc2687d1e1bdaf011bc3de55b6faf7 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 17:13:47 +0100 Subject: [PATCH 094/168] remove useless function --- src/opener.rs | 22 +++------------------- src/utils.rs | 5 ++++- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/opener.rs b/src/opener.rs index 1f360014..565f3d0d 100644 --- a/src/opener.rs +++ b/src/opener.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::env; use std::fmt; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -17,22 +16,7 @@ use crate::constant_strings_paths::{ use crate::decompress::{decompress_gz, decompress_xz, decompress_zip}; use crate::fileinfo::extract_extension; use crate::log::write_log_line; - -fn find_it

(exe_name: P) -> Option -where - P: AsRef, -{ - env::var_os("PATH").and_then(|paths| { - env::split_paths(&paths).find_map(|dir| { - let full_path = dir.join(&exe_name); - if full_path.is_file() { - Some(full_path) - } else { - None - } - }) - }) -} +use crate::utils::is_program_in_path; /// Different kind of extensions for default openers. #[derive(Clone, Hash, Eq, PartialEq, Debug, Display, Default, EnumString, EnumIter)] @@ -210,7 +194,7 @@ impl OpenerAssociation { fn validate_openers(&mut self) { self.association.retain(|_, opener| { opener.external_program.is_none() - || find_it(opener.external_program.as_ref().unwrap()).is_some() + || is_program_in_path(opener.external_program.as_ref().unwrap()) }); } } @@ -241,7 +225,7 @@ pub struct OpenerInfo { impl Default for OpenerInfo { fn default() -> Self { Self { - external_program: Some("/usr/bin/xdg-open".to_owned()), + external_program: Some(DEFAULT_OPENER.0.to_owned()), internal_variant: None, use_term: false, } diff --git a/src/utils.rs b/src/utils.rs index c5d8e5a8..cffe90fc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -96,7 +96,10 @@ pub fn current_username() -> Result { } /// True iff the command is available in $PATH. -pub fn is_program_in_path(program: &str) -> bool { +pub fn is_program_in_path(program: S) -> bool +where + S: Into + std::fmt::Display, +{ if let Ok(path) = std::env::var("PATH") { for p in path.split(':') { let p_str = &format!("{p}/{program}"); From edf7ce1637de06a181ed497ffa3264bbe54f3e6d Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 17:20:38 +0100 Subject: [PATCH 095/168] remove spaces --- src/fileinfo.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 66c0b79b..1a502946 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -202,9 +202,7 @@ impl FileInfo { let system_time = extract_datetime(&metadata)?; let is_selected = false; let true_size = extract_file_size(&metadata); - let file_kind = FileKind::new(&metadata, &path); - let size_column = SizeColumn::new(true_size, &metadata, &file_kind); let extension = extract_extension(&path).into(); let kind_format = filekind_and_filename(&filename, &file_kind); From da00b6f3291f41a42718bb4393ae1767108b3e62 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 17:54:52 +0100 Subject: [PATCH 096/168] refactor fileinfo --- src/fileinfo.rs | 104 ++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 1a502946..436ac141 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -64,7 +64,7 @@ impl FileKind { /// Returns the expected first symbol from `ln -l` line. /// d for directory, s for socket, . for file, c for char device, /// b for block, l for links. - fn extract_dir_symbol(&self) -> char { + fn dir_symbol(&self) -> char { match self { FileKind::Fifo => 'p', FileKind::Socket => 's', @@ -157,44 +157,8 @@ pub struct FileInfo { } impl FileInfo { - /// Reads every information about a file from its metadata and returs - /// a new `FileInfo` object if we can create one. - pub fn new(direntry: &DirEntry, users: &Users) -> Result { - let path = direntry.path(); - let filename = extract_filename(direntry)?; - - Self::create_from_metadata_and_filename(&path, &filename, users) - } - - /// Creates a fileinfo from a path and a filename. - /// The filename is used when we create the fileinfo for "." and ".." in every folder. - pub fn from_path_with_name(path: &path::Path, filename: &str, users: &Users) -> Result { - Self::create_from_metadata_and_filename(path, filename, users) - } - - pub fn from_path(path: &path::Path, users: &Users) -> Result { - let filename = path - .file_name() - .context("from path: couldn't read filenale")? - .to_str() - .context("from path: couldn't parse filenale")?; - Self::create_from_metadata_and_filename(path, filename, users) - } - - fn metadata(&self) -> Result { - Ok(symlink_metadata(&self.path)?) - } - - pub fn permissions(&self) -> Result { - Ok(extract_permissions_string(&self.metadata()?)) - } - - fn create_from_metadata_and_filename( - path: &path::Path, - filename: &str, - users: &Users, - ) -> Result { - let filename = filename.to_owned(); + fn new(path: &path::Path, users: &Users) -> Result { + let filename = extract_filename(path)?; let metadata = symlink_metadata(path)?; let path = path.to_owned(); let owner = extract_owner(&metadata, users)?; @@ -222,6 +186,30 @@ impl FileInfo { }) } + /// Reads every information about a file from its metadata and returs + /// a new `FileInfo` object if we can create one. + pub fn from_direntry(direntry: &DirEntry, users: &Users) -> Result { + Self::new(&direntry.path(), users) + } + + /// Creates a fileinfo from a path and a filename. + /// The filename is used when we create the fileinfo for "." and ".." in every folder. + pub fn from_path_with_name(path: &path::Path, filename: &str, users: &Users) -> Result { + let mut file_info = Self::new(path, users)?; + file_info.filename = filename.to_owned(); + file_info.kind_format = filekind_and_filename(filename, &file_info.file_kind); + Ok(file_info) + } + + fn metadata(&self) -> Result { + Ok(symlink_metadata(&self.path)?) + } + + /// String representation of file permissions + pub fn permissions(&self) -> Result { + Ok(extract_permissions_string(&self.metadata()?)) + } + /// Format the file line. /// Since files can have different owners in the same directory, we need to /// know the maximum size of owner column for formatting purpose. @@ -258,7 +246,7 @@ impl FileInfo { } pub fn dir_symbol(&self) -> char { - self.file_kind.extract_dir_symbol() + self.file_kind.dir_symbol() } pub fn format_simple(&self) -> Result { @@ -275,6 +263,7 @@ impl FileInfo { self.is_selected = false; } + /// True iff the file is hidden (aka starts with a '.'). pub fn is_hidden(&self) -> bool { self.filename.starts_with('.') } @@ -283,13 +272,16 @@ impl FileInfo { self.path.is_dir() } - /// Name of proper files, empty string for `.` and `..`. + /// Formated proper name. + /// "/ " for `.` pub fn filename_without_dot_dotdot(&self) -> String { - let name = &self.filename; - if name == "." || name == ".." { - "".to_owned() - } else { - format!("/{name} ") + match self.filename.as_ref() { + "." => "/ ".to_owned(), + ".." => format!( + "/{name} ", + name = extract_filename(&self.path).unwrap_or_default() + ), + _ => format!("/{name} ", name = self.filename), } } } @@ -588,21 +580,21 @@ pub fn is_not_hidden(entry: &DirEntry) -> Result { Ok(b) } +fn extract_filename(path: &path::Path) -> Result { + Ok(path + .file_name() + .context("from path: couldn't read filename")? + .to_str() + .context("from path: couldn't parse filename")? + .to_owned()) +} + /// Returns the modified time. fn extract_datetime(metadata: &Metadata) -> Result { let datetime: DateTime = metadata.modified()?.into(); Ok(format!("{}", datetime.format("%Y/%m/%d %T"))) } -/// Returns the filename. -fn extract_filename(direntry: &DirEntry) -> Result { - Ok(direntry - .file_name() - .to_str() - .context("Couldn't read filename")? - .to_owned()) -} - /// Reads the permission and converts them into a string. fn extract_permissions_string(metadata: &Metadata) -> String { let mode = (metadata.mode() & 511) as usize; @@ -693,7 +685,7 @@ pub fn files_collection( read_dir .filter_map(|direntry| direntry.ok()) .filter(|direntry| show_hidden || is_not_hidden(direntry).unwrap_or(true)) - .map(|direntry| FileInfo::new(&direntry, users)) + .map(|direntry| FileInfo::from_direntry(&direntry, users)) .filter_map(|fileinfo| fileinfo.ok()) .filter(|fileinfo| filter_kind.filter_by(fileinfo, keep_dir)) .collect(), From 1e299835cc7a29a8d204da48ad1db5803e500656 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 19:54:44 +0100 Subject: [PATCH 097/168] FIX: don't crash if user tries to use ':' for mark --- src/marks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/marks.rs b/src/marks.rs index e6e6db85..0d5a8ce4 100644 --- a/src/marks.rs +++ b/src/marks.rs @@ -102,7 +102,7 @@ impl Marks { if ch == ':' { let log_line = "new mark - ':' can't be used as a mark"; write_log_line(log_line.to_owned()); - return Err(anyhow!(log_line)); + return Ok(()); } if self.used_chars.contains(&ch) { let mut found_index = None; From 97af82a5364fce6918b76bb600a4da8b9af189ab Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 20:37:37 +0100 Subject: [PATCH 098/168] use macro to log line --- src/bulkrename.rs | 17 ++++++---------- src/cli_info.rs | 5 ++--- src/compress.rs | 5 ++--- src/copy_move.rs | 4 ++-- src/event_exec.rs | 52 ++++++++++++++++++++--------------------------- src/log.rs | 9 ++++++++ src/marks.rs | 37 +++++++++++++++++---------------- src/opener.rs | 5 ++--- src/password.rs | 8 +++----- src/shell_menu.rs | 5 ++--- src/status.rs | 8 +++----- src/trash.rs | 14 +++++-------- 12 files changed, 77 insertions(+), 92 deletions(-) diff --git a/src/bulkrename.rs b/src/bulkrename.rs index 9729ac75..91aebb2d 100644 --- a/src/bulkrename.rs +++ b/src/bulkrename.rs @@ -7,7 +7,7 @@ use std::time::{Duration, SystemTime}; use crate::constant_strings_paths::TMP_FOLDER_PATH; use crate::impl_selectable_content; -use crate::log::write_log_line; +use crate::log_line; use crate::opener::Opener; use crate::status::Status; use crate::utils::random_name; @@ -163,11 +163,9 @@ impl<'a> Bulkrename<'a> { let new_name = sanitize_filename::sanitize(filename); self.rename_file(path, &new_name)?; counter += 1; - let log_line = format!("Bulk renamed {path} to {new_name}", path = path.display()); - write_log_line(log_line); + log_line!("Bulk renamed {path} to {new_name}", path = path.display()); } - let log_line = format!("Bulk renamed {counter} files"); - write_log_line(log_line); + log_line!("Bulk renamed {counter} files"); Ok(()) } @@ -186,20 +184,17 @@ impl<'a> Bulkrename<'a> { }; info!("creating: {new_path:?}"); std::fs::File::create(&new_path)?; - let log_line = format!("Bulk created {new_path}", new_path = new_path.display()); - write_log_line(log_line); + log_line!("Bulk created {new_path}", new_path = new_path.display()); counter += 1; } else { new_path.push(filename); info!("Bulk creating dir: {}", new_path.display()); std::fs::create_dir_all(&new_path)?; - let log_line = format!("Bulk created {new_path}", new_path = new_path.display()); - write_log_line(log_line); + log_line!("Bulk created {new_path}", new_path = new_path.display()); counter += 1; } } - let log_line = format!("Bulk created {counter} files"); - write_log_line(log_line); + log_line!("Bulk created {counter} files"); Ok(()) } diff --git a/src/cli_info.rs b/src/cli_info.rs index aa29b50f..91544fd5 100644 --- a/src/cli_info.rs +++ b/src/cli_info.rs @@ -5,7 +5,7 @@ use log::info; use crate::constant_strings_paths::CLI_INFO_COMMANDS; use crate::impl_selectable_content; -use crate::log::write_log_line; +use crate::log_line; use crate::utils::is_program_in_path; /// Holds the command line commands we can run and display @@ -45,8 +45,7 @@ impl CliInfo { pub fn execute(&self) -> Result { let args = self.commands[self.index].clone(); info!("execute. {args:?}"); - let log_line = format!("Executed {args:?}"); - write_log_line(log_line); + log_line!("Executed {args:?}"); let child = Command::new(args[0]) .args(&args[1..]) .env("CLICOLOR_FORCE", "1") diff --git a/src/compress.rs b/src/compress.rs index d1bb6fb7..8365542c 100644 --- a/src/compress.rs +++ b/src/compress.rs @@ -5,7 +5,7 @@ use std::io::Write; use anyhow::Result; use crate::impl_selectable_content; -use crate::log::write_log_line; +use crate::log_line; use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder}; use flate2::Compression; use lzma::LzmaWriter; @@ -71,8 +71,7 @@ impl Compresser { CompressionMethod::ZIP => Self::zip(Self::archive(here, "archive.zip")?, files)?, CompressionMethod::LZMA => Self::lzma(Self::archive(here, "archive.tar.xz")?, files)?, } - let log_line = format!("Compressed with {selected}"); - write_log_line(log_line); + log_line!("Compressed with {selected}"); Ok(()) } diff --git a/src/copy_move.rs b/src/copy_move.rs index 35c903e1..a84202cc 100644 --- a/src/copy_move.rs +++ b/src/copy_move.rs @@ -11,7 +11,7 @@ use tuikit::prelude::{Attr, Color, Effect, Event, Key, Term}; use crate::constant_strings_paths::NOTIFY_EXECUTABLE; use crate::fileinfo::human_size; -use crate::log::write_log_line; +use crate::log_line; use crate::opener::execute_in_child; use crate::utils::{is_program_in_path, random_name}; @@ -90,7 +90,7 @@ impl CopyMove { let message = format!("{preterit} {hs_bytes} bytes", preterit = self.preterit()); let _ = notify(&message); info!("{message}"); - write_log_line(message); + log_line!("{message}"); } fn setup_progress_bar( diff --git a/src/event_exec.rs b/src/event_exec.rs index 881b043e..f908b664 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -14,7 +14,8 @@ use crate::constant_strings_paths::{ }; use crate::cryptsetup::{lsblk_and_cryptsetup_installed, BlockDeviceAction}; use crate::filter::FilterKind; -use crate::log::{read_log, write_log_line}; +use crate::log::read_log; +use crate::log_line; use crate::mocp::Mocp; use crate::mocp::MOCP; use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation}; @@ -143,12 +144,11 @@ impl EventAction { .directory_of_selected()? .join(filename); std::os::unix::fs::symlink(original_file, &link)?; - let log_line = format!( + log_line!( "Symlink {link} links to {original_file}", original_file = original_file.display(), link = link.display() ); - write_log_line(log_line); } status.clear_flags_and_reset_view() } @@ -457,7 +457,7 @@ impl EventAction { /// It obviously requires the `dragon-drop` command to be installed. pub fn drag_n_drop(status: &mut Status) -> Result<()> { if !is_program_in_path(DEFAULT_DRAGNDROP) { - write_log_line(format!("{DEFAULT_DRAGNDROP} must be installed.")); + log_line!("{DEFAULT_DRAGNDROP} must be installed."); return Ok(()); } let Some(file) = status.selected_non_mut().selected() else { @@ -795,7 +795,7 @@ impl EventAction { /// Display mediainfo details of an image pub fn mediainfo(tab: &mut Tab) -> Result<()> { if !is_program_in_path(MEDIAINFO) { - write_log_line(format!("{} isn't installed", MEDIAINFO)); + log_line!("{} isn't installed", MEDIAINFO); return Ok(()); } if let Mode::Normal | Mode::Tree = tab.mode { @@ -813,7 +813,7 @@ impl EventAction { /// Display a diff between the first 2 flagged files or dir. pub fn diff(status: &mut Status) -> Result<()> { if !is_program_in_path(DIFF) { - write_log_line(format!("{DIFF} isn't installed")); + log_line!("{DIFF} isn't installed"); return Ok(()); } if status.flagged.len() < 2 { @@ -923,7 +923,7 @@ impl EventAction { /// a luks encrypted device. pub fn encrypted_drive(status: &mut Status) -> Result<()> { if !lsblk_and_cryptsetup_installed() { - write_log_line("lsblk and cryptsetup must be installed.".to_owned()); + log_line!("lsblk and cryptsetup must be installed."); return Ok(()); } if status.encrypted_devices.is_empty() { @@ -937,7 +937,7 @@ impl EventAction { pub fn removable_devices(status: &mut Status) -> Result<()> { if !is_program_in_path(GIO) { - write_log_line("gio must be installed.".to_owned()); + log_line!("gio must be installed."); return Ok(()); } status.removable_devices = RemovableDevices::from_gio(); @@ -991,7 +991,7 @@ impl EventAction { /// Requires `nitrogen` to be installed. pub fn set_wallpaper(tab: &Tab) -> Result<()> { if !is_program_in_path(NITROGEN) { - write_log_line("nitrogen must be installed".to_owned()); + log_line!("nitrogen must be installed"); return Ok(()); } let Some(fileinfo) = tab.path_content.selected() else { @@ -1013,7 +1013,7 @@ impl EventAction { /// Add a song or a folder to MOC playlist. Start it first... pub fn mocp_add_to_playlist(tab: &Tab) -> Result<()> { if !is_program_in_path(MOCP) { - write_log_line("mocp isn't installed".to_owned()); + log_line!("mocp isn't installed"); return Ok(()); } Mocp::add_to_playlist(tab) @@ -1021,7 +1021,7 @@ impl EventAction { pub fn mocp_clear_playlist() -> Result<()> { if !is_program_in_path(MOCP) { - write_log_line("mocp isn't installed".to_owned()); + log_line!("mocp isn't installed"); return Ok(()); } Mocp::clear() @@ -1031,7 +1031,7 @@ impl EventAction { pub fn mocp_go_to_song(status: &mut Status) -> Result<()> { let tab = status.selected(); if !is_program_in_path(MOCP) { - write_log_line("mocp isn't installed".to_owned()); + log_line!("mocp isn't installed"); return Ok(()); } Mocp::go_to_song(tab)?; @@ -1044,7 +1044,7 @@ impl EventAction { /// Then toggle play/pause pub fn mocp_toggle_pause(status: &mut Status) -> Result<()> { if !is_program_in_path(MOCP) { - write_log_line("mocp isn't installed".to_owned()); + log_line!("mocp isn't installed"); return Ok(()); } Mocp::toggle_pause(status) @@ -1053,7 +1053,7 @@ impl EventAction { /// Skip to the next song in MOC pub fn mocp_next() -> Result<()> { if !is_program_in_path(MOCP) { - write_log_line("mocp isn't installed".to_owned()); + log_line!("mocp isn't installed"); return Ok(()); } Mocp::next() @@ -1062,7 +1062,7 @@ impl EventAction { /// Go to the previous song in MOC pub fn mocp_previous() -> Result<()> { if !is_program_in_path(MOCP) { - write_log_line("mocp isn't installed".to_owned()); + log_line!("mocp isn't installed"); return Ok(()); } Mocp::previous() @@ -1109,10 +1109,7 @@ impl NodeCreation { log::info!("root_path: {root_path:?}"); let path = root_path.join(sanitize_filename::sanitize(tab.input.string())); if path.exists() { - write_log_line(format!( - "{self} {path} already exists", - path = path.display() - )); + log_line!("{self} {path} already exists", path = path.display()); } else { match self { Self::Newdir => { @@ -1122,8 +1119,7 @@ impl NodeCreation { fs::File::create(&path)?; } } - let log_line = format!("Created new {self}: {path}", path = path.display()); - write_log_line(log_line); + log_line!("Created new {self}: {path}", path = path.display()); } tab.refresh_view() } @@ -1177,8 +1173,7 @@ impl LeaveMode { if let Some(path_str) = status.selected_non_mut().path_content_str() { let p = path::PathBuf::from(path_str); status.marks.new_mark(*ch, &p)?; - let log_line = format!("Saved mark {ch} -> {p}", p = p.display()); - write_log_line(log_line); + log_line!("Saved mark {ch} -> {p}", p = p.display()); } status.selected().window.reset(len); status.selected().input.reset(); @@ -1221,8 +1216,7 @@ impl LeaveMode { Status::set_permissions(path, permissions)? } status.flagged.clear(); - let log_line = format!("Changed permissions to {input_permission}"); - write_log_line(log_line); + log_line!("Changed permissions to {input_permission}"); } status.selected().refresh_view()?; status.reset_tabs_view() @@ -1318,12 +1312,11 @@ impl LeaveMode { original_path.display(), new_path.display() ); - let log_line = format!( + log_line!( "renaming: original: {} - new: {}", original_path.display(), new_path.display() ); - write_log_line(log_line); fs::rename(original_path, new_path)?; } @@ -1563,9 +1556,8 @@ impl LeaveMode { let first_arg = &format!("{username}@{hostname}:{remote_path}"); let command_output = execute_and_capture_output(SSHFS_EXECUTABLE, &[first_arg, current_path]); - let log_line = format!("{SSHFS_EXECUTABLE} output {command_output:?}"); - info!("{log_line}"); - write_log_line(log_line); + info!("{SSHFS_EXECUTABLE} output {command_output:?}"); + log_line!("{SSHFS_EXECUTABLE} output {command_output:?}"); Ok(()) } } diff --git a/src/log.rs b/src/log.rs index 081918d0..c85f7420 100644 --- a/src/log.rs +++ b/src/log.rs @@ -93,3 +93,12 @@ where log::info!(target: "special", "{log_line}"); write_last_log_line(log_line); } + +#[macro_export] +macro_rules! log_line { + ($($arg:tt)+) => ( + $crate::log::write_log_line( + format!($($arg)+) + ) + ); +} diff --git a/src/marks.rs b/src/marks.rs index 0d5a8ce4..87f63118 100644 --- a/src/marks.rs +++ b/src/marks.rs @@ -7,7 +7,7 @@ use log::info; use crate::constant_strings_paths::MARKS_FILEPATH; use crate::impl_selectable_content; -use crate::log::write_log_line; +use crate::log_line; use crate::utils::read_lines; /// Holds the marks created by the user. @@ -82,7 +82,7 @@ impl Marks { fn parse_line(line: Result) -> Result<(char, PathBuf)> { let line = line?; let sp: Vec<&str> = line.split(':').collect(); - if sp.len() <= 1 { + if sp.len() != 2 { return Err(anyhow!("marks: parse_line: Invalid mark line: {line}")); } if let Some(ch) = sp[0].chars().next() { @@ -100,30 +100,31 @@ impl Marks { /// If an update is done, the marks are saved again. pub fn new_mark(&mut self, ch: char, path: &Path) -> Result<()> { if ch == ':' { - let log_line = "new mark - ':' can't be used as a mark"; - write_log_line(log_line.to_owned()); + log_line!("new mark - ':' can't be used as a mark"); return Ok(()); } if self.used_chars.contains(&ch) { - let mut found_index = None; - for (index, (k, _)) in self.content.iter().enumerate() { - if *k == ch { - found_index = Some(index); - break; - } - } - let Some(found_index) = found_index else { - return Ok(()); - }; - self.content[found_index] = (ch, path.to_path_buf()); + self.update_mark(ch, path); } else { self.content.push((ch, path.to_path_buf())) } - let log_line = format!("Saved mark {ch} -> {p}", p = path.display()); - write_log_line(log_line); + self.save_marks()?; + log_line!("Saved mark {ch} -> {p}", p = path.display()); + Ok(()) + } - self.save_marks() + fn update_mark(&mut self, ch: char, path: &Path) { + let mut found_index = None; + for (index, (k, _)) in self.content.iter().enumerate() { + if *k == ch { + found_index = Some(index); + break; + } + } + if let Some(found_index) = found_index { + self.content[found_index] = (ch, path.to_path_buf()) + } } fn save_marks(&self) -> Result<()> { diff --git a/src/opener.rs b/src/opener.rs index 565f3d0d..1b77ad74 100644 --- a/src/opener.rs +++ b/src/opener.rs @@ -15,7 +15,7 @@ use crate::constant_strings_paths::{ }; use crate::decompress::{decompress_gz, decompress_xz, decompress_zip}; use crate::fileinfo::extract_extension; -use crate::log::write_log_line; +use crate::log_line; use crate::utils::is_program_in_path; /// Different kind of extensions for default openers. @@ -433,8 +433,7 @@ pub fn execute_in_child + fmt::Debug>( args: &[&str], ) -> Result { info!("execute_in_child. executable: {exe:?}, arguments: {args:?}"); - let log_line = format!("Execute: {exe:?}, arguments: {args:?}"); - write_log_line(log_line); + log_line!("Execute: {exe:?}, arguments: {args:?}"); Ok(Command::new(exe).args(args).spawn()?) } diff --git a/src/password.rs b/src/password.rs index 24ec0a7f..3f98025f 100644 --- a/src/password.rs +++ b/src/password.rs @@ -4,7 +4,7 @@ use std::process::{Command, Stdio}; use anyhow::{Context, Result}; use log::info; -use crate::log::write_log_line; +use crate::log_line; use crate::utils::current_username; /// Different kind of password @@ -132,8 +132,7 @@ where P: AsRef + std::fmt::Debug, { info!("sudo_with_password {args:?} CWD {path:?}"); - let log_line = format!("running sudo command with password. args: {args:?}, CWD: {path:?}"); - write_log_line(log_line); + log_line!("running sudo command with password. args: {args:?}, CWD: {path:?}"); let mut child = new_sudo_command_awaiting_password(args, path)?; inject_password(password, &mut child)?; let output = child.wait_with_output()?; @@ -165,8 +164,7 @@ where S: AsRef + std::fmt::Debug, { info!("running sudo {:?}", args); - let log_line = format!("running sudo command. {args:?}"); - write_log_line(log_line); + log_line!("running sudo command. {args:?}"); let child = new_sudo_command_passwordless(args)?; let output = child.wait_with_output()?; Ok(( diff --git a/src/shell_menu.rs b/src/shell_menu.rs index 05fed469..36d17ba6 100644 --- a/src/shell_menu.rs +++ b/src/shell_menu.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use crate::impl_selectable_content; -use crate::log::write_log_line; +use crate::log_line; use crate::opener::{execute_in_child_without_output, execute_in_child_without_output_with_path}; use crate::status::Status; use crate::utils::is_program_in_path; @@ -59,8 +59,7 @@ impl ShellMenu { } else { Self::simple(status, name.as_str())? }; - let log_line = format!("Executed {name}"); - write_log_line(log_line); + log_line!("Executed {name}"); Ok(()) } diff --git a/src/status.rs b/src/status.rs index 69f4bdae..063118d9 100644 --- a/src/status.rs +++ b/src/status.rs @@ -23,7 +23,7 @@ use crate::cryptsetup::{BlockDeviceAction, CryptoDeviceOpener}; use crate::fileinfo::FileKind; use crate::flagged::Flagged; use crate::iso::IsoDevice; -use crate::log::write_log_line; +use crate::log_line; use crate::marks::Marks; use crate::mode::{InputSimple, Mode, NeedConfirmation}; use crate::mount_help::MountHelper; @@ -728,8 +728,7 @@ impl Status { if iso_device.mount(¤t_username()?, &mut self.password_holder)? { info!("iso mounter mounted {iso_device:?}"); - let log_line = format!("iso : {}", iso_device.as_string()?,); - write_log_line(log_line); + log_line!("iso : {}", iso_device.as_string()?); let path = iso_device.mountpoints.clone().context("no mount point")?; self.selected().set_pathcontent(&path)?; }; @@ -942,8 +941,7 @@ impl Status { std::fs::remove_file(pathbuf)?; } } - let log_line = format!("Deleted {nb} flagged files"); - write_log_line(log_line); + log_line!("Deleted {nb} flagged files"); self.selected().reset_mode(); self.clear_flags_and_reset_view()?; self.refresh_status() diff --git a/src/trash.rs b/src/trash.rs index 33329012..77adaf06 100644 --- a/src/trash.rs +++ b/src/trash.rs @@ -10,7 +10,7 @@ use rand::{thread_rng, Rng}; use crate::constant_strings_paths::{TRASH_FOLDER_FILES, TRASH_FOLDER_INFO, TRASH_INFO_EXTENSION}; use crate::impl_selectable_content; -use crate::log::write_log_line; +use crate::log_line; use crate::utils::read_lines; const TRASHINFO_DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S"; @@ -305,8 +305,7 @@ impl Trash { fn log_trash_add(origin: &Path, dest_file_name: &str) { info!("moved to trash {:?} -> {:?}", origin, dest_file_name); - let log_line = format!("moved to trash {:?} -> {:?}", origin, dest_file_name); - write_log_line(log_line); + log_line!("moved to trash {:?} -> {:?}", origin, dest_file_name); } /// Empty the trash, removing all the files and the trashinfo. @@ -331,8 +330,7 @@ impl Trash { } fn log_trash_empty(number_of_elements: usize) { - let log_line = format!("Emptied the trash: {number_of_elements} files permanently deleted"); - write_log_line(log_line); + log_line!("Emptied the trash: {number_of_elements} files permanently deleted"); info!("Emptied the trash: {number_of_elements} files permanently deleted"); } @@ -390,8 +388,7 @@ impl Trash { } fn log_trash_restore(origin: &Path) { - let log_line = format!("Trash restored: {origin}", origin = origin.display()); - write_log_line(log_line); + log_line!("Trash restored: {origin}", origin = origin.display()); } /// Deletes a file permanently from the trash. @@ -412,11 +409,10 @@ impl Trash { } fn log_trash_remove(trashed_file_content: &Path) { - let log_line = format!( + log_line!( "Trash removed: {trashed_file_content}", trashed_file_content = trashed_file_content.display() ); - write_log_line(log_line); } } From 4ae9f64a5d8ab81d2ce640352769c0c07e304d51 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 20:47:58 +0100 Subject: [PATCH 099/168] simplify cryptdevices update --- src/cryptsetup.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/cryptsetup.rs b/src/cryptsetup.rs index f72b352a..b6900cbe 100644 --- a/src/cryptsetup.rs +++ b/src/cryptsetup.rs @@ -35,16 +35,6 @@ fn get_devices() -> Result { Ok(String::from_utf8(output.stdout)?) } -/// Parse the output of an lsblk detailed output and filter the line -/// Containing "crypto" aka Luks encrypted crypto devices -fn filter_crypto_devices_lines(output: String, key: &str) -> Vec { - output - .lines() - .filter(|line| line.contains(key)) - .map(|line| line.into()) - .collect() -} - /// True iff `lsblk` and `cryptsetup` are in path. /// Nothing here can be done without those programs. pub fn lsblk_and_cryptsetup_installed() -> bool { @@ -320,9 +310,10 @@ pub struct CryptoDeviceOpener { impl CryptoDeviceOpener { /// Updates itself from the output of cryptsetup. pub fn update(&mut self) -> Result<()> { - self.content = filter_crypto_devices_lines(get_devices()?, "crypto") - .iter() - .map(|line| CryptoDevice::from_line(line)) + self.content = get_devices()? + .lines() + .filter(|line| line.contains("crypto")) + .map(CryptoDevice::from_line) .filter_map(|r| r.ok()) .collect(); self.index = 0; From c6d453b9992ad87cde75b2c3c039d512a59ed272 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 21:00:22 +0100 Subject: [PATCH 100/168] improve cryptsetup mountpoint --- src/cryptsetup.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cryptsetup.rs b/src/cryptsetup.rs index b6900cbe..4f9c09b5 100644 --- a/src/cryptsetup.rs +++ b/src/cryptsetup.rs @@ -2,7 +2,7 @@ use std::process::{Command, Stdio}; use anyhow::{anyhow, Context, Result}; use log::info; -use sysinfo::{DiskExt, System, SystemExt}; +use sysinfo::{DiskExt, RefreshKind, System, SystemExt}; use crate::constant_strings_paths::{CRYPTSETUP, LSBLK}; use crate::impl_selectable_content; @@ -102,14 +102,13 @@ impl CryptoDevice { } pub fn mount_point(&self) -> Option { - let system_info = System::new_all(); - system_info + System::new_with_specifics(RefreshKind::new().with_disks()) .disks() .iter() .map(|d| d.mount_point()) .map(|p| p.to_str()) - .filter(|s| s.is_some()) - .map(|s| s.unwrap().to_owned()) + .flatten() + .map(|s| s.to_owned()) .find(|s| s.contains(&self.uuid)) } From 51cbf2f733a1c1e6352f0643829c61dcd693d367 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 21:33:40 +0100 Subject: [PATCH 101/168] clippy --- src/cryptsetup.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cryptsetup.rs b/src/cryptsetup.rs index 4f9c09b5..59b37c4e 100644 --- a/src/cryptsetup.rs +++ b/src/cryptsetup.rs @@ -106,8 +106,7 @@ impl CryptoDevice { .disks() .iter() .map(|d| d.mount_point()) - .map(|p| p.to_str()) - .flatten() + .filter_map(|p| p.to_str()) .map(|s| s.to_owned()) .find(|s| s.contains(&self.uuid)) } From b5b78246398c0cbded52aaa5373c9e09296679b3 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 21:35:54 +0100 Subject: [PATCH 102/168] don't crash the app if skim can't be initialized. Improve skim --- src/skim.rs | 21 ++++++++++----------- src/status.rs | 50 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/skim.rs b/src/skim.rs index 5a0c0121..ba84a13e 100644 --- a/src/skim.rs +++ b/src/skim.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result}; use skim::prelude::*; use tuikit::term::Term; @@ -10,24 +11,22 @@ use crate::utils::is_program_in_path; /// It's a simple wrapper around `Skim` which is used to simplify the interface. pub struct Skimer { skim: Skim, - previewer: String, - file_matcher: String, + previewer: &'static str, + file_matcher: &'static str, } impl Skimer { /// Creates a new `Skimer` instance. /// `term` is an `Arc` clone of the default term. /// It tries to preview with `bat`, but choose `cat` if it can't. - pub fn new(term: Arc) -> Self { - Self { + pub fn new(term: Arc) -> Result { + Ok(Self { skim: Skim::new_from_term(term), previewer: pick_first_installed(&[BAT_EXECUTABLE, CAT_EXECUTABLE]) - .expect("Skimer new: at least a previewer should be installed") - .to_owned(), + .context("Neither bat nor cat are installed")?, file_matcher: pick_first_installed(&[RG_EXECUTABLE, GREP_EXECUTABLE]) - .expect("Skimer new: at least a line matcher should be installed") - .to_owned(), - } + .context("Neither ripgrep nor grep are installed")?, + }) } /// Call skim on its term. @@ -38,7 +37,7 @@ impl Skimer { pub fn search_filename(&self, path_str: &str) -> Vec> { let Some(output) = self.skim - .run_internal(None, path_str.to_owned(), Some(&self.previewer), None) + .run_internal(None, path_str.to_owned(), Some(self.previewer), None) else { return vec![]; }; @@ -69,7 +68,7 @@ impl Skimer { /// Search in a text content, splitted by line. /// Returns the selected line. - pub fn search_in_text(&self, text: String) -> Vec> { + pub fn search_in_text(&self, text: &str) -> Vec> { let (tx_item, rx_item): (SkimItemSender, SkimItemReceiver) = unbounded(); for line in text.lines().rev() { let _ = tx_item.send(Arc::new(StringWrapper { diff --git a/src/status.rs b/src/status.rs index 063118d9..9aae2ded 100644 --- a/src/status.rs +++ b/src/status.rs @@ -64,7 +64,7 @@ pub struct Status { pub marks: Marks, /// terminal pub term: Arc, - skimer: Option, + skimer: Option>, /// do we display one or two tabs ? pub dual_pane: bool, pub system_info: System, @@ -278,7 +278,12 @@ impl Status { /// It calls skim, reads its output, then update the tab content. pub fn skim_output_to_tab(&mut self) -> Result<()> { self.skim_init(); - let Some(skimer) = &self.skimer else { + let _ = self._skim_output_to_tab(); + self.drop_skim() + } + + fn _skim_output_to_tab(&mut self) -> Result<()> { + let Some(Ok(skimer)) = &self.skimer else { return Ok(()); }; let skim = skimer.search_filename( @@ -289,9 +294,7 @@ impl Status { let Some(output) = skim.first() else { return Ok(()); }; - self._update_tab_from_skim_output(output)?; - self.skimer = None; - Ok(()) + self._update_tab_from_skim_output(output) } /// Replace the tab content with the first result of skim. @@ -299,7 +302,12 @@ impl Status { /// The output is splited at `:` since we only care about the path, not the line number. pub fn skim_line_output_to_tab(&mut self) -> Result<()> { self.skim_init(); - let Some(skimer) = &self.skimer else { + let _ = self._skim_line_output_to_tab(); + self.drop_skim() + } + + fn _skim_line_output_to_tab(&mut self) -> Result<()> { + let Some(Ok(skimer)) = &self.skimer else { return Ok(()); }; let skim = skimer.search_line_in_file( @@ -310,9 +318,7 @@ impl Status { let Some(output) = skim.first() else { return Ok(()); }; - self._update_tab_from_skim_line_output(output)?; - self.skimer = None; - Ok(()) + self._update_tab_from_skim_line_output(output) } /// Run a command directly from help. @@ -320,27 +326,30 @@ impl Status { /// If the result can't be parsed, nothing is done. pub fn skim_find_keybinding(&mut self) -> Result<()> { self.skim_init(); - let Some(skimer) = &mut self.skimer else { - return Ok(()); + self._skim_find_keybinding(); + self.drop_skim() + } + + fn _skim_find_keybinding(&mut self) { + let Some(Ok(skimer)) = &mut self.skimer else { + return; }; - let skim = skimer.search_in_text(self.help.clone()); + let skim = skimer.search_in_text(&self.help); let Some(output) = skim.first() else { - return Ok(()); + return; }; let line = output.output().into_owned(); let Some(keybind) = line.split(':').next() else { - return Ok(()); + return; }; let Some(keyname) = parse_keyname(keybind) else { - return Ok(()); + return; }; let Some(key) = from_keyname(&keyname) else { - return Ok(()); + return; }; let event = Event::Key(key); let _ = self.term.send_event(event); - self.skimer = None; - Ok(()) } fn _update_tab_from_skim_line_output(&mut self, skim_output: &Arc) -> Result<()> { @@ -373,6 +382,11 @@ impl Status { Ok(()) } + fn drop_skim(&mut self) -> Result<()> { + self.skimer = None; + Ok(()) + } + /// Returns a vector of path of files which are both flagged and in current /// directory. /// It's necessary since the user may have flagged files OUTSIDE of current From 75f487d879adb12d906ab88bd70e372fe03b0f74 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 1 Nov 2023 21:52:45 +0100 Subject: [PATCH 103/168] ensure deduplication of completion --- src/completion.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index c3fcf75a..fca4559f 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -74,11 +74,13 @@ impl Completion { fn update(&mut self, proposals: Vec) { self.index = 0; self.proposals = proposals; + self.proposals.dedup() } fn extend(&mut self, proposals: &[String]) { self.index = 0; - self.proposals.extend_from_slice(proposals) + self.proposals.extend_from_slice(proposals); + self.proposals.dedup() } /// Empty the proposals `Vec`. @@ -98,7 +100,6 @@ impl Completion { } self.extend_absolute_paths(&parent, &last_name); self.extend_relative_paths(current_path, &last_name); - self.proposals.dedup(); Ok(()) } From 6fdbccecd83f573e43e874a59713680451d9b991 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 2 Nov 2023 07:52:13 +0100 Subject: [PATCH 104/168] Only store a color and an effect in trees instead of a full attr. --- src/fileinfo.rs | 48 +++++++++++++++++++++++++++++++++------------ src/term_manager.rs | 5 +++-- src/tree.rs | 19 ++++++++++-------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 436ac141..f882ed4b 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -551,23 +551,45 @@ fn fileinfo_color(fileinfo: &FileInfo) -> Color { } } +/// Holds a `tuikit::attr::Color` and a `tuikit::attr::Effect` +/// Both are used to print the file. +/// When printing we still need to know if the file is flagged, +/// which may change the `tuikit::attr::Effect`. +#[derive(Clone, Debug)] +pub struct ColorEffect { + color: Color, + pub effect: Effect, +} + +impl ColorEffect { + /// Calculates a color and an effect from `fm::file_info::FileInfo`. + pub fn new(fileinfo: &FileInfo) -> ColorEffect { + let color = fileinfo_color(fileinfo); + + let effect = if fileinfo.is_selected { + Effect::REVERSE + } else { + Effect::empty() + }; + + Self { color, effect } + } + + /// Makes a new `tuikit::attr::Attr` where `bg` is default. + pub fn attr(&self) -> Attr { + Attr { + fg: self.color, + bg: Color::default(), + effect: self.effect, + } + } +} + /// Associates a filetype to `tuikit::prelude::Attr` : fg color, bg color and /// effect. /// Selected file is reversed. pub fn fileinfo_attr(fileinfo: &FileInfo) -> Attr { - let fg = fileinfo_color(fileinfo); - - let effect = if fileinfo.is_selected { - Effect::REVERSE - } else { - Effect::empty() - }; - - Attr { - fg, - bg: Color::default(), - effect, - } + ColorEffect::new(fileinfo).attr() } /// True if the file isn't hidden. diff --git a/src/term_manager.rs b/src/term_manager.rs index 4b097d4f..ce3c8c1f 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -400,7 +400,7 @@ impl<'a> WinMain<'a> { for (i, (metadata, prefix, colored_string)) in tab.directory.window(top, bottom, len) { let row = i + ContentWindow::WINDOW_MARGIN_TOP - top; - let mut attr = colored_string.attr; + let mut attr = colored_string.color_effect.attr(); if status.flagged.contains(&colored_string.path) { attr.effect |= Effect::BOLD; canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; @@ -488,11 +488,12 @@ impl<'a> WinMain<'a> { { let row = calc_line_row(i, window); let col = canvas.print(row, line_number_width, prefix)?; + canvas.print_with_attr( row, line_number_width + col + 1, &colored_string.text, - colored_string.attr, + colored_string.color_effect.attr(), )?; } } diff --git a/src/tree.rs b/src/tree.rs index e6818b81..a6591911 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,9 +1,8 @@ use std::path::Path; use anyhow::{Context, Result}; -use tuikit::attr::Attr; -use crate::fileinfo::{fileinfo_attr, files_collection, FileInfo, FileKind}; +use crate::fileinfo::{files_collection, ColorEffect, FileInfo, FileKind}; use crate::filter::FilterKind; use crate::preview::ColoredTriplet; use crate::sort::SortKind; @@ -16,14 +15,18 @@ pub struct ColoredString { /// A text to be printed. In most case, it should be a filename. pub text: String, /// A tuikit::attr::Attr (fg, bg, effect) to enhance the text. - pub attr: Attr, + pub color_effect: ColorEffect, /// The complete path of this string. pub path: std::path::PathBuf, } impl ColoredString { - fn new(text: String, attr: Attr, path: std::path::PathBuf) -> Self { - Self { text, attr, path } + fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { + Self { + text, + color_effect, + path, + } } fn from_node(current_node: &Node) -> Self { @@ -36,7 +39,7 @@ impl ColoredString { } else { current_node.filename() }; - Self::new(text, current_node.attr(), current_node.filepath()) + Self::new(text, current_node.color_effect(), current_node.filepath()) } } @@ -63,8 +66,8 @@ impl Node { self.fileinfo.path.to_owned() } - fn attr(&self) -> Attr { - fileinfo_attr(&self.fileinfo) + fn color_effect(&self) -> ColorEffect { + ColorEffect::new(&self.fileinfo) } fn select(&mut self) { From 26f78b695397760c7ad03ca52ecc4e6429a20f24 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 2 Nov 2023 22:41:12 +0100 Subject: [PATCH 105/168] lazy_static load of colors. They are configurable again... --- config_files/fm/config.yaml | 19 +++++- src/config.rs | 113 +++++++++++++++++++++++++++++++++++- src/fileinfo.rs | 15 ++--- 3 files changed, 136 insertions(+), 11 deletions(-) diff --git a/config_files/fm/config.yaml b/config_files/fm/config.yaml index b5719f8e..917a3cc4 100644 --- a/config_files/fm/config.yaml +++ b/config_files/fm/config.yaml @@ -1,6 +1,23 @@ -# the terminal must be installed +# the terminal emulator, which must be accessible from $PATH terminal: st +# configurable colors +colors: + # you can define an ANSI color for any kind of file except "normal" files. + # colors for normal files are calculated on the fly. + # List of allowed colors: + # white, black, red, green, blue, yellow, cyan, magenta + # light_white, light_black, light_red, light_green, light_blue, light_yellow, light_cyan, light_magenta + directory: red + block: yellow + char: green + fifo: blue + socket: cyan + symlink: magenta # keybindings +# You can bind any key to any action. +# List of valid actions is accessible from `help` (default key H) and from the readme.md file. +# Invalid actions are skipped. +# AltPageUp can't be bound, it is reserved for internal use. keys: 'esc': ResetMode 'up': MoveUp diff --git a/src/config.rs b/src/config.rs index 3909cb91..fa2b48c8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,9 +2,9 @@ use std::{fs::File, path}; use anyhow::Result; use serde_yaml; -// use tuikit::attr::Color; +use tuikit::attr::Color; -use crate::constant_strings_paths::DEFAULT_TERMINAL_APPLICATION; +use crate::constant_strings_paths::{CONFIG_PATH, DEFAULT_TERMINAL_APPLICATION}; use crate::keybindings::Bindings; use crate::utils::is_program_in_path; @@ -38,7 +38,6 @@ impl Settings { } } } - /// Holds every configurable aspect of the application. /// All attributes are hardcoded then updated from optional values /// of the config file. @@ -104,3 +103,111 @@ pub fn load_config(path: &str) -> Result { let _ = config.update_from_config(&yaml); Ok(config) } + +/// Convert a string color into a `tuikit::Color` instance. +pub fn str_to_tuikit(color: S) -> Color +where + S: AsRef, +{ + match color.as_ref() { + "white" => Color::WHITE, + "red" => Color::RED, + "green" => Color::GREEN, + "blue" => Color::BLUE, + "yellow" => Color::YELLOW, + "cyan" => Color::CYAN, + "magenta" => Color::MAGENTA, + "black" => Color::BLACK, + "light_white" => Color::LIGHT_WHITE, + "light_red" => Color::LIGHT_RED, + "light_green" => Color::LIGHT_GREEN, + "light_blue" => Color::LIGHT_BLUE, + "light_yellow" => Color::LIGHT_YELLOW, + "light_cyan" => Color::LIGHT_CYAN, + "light_magenta" => Color::LIGHT_MAGENTA, + "light_black" => Color::LIGHT_BLACK, + _ => Color::default(), + } +} + +macro_rules! update_attribute { + ($self_attr:expr, $yaml:ident, $key:expr) => { + if let Some(attr) = read_yaml_value($yaml, $key) { + $self_attr = str_to_tuikit(attr); + } + }; +} +fn read_yaml_value(yaml: &serde_yaml::value::Value, key: &str) -> Option { + yaml[key].as_str().map(|s| s.to_string()) +} + +/// Holds configurable colors for every kind of file. +/// "Normal" files are displayed with a different color by extension. +#[derive(Debug, Clone)] +pub struct Colors { + /// Color for `directory` files. + pub directory: Color, + /// Color for `block` files. + pub block: Color, + /// Color for `char` files. + pub char: Color, + /// Color for `fifo` files. + pub fifo: Color, + /// Color for `socket` files. + pub socket: Color, + /// Color for `symlink` files. + pub symlink: Color, + /// Color for broken `symlink` files. + pub broken: Color, +} + +impl Colors { + fn new() -> Self { + Self { + directory: Color::RED, + block: Color::YELLOW, + char: Color::GREEN, + fifo: Color::BLUE, + socket: Color::CYAN, + symlink: Color::MAGENTA, + broken: Color::WHITE, + } + } + + /// Update every color from a yaml value (read from the config file). + pub fn update_from_config(&mut self, yaml: &serde_yaml::value::Value) { + update_attribute!(self.directory, yaml, "directory"); + update_attribute!(self.block, yaml, "block"); + update_attribute!(self.char, yaml, "char"); + update_attribute!(self.fifo, yaml, "fifo"); + update_attribute!(self.socket, yaml, "socket"); + update_attribute!(self.symlink, yaml, "symlink"); + update_attribute!(self.broken, yaml, "broken"); + } +} + +impl Default for Colors { + fn default() -> Self { + Self::new() + } +} + +lazy_static::lazy_static! { + /// Colors read from the config file. + /// We define a colors for every kind of file except normal files. + /// Colors for normal files are calculated from their extension and + /// are greens or blues. + /// + /// Colors are setup on start and never change afterwards. + /// Since many functions use colors for formating, using `lazy_static` + /// avoids to pass them everytime. + pub static ref COLORS: Colors = { + let mut colors = Colors::default(); + if let Ok(file) = File::open(path::Path::new(&shellexpand::tilde(CONFIG_PATH).to_string())) { + if let Ok(yaml) = serde_yaml::from_reader::(file) { + colors.update_from_config(&yaml["colors"]); + }; + }; + colors + }; +} diff --git a/src/fileinfo.rs b/src/fileinfo.rs index f882ed4b..a9f03cad 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -10,6 +10,7 @@ use log::info; use tuikit::prelude::{Attr, Color, Effect}; use crate::colors::extension_color; +use crate::config::COLORS; use crate::constant_strings_paths::PERMISSIONS_STR; use crate::filter::FilterKind; use crate::git::git; @@ -540,13 +541,13 @@ impl_selectable_content!(FileInfo, PathContent); fn fileinfo_color(fileinfo: &FileInfo) -> Color { match fileinfo.file_kind { - FileKind::Directory => Color::RED, - FileKind::BlockDevice => Color::YELLOW, - FileKind::CharDevice => Color::GREEN, - FileKind::Fifo => Color::BLUE, - FileKind::Socket => Color::CYAN, - FileKind::SymbolicLink(true) => Color::MAGENTA, - FileKind::SymbolicLink(false) => Color::WHITE, + FileKind::Directory => COLORS.directory, + FileKind::BlockDevice => COLORS.block, + FileKind::CharDevice => COLORS.char, + FileKind::Fifo => COLORS.fifo, + FileKind::Socket => COLORS.socket, + FileKind::SymbolicLink(true) => COLORS.symlink, + FileKind::SymbolicLink(false) => COLORS.broken, _ => extension_color(&fileinfo.extension), } } From e14befd23a419a6501899fd93804487e718767c2 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 2 Nov 2023 22:45:43 +0100 Subject: [PATCH 106/168] dev --- development.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/development.md b/development.md index ec290089..59b59a18 100644 --- a/development.md +++ b/development.md @@ -595,7 +595,11 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] list, mount, unmount mtp mount points - [x] bulk, skim & removable are `None` until first use. - [x] remove dependencies -- [x] remove colors configuration. Calculate every color for every extension +- [x] complete refactor of many files. +- [x] Use `lazy_static` to load `Colors` configuration. Don't use a cache. Calculate every color for every extension +- [ ] allow rgb colors in config file +- [ ] allow rgb palette for normal file colors +- [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself From 19e659849bfdc2584dab1bc6a41423e06ea8879c Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 3 Nov 2023 21:04:00 +0100 Subject: [PATCH 107/168] allow rgb colors in config file --- config_files/fm/config.yaml | 4 +++- development.md | 2 +- src/config.rs | 26 +++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/config_files/fm/config.yaml b/config_files/fm/config.yaml index 917a3cc4..c5b69c51 100644 --- a/config_files/fm/config.yaml +++ b/config_files/fm/config.yaml @@ -2,17 +2,19 @@ terminal: st # configurable colors colors: - # you can define an ANSI color for any kind of file except "normal" files. + # you can define an ANSI color or an rgb color for any kind of file except "normal" files. # colors for normal files are calculated on the fly. # List of allowed colors: # white, black, red, green, blue, yellow, cyan, magenta # light_white, light_black, light_red, light_green, light_blue, light_yellow, light_cyan, light_magenta + # Allowed Format for rgb color : `rgb(r, g, b)` where r, g and b are integers between 0 and 255 included. directory: red block: yellow char: green fifo: blue socket: cyan symlink: magenta + broken: light_magenta # keybindings # You can bind any key to any action. # List of valid actions is accessible from `help` (default key H) and from the readme.md file. diff --git a/development.md b/development.md index 59b59a18..0faa85d1 100644 --- a/development.md +++ b/development.md @@ -597,7 +597,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] remove dependencies - [x] complete refactor of many files. - [x] Use `lazy_static` to load `Colors` configuration. Don't use a cache. Calculate every color for every extension -- [ ] allow rgb colors in config file +- [x] allow rgb colors in config file - [ ] allow rgb palette for normal file colors - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). diff --git a/src/config.rs b/src/config.rs index fa2b48c8..77160162 100644 --- a/src/config.rs +++ b/src/config.rs @@ -126,10 +126,34 @@ where "light_cyan" => Color::LIGHT_CYAN, "light_magenta" => Color::LIGHT_MAGENTA, "light_black" => Color::LIGHT_BLACK, - _ => Color::default(), + color => parse_rgb_color(color), } } +/// Tries to parse an unknown color into a `Color::Rgb(u8, u8, u8)` +/// rgb format should never fail. +/// Other formats are unknown. +/// rgb( 123, 78, 0) -> Color::Rgb(123, 78, 0) +/// #FF00FF -> Color::default() +/// Unreadable colors are replaced by `Color::default()` which is white. +fn parse_rgb_color(color: &str) -> Color { + let color = color.to_lowercase(); + if color.starts_with("rgb(") && color.ends_with(')') { + let triplet: Vec = color + .replace("rgb(", "") + .replace([')', ' '], "") + .trim() + .split(',') + .filter_map(|s| s.parse().ok()) + .collect(); + if triplet.len() == 3 { + return Color::Rgb(triplet[0], triplet[1], triplet[2]); + } + } + + Color::default() +} + macro_rules! update_attribute { ($self_attr:expr, $yaml:ident, $key:expr) => { if let Some(attr) = read_yaml_value($yaml, $key) { From 483537582de727aabf8493432622ff8650c8b54c Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 3 Nov 2023 21:24:27 +0100 Subject: [PATCH 108/168] FIX: can't read filename from / ... which crashes the app. --- development.md | 1 + src/fileinfo.rs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/development.md b/development.md index 0faa85d1..9ddd9390 100644 --- a/development.md +++ b/development.md @@ -598,6 +598,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] complete refactor of many files. - [x] Use `lazy_static` to load `Colors` configuration. Don't use a cache. Calculate every color for every extension - [x] allow rgb colors in config file +- [x] FIX: can't read filename from / ... which crashes the app. - [ ] allow rgb palette for normal file colors - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). diff --git a/src/fileinfo.rs b/src/fileinfo.rs index a9f03cad..376981ef 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -3,7 +3,7 @@ use std::iter::Enumerate; use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::path; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use chrono::offset::Local; use chrono::DateTime; use log::info; @@ -606,9 +606,9 @@ pub fn is_not_hidden(entry: &DirEntry) -> Result { fn extract_filename(path: &path::Path) -> Result { Ok(path .file_name() - .context("from path: couldn't read filename")? + .unwrap_or_default() .to_str() - .context("from path: couldn't parse filename")? + .context(format!("Couldn't read filename of {p}", p = path.display()))? .to_owned()) } From bf067abc2bd12e5397a7d180dae6282d62e94ec4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 3 Nov 2023 21:27:37 +0100 Subject: [PATCH 109/168] FIX: exploring root folder leads to wrong first line display. --- development.md | 1 + src/fileinfo.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/development.md b/development.md index 9ddd9390..0500cca1 100644 --- a/development.md +++ b/development.md @@ -599,6 +599,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Use `lazy_static` to load `Colors` configuration. Don't use a cache. Calculate every color for every extension - [x] allow rgb colors in config file - [x] FIX: can't read filename from / ... which crashes the app. +- [x] FIX: exploring root folder leads to wrong first line display. - [ ] allow rgb palette for normal file colors - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 376981ef..f4156c33 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -3,7 +3,7 @@ use std::iter::Enumerate; use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::path; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use chrono::offset::Local; use chrono::DateTime; use log::info; @@ -728,6 +728,9 @@ const MAX_PATH_ELEM_SIZE: usize = 50; /// Shorten a path to be displayed in [`MAX_PATH_ELEM_SIZE`] chars or less. /// Each element of the path is shortened if needed. pub fn shorten_path(path: &path::Path, size: Option) -> Result { + if path == path::Path::new("/") { + return Ok("".to_owned()); + } let size = match size { Some(size) => size, None => MAX_PATH_ELEM_SIZE, From d8c440273387c5a3b0cb6480556f06239dc8eb1c Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 3 Nov 2023 22:21:22 +0100 Subject: [PATCH 110/168] allow seveval palettes for normal file colors --- config_files/fm/config.yaml | 3 ++ development.md | 3 +- src/colors.rs | 64 ++++++++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/config_files/fm/config.yaml b/config_files/fm/config.yaml index c5b69c51..702e9b4e 100644 --- a/config_files/fm/config.yaml +++ b/config_files/fm/config.yaml @@ -1,6 +1,9 @@ # the terminal emulator, which must be accessible from $PATH terminal: st # configurable colors +# palette of the normal files. Accepted values are "red-green", "red-blue" and "green-blue" +# other value will lead to the default palette which is "green-blue". +palette: green-blue colors: # you can define an ANSI color or an rgb color for any kind of file except "normal" files. # colors for normal files are calculated on the fly. diff --git a/development.md b/development.md index 0500cca1..f8b2ea14 100644 --- a/development.md +++ b/development.md @@ -600,7 +600,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] allow rgb colors in config file - [x] FIX: can't read filename from / ... which crashes the app. - [x] FIX: exploring root folder leads to wrong first line display. -- [ ] allow rgb palette for normal file colors +- [x] allow seveval palettes for normal file colors +- [ ] move every lazy_static into config. - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/colors.rs b/src/colors.rs index f7d20ac7..b615998b 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -2,17 +2,65 @@ use std::hash::Hasher; use tuikit::attr::Color; -/// Picks a blueish/greenish color on color picker hexagon's perimeter. -fn color(hash: usize) -> Color { - (128..255) - .map(|b| Color::Rgb(0, 255, b)) - .chain((128..255).map(|g| Color::Rgb(0, g, 255))) - .nth(hash % 254) - .unwrap() +use crate::constant_strings_paths::CONFIG_PATH; + +/// No attr but 3 static methods. +struct Colorer {} + +impl Colorer { + /// Picks a blueish/greenish color on color picker hexagon's perimeter. + fn color_green_blue(hash: usize) -> Color { + (128..255) + .map(|b| Color::Rgb(0, 255, b)) + .chain((128..255).map(|g| Color::Rgb(0, g, 255))) + .nth(hash % 254) + .unwrap() + } + + /// Picks a redish/blueish color on color picker hexagon's perimeter. + fn color_red_blue(hash: usize) -> Color { + (128..255) + .map(|b| Color::Rgb(255, 0, b)) + .chain((128..255).map(|r| Color::Rgb(r, 0, 255))) + .nth(hash % 254) + .unwrap() + } + + /// Picks a redish/greenish color on color picker hexagon's perimeter. + fn color_red_green(hash: usize) -> Color { + (128..255) + .map(|r| Color::Rgb(r, 255, 0)) + .chain((128..255).map(|g| Color::Rgb(255, g, 0))) + .nth(hash % 254) + .unwrap() + } +} + +lazy_static::lazy_static! { + /// Defines a palette which will color the "normal" files based on their extension. + /// We try to read a yaml value and pick one of 3 palettes : + /// "red-green", "red-blue" and "green-blue" which is the default. + static ref COLORER: fn(usize) -> Color = { + let mut colorer = Colorer::color_green_blue as fn(usize) -> Color; + if let Ok(file) = std::fs::File::open(std::path::Path::new(&shellexpand::tilde(CONFIG_PATH).to_string())) { + if let Ok(yaml) = serde_yaml::from_reader::(file) { + if let Some(palette) = yaml["palette"].as_str() { + match palette { + "red-blue" => {colorer = Colorer::color_red_blue as fn(usize) -> Color;}, + "red-green" => {colorer = Colorer::color_red_green as fn(usize) -> Color;}, + _ => () + } + } + }; + }; + colorer + }; } +/// Returns a color based on the extension. +/// Those colors will always be the same, but a palette is defined from a yaml value. pub fn extension_color(extension: &str) -> Color { let mut hasher = std::collections::hash_map::DefaultHasher::new(); hasher.write(extension.as_bytes()); - color(hasher.finish() as usize) + COLORER(hasher.finish() as usize) } From efe46e9abb71c6cfc6a4c56a15920ec73183f0a0 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 3 Nov 2023 22:31:23 +0100 Subject: [PATCH 111/168] move every lazy_static configuration into config. --- development.md | 2 +- src/colors.rs | 31 +++++-------------------------- src/config.rs | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/development.md b/development.md index f8b2ea14..7cd376bb 100644 --- a/development.md +++ b/development.md @@ -601,7 +601,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: can't read filename from / ... which crashes the app. - [x] FIX: exploring root folder leads to wrong first line display. - [x] allow seveval palettes for normal file colors -- [ ] move every lazy_static into config. +- [x] move every lazy_static configuration into config. - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself diff --git a/src/colors.rs b/src/colors.rs index b615998b..7f56e85c 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -2,14 +2,14 @@ use std::hash::Hasher; use tuikit::attr::Color; -use crate::constant_strings_paths::CONFIG_PATH; +use crate::config::COLORER; /// No attr but 3 static methods. -struct Colorer {} +pub struct Colorer {} impl Colorer { /// Picks a blueish/greenish color on color picker hexagon's perimeter. - fn color_green_blue(hash: usize) -> Color { + pub fn color_green_blue(hash: usize) -> Color { (128..255) .map(|b| Color::Rgb(0, 255, b)) .chain((128..255).map(|g| Color::Rgb(0, g, 255))) @@ -18,7 +18,7 @@ impl Colorer { } /// Picks a redish/blueish color on color picker hexagon's perimeter. - fn color_red_blue(hash: usize) -> Color { + pub fn color_red_blue(hash: usize) -> Color { (128..255) .map(|b| Color::Rgb(255, 0, b)) .chain((128..255).map(|r| Color::Rgb(r, 0, 255))) @@ -27,7 +27,7 @@ impl Colorer { } /// Picks a redish/greenish color on color picker hexagon's perimeter. - fn color_red_green(hash: usize) -> Color { + pub fn color_red_green(hash: usize) -> Color { (128..255) .map(|r| Color::Rgb(r, 255, 0)) .chain((128..255).map(|g| Color::Rgb(255, g, 0))) @@ -36,27 +36,6 @@ impl Colorer { } } -lazy_static::lazy_static! { - /// Defines a palette which will color the "normal" files based on their extension. - /// We try to read a yaml value and pick one of 3 palettes : - /// "red-green", "red-blue" and "green-blue" which is the default. - static ref COLORER: fn(usize) -> Color = { - let mut colorer = Colorer::color_green_blue as fn(usize) -> Color; - if let Ok(file) = std::fs::File::open(std::path::Path::new(&shellexpand::tilde(CONFIG_PATH).to_string())) { - if let Ok(yaml) = serde_yaml::from_reader::(file) { - if let Some(palette) = yaml["palette"].as_str() { - match palette { - "red-blue" => {colorer = Colorer::color_red_blue as fn(usize) -> Color;}, - "red-green" => {colorer = Colorer::color_red_green as fn(usize) -> Color;}, - _ => () - } - } - }; - }; - colorer - }; -} - /// Returns a color based on the extension. /// Those colors will always be the same, but a palette is defined from a yaml value. pub fn extension_color(extension: &str) -> Color { diff --git a/src/config.rs b/src/config.rs index 77160162..0e41be1d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use anyhow::Result; use serde_yaml; use tuikit::attr::Color; +use crate::colors::Colorer; use crate::constant_strings_paths::{CONFIG_PATH, DEFAULT_TERMINAL_APPLICATION}; use crate::keybindings::Bindings; use crate::utils::is_program_in_path; @@ -235,3 +236,24 @@ lazy_static::lazy_static! { colors }; } + +lazy_static::lazy_static! { + /// Defines a palette which will color the "normal" files based on their extension. + /// We try to read a yaml value and pick one of 3 palettes : + /// "red-green", "red-blue" and "green-blue" which is the default. + pub static ref COLORER: fn(usize) -> Color = { + let mut colorer = Colorer::color_green_blue as fn(usize) -> Color; + if let Ok(file) = std::fs::File::open(std::path::Path::new(&shellexpand::tilde(CONFIG_PATH).to_string())) { + if let Ok(yaml) = serde_yaml::from_reader::(file) { + if let Some(palette) = yaml["palette"].as_str() { + match palette { + "red-blue" => {colorer = Colorer::color_red_blue as fn(usize) -> Color;}, + "red-green" => {colorer = Colorer::color_red_green as fn(usize) -> Color;}, + _ => () + } + } + }; + }; + colorer + }; +} From bd1cb2b3f325ddaa019972762aa32fd4db286eaf Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 02:09:54 +0100 Subject: [PATCH 112/168] doesn't work yet, is way quicker --- Cargo.lock | 10 ++ Cargo.toml | 1 + src/fileinfo.rs | 2 +- src/lib.rs | 1 + src/status.rs | 1 + src/tab.rs | 16 +- src/term_manager.rs | 38 ++++- src/tree.rs | 2 +- src/trees.rs | 368 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 435 insertions(+), 4 deletions(-) create mode 100644 src/trees.rs diff --git a/Cargo.lock b/Cargo.lock index 06c4b63b..2d98cf3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -808,6 +808,7 @@ dependencies = [ "flate2", "fs_extra", "indicatif", + "itertools", "lazy_static", "log", "log4rs", @@ -1196,6 +1197,15 @@ dependencies = [ "libc 0.2.149", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" diff --git a/Cargo.toml b/Cargo.toml index 53795c93..522a8a53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ copypasta = "0.8.1" flate2 = "1.0" fs_extra = "1.2.0" indicatif = { version = "0.17.1", features= ["in_memory"] } +itertools = "0.11.0" lazy_static = "1.4.0" log = { version = "0.4.0", features = ["std"] } log4rs = { version = "1.2.0", features = ["rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } diff --git a/src/fileinfo.rs b/src/fileinfo.rs index f4156c33..335ae9e3 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -158,7 +158,7 @@ pub struct FileInfo { } impl FileInfo { - fn new(path: &path::Path, users: &Users) -> Result { + pub fn new(path: &path::Path, users: &Users) -> Result { let filename = extract_filename(path)?; let metadata = symlink_metadata(path)?; let path = path.to_owned(); diff --git a/src/lib.rs b/src/lib.rs index 900c090d..c1e0b659 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,5 +45,6 @@ pub mod tab; pub mod term_manager; pub mod trash; pub mod tree; +pub mod trees; pub mod users; pub mod utils; diff --git a/src/status.rs b/src/status.rs index 9aae2ded..132ea1a3 100644 --- a/src/status.rs +++ b/src/status.rs @@ -623,6 +623,7 @@ impl Status { self.selected().set_mode(Mode::Normal) } else { self.display_full = true; + // self.selected().make_tree()?; self.selected().make_tree()?; self.selected().set_mode(Mode::Tree); let len = self.selected_non_mut().directory.len(); diff --git a/src/tab.rs b/src/tab.rs index 801c86e7..31d0d6b6 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -16,6 +16,7 @@ use crate::opener::execute_in_child; use crate::preview::{Directory, Preview}; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; +use crate::trees::FileSystem; use crate::users::Users; use crate::utils::{filename_from_path, row_to_window_index, set_clipboard}; @@ -56,6 +57,7 @@ pub struct Tab { pub history: History, /// Users & groups pub users: Users, + pub tree: FileSystem, } impl Tab { @@ -89,6 +91,7 @@ impl Tab { shortcut.extend_with_mount_points(mount_points); let searched = None; let index = path_content.select_file(&path); + let tree = FileSystem::empty(); window.scroll_to(index); Ok(Self { mode, @@ -107,6 +110,7 @@ impl Tab { show_hidden, history, users, + tree, }) } @@ -427,7 +431,15 @@ impl Tab { pub fn make_tree(&mut self) -> Result<()> { let path = self.path_content.path.clone(); let users = &self.users; - self.directory = Directory::new(&path, users, &self.filter, self.show_hidden, None)?; + // self.directory = Directory::new(&path, users, &self.filter, self.show_hidden, None)?; + self.tree = FileSystem::new( + path, + 5, + self.path_content.sort_kind.clone(), + users, + self.show_hidden, + &self.filter, + ); Ok(()) } @@ -530,11 +542,13 @@ impl Tab { /// Select the next sibling of the current node. pub fn select_next(&mut self) -> Result<()> { + self.tree.next(); self.tree_select_next() } /// Select the previous sibling of the current node. pub fn select_prev(&mut self) -> Result<()> { + self.tree.prev(); self.tree_select_prev() } diff --git a/src/term_manager.rs b/src/term_manager.rs index ce3c8c1f..945745f1 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -25,6 +25,7 @@ use crate::selectable_content::SelectableContent; use crate::status::Status; use crate::tab::Tab; use crate::trash::TrashInfo; +use crate::trees::calculate_tree_window; /// Iter over the content, returning a triplet of `(index, line, attr)`. macro_rules! enumerated_colored_iter { @@ -138,7 +139,7 @@ impl<'a> Draw for WinMain<'a> { } match self.tab.mode { Mode::Preview => self.preview(self.tab, &self.tab.window, canvas), - Mode::Tree => self.tree(self.status, self.tab, canvas), + Mode::Tree => self.trees(self.status, self.tab, canvas), Mode::Normal => self.files(self.status, self.tab, canvas), _ => match self.tab.previous_mode { Mode::Tree => self.tree(self.status, self.tab, canvas), @@ -423,6 +424,41 @@ impl<'a> WinMain<'a> { Ok(()) } + fn trees(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + let left_margin = if status.display_full { 1 } else { 3 }; + let (_, height) = canvas.size()?; + let (selected_index, content) = tab.tree.into_navigable_content(&tab.users); + let (top, bottom, len) = calculate_tree_window(selected_index, canvas.size()?.1, height); + + for (i, (metadata, prefix, colored_string)) in content + .iter() + .enumerate() + .skip(top) + .take(min(len, bottom + 1)) + { + let row = i + ContentWindow::WINDOW_MARGIN_TOP - top; + let mut attr = colored_string.color_effect.attr(); + if status.flagged.contains(&colored_string.path) { + attr.effect |= Effect::BOLD; + canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; + } + + let col_metadata = if status.display_full { + canvas.print_with_attr(row, left_margin, metadata, attr)? + } else { + 0 + }; + let col_tree_prefix = canvas.print(row, left_margin + col_metadata, prefix)?; + canvas.print_with_attr( + row, + left_margin + col_metadata + col_tree_prefix, + &colored_string.text, + attr, + )?; + } + self.second_line(status, tab, canvas)?; + Ok(()) + } fn print_line_number( row_position_in_canvas: usize, line_number_to_print: usize, diff --git a/src/tree.rs b/src/tree.rs index a6591911..cb63eba8 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -21,7 +21,7 @@ pub struct ColoredString { } impl ColoredString { - fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { + pub fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { Self { text, color_effect, diff --git a/src/trees.rs b/src/trees.rs new file mode 100644 index 00000000..9cf5f167 --- /dev/null +++ b/src/trees.rs @@ -0,0 +1,368 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use anyhow::Result; + +use crate::{ + content_window::ContentWindow, + fileinfo::{files_collection, ColorEffect, FileInfo}, + filter::FilterKind, + preview::ColoredTriplet, + sort::SortKind, + tree::ColoredString, + users::Users, + utils::filename_from_path, +}; +#[derive(Debug, Clone)] +pub struct Node { + pub path: PathBuf, + pub children: Option>, + pub folded: bool, + pub selected: bool, +} + +impl Node { + pub fn new(path: &Path, children: Option>) -> Self { + Self { + path: path.to_owned(), + children, + folded: false, + selected: false, + } + } + + pub fn fold(&mut self) { + self.folded = true + } + + pub fn unfold(&mut self) { + self.folded = false + } + + pub fn toggle_fold(&mut self) { + self.folded = !self.folded + } + + pub fn select(&mut self) { + self.selected = true + } + + pub fn unselect(&mut self) { + self.selected = false + } + + pub fn fileinfo(&self, users: &Users) -> Result { + FileInfo::new(&self.path, users) + } + + pub fn color_effect(&self, users: &Users) -> Result { + Ok(ColorEffect::new(&self.fileinfo(users)?)) + } + + pub fn set_children(&mut self, children: Option>) { + self.children = children + } +} + +#[derive(Debug, Clone)] +pub struct FileSystem { + root_path: PathBuf, + selected: PathBuf, + nodes: HashMap, + required_height: usize, +} + +impl FileSystem { + pub const REQUIRED_HEIGHT: usize = 80; + + pub fn new( + root_path: PathBuf, + depth: usize, + sort_kind: SortKind, + users: &Users, + show_hidden: bool, + filter_kind: &FilterKind, + ) -> Self { + // keep track of the depth + let start_depth = root_path.components().collect::>().len(); + let mut stack = vec![root_path.to_owned()]; + let mut nodes: HashMap = HashMap::new(); + + while let Some(path) = stack.pop() { + let reached_depth = path.components().collect::>().len(); + if reached_depth >= depth + start_depth { + continue; + } + let mut node = Node::new(&path, None); + if let Ok(fileinfo) = node.fileinfo(users) { + if path.is_dir() && !path.is_symlink() { + if let Some(mut files) = + files_collection(&fileinfo, users, show_hidden, filter_kind, true) + { + sort_kind.sort(&mut files); + let children = files + .iter() + .map(|fileinfo| { + stack.push(fileinfo.path.to_owned()); + fileinfo + }) + .map(|fileinfo| fileinfo.path.to_owned()) + .collect(); + node.set_children(Some(children)); + }; + } + } + nodes.insert(node.path.to_owned(), node); + } + + if let Some(node) = nodes.get_mut(&root_path) { + node.select() + } + + Self { + selected: root_path.clone(), + root_path, + nodes, + required_height: Self::REQUIRED_HEIGHT, + } + } + + pub fn empty() -> Self { + Self { + root_path: PathBuf::default(), + selected: PathBuf::default(), + nodes: HashMap::new(), + required_height: 0, + } + } + + pub fn selected(&self) -> &Path { + self.selected.as_path() + } + + pub fn root_path(&self) -> &Path { + self.root_path.as_path() + } + + pub fn selected_node(&self) -> Option<&Node> { + self.nodes.get(self.selected()) + } + + pub fn sort(&mut self, sort_kind: SortKind) -> Result<()> { + todo!() + } + + pub fn select

(&mut self, path: P) + where + P: AsRef + Into, + { + self.unselect_current(); + self.selected = path.into(); + self.select_current(); + } + + fn unselect_current(&mut self) { + if let Some(node) = self.nodes.get_mut(&self.selected) { + node.unselect() + } + } + + fn select_current(&mut self) { + if let Some(node) = self.nodes.get_mut(&self.selected) { + node.select() + } + } + + // TODO: remove indentation with let ... else + /// Select next sibling or the next sibling of the parent + pub fn next(&mut self) { + if let Some(current) = self.selected_node() { + if let Some(children) = ¤t.children { + if let Some(child_path) = children.get(0) { + log::info!("first child {sel}", sel = self.selected.display()); + self.select(child_path) + } + } + } + + if let Some(parent) = self.selected.parent() { + if let Some(parent_node) = self.nodes.get(parent) { + if let Some(siblings) = &parent_node.children { + if let Some(index_selected) = + siblings.iter().position(|path| path == &self.selected) + { + if let Some(next_sibling) = siblings.get(index_selected + 1) { + self.selected = next_sibling.to_owned(); + log::info!("next sibling {sel}", sel = self.selected.display()); + self.select_current() + } else { + if let Some(parent_parent) = parent.parent() { + if let Some(parent_parent_node) = self.nodes.get(parent_parent) { + if let Some(parent_siblings) = &parent_parent_node.children { + if let Some(index_parent) = + parent_siblings.iter().position(|path| path == &parent) + { + if let Some(next_parent_sibling) = + parent_siblings.get(index_parent + 1) + { + self.selected = next_parent_sibling.to_owned(); + log::info!( + "parent sibling {sel}", + sel = self.selected.display() + ); + self.select_current() + } + } + } + } + } + } + } + } + } + } + } + + // TODO: remove indentation with let ... else + /// Select previous sibling or parent if it's the first. + pub fn prev(&mut self) { + if let Some(parent) = self.selected.parent() { + if let Some(parent_node) = self.nodes.get(parent) { + if let Some(siblings) = &parent_node.children { + if let Some(index_selected) = + siblings.iter().position(|path| path == &self.selected) + { + if index_selected == 0 { + self.selected = parent.to_owned(); + } else if let Some(prev_sibling) = siblings.get(index_selected - 1) { + self.selected = prev_sibling.to_owned(); + } + self.select_current() + } + } + } + } + } + + /// Fold selected node + pub fn toggle_fold(&mut self) { + if let Some(node) = self.nodes.get_mut(&self.selected) { + node.toggle_fold(); + } + } + + pub fn toggle_fold_all(&mut self) { + for (_, node) in self.nodes.iter_mut() { + node.toggle_fold() + } + } + + pub fn search_first_match(&mut self, pattern: &str) { + if let Some(filename) = self.selected.file_name() { + let filename = filename.to_string_lossy(); + if filename.contains(pattern) { + return; + } + } + todo!() + } + + pub fn len(&mut self) -> usize { + self.nodes.len() + } + + pub fn is_empty(self) -> bool { + self.nodes.is_empty() + } + + pub fn into_navigable_content(&self, users: &Users) -> (usize, Vec) { + let required_height = self.required_height; + let mut stack = vec![("".to_owned(), self.root_path())]; + let mut content = vec![]; + let mut selected_index = 0; + + while let Some((prefix, current)) = stack.pop() { + let Some(node) = &self.nodes.get(current) else { + continue; + }; + + if node.selected { + selected_index = content.len(); + } + + let Ok(fileinfo) = FileInfo::new(current, users) else { + continue; + }; + let filename = filename_from_path(current).unwrap_or_default().to_owned(); + + let mut color_effect = ColorEffect::new(&fileinfo); + if node.selected { + color_effect.effect |= tuikit::attr::Effect::REVERSE; + } + content.push(( + fileinfo.format_no_filename().unwrap_or_default(), + prefix.to_owned(), + ColoredString::new(filename, color_effect, current.to_owned()), + )); + + if !node.folded { + let first_prefix = first_prefix(prefix.clone()); + let other_prefix = other_prefix(prefix); + + if let Some(children) = &node.children { + let mut leaves = children.iter(); + let Some(first_leaf) = leaves.next() else { + continue; + }; + stack.push((first_prefix.clone(), first_leaf)); + + for leaf in leaves { + stack.push((other_prefix.clone(), leaf)); + } + } + } + if content.len() > required_height { + break; + } + } + (selected_index, content) + } +} + +fn first_prefix(mut prefix: String) -> String { + prefix.push(' '); + prefix = prefix.replace("└──", " "); + prefix = prefix.replace("├──", "│ "); + prefix.push_str("└──"); + prefix +} + +fn other_prefix(mut prefix: String) -> String { + prefix.push(' '); + prefix = prefix.replace("└──", " "); + prefix = prefix.replace("├──", "│ "); + prefix.push_str("├──"); + prefix +} + +pub fn calculate_tree_window( + selected_index: usize, + terminal_height: usize, + length: usize, +) -> (usize, usize, usize) { + let top: usize; + let bottom: usize; + let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP; + if selected_index < terminal_height - 1 { + top = 0; + bottom = window_height; + } else { + let padding = std::cmp::max(10, terminal_height / 2); + top = selected_index - padding; + bottom = top + window_height; + } + + (top, bottom, length) +} From bb1ba31144fef94e4c7fc6d9e99f26bbac952f60 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 14:14:42 +0100 Subject: [PATCH 113/168] tree next --- src/trees.rs | 90 +++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/trees.rs b/src/trees.rs index 9cf5f167..cbdbdf9d 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -3,7 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::Result; +use anyhow::{Context, Result}; use crate::{ content_window::ContentWindow, @@ -177,52 +177,54 @@ impl FileSystem { // TODO: remove indentation with let ... else /// Select next sibling or the next sibling of the parent - pub fn next(&mut self) { - if let Some(current) = self.selected_node() { - if let Some(children) = ¤t.children { - if let Some(child_path) = children.get(0) { - log::info!("first child {sel}", sel = self.selected.display()); - self.select(child_path) - } + pub fn next(&mut self) -> Result<()> { + let current_node = self + .nodes + .get_mut(&self.selected) + .context("no selected node")?; + + if let Some(children_paths) = ¤t_node.children { + if let Some(child_path) = children_paths.get(0) { + let child_path = child_path.to_owned(); + current_node.unselect(); + self.selected = child_path.clone(); + self.nodes + .get_mut(&child_path) + .context("no child path in nodes")? + .select(); } - } - - if let Some(parent) = self.selected.parent() { - if let Some(parent_node) = self.nodes.get(parent) { - if let Some(siblings) = &parent_node.children { - if let Some(index_selected) = - siblings.iter().position(|path| path == &self.selected) - { - if let Some(next_sibling) = siblings.get(index_selected + 1) { - self.selected = next_sibling.to_owned(); - log::info!("next sibling {sel}", sel = self.selected.display()); - self.select_current() - } else { - if let Some(parent_parent) = parent.parent() { - if let Some(parent_parent_node) = self.nodes.get(parent_parent) { - if let Some(parent_siblings) = &parent_parent_node.children { - if let Some(index_parent) = - parent_siblings.iter().position(|path| path == &parent) - { - if let Some(next_parent_sibling) = - parent_siblings.get(index_parent + 1) - { - self.selected = next_parent_sibling.to_owned(); - log::info!( - "parent sibling {sel}", - sel = self.selected.display() - ); - self.select_current() - } - } - } - } - } - } - } - } + } else { + let mut current_path = self.selected.to_owned(); + + while let Some(parent_path) = current_path.parent() { + let Some(parent_node) = self.nodes.get(parent_path) else { + current_path = parent_path.to_owned(); + continue; + }; + let Some(siblings_paths) = &parent_node.children else { + current_path = parent_path.to_owned(); + continue; + }; + let Some(index_current) = + siblings_paths.iter().position(|path| path == ¤t_path) + else { + current_path = parent_path.to_owned(); + continue; + }; + let Some(next_sibling_path) = siblings_paths.get(index_current + 1) else { + current_path = parent_path.to_owned(); + continue; + }; + self.selected = next_sibling_path.to_owned(); + let Some(node) = self.nodes.get_mut(&self.selected) else { + current_path = parent_path.to_owned(); + continue; + }; + node.select(); + break; } } + Ok(()) } // TODO: remove indentation with let ... else From da41b5907a97326ede110cfeac1f27c9f8aaecd6 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 14:36:25 +0100 Subject: [PATCH 114/168] select prev --- src/tab.rs | 18 ++++++++---------- src/trees.rs | 45 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 31d0d6b6..9d0cbbe5 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -366,12 +366,12 @@ impl Tab { /// Select the next sibling. pub fn tree_select_next(&mut self) -> Result<()> { - self.directory.select_next() + self.tree.select_next() } /// Select the previous siblging pub fn tree_select_prev(&mut self) -> Result<()> { - self.directory.select_prev() + self.tree.select_prev() } /// Select the first child if any. @@ -540,16 +540,14 @@ impl Tab { self.tree_select_first_child() } - /// Select the next sibling of the current node. - pub fn select_next(&mut self) -> Result<()> { - self.tree.next(); - self.tree_select_next() - } - /// Select the previous sibling of the current node. pub fn select_prev(&mut self) -> Result<()> { - self.tree.prev(); - self.tree_select_prev() + self.tree.select_prev() + } + + /// Select the next sibling of the current node. + pub fn select_next(&mut self) -> Result<()> { + self.tree.select_next() } /// Copy the selected filename to the clipboard. Only the filename. diff --git a/src/trees.rs b/src/trees.rs index cbdbdf9d..fab2b65b 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -150,7 +150,7 @@ impl FileSystem { self.nodes.get(self.selected()) } - pub fn sort(&mut self, sort_kind: SortKind) -> Result<()> { + pub fn sort(&mut self, _sort_kind: SortKind) -> Result<()> { todo!() } @@ -175,9 +175,8 @@ impl FileSystem { } } - // TODO: remove indentation with let ... else /// Select next sibling or the next sibling of the parent - pub fn next(&mut self) -> Result<()> { + pub fn select_next(&mut self) -> Result<()> { let current_node = self .nodes .get_mut(&self.selected) @@ -227,9 +226,43 @@ impl FileSystem { Ok(()) } + /// Select previous sibling or the parent + pub fn select_prev(&mut self) -> Result<()> { + let current_path = self.selected().to_owned(); + let Some(parent_path) = current_path.parent() else { + return Ok(()); + }; + let Some(parent_node) = self.nodes.get(parent_path) else { + return Ok(()); + }; + let Some(siblings_paths) = &parent_node.children else { + return Ok(()); + }; + let Some(index_current) = siblings_paths.iter().position(|path| path == ¤t_path) + else { + return Ok(()); + }; + if index_current > 0 { + // Previous sibling + self.selected = siblings_paths[index_current - 1].to_owned(); + let Some(node) = self.nodes.get_mut(&self.selected) else { + return Ok(()); + }; + node.select(); + } else { + // parent + let Some(node) = self.nodes.get_mut(parent_path) else { + return Ok(()); + }; + self.selected = parent_path.to_owned(); + node.select(); + } + Ok(()) + } + // TODO: remove indentation with let ... else /// Select previous sibling or parent if it's the first. - pub fn prev(&mut self) { + pub fn prev_not_working(&mut self) { if let Some(parent) = self.selected.parent() { if let Some(parent_node) = self.nodes.get(parent) { if let Some(siblings) = &parent_node.children { @@ -271,11 +304,11 @@ impl FileSystem { todo!() } - pub fn len(&mut self) -> usize { + pub fn len(&self) -> usize { self.nodes.len() } - pub fn is_empty(self) -> bool { + pub fn is_empty(&self) -> bool { self.nodes.is_empty() } From c4a3e928dcbca9aa1db876d9a51d0f838f6913b4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 14:38:41 +0100 Subject: [PATCH 115/168] remove useless result since select prev can't fail --- src/tab.rs | 3 ++- src/trees.rs | 36 +++++++----------------------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 9d0cbbe5..5a31edc0 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -371,7 +371,8 @@ impl Tab { /// Select the previous siblging pub fn tree_select_prev(&mut self) -> Result<()> { - self.tree.select_prev() + self.tree.select_prev(); + Ok(()) } /// Select the first child if any. diff --git a/src/trees.rs b/src/trees.rs index fab2b65b..307c3627 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -227,58 +227,36 @@ impl FileSystem { } /// Select previous sibling or the parent - pub fn select_prev(&mut self) -> Result<()> { + pub fn select_prev(&mut self) { let current_path = self.selected().to_owned(); let Some(parent_path) = current_path.parent() else { - return Ok(()); + return; }; let Some(parent_node) = self.nodes.get(parent_path) else { - return Ok(()); + return; }; let Some(siblings_paths) = &parent_node.children else { - return Ok(()); + return; }; let Some(index_current) = siblings_paths.iter().position(|path| path == ¤t_path) else { - return Ok(()); + return; }; if index_current > 0 { // Previous sibling self.selected = siblings_paths[index_current - 1].to_owned(); let Some(node) = self.nodes.get_mut(&self.selected) else { - return Ok(()); + return; }; node.select(); } else { // parent let Some(node) = self.nodes.get_mut(parent_path) else { - return Ok(()); + return; }; self.selected = parent_path.to_owned(); node.select(); } - Ok(()) - } - - // TODO: remove indentation with let ... else - /// Select previous sibling or parent if it's the first. - pub fn prev_not_working(&mut self) { - if let Some(parent) = self.selected.parent() { - if let Some(parent_node) = self.nodes.get(parent) { - if let Some(siblings) = &parent_node.children { - if let Some(index_selected) = - siblings.iter().position(|path| path == &self.selected) - { - if index_selected == 0 { - self.selected = parent.to_owned(); - } else if let Some(prev_sibling) = siblings.get(index_selected - 1) { - self.selected = prev_sibling.to_owned(); - } - self.select_current() - } - } - } - } } /// Fold selected node From 5afa594ed846ab9317072adbf4cbc53ac8a8ec84 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 14:53:06 +0100 Subject: [PATCH 116/168] select prev don't return a result --- src/tab.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tab.rs b/src/tab.rs index 5a31edc0..369e3eb5 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -543,7 +543,8 @@ impl Tab { /// Select the previous sibling of the current node. pub fn select_prev(&mut self) -> Result<()> { - self.tree.select_prev() + self.tree.select_prev(); + Ok(()) } /// Select the next sibling of the current node. From a92709acb4ded784a718e5d47dafd51eb24730de Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 14:53:23 +0100 Subject: [PATCH 117/168] add fold symbols in filesystem --- src/trees.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/trees.rs b/src/trees.rs index 307c3627..f1b66ec0 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -297,11 +297,11 @@ impl FileSystem { let mut selected_index = 0; while let Some((prefix, current)) = stack.pop() { - let Some(node) = &self.nodes.get(current) else { + let Some(current_node) = &self.nodes.get(current) else { continue; }; - if node.selected { + if current_node.selected { selected_index = content.len(); } @@ -311,20 +311,29 @@ impl FileSystem { let filename = filename_from_path(current).unwrap_or_default().to_owned(); let mut color_effect = ColorEffect::new(&fileinfo); - if node.selected { + if current_node.selected { color_effect.effect |= tuikit::attr::Effect::REVERSE; } + let filename_text = if current.is_dir() && !current.is_symlink() { + if current_node.folded { + format!("▸ {}", filename) + } else { + format!("▾ {}", filename) + } + } else { + filename + }; content.push(( fileinfo.format_no_filename().unwrap_or_default(), prefix.to_owned(), - ColoredString::new(filename, color_effect, current.to_owned()), + ColoredString::new(filename_text, color_effect, current.to_owned()), )); - if !node.folded { + if current.is_dir() && !current.is_symlink() && !current_node.folded { let first_prefix = first_prefix(prefix.clone()); let other_prefix = other_prefix(prefix); - if let Some(children) = &node.children { + if let Some(children) = ¤t_node.children { let mut leaves = children.iter(); let Some(first_leaf) = leaves.next() else { continue; @@ -336,6 +345,7 @@ impl FileSystem { } } } + if content.len() > required_height { break; } From ef93694d1f3733ddd92114a51005aac37c192117 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 15:31:39 +0100 Subject: [PATCH 118/168] remove useless methods --- src/event_exec.rs | 4 ++-- src/tab.rs | 14 ++------------ src/term_manager.rs | 6 ++++-- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index f908b664..3d3613dc 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -527,7 +527,7 @@ impl EventAction { Mode::Navigate(Navigate::CliInfo) => status.cli_info.next(), Mode::Navigate(Navigate::EncryptedDrive) => status.encrypted_devices.next(), Mode::InputCompleted(_) => status.selected().completion.next(), - Mode::Tree => status.selected().select_next()?, + Mode::Tree => status.selected().tree_select_next()?, _ => (), }; status.update_second_pane_for_preview() @@ -898,7 +898,7 @@ impl EventAction { let (tree, _, _) = tab.directory.tree.explore_position(false); tree.node.toggle_fold(); tab.directory.make_preview(); - tab.select_next() + tab.tree_select_next() } /// Unfold every child node in the tree. diff --git a/src/tab.rs b/src/tab.rs index 369e3eb5..72e5ef29 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -16,6 +16,7 @@ use crate::opener::execute_in_child; use crate::preview::{Directory, Preview}; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; +use crate::sort::SortKind; use crate::trees::FileSystem; use crate::users::Users; use crate::utils::{filename_from_path, row_to_window_index, set_clipboard}; @@ -436,7 +437,7 @@ impl Tab { self.tree = FileSystem::new( path, 5, - self.path_content.sort_kind.clone(), + SortKind::tree_default(), users, self.show_hidden, &self.filter, @@ -541,17 +542,6 @@ impl Tab { self.tree_select_first_child() } - /// Select the previous sibling of the current node. - pub fn select_prev(&mut self) -> Result<()> { - self.tree.select_prev(); - Ok(()) - } - - /// Select the next sibling of the current node. - pub fn select_next(&mut self) -> Result<()> { - self.tree.select_next() - } - /// Copy the selected filename to the clipboard. Only the filename. pub fn filename_to_clipboard(&self) -> Result<()> { set_clipboard( diff --git a/src/term_manager.rs b/src/term_manager.rs index 945745f1..bc119f49 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -448,10 +448,12 @@ impl<'a> WinMain<'a> { } else { 0 }; - let col_tree_prefix = canvas.print(row, left_margin + col_metadata, prefix)?; + let offset = if i == 0 { 1 } else { 0 }; + + let col_tree_prefix = canvas.print(row, left_margin + col_metadata + offset, prefix)?; canvas.print_with_attr( row, - left_margin + col_metadata + col_tree_prefix, + left_margin + col_metadata + col_tree_prefix + offset, &colored_string.text, attr, )?; From 0b4667503c2575d153a0f5aa064e0cd55e18b806 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 17:03:54 +0100 Subject: [PATCH 119/168] somehow fixes select next and select prev. Still buggy --- src/trees.rs | 94 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/src/trees.rs b/src/trees.rs index f1b66ec0..ddf54e7c 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -3,7 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::{Context, Result}; +use anyhow::Result; use crate::{ content_window::ContentWindow, @@ -177,20 +177,31 @@ impl FileSystem { /// Select next sibling or the next sibling of the parent pub fn select_next(&mut self) -> Result<()> { - let current_node = self - .nodes - .get_mut(&self.selected) - .context("no selected node")?; + log::info!("select_next START {sel}", sel = self.selected.display()); + if let Some(next_path) = self.find_next_path() { + let Some(next_node) = self.nodes.get_mut(&next_path) else { + return Ok(()); + }; + log::info!("selecting {next_node:?}"); + next_node.select(); + let Some(selected_node) = self.nodes.get_mut(&self.selected) else { + unreachable!("current_node should be in nodes"); + }; + selected_node.unselect(); + self.selected = next_path; + } + Ok(()) + } + + fn find_next_path(&self) -> Option { + let Some(current_node) = self.nodes.get(&self.selected) else { + unreachable!("selected path should be in nodes") + }; if let Some(children_paths) = ¤t_node.children { - if let Some(child_path) = children_paths.get(0) { + if let Some(child_path) = children_paths.last() { let child_path = child_path.to_owned(); - current_node.unselect(); - self.selected = child_path.clone(); - self.nodes - .get_mut(&child_path) - .context("no child path in nodes")? - .select(); + return Some(child_path.to_owned()); } } else { let mut current_path = self.selected.to_owned(); @@ -204,58 +215,73 @@ impl FileSystem { current_path = parent_path.to_owned(); continue; }; + log::info!("siblings {siblings_paths:?}"); let Some(index_current) = siblings_paths.iter().position(|path| path == ¤t_path) else { current_path = parent_path.to_owned(); continue; }; - let Some(next_sibling_path) = siblings_paths.get(index_current + 1) else { + if index_current == 0 { + current_path = parent_path.to_owned(); + continue; + } + let Some(next_sibling_path) = siblings_paths.get(index_current - 1) else { current_path = parent_path.to_owned(); continue; }; - self.selected = next_sibling_path.to_owned(); - let Some(node) = self.nodes.get_mut(&self.selected) else { + if self.nodes.contains_key(next_sibling_path) { + log::info!("returning {next_sibling_path:?}"); + return Some(next_sibling_path.to_owned()); + } else { current_path = parent_path.to_owned(); continue; }; - node.select(); - break; } } - Ok(()) + None } + // TODO! find the bottom child of parent instead of jumping back 1 level /// Select previous sibling or the parent pub fn select_prev(&mut self) { + log::info!("select_prev START {sel}", sel = self.selected.display()); + + if let Some(previous_path) = self.find_prev_path() { + let Some(previous_node) = self.nodes.get_mut(&previous_path) else { + return; + }; + previous_node.select(); + let Some(selected_node) = self.nodes.get_mut(&self.selected) else { + unreachable!("current_node should be in nodes"); + }; + selected_node.unselect(); + self.selected = previous_path; + } + } + + fn find_prev_path(&self) -> Option { let current_path = self.selected().to_owned(); let Some(parent_path) = current_path.parent() else { - return; + return None; }; let Some(parent_node) = self.nodes.get(parent_path) else { - return; + return None; }; let Some(siblings_paths) = &parent_node.children else { - return; + return None; }; let Some(index_current) = siblings_paths.iter().position(|path| path == ¤t_path) else { - return; + return None; }; - if index_current > 0 { - // Previous sibling - self.selected = siblings_paths[index_current - 1].to_owned(); - let Some(node) = self.nodes.get_mut(&self.selected) else { - return; - }; - node.select(); + if index_current + 1 < siblings_paths.len() { + Some(siblings_paths[index_current + 1].to_owned()) } else { - // parent - let Some(node) = self.nodes.get_mut(parent_path) else { - return; + let Some(_node) = self.nodes.get(parent_path) else { + return None; }; - self.selected = parent_path.to_owned(); - node.select(); + Some(parent_path.to_owned()) } } From a6b4c084c56a2e4368fa431b9e361d8b9eb1a62e Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 17:11:40 +0100 Subject: [PATCH 120/168] tree select root --- src/tab.rs | 6 ++++-- src/trees.rs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 72e5ef29..ff111adf 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -533,8 +533,10 @@ impl Tab { /// Fold every child node in the tree. /// Recursively explore the tree and fold every node. Reset the display. pub fn tree_go_to_root(&mut self) -> Result<()> { - self.directory.tree.reset_required_height(); - self.tree_select_root() + // self.directory.tree.reset_required_height(); + // self.tree_select_root() + self.tree.select_root(); + Ok(()) } /// Select the first child of the current node and reset the display. diff --git a/src/trees.rs b/src/trees.rs index ddf54e7c..e19cec19 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -285,6 +285,18 @@ impl FileSystem { } } + pub fn select_root(&mut self) { + let Some(selected_node) = self.nodes.get_mut(&self.selected) else { + unreachable!("selected path should be in node") + }; + selected_node.unselect(); + let Some(root_node) = self.nodes.get_mut(&self.root_path) else { + unreachable!("root path should be in nodes") + }; + root_node.select(); + self.selected = self.root_path.to_owned(); + } + /// Fold selected node pub fn toggle_fold(&mut self) { if let Some(node) = self.nodes.get_mut(&self.selected) { From 6ed77663393df5bb73f7a329c6e590dc06d86e8f Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 17:45:15 +0100 Subject: [PATCH 121/168] remove useless methods. Search in tree --- src/event_exec.rs | 43 +++++++------ src/trees.rs | 152 +++++++++++++++++++--------------------------- 2 files changed, 86 insertions(+), 109 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index 3d3613dc..247d4f70 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -895,18 +895,21 @@ impl EventAction { /// Fold the current node of the tree. /// Has no effect on "file" nodes. pub fn tree_fold(tab: &mut Tab) -> Result<()> { - let (tree, _, _) = tab.directory.tree.explore_position(false); - tree.node.toggle_fold(); - tab.directory.make_preview(); - tab.tree_select_next() + tab.tree.toggle_fold(); + // let (tree, _, _) = tab.directory.tree.explore_position(false); + // tree.node.toggle_fold(); + // tab.directory.make_preview(); + // tab.tree_select_next() + Ok(()) } /// Unfold every child node in the tree. /// Recursively explore the tree and unfold every node. /// Reset the display. pub fn tree_unfold_all(tab: &mut Tab) -> Result<()> { - tab.directory.tree.unfold_children(); - tab.directory.make_preview(); + tab.tree.fold_all(); + // tab.directory.tree.unfold_children(); + // tab.directory.make_preview(); Ok(()) } @@ -914,8 +917,9 @@ impl EventAction { /// Recursively explore the tree and fold every node. /// Reset the display. pub fn tree_fold_all(tab: &mut Tab) -> Result<()> { - tab.directory.tree.fold_children(); - tab.directory.make_preview(); + tab.tree.fold_all(); + // tab.directory.tree.fold_children(); + // tab.directory.make_preview(); Ok(()) } @@ -1365,7 +1369,7 @@ impl LeaveMode { /// The current order of files is used. pub fn search(status: &mut Status) -> Result<()> { let tab = status.selected(); - let searched = tab.input.string(); + let searched = &tab.input.string(); tab.input.reset(); if searched.is_empty() { tab.searched = None; @@ -1374,19 +1378,20 @@ impl LeaveMode { tab.searched = Some(searched.clone()); match tab.previous_mode { Mode::Tree => { - tab.directory.tree.unselect_children(); - if let Some(position) = tab.directory.tree.select_first_match(&searched) { - tab.directory.tree.position = position; - (_, _, tab.directory.tree.current_node) = - tab.directory.tree.select_from_position()?; - } else { - tab.directory.tree.select_root() - }; - tab.directory.make_preview(); + tab.tree.search_first_match(searched); + // tab.directory.tree.unselect_children(); + // if let Some(position) = tab.directory.tree.select_first_match(&searched) { + // tab.directory.tree.position = position; + // (_, _, tab.directory.tree.current_node) = + // tab.directory.tree.select_from_position()?; + // } else { + // tab.directory.tree.select_root() + // }; + // tab.directory.make_preview(); } _ => { let next_index = tab.path_content.index; - tab.search_from(&searched, next_index); + tab.search_from(searched, next_index); } }; status.update_second_pane_for_preview() diff --git a/src/trees.rs b/src/trees.rs index e19cec19..c7358732 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -138,43 +138,10 @@ impl FileSystem { } } - pub fn selected(&self) -> &Path { - self.selected.as_path() - } - - pub fn root_path(&self) -> &Path { - self.root_path.as_path() - } - - pub fn selected_node(&self) -> Option<&Node> { - self.nodes.get(self.selected()) - } - pub fn sort(&mut self, _sort_kind: SortKind) -> Result<()> { todo!() } - pub fn select

(&mut self, path: P) - where - P: AsRef + Into, - { - self.unselect_current(); - self.selected = path.into(); - self.select_current(); - } - - fn unselect_current(&mut self) { - if let Some(node) = self.nodes.get_mut(&self.selected) { - node.unselect() - } - } - - fn select_current(&mut self) { - if let Some(node) = self.nodes.get_mut(&self.selected) { - node.select() - } - } - /// Select next sibling or the next sibling of the parent pub fn select_next(&mut self) -> Result<()> { log::info!("select_next START {sel}", sel = self.selected.display()); @@ -194,51 +161,53 @@ impl FileSystem { Ok(()) } + // FIX: Still a problem when reaching max depth of tree, + // can't find next sibling since we look for children which exists but aren't in tree. + // Check if the children are listed (they shouldn't be) in node.children and are in self.nodes. fn find_next_path(&self) -> Option { let Some(current_node) = self.nodes.get(&self.selected) else { unreachable!("selected path should be in nodes") }; - if let Some(children_paths) = ¤t_node.children { - if let Some(child_path) = children_paths.last() { - let child_path = child_path.to_owned(); - return Some(child_path.to_owned()); - } - } else { - let mut current_path = self.selected.to_owned(); - - while let Some(parent_path) = current_path.parent() { - let Some(parent_node) = self.nodes.get(parent_path) else { - current_path = parent_path.to_owned(); - continue; - }; - let Some(siblings_paths) = &parent_node.children else { - current_path = parent_path.to_owned(); - continue; - }; - log::info!("siblings {siblings_paths:?}"); - let Some(index_current) = - siblings_paths.iter().position(|path| path == ¤t_path) - else { - current_path = parent_path.to_owned(); - continue; - }; - if index_current == 0 { - current_path = parent_path.to_owned(); - continue; + if !self.selected.is_dir() || !current_node.folded { + if let Some(children_paths) = ¤t_node.children { + if let Some(child_path) = children_paths.last() { + let child_path = child_path.to_owned(); + return Some(child_path.to_owned()); } - let Some(next_sibling_path) = siblings_paths.get(index_current - 1) else { - current_path = parent_path.to_owned(); - continue; - }; - if self.nodes.contains_key(next_sibling_path) { - log::info!("returning {next_sibling_path:?}"); - return Some(next_sibling_path.to_owned()); - } else { - current_path = parent_path.to_owned(); - continue; - }; } } + let mut current_path = self.selected.to_owned(); + + while let Some(parent_path) = current_path.parent() { + let Some(parent_node) = self.nodes.get(parent_path) else { + current_path = parent_path.to_owned(); + continue; + }; + let Some(siblings_paths) = &parent_node.children else { + current_path = parent_path.to_owned(); + continue; + }; + let Some(index_current) = siblings_paths.iter().position(|path| path == ¤t_path) + else { + current_path = parent_path.to_owned(); + continue; + }; + if index_current == 0 { + current_path = parent_path.to_owned(); + continue; + } + let Some(next_sibling_path) = siblings_paths.get(index_current - 1) else { + current_path = parent_path.to_owned(); + continue; + }; + if self.nodes.contains_key(next_sibling_path) { + log::info!("returning {next_sibling_path:?}"); + return Some(next_sibling_path.to_owned()); + } else { + current_path = parent_path.to_owned(); + continue; + }; + } None } @@ -261,7 +230,7 @@ impl FileSystem { } fn find_prev_path(&self) -> Option { - let current_path = self.selected().to_owned(); + let current_path = self.selected.to_owned(); let Some(parent_path) = current_path.parent() else { return None; }; @@ -297,6 +266,8 @@ impl FileSystem { self.selected = self.root_path.to_owned(); } + pub fn select_last(&mut self) {} + /// Fold selected node pub fn toggle_fold(&mut self) { if let Some(node) = self.nodes.get_mut(&self.selected) { @@ -304,33 +275,34 @@ impl FileSystem { } } - pub fn toggle_fold_all(&mut self) { + pub fn fold_all(&mut self) { for (_, node) in self.nodes.iter_mut() { - node.toggle_fold() + node.fold() } } + // FIX: can only find the first match and nothing else pub fn search_first_match(&mut self, pattern: &str) { - if let Some(filename) = self.selected.file_name() { - let filename = filename.to_string_lossy(); - if filename.contains(pattern) { - return; - } - } - todo!() - } - - pub fn len(&self) -> usize { - self.nodes.len() - } - - pub fn is_empty(&self) -> bool { - self.nodes.is_empty() + let initial_selected = self.selected.to_owned(); + let Some((found_path, found_node)) = self.nodes.iter_mut().find(|(path, _)| { + path.file_name() + .unwrap_or_default() + .to_string_lossy() + .contains(pattern) + }) else { + return; + }; + self.selected = found_path.to_owned(); + found_node.select(); + let Some(current_node) = self.nodes.get_mut(&initial_selected) else { + unreachable!("selected path should be in nodes"); + }; + current_node.unselect(); } pub fn into_navigable_content(&self, users: &Users) -> (usize, Vec) { let required_height = self.required_height; - let mut stack = vec![("".to_owned(), self.root_path())]; + let mut stack = vec![("".to_owned(), self.root_path.as_path())]; let mut content = vec![]; let mut selected_index = 0; From 72024f2b37f2ff71e23b6f4f194133b80a96d334 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 18:25:50 +0100 Subject: [PATCH 122/168] sort for trees --- src/event_exec.rs | 6 +++--- src/status.rs | 11 +++++------ src/tab.rs | 31 +++++++++++++++---------------- src/term_manager.rs | 4 ++-- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index 247d4f70..d3e3a3db 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -302,7 +302,7 @@ impl EventAction { .reset_files(&tab.filter, tab.show_hidden, &tab.users)?; tab.window.reset(tab.path_content.content.len()); if let Mode::Tree = tab.mode { - tab.make_tree()? + tab.make_tree(None)? } Ok(()) } @@ -1454,7 +1454,7 @@ impl LeaveMode { EventAction::open_file(status) } else { tab.set_pathcontent(&node.filepath())?; - tab.make_tree()?; + tab.make_tree(None)?; Ok(()) } } @@ -1525,7 +1525,7 @@ impl LeaveMode { tab.path_content .reset_files(&tab.filter, tab.show_hidden, &tab.users)?; if let Mode::Tree = tab.previous_mode { - tab.make_tree()?; + tab.make_tree(None)?; } tab.window.reset(tab.path_content.content.len()); Ok(()) diff --git a/src/status.rs b/src/status.rs index 132ea1a3..9fe1871a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -41,6 +41,7 @@ use crate::skim::Skimer; use crate::tab::Tab; use crate::term_manager::MIN_WIDTH_FOR_DUAL_PANE; use crate::trash::Trash; +use crate::trees::FileSystem; use crate::users::Users; use crate::utils::{current_username, disk_space, filename_from_path, is_program_in_path}; @@ -617,17 +618,15 @@ impl Status { pub fn tree(&mut self) -> Result<()> { if let Mode::Tree = self.selected_non_mut().mode { { - let tab: &mut Tab = self.selected(); + let tab = self.selected(); + tab.tree = FileSystem::empty(); tab.refresh_view() }?; self.selected().set_mode(Mode::Normal) } else { self.display_full = true; - // self.selected().make_tree()?; - self.selected().make_tree()?; + self.selected().make_tree(None)?; self.selected().set_mode(Mode::Tree); - let len = self.selected_non_mut().directory.len(); - self.selected().window.reset(len); } Ok(()) } @@ -929,7 +928,7 @@ impl Status { self.refresh_users()?; self.selected().refresh_view()?; if let Mode::Tree = self.selected_non_mut().mode { - self.selected().make_tree()? + self.selected().make_tree(None)? } Ok(()) } diff --git a/src/tab.rs b/src/tab.rs index ff111adf..cde51108 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -334,7 +334,7 @@ impl Tab { self.scroll_to(index); self.history.content.pop(); if let Mode::Tree = self.mode { - self.make_tree()? + self.make_tree(None)? } Ok(()) @@ -346,7 +346,7 @@ impl Tab { self.directory.unselect_children(); if self.directory.tree.position.len() <= 1 { self.move_to_parent()?; - self.make_tree()? + self.make_tree(None)? } self.directory.select_parent() } @@ -430,18 +430,14 @@ impl Tab { } /// Makes a new tree of the current path. - pub fn make_tree(&mut self) -> Result<()> { + pub fn make_tree(&mut self, sort_kind: Option) -> Result<()> { + let sort_kind = match sort_kind { + Some(sort_kind) => sort_kind, + None => SortKind::tree_default(), + }; let path = self.path_content.path.clone(); let users = &self.users; - // self.directory = Directory::new(&path, users, &self.filter, self.show_hidden, None)?; - self.tree = FileSystem::new( - path, - 5, - SortKind::tree_default(), - users, - self.show_hidden, - &self.filter, - ); + self.tree = FileSystem::new(path, 5, sort_kind, users, self.show_hidden, &self.filter); Ok(()) } @@ -697,10 +693,13 @@ impl Tab { self.path_content.select_index(0); } Mode::Tree => { - self.directory.tree.update_sort_from_char(c); - self.directory.tree.sort(); - self.tree_select_root()?; - self.directory.tree.into_navigable_content(); + self.path_content.update_sort_from_char(c); + self.make_tree(Some(self.path_content.sort_kind.clone()))?; + + // self.directory.tree.update_sort_from_char(c); + // self.directory.tree.sort(); + // self.tree_select_root()?; + // self.directory.tree.into_navigable_content(); } _ => (), } diff --git a/src/term_manager.rs b/src/term_manager.rs index bc119f49..e7b57494 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -142,7 +142,7 @@ impl<'a> Draw for WinMain<'a> { Mode::Tree => self.trees(self.status, self.tab, canvas), Mode::Normal => self.files(self.status, self.tab, canvas), _ => match self.tab.previous_mode { - Mode::Tree => self.tree(self.status, self.tab, canvas), + Mode::Tree => self.trees(self.status, self.tab, canvas), _ => self.files(self.status, self.tab, canvas), }, }?; @@ -394,7 +394,7 @@ impl<'a> WinMain<'a> { Ok(()) } - fn tree(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + fn _tree(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { let left_margin = if status.display_full { 1 } else { 3 }; let (_, height) = canvas.size()?; let (top, bottom, len) = tab.directory.calculate_tree_window(height); From ab804f48b6405775aa51c8ca8ddf4b9829d9d06b Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 18:27:06 +0100 Subject: [PATCH 123/168] tree unflod --- src/event_exec.rs | 10 +--------- src/trees.rs | 6 ++++++ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index d3e3a3db..6117a638 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -896,10 +896,6 @@ impl EventAction { /// Has no effect on "file" nodes. pub fn tree_fold(tab: &mut Tab) -> Result<()> { tab.tree.toggle_fold(); - // let (tree, _, _) = tab.directory.tree.explore_position(false); - // tree.node.toggle_fold(); - // tab.directory.make_preview(); - // tab.tree_select_next() Ok(()) } @@ -907,9 +903,7 @@ impl EventAction { /// Recursively explore the tree and unfold every node. /// Reset the display. pub fn tree_unfold_all(tab: &mut Tab) -> Result<()> { - tab.tree.fold_all(); - // tab.directory.tree.unfold_children(); - // tab.directory.make_preview(); + tab.tree.unfold_all(); Ok(()) } @@ -918,8 +912,6 @@ impl EventAction { /// Reset the display. pub fn tree_fold_all(tab: &mut Tab) -> Result<()> { tab.tree.fold_all(); - // tab.directory.tree.fold_children(); - // tab.directory.make_preview(); Ok(()) } diff --git a/src/trees.rs b/src/trees.rs index c7358732..ad94120b 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -281,6 +281,12 @@ impl FileSystem { } } + pub fn unfold_all(&mut self) { + for (_, node) in self.nodes.iter_mut() { + node.unfold() + } + } + // FIX: can only find the first match and nothing else pub fn search_first_match(&mut self, pattern: &str) { let initial_selected = self.selected.to_owned(); From 24047aeb4af565ba1eb25d6948aeec8ea83c7531 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 18:29:20 +0100 Subject: [PATCH 124/168] tree page up page down --- src/tab.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index cde51108..e9c39e5c 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -353,16 +353,18 @@ impl Tab { /// Move down 10 times in the tree pub fn tree_page_down(&mut self) -> Result<()> { - self.directory.tree.increase_required_height_by_ten(); - self.directory.unselect_children(); - self.directory.page_down() + for _ in 1..10 { + self.tree.select_next()?; + } + Ok(()) } /// Move up 10 times in the tree pub fn tree_page_up(&mut self) -> Result<()> { - self.directory.tree.decrease_required_height_by_ten(); - self.directory.unselect_children(); - self.directory.page_up() + for _ in 1..10 { + self.tree.select_prev(); + } + Ok(()) } /// Select the next sibling. From cd52d2a0379b51861fd9b18a90db41809b655c68 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 18:37:16 +0100 Subject: [PATCH 125/168] tree select last. Ugly fix --- src/tab.rs | 5 ++--- src/trees.rs | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index e9c39e5c..2abae8ff 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -386,9 +386,8 @@ impl Tab { /// Go to the last leaf. pub fn tree_go_to_bottom_leaf(&mut self) -> Result<()> { - self.directory.tree.set_required_height_to_max(); - self.directory.unselect_children(); - self.directory.go_to_bottom_leaf() + self.tree.select_last(); + Ok(()) } /// Returns the current path. diff --git a/src/trees.rs b/src/trees.rs index ad94120b..0b77f621 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -70,6 +70,7 @@ impl Node { pub struct FileSystem { root_path: PathBuf, selected: PathBuf, + last_path: PathBuf, nodes: HashMap, required_height: usize, } @@ -90,6 +91,7 @@ impl FileSystem { let mut stack = vec![root_path.to_owned()]; let mut nodes: HashMap = HashMap::new(); + let mut last_path = root_path.to_owned(); while let Some(path) = stack.pop() { let reached_depth = path.components().collect::>().len(); if reached_depth >= depth + start_depth { @@ -114,6 +116,7 @@ impl FileSystem { }; } } + last_path = node.path.to_owned(); nodes.insert(node.path.to_owned(), node); } @@ -124,6 +127,7 @@ impl FileSystem { Self { selected: root_path.clone(), root_path, + last_path, nodes, required_height: Self::REQUIRED_HEIGHT, } @@ -133,6 +137,7 @@ impl FileSystem { Self { root_path: PathBuf::default(), selected: PathBuf::default(), + last_path: PathBuf::default(), nodes: HashMap::new(), required_height: 0, } @@ -266,7 +271,17 @@ impl FileSystem { self.selected = self.root_path.to_owned(); } - pub fn select_last(&mut self) {} + pub fn select_last(&mut self) { + let Some(selected_node) = self.nodes.get_mut(&self.selected) else { + unreachable!("selected path should be in node") + }; + selected_node.unselect(); + let Some(last_node) = self.nodes.get_mut(&self.last_path) else { + unreachable!("root path should be in nodes") + }; + last_node.select(); + self.selected = self.last_path.to_owned(); + } /// Fold selected node pub fn toggle_fold(&mut self) { From e5a0a45532d3f537780f3f56b48ad1d20e9df1e9 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sun, 5 Nov 2023 22:28:17 +0100 Subject: [PATCH 126/168] Tree first draft. No refactor, no test, no doc --- src/completion.rs | 12 ++- src/event_exec.rs | 59 +++++++------- src/preview.rs | 195 +++++--------------------------------------- src/status.rs | 38 ++++----- src/tab.rs | 111 +++++++++---------------- src/term_manager.rs | 42 ++-------- src/trees.rs | 66 ++++++++++++--- 7 files changed, 176 insertions(+), 347 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index fca4559f..38ed6150 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -4,7 +4,6 @@ use anyhow::Result; use strum::IntoEnumIterator; use crate::fileinfo::PathContent; -use crate::preview::ColoredTriplet; /// Different kind of completions #[derive(Clone, Default, Copy)] @@ -216,13 +215,18 @@ impl Completion { pub fn search_from_tree( &mut self, input_string: &str, - content: &[ColoredTriplet], + content: &[&std::ffi::OsStr], ) -> Result<()> { self.update( content .iter() - .filter(|(_, _, s)| s.text.contains(input_string)) - .map(|(_, _, s)| s.text.replace("▸ ", "").replace("▾ ", "")) + .filter(|&p| p.to_string_lossy().contains(input_string)) + .map(|p| { + p.to_string_lossy() + .into_owned() + .replace("▸ ", "") + .replace("▾ ", "") + }) .collect(), ); diff --git a/src/event_exec.rs b/src/event_exec.rs index 6117a638..611f8961 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -73,18 +73,14 @@ impl EventAction { let tab = status.selected_non_mut(); match tab.mode { - Mode::Normal => { - let Some(file) = tab.path_content.selected() else { + Mode::Normal | Mode::Tree => { + let Ok(file) = tab.selected() else { return Ok(()); }; let path = file.path.clone(); status.toggle_flag_on_path(&path); status.selected().down_one_row(); } - Mode::Tree => { - let path = tab.directory.tree.current_node.filepath(); - status.toggle_flag_on_path(&path); - } _ => (), } Ok(()) @@ -378,7 +374,7 @@ impl EventAction { /// Basic folders (/, /dev... $HOME) and mount points (even impossible to /// visit ones) are proposed. pub fn shortcut(tab: &mut Tab) -> Result<()> { - std::env::set_current_dir(tab.current_directory_path())?; + std::env::set_current_dir(tab.current_directory_path().context("no parent")?)?; tab.shortcut.update_git_root(); tab.set_mode(Mode::Navigate(Navigate::Shortcut)); Ok(()) @@ -396,7 +392,7 @@ impl EventAction { }; let nvim_server = status.nvim_server.clone(); if status.flagged.is_empty() { - let Some(fileinfo) = status.selected_non_mut().selected() else { + let Ok(fileinfo) = status.selected_non_mut().selected() else { return Ok(()); }; let Some(path_str) = fileinfo.path.to_str() else { @@ -460,7 +456,7 @@ impl EventAction { log_line!("{DEFAULT_DRAGNDROP} must be installed."); return Ok(()); } - let Some(file) = status.selected_non_mut().selected() else { + let Ok(file) = status.selected_non_mut().selected() else { return Ok(()); }; let path_str = file @@ -539,7 +535,7 @@ impl EventAction { let tab = status.selected(); match tab.mode { Mode::Normal => tab.move_to_parent()?, - Mode::Tree => tab.tree_select_parent()?, + Mode::Tree => tab.tree_select_parent(), Mode::InputSimple(_) | Mode::InputCompleted(_) => { tab.input.cursor_left(); } @@ -556,7 +552,11 @@ impl EventAction { match tab.mode { Mode::Normal => LeaveMode::open_file(status), Mode::Tree => { - tab.select_first_child()?; + if tab.tree.selected_path().is_file() { + tab.tree_select_next()?; + } else { + LeaveMode::open_file(status)?; + }; status.update_second_pane_for_preview() } Mode::InputSimple(_) | Mode::InputCompleted(_) => { @@ -799,7 +799,7 @@ impl EventAction { return Ok(()); } if let Mode::Normal | Mode::Tree = tab.mode { - let Some(file_info) = tab.selected() else { + let Ok(file_info) = tab.selected() else { return Ok(()); }; info!("selected {:?}", file_info); @@ -1099,7 +1099,11 @@ impl Display for NodeCreation { impl NodeCreation { fn create(&self, tab: &mut Tab) -> Result<()> { let root_path = match tab.previous_mode { - Mode::Tree => tab.directory.tree.directory_of_selected()?.to_owned(), + Mode::Tree => tab + .tree + .directory_of_selected() + .context("no parent")? + .to_owned(), _ => tab.path_content.path.clone(), }; log::info!("root_path: {root_path:?}"); @@ -1137,6 +1141,9 @@ impl LeaveMode { /// Open the file with configured opener or enter the directory. pub fn open_file(status: &mut Status) -> Result<()> { let tab = status.selected(); + if matches!(tab.mode, Mode::Tree) { + return EventAction::open_file(status); + }; if tab.path_content.is_empty() { return Ok(()); } @@ -1292,15 +1299,15 @@ impl LeaveMode { /// We only try to rename in the same directory, so it shouldn't be a problem. /// Filename is sanitized before processing. pub fn rename(tab: &mut Tab) -> Result<()> { - let fileinfo = match tab.previous_mode { - Mode::Tree => &tab.directory.tree.current_node.fileinfo, - _ => tab - .path_content + let original_path = if let Mode::Tree = tab.mode { + tab.tree.selected_path() + } else { + tab.path_content .selected() - .context("rename: couldnt parse selected")?, + .context("rename: couldn't parse selected file")? + .path + .as_path() }; - - let original_path = &fileinfo.path; if let Some(parent) = original_path.parent() { let new_path = parent.join(sanitize_filename::sanitize(tab.input.string())); info!( @@ -1438,17 +1445,10 @@ impl LeaveMode { status.update_second_pane_for_preview() } + // TODO! enter the tree if it's a directory /// Execute the selected node if it's a file else enter the directory. pub fn tree(status: &mut Status) -> Result<()> { - let tab = status.selected(); - let node = tab.directory.tree.current_node.clone(); - if !node.is_dir { - EventAction::open_file(status) - } else { - tab.set_pathcontent(&node.filepath())?; - tab.make_tree(None)?; - Ok(()) - } + EventAction::open_file(status) } /// Store a password of some kind (sudo or device passphrase). @@ -1548,6 +1548,7 @@ impl LeaveMode { let (username, hostname, remote_path) = (strings[0], strings[1], strings[2]); let current_path: &str = tab .current_directory_path() + .context("no parent")? .to_str() .context("couldn't parse the path")?; let first_arg = &format!("{username}@{hostname}:{remote_path}"); diff --git a/src/preview.rs b/src/preview.rs index 89d656d3..221bf2c3 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -24,7 +24,9 @@ use crate::decompress::{list_files_tar, list_files_zip}; use crate::fileinfo::{FileInfo, FileKind}; use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; -use crate::tree::{ColoredString, Tree}; +use crate::sort::SortKind; +use crate::tree::ColoredString; +use crate::trees::FileSystem; use crate::users::Users; use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path}; @@ -114,19 +116,11 @@ impl Preview { /// Creates a new `Directory` from the file_info /// It explores recursivelly the directory and creates a tree. /// The recursive exploration is limited to depth 2. - pub fn directory( - file_info: &FileInfo, - users: &Users, - filter: &FilterKind, - show_hidden: bool, - ) -> Result { + pub fn directory(file_info: &FileInfo, users: &Users) -> Result { Ok(Self::Directory(Directory::new( - &file_info.path, + file_info.path.to_owned(), users, - filter, - show_hidden, - Some(2), - )?)) + ))) } /// Creates a new preview instance based on the filekind and the extension of @@ -1049,181 +1043,34 @@ impl ColoredText { #[derive(Clone, Debug)] pub struct Directory { pub content: Vec, - pub tree: Tree, - len: usize, - pub selected_index: usize, } impl Directory { - /// Creates a new tree view of the directory. - /// We only hold the result here, since the tree itself has now usage atm. - pub fn new( - path: &Path, - users: &Users, - filter_kind: &FilterKind, - show_hidden: bool, - max_depth: Option, - ) -> Result { - let max_depth = match max_depth { - Some(max_depth) => max_depth, - None => Tree::MAX_DEPTH, - }; + pub fn new(path: PathBuf, users: &Users) -> Self { + let tree = FileSystem::new( + path, + 4, + SortKind::tree_default(), + users, + false, + &FilterKind::All, + ); - let mut tree = Tree::from_path(path, max_depth, users, filter_kind, show_hidden, vec![0])?; - tree.select_root(); - let (selected_index, content) = tree.into_navigable_content(); - Ok(Self { - tree, - len: content.len(), - content, - selected_index, - }) - } + let (_selected_index, content) = tree.into_navigable_content(users); - /// Creates an empty directory preview. - pub fn empty(path: &Path, users: &Users) -> Result { - Ok(Self { - tree: Tree::empty(path, users)?, - len: 0, - content: vec![], - selected_index: 0, - }) + Self { content } } - /// Reset the attributes to default one and free some unused memory. - pub fn clear(&mut self) { - self.len = 0; - self.content = vec![]; - self.selected_index = 0; - self.tree.clear(); + pub fn empty() -> Self { + Self { content: vec![] } } - /// Number of displayed lines. pub fn len(&self) -> usize { - self.len + self.content.len() } - /// True if there's no lines in preview. pub fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Select the root node and reset the view. - pub fn select_root(&mut self) -> Result<()> { - self.tree.select_root(); - (self.selected_index, self.content) = self.tree.into_navigable_content(); - self.update_tree_position_from_index()?; - Ok(()) - } - - /// Unselect every child node. - pub fn unselect_children(&mut self) { - self.tree.unselect_children() - } - - /// Select the "next" element of the tree if any. - /// This is the element immediatly below the current one. - pub fn select_next(&mut self) -> Result<()> { - if self.selected_index < self.content.len() { - self.tree.increase_required_height(); - self.unselect_children(); - self.selected_index += 1; - self.update_tree_position_from_index()?; - } - Ok(()) - } - - /// Select the previous sibling if any. - /// This is the element immediatly below the current one. - pub fn select_prev(&mut self) -> Result<()> { - if self.selected_index > 0 { - self.tree.decrease_required_height(); - self.unselect_children(); - self.selected_index -= 1; - self.update_tree_position_from_index()?; - } - Ok(()) - } - - /// Move up 10 times. - pub fn page_up(&mut self) -> Result<()> { - if self.selected_index > 10 { - self.selected_index -= 10; - } else { - self.selected_index = 1; - } - self.update_tree_position_from_index() - } - - /// Move down 10 times - pub fn page_down(&mut self) -> Result<()> { - self.selected_index += 10; - if self.selected_index > self.content.len() { - if !self.content.is_empty() { - self.selected_index = self.content.len(); - } else { - self.selected_index = 1; - } - } - self.update_tree_position_from_index() - } - - /// Update the position of the selected element from its index. - pub fn update_tree_position_from_index(&mut self) -> Result<()> { - self.tree.position = self.tree.position_from_index(self.selected_index); - let (_, _, node) = self.tree.select_from_position()?; - self.tree.current_node = node; - (_, self.content) = self.tree.into_navigable_content(); - Ok(()) - } - - /// Select the first child, if any. - pub fn select_first_child(&mut self) -> Result<()> { - self.tree.select_first_child()?; - (self.selected_index, self.content) = self.tree.into_navigable_content(); - self.update_tree_position_from_index()?; - Ok(()) - } - - /// Select the parent of current node. - pub fn select_parent(&mut self) -> Result<()> { - self.tree.select_parent()?; - (self.selected_index, self.content) = self.tree.into_navigable_content(); - self.update_tree_position_from_index()?; - Ok(()) - } - - /// Select the last leaf of the tree (ie the last line.) - pub fn go_to_bottom_leaf(&mut self) -> Result<()> { - self.tree.go_to_bottom_leaf()?; - (self.selected_index, self.content) = self.tree.into_navigable_content(); - self.update_tree_position_from_index()?; - Ok(()) - } - - /// Make a preview of the tree. - pub fn make_preview(&mut self) { - (self.selected_index, self.content) = self.tree.into_navigable_content(); - } - - /// Calculates the top, bottom and lenght of the view, depending on which element - /// is selected and the size of the window used to display. - pub fn calculate_tree_window(&self, terminal_height: usize) -> (usize, usize, usize) { - let length = self.content.len(); - - let top: usize; - let bottom: usize; - let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP; - if self.selected_index < terminal_height - 1 { - top = 0; - bottom = window_height; - } else { - let padding = std::cmp::max(10, terminal_height / 2); - top = self.selected_index - padding; - bottom = top + window_height; - } - - (top, bottom, length) + self.content.is_empty() } } diff --git a/src/status.rs b/src/status.rs index 9fe1871a..341585c5 100644 --- a/src/status.rs +++ b/src/status.rs @@ -32,7 +32,7 @@ use crate::password::{ drop_sudo_privileges, execute_sudo_command_with_password, reset_sudo_faillock, PasswordHolder, PasswordKind, PasswordUsage, }; -use crate::preview::{Directory, Preview}; +use crate::preview::Preview; use crate::removable_devices::RemovableDevices; use crate::selectable_content::SelectableContent; use crate::shell_menu::ShellMenu; @@ -408,9 +408,9 @@ impl Status { let dest = match self.selected_non_mut().previous_mode { Mode::Tree => self .selected_non_mut() - .directory .tree - .directory_of_selected()? + .directory_of_selected() + .context("no parent")? .display() .to_string(), _ => self @@ -582,9 +582,7 @@ impl Status { /// Drop the current tree, replace it with an empty one. pub fn remove_tree(&mut self) -> Result<()> { - let path = self.selected_non_mut().path_content.path.clone(); - let users = &self.selected_non_mut().users; - self.selected().directory = Directory::empty(&path, users)?; + self.selected().tree = FileSystem::empty(); Ok(()) } @@ -598,12 +596,12 @@ impl Status { if self.selected_non_mut().path_content.is_empty() { return Ok(()); } - let Some(file_info) = self.selected_non_mut().selected() else { + let Ok(file_info) = self.selected_non_mut().selected() else { return Ok(()); }; match file_info.file_kind { FileKind::NormalFile => { - let preview = Preview::file(file_info).unwrap_or_default(); + let preview = Preview::file(&file_info).unwrap_or_default(); self.selected().set_mode(Mode::Preview); self.selected().window.reset(preview.len()); self.selected().preview = preview; @@ -652,13 +650,8 @@ impl Status { .selected() .context("force preview: No file to select")?; let preview = match fileinfo.file_kind { - FileKind::Directory => Preview::directory( - fileinfo, - &self.tabs[0].users, - &self.tabs[0].filter, - self.tabs[0].show_hidden, - ), - _ => Preview::file(fileinfo), + FileKind::Directory => Preview::directory(&fileinfo, &self.tabs[0].users), + _ => Preview::file(&fileinfo), }; self.tabs[1].preview = preview.unwrap_or_default(); @@ -691,17 +684,16 @@ impl Status { /// Open a the selected file with its opener pub fn open_selected_file(&mut self) -> Result<()> { - let filepath = &self - .selected_non_mut() - .selected() - .context("Empty directory")? - .path - .clone(); - let opener = self.opener.open_info(filepath); + let filepath = if matches!(self.selected_non_mut().mode, Mode::Tree) { + self.selected_non_mut().tree.selected_path().to_owned() + } else { + self.selected_non_mut().selected()?.path.to_owned() + }; + let opener = self.opener.open_info(&filepath); if let Some(InternalVariant::NotSupported) = opener.internal_variant.as_ref() { self.mount_iso_drive()?; } else { - match self.opener.open(filepath) { + match self.opener.open(&filepath) { Ok(_) => (), Err(e) => info!( "Error opening {:?}: {:?}", diff --git a/src/tab.rs b/src/tab.rs index 2abae8ff..ddf6f681 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -13,13 +13,13 @@ use crate::history::History; use crate::input::Input; use crate::mode::{InputSimple, Mode}; use crate::opener::execute_in_child; -use crate::preview::{Directory, Preview}; +use crate::preview::Preview; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; use crate::sort::SortKind; -use crate::trees::FileSystem; +use crate::trees::{calculate_tree_window, FileSystem}; use crate::users::Users; -use crate::utils::{filename_from_path, row_to_window_index, set_clipboard}; +use crate::utils::{row_to_window_index, set_clipboard}; /// Holds every thing about the current tab of the application. /// Most of the mutation is done externally. @@ -50,8 +50,6 @@ pub struct Tab { pub shortcut: Shortcut, /// Last searched string pub searched: Option, - /// Optional tree view - pub directory: Directory, /// The filter use before displaying files pub filter: FilterKind, /// Visited directories @@ -76,7 +74,6 @@ impl Tab { } else { path.parent().context("")? }; - let directory = Directory::empty(start_dir, &users)?; let filter = FilterKind::All; let show_hidden = args.all || settings.all; let mut path_content = PathContent::new(start_dir, &users, &filter, show_hidden)?; @@ -106,7 +103,6 @@ impl Tab { preview, shortcut, searched, - directory, filter, show_hidden, history, @@ -135,7 +131,7 @@ impl Tab { if matches!(self.previous_mode, Mode::Tree) => { self.completion - .search_from_tree(&self.input.string(), &self.directory.content) + .search_from_tree(&self.input.string(), &self.tree.paths()) } Mode::InputCompleted(InputCompleted::Command) => { self.completion.command(&self.input.string()) @@ -150,7 +146,7 @@ impl Tab { self.input.reset(); self.preview = Preview::empty(); self.completion.reset(); - self.directory.clear(); + self.tree = FileSystem::empty(); Ok(()) } @@ -303,12 +299,6 @@ impl Tab { } } - /// Select the root node of the tree. - pub fn tree_select_root(&mut self) -> Result<()> { - self.directory.unselect_children(); - self.directory.select_root() - } - /// Move to the parent of current path pub fn move_to_parent(&mut self) -> Result<()> { let path = self.path_content.path.clone(); @@ -342,13 +332,8 @@ impl Tab { /// Select the parent of current node. /// If we were at the root node, move to the parent and make a new tree. - pub fn tree_select_parent(&mut self) -> Result<()> { - self.directory.unselect_children(); - if self.directory.tree.position.len() <= 1 { - self.move_to_parent()?; - self.make_tree(None)? - } - self.directory.select_parent() + pub fn tree_select_parent(&mut self) { + self.tree.select_parent() } /// Move down 10 times in the tree @@ -378,12 +363,6 @@ impl Tab { Ok(()) } - /// Select the first child if any. - pub fn tree_select_first_child(&mut self) -> Result<()> { - self.directory.unselect_children(); - self.directory.select_first_child() - } - /// Go to the last leaf. pub fn tree_go_to_bottom_leaf(&mut self) -> Result<()> { self.tree.select_last(); @@ -395,19 +374,10 @@ impl Tab { /// if the selected node is a directory, that's it. /// else, it is the parent of the selected node. /// In other modes, it's the current path of pathcontent. - pub fn current_directory_path(&mut self) -> &path::Path { + pub fn current_directory_path(&mut self) -> Option<&path::Path> { match self.mode { - Mode::Tree => { - let path = &self.directory.tree.current_node.fileinfo.path; - if path.is_dir() { - return path; - } - let Some(parent) = path.parent() else { - return path::Path::new("/"); - }; - parent - } - _ => &self.path_content.path, + Mode::Tree => return self.tree.directory_of_selected(), + _ => Some(&self.path_content.path), } } @@ -417,16 +387,23 @@ impl Tab { /// In normal mode it's the current working directory. pub fn directory_of_selected(&self) -> Result<&path::Path> { match self.mode { - Mode::Tree => self.directory.tree.directory_of_selected(), + Mode::Tree => self.tree.directory_of_selected().context("No parent"), _ => Ok(&self.path_content.path), } } /// Optional Fileinfo of the selected element. - pub fn selected(&self) -> Option<&FileInfo> { + pub fn selected(&self) -> Result { match self.mode { - Mode::Tree => Some(&self.directory.tree.current_node.fileinfo), - _ => self.path_content.selected(), + Mode::Tree => { + let node = self.tree.selected_node().context("no selected node")?; + node.fileinfo(&self.users) + } + _ => Ok(self + .path_content + .selected() + .context("no selected file")? + .to_owned()), } } @@ -536,11 +513,6 @@ impl Tab { Ok(()) } - /// Select the first child of the current node and reset the display. - pub fn select_first_child(&mut self) -> Result<()> { - self.tree_select_first_child() - } - /// Copy the selected filename to the clipboard. Only the filename. pub fn filename_to_clipboard(&self) -> Result<()> { set_clipboard( @@ -659,15 +631,12 @@ impl Tab { } fn tree_select_row(&mut self, row: u16, term_height: usize) -> Result<()> { - let screen_index = row_to_window_index(row) + 1; - // term.height = canvas.height + 2 rows for the canvas border - let (top, _, _) = self.directory.calculate_tree_window(term_height - 2); + let screen_index = row_to_window_index(row); + let (selected_index, content) = self.tree.into_navigable_content(&self.users); + let (top, _, _) = calculate_tree_window(selected_index, term_height, term_height); let index = screen_index + top; - self.directory.tree.unselect_children(); - self.directory.tree.position = self.directory.tree.position_from_index(index); - let (_, _, node) = self.directory.tree.select_from_position()?; - self.directory.make_preview(); - self.directory.tree.current_node = node; + let (_, _, colored_path) = content.get(index).context("no selected file")?; + self.tree.select_from_path(&colored_path.path); Ok(()) } @@ -723,21 +692,21 @@ impl Tab { } pub fn rename(&mut self) -> Result<()> { - if self.selected().is_some() { - let old_name = match self.mode { - Mode::Tree => self.directory.tree.current_node.filename(), - _ => filename_from_path( - &self - .path_content - .selected() - .context("Event rename: no file in current directory")? - .path, - )? - .to_owned(), + let old_name: String = if matches!(self.mode, Mode::Tree) { + self.tree + .selected_path() + .file_name() + .context("no filename")? + .to_string_lossy() + .into() + } else { + let Ok(fileinfo) = self.selected() else { + return Ok(()); }; - self.input.replace(&old_name); - self.set_mode(Mode::InputSimple(InputSimple::Rename)); - } + fileinfo.filename.to_owned() + }; + self.input.replace(&old_name); + self.set_mode(Mode::InputSimple(InputSimple::Rename)); Ok(()) } } diff --git a/src/term_manager.rs b/src/term_manager.rs index e7b57494..15bbf61a 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -198,10 +198,10 @@ impl<'a> WinMain<'a> { match tab.mode { Mode::Normal | Mode::Tree => { if !status.display_full { - let Some(file) = tab.selected() else { + let Ok(file) = tab.selected() else { return Ok(0); }; - self.second_line_detailed(file, status, canvas) + self.second_line_detailed(&file, status, canvas) } else { self.second_line_simple(status, canvas) } @@ -296,7 +296,7 @@ impl<'a> WinMain<'a> { ] } - fn pick_previewed_fileinfo(&self) -> Option<&FileInfo> { + fn pick_previewed_fileinfo(&self) -> Result { if self.status.dual_pane && self.status.preview_second { self.status.tabs[0].selected() } else { @@ -305,7 +305,7 @@ impl<'a> WinMain<'a> { } fn default_preview_first_line(&self, tab: &Tab) -> Vec { - if let Some(fileinfo) = self.pick_previewed_fileinfo() { + if let Ok(fileinfo) = self.pick_previewed_fileinfo() { let mut strings = vec![" Preview ".to_owned()]; if !tab.preview.is_empty() { let index = match &tab.preview { @@ -394,36 +394,6 @@ impl<'a> WinMain<'a> { Ok(()) } - fn _tree(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { - let left_margin = if status.display_full { 1 } else { 3 }; - let (_, height) = canvas.size()?; - let (top, bottom, len) = tab.directory.calculate_tree_window(height); - - for (i, (metadata, prefix, colored_string)) in tab.directory.window(top, bottom, len) { - let row = i + ContentWindow::WINDOW_MARGIN_TOP - top; - let mut attr = colored_string.color_effect.attr(); - if status.flagged.contains(&colored_string.path) { - attr.effect |= Effect::BOLD; - canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; - } - - let col_metadata = if status.display_full { - canvas.print_with_attr(row, left_margin, metadata, attr)? - } else { - 0 - }; - let col_tree_prefix = canvas.print(row, left_margin + col_metadata, prefix)?; - canvas.print_with_attr( - row, - left_margin + col_metadata + col_tree_prefix, - &colored_string.text, - attr, - )?; - } - self.second_line(status, tab, canvas)?; - Ok(()) - } - fn trees(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { let left_margin = if status.display_full { 1 } else { 3 }; let (_, height) = canvas.size()?; @@ -975,9 +945,9 @@ impl<'a> WinSecondary<'a> { } let dest = match tab.previous_mode { Mode::Tree => tab - .directory .tree - .directory_of_selected()? + .directory_of_selected() + .context("No directory_of_selected")? .display() .to_string(), _ => tab.path_content.path_to_str(), diff --git a/src/trees.rs b/src/trees.rs index 0b77f621..a016ca76 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -143,8 +143,12 @@ impl FileSystem { } } - pub fn sort(&mut self, _sort_kind: SortKind) -> Result<()> { - todo!() + pub fn selected_path(&self) -> &Path { + self.selected.as_path() + } + + pub fn selected_node(&self) -> Option<&Node> { + self.nodes.get(&self.selected) } /// Select next sibling or the next sibling of the parent @@ -215,7 +219,6 @@ impl FileSystem { } None } - // TODO! find the bottom child of parent instead of jumping back 1 level /// Select previous sibling or the parent pub fn select_prev(&mut self) { @@ -283,6 +286,32 @@ impl FileSystem { self.selected = self.last_path.to_owned(); } + pub fn select_parent(&mut self) { + if let Some(parent_path) = self.selected.parent() { + let Some(parent_node) = self.nodes.get_mut(parent_path) else { + return; + }; + parent_node.select(); + let Some(selected_node) = self.nodes.get_mut(&self.selected) else { + unreachable!("current_node should be in nodes"); + }; + selected_node.unselect(); + self.selected = parent_path.to_owned(); + } + } + + pub fn select_from_path(&mut self, clicked_path: &Path) { + let Some(new_node) = self.nodes.get_mut(clicked_path) else { + return; + }; + new_node.select(); + let Some(selected_node) = self.nodes.get_mut(&self.selected) else { + unreachable!("current_node should be in nodes"); + }; + selected_node.unselect(); + self.selected = clicked_path.to_owned(); + } + /// Fold selected node pub fn toggle_fold(&mut self) { if let Some(node) = self.nodes.get_mut(&self.selected) { @@ -302,6 +331,14 @@ impl FileSystem { } } + pub fn directory_of_selected(&self) -> Option<&Path> { + if self.selected.is_dir() && !self.selected.is_symlink() { + Some(self.selected.as_path()) + } else { + self.selected.parent() + } + } + // FIX: can only find the first match and nothing else pub fn search_first_match(&mut self, pattern: &str) { let initial_selected = self.selected.to_owned(); @@ -327,8 +364,8 @@ impl FileSystem { let mut content = vec![]; let mut selected_index = 0; - while let Some((prefix, current)) = stack.pop() { - let Some(current_node) = &self.nodes.get(current) else { + while let Some((prefix, current_path)) = stack.pop() { + let Some(current_node) = &self.nodes.get(current_path) else { continue; }; @@ -336,16 +373,18 @@ impl FileSystem { selected_index = content.len(); } - let Ok(fileinfo) = FileInfo::new(current, users) else { + let Ok(fileinfo) = FileInfo::new(current_path, users) else { continue; }; - let filename = filename_from_path(current).unwrap_or_default().to_owned(); + let filename = filename_from_path(current_path) + .unwrap_or_default() + .to_owned(); let mut color_effect = ColorEffect::new(&fileinfo); if current_node.selected { color_effect.effect |= tuikit::attr::Effect::REVERSE; } - let filename_text = if current.is_dir() && !current.is_symlink() { + let filename_text = if current_path.is_dir() && !current_path.is_symlink() { if current_node.folded { format!("▸ {}", filename) } else { @@ -357,10 +396,10 @@ impl FileSystem { content.push(( fileinfo.format_no_filename().unwrap_or_default(), prefix.to_owned(), - ColoredString::new(filename_text, color_effect, current.to_owned()), + ColoredString::new(filename_text, color_effect, current_path.to_owned()), )); - if current.is_dir() && !current.is_symlink() && !current_node.folded { + if current_path.is_dir() && !current_path.is_symlink() && !current_node.folded { let first_prefix = first_prefix(prefix.clone()); let other_prefix = other_prefix(prefix); @@ -383,6 +422,13 @@ impl FileSystem { } (selected_index, content) } + + pub fn paths(&self) -> Vec<&std::ffi::OsStr> { + self.nodes + .keys() + .filter_map(|path| path.file_name()) + .collect() + } } fn first_prefix(mut prefix: String) -> String { From c62e8e8615bc78b7955b37c68250097708c22a4e Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 13:22:39 +0100 Subject: [PATCH 127/168] dev, remove useless dep --- Cargo.toml | 1 - development.md | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 522a8a53..53795c93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ copypasta = "0.8.1" flate2 = "1.0" fs_extra = "1.2.0" indicatif = { version = "0.17.1", features= ["in_memory"] } -itertools = "0.11.0" lazy_static = "1.4.0" log = { version = "0.4.0", features = ["std"] } log4rs = { version = "1.2.0", features = ["rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } diff --git a/development.md b/development.md index 7cd376bb..9e4e6559 100644 --- a/development.md +++ b/development.md @@ -605,6 +605,15 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself +- [ ] Tree remade without recursion. Improve ram usage + - [ ] FIX: folders are max depth hangs the app + - [ ] FIX: rename renames the root path + - [ ] FIX: scrolling to bottom of tree is bugged + - [ ] search can only find the first match + - [ ] can't "open" a folder to redo the tree there + - [ ] test everything + - [ ] refactor + - [ ] document ## TODO From f8fdb56ba5054f21372287391975328e6d114961 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 13:26:40 +0100 Subject: [PATCH 128/168] remove tree module --- Cargo.lock | 10 - src/lib.rs | 1 - src/preview.rs | 3 +- src/sort.rs | 24 +- src/tree.rs | 593 ------------------------------------------------- src/trees.rs | 23 +- 6 files changed, 24 insertions(+), 630 deletions(-) delete mode 100644 src/tree.rs diff --git a/Cargo.lock b/Cargo.lock index 2d98cf3a..06c4b63b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -808,7 +808,6 @@ dependencies = [ "flate2", "fs_extra", "indicatif", - "itertools", "lazy_static", "log", "log4rs", @@ -1197,15 +1196,6 @@ dependencies = [ "libc 0.2.149", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" diff --git a/src/lib.rs b/src/lib.rs index c1e0b659..013318fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,6 @@ pub mod status; pub mod tab; pub mod term_manager; pub mod trash; -pub mod tree; pub mod trees; pub mod users; pub mod utils; diff --git a/src/preview.rs b/src/preview.rs index 221bf2c3..4bfde0fd 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -25,8 +25,7 @@ use crate::fileinfo::{FileInfo, FileKind}; use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; use crate::sort::SortKind; -use crate::tree::ColoredString; -use crate::trees::FileSystem; +use crate::trees::{ColoredString, FileSystem}; use crate::users::Users; use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path}; diff --git a/src/sort.rs b/src/sort.rs index 8d215881..3425fd3b 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use crate::{fileinfo::FileInfo, tree::Tree}; +use crate::fileinfo::FileInfo; /// Different kind of sort #[derive(Debug, Clone, Default)] @@ -118,7 +118,6 @@ impl SortKind { slice.sort_unstable_by(|a, b| Ordering::reverse(f(a).cmp(f(b)))) } - // TODO! refactor both methods. // A second version should take 2 parameters. // 1. the way to access the data depending on T where files: &mut [T], // 2. a closure returning the correct data. @@ -143,27 +142,6 @@ impl SortKind { } } } - - /// Sort leaves of a tree depending of enum variants. - pub fn sort_tree(&self, trees: &mut [Tree]) { - if let Order::Ascending = self.order { - match self.sort_by { - SortBy::Kind => Self::sort_by_key_hrtb(trees, |f| &f.file().kind_format), - SortBy::File => Self::sort_by_key_hrtb(trees, |f| &f.file().filename), - SortBy::Date => Self::sort_by_key_hrtb(trees, |f| &f.file().system_time), - SortBy::Size => Self::sort_by_key_hrtb(trees, |f| &f.file().true_size), - SortBy::Exte => Self::sort_by_key_hrtb(trees, |f| &f.file().extension), - } - } else { - match self.sort_by { - SortBy::Kind => Self::reversed_sort_by_key_hrtb(trees, |f| &f.file().kind_format), - SortBy::File => Self::reversed_sort_by_key_hrtb(trees, |f| &f.file().filename), - SortBy::Date => Self::reversed_sort_by_key_hrtb(trees, |f| &f.file().system_time), - SortBy::Size => Self::reversed_sort_by_key_hrtb(trees, |f| &f.file().true_size), - SortBy::Exte => Self::reversed_sort_by_key_hrtb(trees, |f| &f.file().extension), - } - } - } } impl std::fmt::Display for SortKind { diff --git a/src/tree.rs b/src/tree.rs deleted file mode 100644 index cb63eba8..00000000 --- a/src/tree.rs +++ /dev/null @@ -1,593 +0,0 @@ -use std::path::Path; - -use anyhow::{Context, Result}; - -use crate::fileinfo::{files_collection, ColorEffect, FileInfo, FileKind}; -use crate::filter::FilterKind; -use crate::preview::ColoredTriplet; -use crate::sort::SortKind; -use crate::users::Users; -use crate::utils::filename_from_path; - -/// Holds a string and its display attributes. -#[derive(Clone, Debug)] -pub struct ColoredString { - /// A text to be printed. In most case, it should be a filename. - pub text: String, - /// A tuikit::attr::Attr (fg, bg, effect) to enhance the text. - pub color_effect: ColorEffect, - /// The complete path of this string. - pub path: std::path::PathBuf, -} - -impl ColoredString { - pub fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { - Self { - text, - color_effect, - path, - } - } - - fn from_node(current_node: &Node) -> Self { - let text = if current_node.is_dir { - if current_node.folded { - format!("▸ {}", ¤t_node.fileinfo.filename) - } else { - format!("▾ {}", ¤t_node.fileinfo.filename) - } - } else { - current_node.filename() - }; - Self::new(text, current_node.color_effect(), current_node.filepath()) - } -} - -/// An element in a tree. -/// Can be a directory or a file (other kind of file). -/// Both hold a fileinfo -#[derive(Clone, Debug)] -pub struct Node { - pub fileinfo: FileInfo, - pub position: Vec, - pub folded: bool, - pub is_dir: bool, - pub metadata_line: String, -} - -impl Node { - /// Returns a copy of the filename. - pub fn filename(&self) -> String { - self.fileinfo.filename.to_owned() - } - - /// Returns a copy of the filepath. - pub fn filepath(&self) -> std::path::PathBuf { - self.fileinfo.path.to_owned() - } - - fn color_effect(&self) -> ColorEffect { - ColorEffect::new(&self.fileinfo) - } - - fn select(&mut self) { - self.fileinfo.select() - } - - fn unselect(&mut self) { - self.fileinfo.unselect() - } - - /// Toggle the fold status of a node. - pub fn toggle_fold(&mut self) { - self.folded = !self.folded; - } - - fn from_fileinfo(fileinfo: FileInfo, parent_position: Vec) -> Result { - let is_dir = matches!(fileinfo.file_kind, FileKind::Directory); - Ok(Self { - is_dir, - metadata_line: fileinfo.format_no_filename()?, - fileinfo, - position: parent_position, - folded: false, - }) - } - - fn empty(fileinfo: FileInfo) -> Self { - Self { - fileinfo, - position: vec![0], - folded: false, - is_dir: false, - metadata_line: "".to_owned(), - } - } -} - -/// Holds a recursive view of a directory. -/// Creation can be long as is explores every subfolder to a certain depth. -/// Parsing into a vector of "prefix" (String) and `ColoredString` is a depthfirst search -/// and it can be long too. -#[derive(Clone, Debug)] -pub struct Tree { - pub node: Node, - pub leaves: Vec, - pub position: Vec, - pub current_node: Node, - sort_kind: SortKind, - required_height: usize, -} - -impl Tree { - /// The max depth when exploring a tree. - /// ATM it's a constant, in future versions it may change - /// It may be better to stop the recursion when too much file - /// are present and the exploration is slow. - pub const MAX_DEPTH: usize = 5; - pub const REQUIRED_HEIGHT: usize = 80; - const MAX_INDEX: usize = 2 << 20; - - pub fn set_required_height_to_max(&mut self) { - self.set_required_height(Self::MAX_INDEX) - } - - /// Set the required height to a given value. - /// The required height is used to stop filling the view content. - pub fn set_required_height(&mut self, height: usize) { - self.required_height = height - } - - /// The required height is used to stop filling the view content. - pub fn increase_required_height(&mut self) { - if self.required_height < Self::MAX_INDEX { - self.required_height += 1; - } - } - - /// Add 10 to the required height. - /// The required height is used to stop filling the view content. - pub fn increase_required_height_by_ten(&mut self) { - if self.required_height < Self::MAX_INDEX { - self.required_height += 10; - } - } - - /// Reset the required height to its default value : Self::MAX_HEIGHT - /// The required height is used to stop filling the view content. - pub fn reset_required_height(&mut self) { - self.required_height = Self::REQUIRED_HEIGHT - } - - /// Decrement the required height if possible. - /// The required height is used to stop filling the view content. - pub fn decrease_required_height(&mut self) { - if self.required_height > Self::REQUIRED_HEIGHT { - self.required_height -= 1; - } - } - - /// Decrease the required height by 10 if possible - /// The required height is used to stop filling the view content. - pub fn decrease_required_height_by_ten(&mut self) { - if self.required_height >= Self::REQUIRED_HEIGHT + 10 { - self.required_height -= 10; - } - } - - /// Recursively explore every subfolder to a certain depth. - /// We start from `path` and add this node first. - /// Then, for every subfolder, we start again. - /// Files in path are added as simple nodes. - /// Both (subfolder and files) ends in a collections of leaves. - pub fn from_path( - path: &Path, - max_depth: usize, - users: &Users, - filter_kind: &FilterKind, - show_hidden: bool, - parent_position: Vec, - ) -> Result { - Self::create_tree_from_fileinfo( - FileInfo::from_path_with_name(path, filename_from_path(path)?, users)?, - max_depth, - users, - filter_kind, - show_hidden, - parent_position, - ) - } - - /// Clear every vector attributes of the tree. - /// It's used to free some unused memory. - pub fn clear(&mut self) { - self.leaves = vec![]; - self.position = vec![]; - } - - /// A reference to the holded node fileinfo. - pub fn file(&self) -> &FileInfo { - &self.node.fileinfo - } - - fn create_tree_from_fileinfo( - fileinfo: FileInfo, - max_depth: usize, - users: &Users, - filter_kind: &FilterKind, - display_hidden: bool, - parent_position: Vec, - ) -> Result { - let sort_kind = SortKind::tree_default(); - let leaves = Self::make_leaves( - &fileinfo, - max_depth, - users, - display_hidden, - filter_kind, - &sort_kind, - parent_position.clone(), - )?; - let node = Node::from_fileinfo(fileinfo, parent_position)?; - let position = vec![0]; - let current_node = node.clone(); - Ok(Self { - node, - leaves, - position, - current_node, - sort_kind, - required_height: Self::REQUIRED_HEIGHT, - }) - } - - fn make_leaves( - fileinfo: &FileInfo, - max_depth: usize, - users: &Users, - display_hidden: bool, - filter_kind: &FilterKind, - sort_kind: &SortKind, - parent_position: Vec, - ) -> Result> { - if max_depth == 0 { - return Ok(vec![]); - } - let FileKind::Directory = fileinfo.file_kind else { - return Ok(vec![]); - }; - let Some(mut files) = files_collection(fileinfo, users, display_hidden, filter_kind, true) - else { - return Ok(vec![]); - }; - sort_kind.sort(&mut files); - let leaves = files - .iter() - .enumerate() - .map(|(index, fileinfo)| { - let mut position = parent_position.clone(); - position.push(files.len() - index - 1); - Self::create_tree_from_fileinfo( - fileinfo.to_owned(), - max_depth - 1, - users, - filter_kind, - display_hidden, - position, - ) - }) - .filter_map(|r| r.ok()) - .collect(); - - Ok(leaves) - } - - /// Sort the leaves with current sort kind. - pub fn sort(&mut self) { - let sort_kind = self.sort_kind.clone(); - self.sort_tree_by_kind(&sort_kind); - } - - fn sort_tree_by_kind(&mut self, sort_kind: &SortKind) { - sort_kind.sort_tree(&mut self.leaves); - for tree in self.leaves.iter_mut() { - tree.sort_tree_by_kind(sort_kind); - } - } - - /// Creates an empty tree. Used when the user changes the CWD and hasn't displayed - /// a tree yet. - pub fn empty(path: &Path, users: &Users) -> Result { - let filename = filename_from_path(path)?; - let fileinfo = FileInfo::from_path_with_name(path, filename, users)?; - let node = Node::empty(fileinfo); - let leaves = vec![]; - let position = vec![0]; - let current_node = node.clone(); - let sort_kind = SortKind::tree_default(); - let required_height = 0; - Ok(Self { - node, - leaves, - position, - current_node, - sort_kind, - required_height, - }) - } - - pub fn update_sort_from_char(&mut self, c: char) { - self.sort_kind.update_from_char(c) - } - - /// Select the root node of the tree. - pub fn select_root(&mut self) { - self.node.select(); - self.position = vec![0] - } - - /// Unselect every node in the tree. - pub fn unselect_children(&mut self) { - self.node.unselect(); - for tree in self.leaves.iter_mut() { - tree.unselect_children() - } - } - - /// Fold every node in the tree. - pub fn fold_children(&mut self) { - self.node.folded = true; - for tree in self.leaves.iter_mut() { - tree.fold_children() - } - } - - /// Unfold every node in the tree. - pub fn unfold_children(&mut self) { - self.node.folded = false; - for tree in self.leaves.iter_mut() { - tree.unfold_children() - } - } - - /// Select the next "brother/sister" of a node. - /// Sibling have the same parents (ie. are in the same directory). - /// Since the position may be wrong (aka the current node is already the last child of - /// it's parent) we have to adjust the postion afterwards. - pub fn select_next_sibling(&mut self) -> Result<()> { - if self.position.is_empty() { - self.position = vec![0] - } else { - let len = self.position.len(); - self.position[len - 1] += 1; - let (depth, last_cord, node) = self.select_from_position()?; - self.fix_position(depth, last_cord); - self.current_node = node; - } - Ok(()) - } - - /// Select the previous "brother/sister" of a node. - /// Sibling have the same parents (ie. are in the same directory). - /// Since the position may be wrong (aka the current node is already the first child of - /// it's parent) we have to adjust the postion afterwards. - pub fn select_prev_sibling(&mut self) -> Result<()> { - if self.position.is_empty() { - self.position = vec![0] - } else { - let len = self.position.len(); - if self.position[len - 1] > 0 { - self.position[len - 1] -= 1; - } else { - self.select_parent()? - } - let (depth, last_cord, node) = self.select_from_position()?; - self.fix_position(depth, last_cord); - self.current_node = node; - } - Ok(()) - } - - fn fix_position(&mut self, depth: usize, last_cord: usize) { - self.position.truncate(depth + 1); - self.position[depth] = last_cord; - } - - /// Select the first child of a current node. - /// Does nothing if the node has no child. - pub fn select_first_child(&mut self) -> Result<()> { - if self.position.is_empty() { - self.position = vec![0] - } - self.position.push(0); - let (depth, last_cord, node) = self.select_from_position()?; - self.fix_position(depth, last_cord); - self.current_node = node; - Ok(()) - } - - /// Move to the parent of current node. - /// If the parent is the root node, it will do nothing. - pub fn select_parent(&mut self) -> Result<()> { - if self.position.is_empty() { - self.position = vec![0]; - } else { - self.position.pop(); - if self.position.is_empty() { - self.position.push(0) - } - let (depth, last_cord, node) = self.select_from_position()?; - self.fix_position(depth, last_cord); - self.current_node = node - } - Ok(()) - } - - /// Move to the last leaf (bottom line on screen). - /// We use a simple trick since we can't know how much node there is - /// at every step. - /// We first create a position with max value (usize::MAX) and max size (Self::MAX_DEPTH). - /// Then we select this node and adjust the position. - pub fn go_to_bottom_leaf(&mut self) -> Result<()> { - self.position = vec![Self::MAX_INDEX; Self::MAX_DEPTH]; - let (depth, last_cord, node) = self.select_from_position()?; - self.fix_position(depth, last_cord); - self.current_node = node; - Ok(()) - } - - /// Select the node at a given position. - /// Returns the reached depth, the last index and a copy of the node itself. - pub fn select_from_position(&mut self) -> Result<(usize, usize, Node)> { - let (tree, reached_depth, last_cord) = self.explore_position(true); - tree.node.select(); - Ok((reached_depth, last_cord, tree.node.clone())) - } - - /// Depth first traversal of the tree. - /// We navigate into the tree and format every element into a pair : - /// - a prefix, wich is a string made of glyphs displaying the tree, - /// - a colored string to be colored relatively to the file type. - /// This method has to parse all the content until the bottom of screen - /// is reached. There's no way atm to avoid parsing the first lines - /// since the "prefix" (straight lines at left of screen) can reach - /// the whole screen. - pub fn into_navigable_content(&mut self) -> (usize, Vec) { - let required_height = self.required_height; - let mut stack = vec![("".to_owned(), self)]; - let mut content = vec![]; - let mut selected_index = 0; - - while let Some((prefix, current)) = stack.pop() { - if current.node.fileinfo.is_selected { - selected_index = content.len(); - } - - content.push(( - current.node.metadata_line.to_owned(), - prefix.to_owned(), - ColoredString::from_node(¤t.node), - )); - - if !current.node.folded { - let first_prefix = first_prefix(prefix.clone()); - let other_prefix = other_prefix(prefix); - - let mut leaves = current.leaves.iter_mut(); - let Some(first_leaf) = leaves.next() else { - continue; - }; - stack.push((first_prefix.clone(), first_leaf)); - - for leaf in leaves { - stack.push((other_prefix.clone(), leaf)); - } - } - if content.len() > required_height { - break; - } - } - (selected_index, content) - } - - /// Select the first node matching a key. - /// We use a breath first search algorithm to ensure we select the less deep one. - pub fn select_first_match(&mut self, key: &str) -> Option> { - if self.node.fileinfo.filename.contains(key) { - return Some(self.node.position.clone()); - } - - for tree in self.leaves.iter_mut().rev() { - let Some(position) = tree.select_first_match(key) else { - continue; - }; - return Some(position); - } - - None - } - - // TODO! refactor to return the new position vector and use it. - /// Recursively explore the tree while only selecting the - /// node from the position. - /// Returns the reached tree, the reached depth and the last index. - /// It may be used to fix the position. - /// position is a vector of node indexes. At each step, we select the - /// existing node. - /// If `unfold` is set to true, it will unfold the trees as it traverses - /// them. - /// Since this method is used to fold every node, this parameter is required. - pub fn explore_position(&mut self, unfold: bool) -> (&mut Tree, usize, usize) { - let mut tree = self; - let pos = tree.position.clone(); - let mut last_cord = 0; - let mut reached_depth = 0; - - for (depth, &coord) in pos.iter().skip(1).enumerate() { - if unfold { - tree.node.folded = false; - } - last_cord = coord; - if depth > pos.len() || tree.leaves.is_empty() { - break; - } - if coord >= tree.leaves.len() { - last_cord = tree.leaves.len() - 1; - } - let len = tree.leaves.len(); - tree = &mut tree.leaves[len - 1 - last_cord]; - reached_depth += 1; - } - (tree, reached_depth, last_cord) - } - - pub fn position_from_index(&self, index: usize) -> Vec { - let mut stack = vec![]; - stack.push(self); - - let mut visited = self; - let mut counter = 0; - while let Some(current) = stack.pop() { - counter += 1; - visited = current; - if counter == index { - break; - } - if !current.node.folded { - for leaf in current.leaves.iter() { - stack.push(leaf); - } - } - } - - visited.node.position.clone() - } - - pub fn directory_of_selected(&self) -> Result<&std::path::Path> { - let fileinfo = &self.current_node.fileinfo; - - match fileinfo.file_kind { - FileKind::Directory => Ok(&self.current_node.fileinfo.path), - _ => Ok(fileinfo - .path - .parent() - .context("selected file should have a parent")?), - } - } -} - -fn first_prefix(mut prefix: String) -> String { - prefix.push(' '); - prefix = prefix.replace("└──", " "); - prefix = prefix.replace("├──", "│ "); - prefix.push_str("└──"); - prefix -} - -fn other_prefix(mut prefix: String) -> String { - prefix.push(' '); - prefix = prefix.replace("└──", " "); - prefix = prefix.replace("├──", "│ "); - prefix.push_str("├──"); - prefix -} diff --git a/src/trees.rs b/src/trees.rs index a016ca76..54e545b1 100644 --- a/src/trees.rs +++ b/src/trees.rs @@ -11,10 +11,31 @@ use crate::{ filter::FilterKind, preview::ColoredTriplet, sort::SortKind, - tree::ColoredString, users::Users, utils::filename_from_path, }; + +/// Holds a string and its display attributes. +#[derive(Clone, Debug)] +pub struct ColoredString { + /// A text to be printed. In most case, it should be a filename. + pub text: String, + /// A tuikit::attr::Attr (fg, bg, effect) to enhance the text. + pub color_effect: ColorEffect, + /// The complete path of this string. + pub path: std::path::PathBuf, +} + +impl ColoredString { + pub fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { + Self { + text, + color_effect, + path, + } + } +} + #[derive(Debug, Clone)] pub struct Node { pub path: PathBuf, From 9b4b17c86540cb48975101d4bd0d7abff89f181c Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 13:28:22 +0100 Subject: [PATCH 129/168] rename trees.rs, rename struct --- src/lib.rs | 2 +- src/preview.rs | 4 ++-- src/status.rs | 6 +++--- src/tab.rs | 10 +++++----- src/term_manager.rs | 2 +- src/{trees.rs => tree.rs} | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) rename src/{trees.rs => tree.rs} (99%) diff --git a/src/lib.rs b/src/lib.rs index 013318fc..900c090d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,6 @@ pub mod status; pub mod tab; pub mod term_manager; pub mod trash; -pub mod trees; +pub mod tree; pub mod users; pub mod utils; diff --git a/src/preview.rs b/src/preview.rs index 4bfde0fd..e0f899db 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -25,7 +25,7 @@ use crate::fileinfo::{FileInfo, FileKind}; use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; use crate::sort::SortKind; -use crate::trees::{ColoredString, FileSystem}; +use crate::tree::{ColoredString, Tree}; use crate::users::Users; use crate::utils::{clear_tmp_file, filename_from_path, is_program_in_path}; @@ -1046,7 +1046,7 @@ pub struct Directory { impl Directory { pub fn new(path: PathBuf, users: &Users) -> Self { - let tree = FileSystem::new( + let tree = Tree::new( path, 4, SortKind::tree_default(), diff --git a/src/status.rs b/src/status.rs index 341585c5..68e940df 100644 --- a/src/status.rs +++ b/src/status.rs @@ -41,7 +41,7 @@ use crate::skim::Skimer; use crate::tab::Tab; use crate::term_manager::MIN_WIDTH_FOR_DUAL_PANE; use crate::trash::Trash; -use crate::trees::FileSystem; +use crate::tree::Tree; use crate::users::Users; use crate::utils::{current_username, disk_space, filename_from_path, is_program_in_path}; @@ -582,7 +582,7 @@ impl Status { /// Drop the current tree, replace it with an empty one. pub fn remove_tree(&mut self) -> Result<()> { - self.selected().tree = FileSystem::empty(); + self.selected().tree = Tree::empty(); Ok(()) } @@ -617,7 +617,7 @@ impl Status { if let Mode::Tree = self.selected_non_mut().mode { { let tab = self.selected(); - tab.tree = FileSystem::empty(); + tab.tree = Tree::empty(); tab.refresh_view() }?; self.selected().set_mode(Mode::Normal) diff --git a/src/tab.rs b/src/tab.rs index ddf6f681..d13d87cf 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -17,7 +17,7 @@ use crate::preview::Preview; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; use crate::sort::SortKind; -use crate::trees::{calculate_tree_window, FileSystem}; +use crate::tree::{calculate_tree_window, Tree}; use crate::users::Users; use crate::utils::{row_to_window_index, set_clipboard}; @@ -56,7 +56,7 @@ pub struct Tab { pub history: History, /// Users & groups pub users: Users, - pub tree: FileSystem, + pub tree: Tree, } impl Tab { @@ -89,7 +89,7 @@ impl Tab { shortcut.extend_with_mount_points(mount_points); let searched = None; let index = path_content.select_file(&path); - let tree = FileSystem::empty(); + let tree = Tree::empty(); window.scroll_to(index); Ok(Self { mode, @@ -146,7 +146,7 @@ impl Tab { self.input.reset(); self.preview = Preview::empty(); self.completion.reset(); - self.tree = FileSystem::empty(); + self.tree = Tree::empty(); Ok(()) } @@ -415,7 +415,7 @@ impl Tab { }; let path = self.path_content.path.clone(); let users = &self.users; - self.tree = FileSystem::new(path, 5, sort_kind, users, self.show_hidden, &self.filter); + self.tree = Tree::new(path, 5, sort_kind, users, self.show_hidden, &self.filter); Ok(()) } diff --git a/src/term_manager.rs b/src/term_manager.rs index 15bbf61a..43b107b0 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -25,7 +25,7 @@ use crate::selectable_content::SelectableContent; use crate::status::Status; use crate::tab::Tab; use crate::trash::TrashInfo; -use crate::trees::calculate_tree_window; +use crate::tree::calculate_tree_window; /// Iter over the content, returning a triplet of `(index, line, attr)`. macro_rules! enumerated_colored_iter { diff --git a/src/trees.rs b/src/tree.rs similarity index 99% rename from src/trees.rs rename to src/tree.rs index 54e545b1..c0b29430 100644 --- a/src/trees.rs +++ b/src/tree.rs @@ -88,7 +88,7 @@ impl Node { } #[derive(Debug, Clone)] -pub struct FileSystem { +pub struct Tree { root_path: PathBuf, selected: PathBuf, last_path: PathBuf, @@ -96,7 +96,7 @@ pub struct FileSystem { required_height: usize, } -impl FileSystem { +impl Tree { pub const REQUIRED_HEIGHT: usize = 80; pub fn new( From 847911d5e0828038a7414172e6315b90098986a1 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 13:32:39 +0100 Subject: [PATCH 130/168] FIX: folders at max depth hangs the app --- development.md | 3 ++- src/tree.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index 9e4e6559..14ed76d6 100644 --- a/development.md +++ b/development.md @@ -606,9 +606,10 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself - [ ] Tree remade without recursion. Improve ram usage - - [ ] FIX: folders are max depth hangs the app + - [x] FIX: folders are max depth hangs the app - [ ] FIX: rename renames the root path - [ ] FIX: scrolling to bottom of tree is bugged + - [ ] FIX: scrolling starts 1 row to low - [ ] search can only find the first match - [ ] can't "open" a folder to redo the tree there - [ ] test everything diff --git a/src/tree.rs b/src/tree.rs index c0b29430..07daa9fd 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -118,9 +118,10 @@ impl Tree { if reached_depth >= depth + start_depth { continue; } + let children_will_be_added = depth + start_depth - reached_depth > 1; let mut node = Node::new(&path, None); if let Ok(fileinfo) = node.fileinfo(users) { - if path.is_dir() && !path.is_symlink() { + if path.is_dir() && !path.is_symlink() && children_will_be_added { if let Some(mut files) = files_collection(&fileinfo, users, show_hidden, filter_kind, true) { From 509e085a47942d9881fba54aa3c50635ba1c6c58 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 13:37:41 +0100 Subject: [PATCH 131/168] FIX: rename renames the root path --- development.md | 2 +- src/event_exec.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index 14ed76d6..1d2eb086 100644 --- a/development.md +++ b/development.md @@ -607,7 +607,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] avoid multiple refreshs if we edit files ourself - [ ] Tree remade without recursion. Improve ram usage - [x] FIX: folders are max depth hangs the app - - [ ] FIX: rename renames the root path + - [x] FIX: rename renames the root path - [ ] FIX: scrolling to bottom of tree is bugged - [ ] FIX: scrolling starts 1 row to low - [ ] search can only find the first match diff --git a/src/event_exec.rs b/src/event_exec.rs index 611f8961..e7250090 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -1299,7 +1299,7 @@ impl LeaveMode { /// We only try to rename in the same directory, so it shouldn't be a problem. /// Filename is sanitized before processing. pub fn rename(tab: &mut Tab) -> Result<()> { - let original_path = if let Mode::Tree = tab.mode { + let original_path = if let Mode::Tree = tab.previous_mode { tab.tree.selected_path() } else { tab.path_content From 2e953412b1c49db263c914214d1b73be39bb2851 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 13:40:22 +0100 Subject: [PATCH 132/168] FIX: scrolling starts 1 row to low --- development.md | 2 +- src/tree.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index 1d2eb086..c7842493 100644 --- a/development.md +++ b/development.md @@ -609,7 +609,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: folders are max depth hangs the app - [x] FIX: rename renames the root path - [ ] FIX: scrolling to bottom of tree is bugged - - [ ] FIX: scrolling starts 1 row to low + - [x] FIX: scrolling starts 1 row to low - [ ] search can only find the first match - [ ] can't "open" a folder to redo the tree there - [ ] test everything diff --git a/src/tree.rs b/src/tree.rs index 07daa9fd..76139fa4 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -477,7 +477,7 @@ pub fn calculate_tree_window( let top: usize; let bottom: usize; let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP; - if selected_index < terminal_height - 1 { + if selected_index < terminal_height - ContentWindow::WINDOW_MARGIN_TOP { top = 0; bottom = window_height; } else { From c56e73e440c4b35a81ff170bc13ce22c5f84cdd3 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 14:01:14 +0100 Subject: [PATCH 133/168] FIX: scrolling starts 1 row to low --- development.md | 3 ++- src/tab.rs | 2 +- src/term_manager.rs | 5 +++-- src/tree.rs | 40 +++++++++++++++++++++++++++++++--------- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/development.md b/development.md index c7842493..3a2d47d1 100644 --- a/development.md +++ b/development.md @@ -608,8 +608,9 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] Tree remade without recursion. Improve ram usage - [x] FIX: folders are max depth hangs the app - [x] FIX: rename renames the root path - - [ ] FIX: scrolling to bottom of tree is bugged + - [x] FIX: scrolling to bottom of tree is bugged - [x] FIX: scrolling starts 1 row to low + - [ ] FIX: filename in first line - [ ] search can only find the first match - [ ] can't "open" a folder to redo the tree there - [ ] test everything diff --git a/src/tab.rs b/src/tab.rs index d13d87cf..bf4ed69c 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -633,7 +633,7 @@ impl Tab { fn tree_select_row(&mut self, row: u16, term_height: usize) -> Result<()> { let screen_index = row_to_window_index(row); let (selected_index, content) = self.tree.into_navigable_content(&self.users); - let (top, _, _) = calculate_tree_window(selected_index, term_height, term_height); + let (top, _) = calculate_tree_window(selected_index, term_height - 2); let index = screen_index + top; let (_, _, colored_path) = content.get(index).context("no selected file")?; self.tree.select_from_path(&colored_path.path); diff --git a/src/term_manager.rs b/src/term_manager.rs index 43b107b0..71e03d20 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -398,13 +398,14 @@ impl<'a> WinMain<'a> { let left_margin = if status.display_full { 1 } else { 3 }; let (_, height) = canvas.size()?; let (selected_index, content) = tab.tree.into_navigable_content(&tab.users); - let (top, bottom, len) = calculate_tree_window(selected_index, canvas.size()?.1, height); + let (top, bottom) = calculate_tree_window(selected_index, height); + let length = content.len(); for (i, (metadata, prefix, colored_string)) in content .iter() .enumerate() .skip(top) - .take(min(len, bottom + 1)) + .take(min(length, bottom + 1)) { let row = i + ContentWindow::WINDOW_MARGIN_TOP - top; let mut attr = colored_string.color_effect.attr(); diff --git a/src/tree.rs b/src/tree.rs index 76139fa4..e437bdda 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -97,7 +97,7 @@ pub struct Tree { } impl Tree { - pub const REQUIRED_HEIGHT: usize = 80; + pub const DEFAULT_REQUIRED_HEIGHT: usize = 80; pub fn new( root_path: PathBuf, @@ -151,10 +151,30 @@ impl Tree { root_path, last_path, nodes, - required_height: Self::REQUIRED_HEIGHT, + required_height: Self::DEFAULT_REQUIRED_HEIGHT, } } + fn increment_required_height(&mut self) { + if self.required_height < usize::MAX { + self.required_height += 1 + } + } + + fn decrement_required_height(&mut self) { + if self.required_height > Self::DEFAULT_REQUIRED_HEIGHT { + self.required_height -= 1 + } + } + + fn set_required_height_to_max(&mut self) { + self.required_height = usize::MAX + } + + fn reset_required_height(&mut self) { + self.required_height = Self::DEFAULT_REQUIRED_HEIGHT + } + pub fn empty() -> Self { Self { root_path: PathBuf::default(), @@ -188,6 +208,7 @@ impl Tree { }; selected_node.unselect(); self.selected = next_path; + self.increment_required_height() } Ok(()) } @@ -256,6 +277,7 @@ impl Tree { }; selected_node.unselect(); self.selected = previous_path; + self.decrement_required_height() } } @@ -294,6 +316,7 @@ impl Tree { }; root_node.select(); self.selected = self.root_path.to_owned(); + self.reset_required_height() } pub fn select_last(&mut self) { @@ -306,6 +329,7 @@ impl Tree { }; last_node.select(); self.selected = self.last_path.to_owned(); + self.set_required_height_to_max() } pub fn select_parent(&mut self) { @@ -319,6 +343,7 @@ impl Tree { }; selected_node.unselect(); self.selected = parent_path.to_owned(); + self.decrement_required_height() } } @@ -332,6 +357,7 @@ impl Tree { }; selected_node.unselect(); self.selected = clicked_path.to_owned(); + self.set_required_height_to_max() } /// Fold selected node @@ -469,15 +495,11 @@ fn other_prefix(mut prefix: String) -> String { prefix } -pub fn calculate_tree_window( - selected_index: usize, - terminal_height: usize, - length: usize, -) -> (usize, usize, usize) { +pub fn calculate_tree_window(selected_index: usize, terminal_height: usize) -> (usize, usize) { let top: usize; let bottom: usize; let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP; - if selected_index < terminal_height - ContentWindow::WINDOW_MARGIN_TOP { + if selected_index < window_height { top = 0; bottom = window_height; } else { @@ -486,5 +508,5 @@ pub fn calculate_tree_window( bottom = top + window_height; } - (top, bottom, length) + (top, bottom) } From 9cf0ab8802c0c51d0da0c9dc41973932245c5737 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 14:16:46 +0100 Subject: [PATCH 134/168] FIX: filename in first line --- development.md | 2 +- src/term_manager.rs | 18 ++++++++++---- src/tree.rs | 60 ++++++++++++++++++++++++--------------------- 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/development.md b/development.md index 3a2d47d1..95e35a96 100644 --- a/development.md +++ b/development.md @@ -610,7 +610,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: rename renames the root path - [x] FIX: scrolling to bottom of tree is bugged - [x] FIX: scrolling starts 1 row to low - - [ ] FIX: filename in first line + - [x] FIX: filename in first line - [ ] search can only find the first match - [ ] can't "open" a folder to redo the tree there - [ ] test everything diff --git a/src/term_manager.rs b/src/term_manager.rs index 71e03d20..a1efa074 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -240,7 +240,7 @@ impl<'a> WinMain<'a> { fn normal_first_row(&self, disk_space: &str) -> Result> { Ok(vec![ format!(" {}", shorten_path(&self.tab.path_content.path, None)?), - self.first_row_filename(), + self.first_row_filename()?, self.first_row_position(), format!("{} ", self.tab.path_content.used_space()), format!(" Avail: {disk_space} "), @@ -250,14 +250,22 @@ impl<'a> WinMain<'a> { ]) } - fn first_row_filename(&self) -> String { + fn first_row_filename(&self) -> Result { match self.tab.mode { - Mode::Tree => "".to_owned(), + Mode::Tree => Ok(format!( + "/{rel}", + rel = self + .tab + .tree + .selected_path_relative_to_root()? + .display() + .to_string(), + )), _ => { if let Some(fileinfo) = self.tab.path_content.selected() { - fileinfo.filename_without_dot_dotdot() + Ok(fileinfo.filename_without_dot_dotdot()) } else { - "".to_owned() + Ok("".to_owned()) } } } diff --git a/src/tree.rs b/src/tree.rs index e437bdda..08de5e76 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -155,26 +155,6 @@ impl Tree { } } - fn increment_required_height(&mut self) { - if self.required_height < usize::MAX { - self.required_height += 1 - } - } - - fn decrement_required_height(&mut self) { - if self.required_height > Self::DEFAULT_REQUIRED_HEIGHT { - self.required_height -= 1 - } - } - - fn set_required_height_to_max(&mut self) { - self.required_height = usize::MAX - } - - fn reset_required_height(&mut self) { - self.required_height = Self::DEFAULT_REQUIRED_HEIGHT - } - pub fn empty() -> Self { Self { root_path: PathBuf::default(), @@ -193,6 +173,18 @@ impl Tree { self.nodes.get(&self.selected) } + pub fn directory_of_selected(&self) -> Option<&Path> { + if self.selected.is_dir() && !self.selected.is_symlink() { + Some(self.selected.as_path()) + } else { + self.selected.parent() + } + } + + pub fn selected_path_relative_to_root(&self) -> Result<&Path> { + Ok(self.selected.strip_prefix(&self.root_path)?) + } + /// Select next sibling or the next sibling of the parent pub fn select_next(&mut self) -> Result<()> { log::info!("select_next START {sel}", sel = self.selected.display()); @@ -360,6 +352,26 @@ impl Tree { self.set_required_height_to_max() } + fn increment_required_height(&mut self) { + if self.required_height < usize::MAX { + self.required_height += 1 + } + } + + fn decrement_required_height(&mut self) { + if self.required_height > Self::DEFAULT_REQUIRED_HEIGHT { + self.required_height -= 1 + } + } + + fn set_required_height_to_max(&mut self) { + self.required_height = usize::MAX + } + + fn reset_required_height(&mut self) { + self.required_height = Self::DEFAULT_REQUIRED_HEIGHT + } + /// Fold selected node pub fn toggle_fold(&mut self) { if let Some(node) = self.nodes.get_mut(&self.selected) { @@ -379,14 +391,6 @@ impl Tree { } } - pub fn directory_of_selected(&self) -> Option<&Path> { - if self.selected.is_dir() && !self.selected.is_symlink() { - Some(self.selected.as_path()) - } else { - self.selected.parent() - } - } - // FIX: can only find the first match and nothing else pub fn search_first_match(&mut self, pattern: &str) { let initial_selected = self.selected.to_owned(); From 357b3c1bf637d5f4594b56f9997fe8475a12cca5 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 14:33:07 +0100 Subject: [PATCH 135/168] FIX: can't "open" a folder to redo the tree there --- development.md | 2 +- src/event_exec.rs | 12 ++++++++++-- src/status.rs | 6 +++++- src/tab.rs | 5 ----- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/development.md b/development.md index 95e35a96..e531cfc3 100644 --- a/development.md +++ b/development.md @@ -611,8 +611,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: scrolling to bottom of tree is bugged - [x] FIX: scrolling starts 1 row to low - [x] FIX: filename in first line + - [x] FIX: can't "open" a folder to redo the tree there - [ ] search can only find the first match - - [ ] can't "open" a folder to redo the tree there - [ ] test everything - [ ] refactor - [ ] document diff --git a/src/event_exec.rs b/src/event_exec.rs index e7250090..ffd63fd8 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -1445,10 +1445,18 @@ impl LeaveMode { status.update_second_pane_for_preview() } - // TODO! enter the tree if it's a directory /// Execute the selected node if it's a file else enter the directory. pub fn tree(status: &mut Status) -> Result<()> { - EventAction::open_file(status) + let path = status.selected_fileinfo()?.path; + let is_dir = path.is_dir(); + if is_dir { + status.selected().set_pathcontent(&path)?; + status.selected().make_tree(None)?; + status.selected().set_mode(Mode::Tree); + Ok(()) + } else { + EventAction::open_file(status) + } } /// Store a password of some kind (sudo or device passphrase). diff --git a/src/status.rs b/src/status.rs index 68e940df..f0b9f016 100644 --- a/src/status.rs +++ b/src/status.rs @@ -20,7 +20,7 @@ use crate::config::Settings; use crate::constant_strings_paths::{NVIM, SS, TUIS_PATH}; use crate::copy_move::{copy_move, CopyMove}; use crate::cryptsetup::{BlockDeviceAction, CryptoDeviceOpener}; -use crate::fileinfo::FileKind; +use crate::fileinfo::{FileInfo, FileKind}; use crate::flagged::Flagged; use crate::iso::IsoDevice; use crate::log_line; @@ -258,6 +258,10 @@ impl Status { &self.tabs[self.index] } + pub fn selected_fileinfo(&self) -> Result { + self.selected_non_mut().selected() + } + /// Reset the view of every tab. pub fn reset_tabs_view(&mut self) -> Result<()> { for tab in self.tabs.iter_mut() { diff --git a/src/tab.rs b/src/tab.rs index bf4ed69c..400cd854 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -665,11 +665,6 @@ impl Tab { Mode::Tree => { self.path_content.update_sort_from_char(c); self.make_tree(Some(self.path_content.sort_kind.clone()))?; - - // self.directory.tree.update_sort_from_char(c); - // self.directory.tree.sort(); - // self.tree_select_root()?; - // self.directory.tree.into_navigable_content(); } _ => (), } From 96eb525c48968251eb56bf5d311d00a19c26b96b Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 14:44:45 +0100 Subject: [PATCH 136/168] FIX: move back from root should redo the parent tree --- development.md | 1 + src/event_exec.rs | 2 +- src/tab.rs | 13 +++++++++++-- src/tree.rs | 8 ++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/development.md b/development.md index e531cfc3..f0af9b3e 100644 --- a/development.md +++ b/development.md @@ -612,6 +612,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: scrolling starts 1 row to low - [x] FIX: filename in first line - [x] FIX: can't "open" a folder to redo the tree there + - [x] FIX: move back from root should redo the parent tree - [ ] search can only find the first match - [ ] test everything - [ ] refactor diff --git a/src/event_exec.rs b/src/event_exec.rs index ffd63fd8..1238c8d0 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -535,7 +535,7 @@ impl EventAction { let tab = status.selected(); match tab.mode { Mode::Normal => tab.move_to_parent()?, - Mode::Tree => tab.tree_select_parent(), + Mode::Tree => tab.tree_select_parent()?, Mode::InputSimple(_) | Mode::InputCompleted(_) => { tab.input.cursor_left(); } diff --git a/src/tab.rs b/src/tab.rs index 400cd854..db5c08cf 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -332,8 +332,17 @@ impl Tab { /// Select the parent of current node. /// If we were at the root node, move to the parent and make a new tree. - pub fn tree_select_parent(&mut self) { - self.tree.select_parent() + pub fn tree_select_parent(&mut self) -> Result<()> { + if self.tree.is_on_root() { + let Some(parent) = self.tree.root_path().parent() else { + return Ok(()); + }; + self.set_pathcontent(&parent.to_owned())?; + self.make_tree(Some(self.path_content.sort_kind.clone())) + } else { + self.tree.select_parent(); + Ok(()) + } } /// Move down 10 times in the tree diff --git a/src/tree.rs b/src/tree.rs index 08de5e76..2491314b 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -165,6 +165,10 @@ impl Tree { } } + pub fn root_path(&self) -> &Path { + self.root_path.as_path() + } + pub fn selected_path(&self) -> &Path { self.selected.as_path() } @@ -185,6 +189,10 @@ impl Tree { Ok(self.selected.strip_prefix(&self.root_path)?) } + pub fn is_on_root(&self) -> bool { + self.selected == self.root_path + } + /// Select next sibling or the next sibling of the parent pub fn select_next(&mut self) -> Result<()> { log::info!("select_next START {sel}", sel = self.selected.display()); From 818ffa4aa6ba561fc3f5f56ad09949bca70bfd50 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 14:50:04 +0100 Subject: [PATCH 137/168] FIX: move up from to go to last and vice versa --- development.md | 1 + src/tab.rs | 6 ++++++ src/tree.rs | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index f0af9b3e..f058f32e 100644 --- a/development.md +++ b/development.md @@ -613,6 +613,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: filename in first line - [x] FIX: can't "open" a folder to redo the tree there - [x] FIX: move back from root should redo the parent tree + - [x] FIX: move up from to go to last and vice versa - [ ] search can only find the first match - [ ] test everything - [ ] refactor diff --git a/src/tab.rs b/src/tab.rs index db5c08cf..e1600a05 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -349,6 +349,9 @@ impl Tab { pub fn tree_page_down(&mut self) -> Result<()> { for _ in 1..10 { self.tree.select_next()?; + if self.tree.is_on_last() { + break; + } } Ok(()) } @@ -357,6 +360,9 @@ impl Tab { pub fn tree_page_up(&mut self) -> Result<()> { for _ in 1..10 { self.tree.select_prev(); + if self.tree.is_on_root() { + break; + } } Ok(()) } diff --git a/src/tree.rs b/src/tree.rs index 2491314b..253e1028 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -193,9 +193,16 @@ impl Tree { self.selected == self.root_path } + pub fn is_on_last(&self) -> bool { + self.selected == self.last_path + } + /// Select next sibling or the next sibling of the parent pub fn select_next(&mut self) -> Result<()> { - log::info!("select_next START {sel}", sel = self.selected.display()); + if self.is_on_last() { + self.select_root(); + return Ok(()); + } if let Some(next_path) = self.find_next_path() { let Some(next_node) = self.nodes.get_mut(&next_path) else { @@ -265,7 +272,10 @@ impl Tree { // TODO! find the bottom child of parent instead of jumping back 1 level /// Select previous sibling or the parent pub fn select_prev(&mut self) { - log::info!("select_prev START {sel}", sel = self.selected.display()); + if self.is_on_root() { + self.select_last(); + return; + } if let Some(previous_path) = self.find_prev_path() { let Some(previous_node) = self.nodes.get_mut(&previous_path) else { From c699b0a150fae351e4a7b80d644654b9986a98dd Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 14:59:07 +0100 Subject: [PATCH 138/168] FIX: enter a dir from normal mode shouldn't set mode tree --- development.md | 1 + src/event_exec.rs | 7 +++++-- src/tab.rs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/development.md b/development.md index f058f32e..6fb8ece4 100644 --- a/development.md +++ b/development.md @@ -614,6 +614,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: can't "open" a folder to redo the tree there - [x] FIX: move back from root should redo the parent tree - [x] FIX: move up from to go to last and vice versa + - [x] FIX: enter a dir from normal mode shouldn't set mode tree - [ ] search can only find the first match - [ ] test everything - [ ] refactor diff --git a/src/event_exec.rs b/src/event_exec.rs index 1238c8d0..1dc4cc58 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -706,7 +706,10 @@ impl EventAction { } Mode::InputCompleted(InputCompleted::Goto) => LeaveMode::goto(status)?, Mode::InputCompleted(InputCompleted::Command) => LeaveMode::command(status)?, - Mode::Normal => LeaveMode::open_file(status)?, + Mode::Normal => { + LeaveMode::open_file(status)?; + must_reset_mode = false; + } Mode::Tree => LeaveMode::tree(status)?, Mode::NeedConfirmation(_) | Mode::Preview @@ -1148,7 +1151,7 @@ impl LeaveMode { return Ok(()); } if tab.path_content.is_selected_dir()? { - tab.go_to_child() + tab.go_to_selected_dir() } else { EventAction::open_file(status) } diff --git a/src/tab.rs b/src/tab.rs index e1600a05..94f1b388 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -193,13 +193,15 @@ impl Tab { /// Move to the currently selected directory. /// Fail silently if the current directory is empty or if the selected /// file isn't a directory. - pub fn go_to_child(&mut self) -> Result<()> { + pub fn go_to_selected_dir(&mut self) -> Result<()> { + log::info!("go to selected"); let childpath = &self .path_content .selected() .context("Empty directory")? .path .clone(); + log::info!("selected : {childpath:?}"); self.set_pathcontent(childpath)?; self.window.reset(self.path_content.content.len()); self.input.cursor_start(); From 723f1ec13275f5ce58bc6a8ee145ede03816c4c5 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 15:23:03 +0100 Subject: [PATCH 139/168] dev --- development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/development.md b/development.md index 6fb8ece4..e0cab53e 100644 --- a/development.md +++ b/development.md @@ -615,6 +615,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: move back from root should redo the parent tree - [x] FIX: move up from to go to last and vice versa - [x] FIX: enter a dir from normal mode shouldn't set mode tree + - [ ] FIX: tab.selected() should return Option<&FileInfo> - [ ] search can only find the first match - [ ] test everything - [ ] refactor From 214a07da9de5d1f4f6ec25d053216031b128c4b4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 15:48:26 +0100 Subject: [PATCH 140/168] some refactoring... to be continued --- src/fileinfo.rs | 11 ++++------- src/tree.rs | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 335ae9e3..64844911 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -359,7 +359,7 @@ impl PathContent { let fileinfo = FileInfo::from_path_with_name(path, filename_from_path(path)?, users)?; if let Some(true_files) = - files_collection(&fileinfo, users, show_hidden, filter_kind, false) + files_collection(&fileinfo.path, users, show_hidden, filter_kind, false) { files.extend(true_files); } @@ -697,13 +697,13 @@ fn filekind_and_filename(filename: &str, file_kind: &FileKind) -> String /// Files are filtered by filterkind and the display hidden flag. /// Returns None if there's no file. pub fn files_collection( - fileinfo: &FileInfo, + path: &path::Path, users: &Users, show_hidden: bool, filter_kind: &FilterKind, keep_dir: bool, ) -> Option> { - match read_dir(&fileinfo.path) { + match read_dir(&path) { Ok(read_dir) => Some( read_dir .filter_map(|direntry| direntry.ok()) @@ -714,10 +714,7 @@ pub fn files_collection( .collect(), ), Err(error) => { - info!( - "Couldn't read path {path} - {error}", - path = fileinfo.path.display(), - ); + info!("Couldn't read path {path} - {error}", path = path.display(),); None } } diff --git a/src/tree.rs b/src/tree.rs index 253e1028..34e97824 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -120,31 +120,23 @@ impl Tree { } let children_will_be_added = depth + start_depth - reached_depth > 1; let mut node = Node::new(&path, None); - if let Ok(fileinfo) = node.fileinfo(users) { - if path.is_dir() && !path.is_symlink() && children_will_be_added { - if let Some(mut files) = - files_collection(&fileinfo, users, show_hidden, filter_kind, true) - { - sort_kind.sort(&mut files); - let children = files - .iter() - .map(|fileinfo| { - stack.push(fileinfo.path.to_owned()); - fileinfo - }) - .map(|fileinfo| fileinfo.path.to_owned()) - .collect(); - node.set_children(Some(children)); - }; - } + if path.is_dir() && !path.is_symlink() && children_will_be_added { + if let Some(mut files) = + files_collection(&path, users, show_hidden, filter_kind, true) + { + sort_kind.sort(&mut files); + let children = Self::make_children_and_stack_them(&mut stack, &files); + node.set_children(Some(children)); + }; } last_path = node.path.to_owned(); nodes.insert(node.path.to_owned(), node); } - if let Some(node) = nodes.get_mut(&root_path) { - node.select() - } + let Some(node) = nodes.get_mut(&root_path) else { + unreachable!("root path should be in nodes"); + }; + node.select(); Self { selected: root_path.clone(), @@ -155,6 +147,17 @@ impl Tree { } } + fn make_children_and_stack_them(stack: &mut Vec, files: &[FileInfo]) -> Vec { + files + .iter() + .map(|fileinfo| fileinfo.path.to_owned()) + .map(|path| { + stack.push(path.to_owned()); + path + }) + .collect() + } + pub fn empty() -> Self { Self { root_path: PathBuf::default(), @@ -237,6 +240,7 @@ impl Tree { } let mut current_path = self.selected.to_owned(); + // TODO: refactor using ancestors. Not so easy since we keep track of parent and current while let Some(parent_path) = current_path.parent() { let Some(parent_node) = self.nodes.get(parent_path) else { current_path = parent_path.to_owned(); From 052d527c5f179380f67078cf423c8ae855a7b52a Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 15:53:24 +0100 Subject: [PATCH 141/168] clippy --- src/fileinfo.rs | 2 +- src/tab.rs | 2 +- src/term_manager.rs | 7 +------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 64844911..b378bd12 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -703,7 +703,7 @@ pub fn files_collection( filter_kind: &FilterKind, keep_dir: bool, ) -> Option> { - match read_dir(&path) { + match read_dir(path) { Ok(read_dir) => Some( read_dir .filter_map(|direntry| direntry.ok()) diff --git a/src/tab.rs b/src/tab.rs index 94f1b388..15805074 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -339,7 +339,7 @@ impl Tab { let Some(parent) = self.tree.root_path().parent() else { return Ok(()); }; - self.set_pathcontent(&parent.to_owned())?; + self.set_pathcontent(parent.to_owned().as_ref())?; self.make_tree(Some(self.path_content.sort_kind.clone())) } else { self.tree.select_parent(); diff --git a/src/term_manager.rs b/src/term_manager.rs index a1efa074..27257efa 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -254,12 +254,7 @@ impl<'a> WinMain<'a> { match self.tab.mode { Mode::Tree => Ok(format!( "/{rel}", - rel = self - .tab - .tree - .selected_path_relative_to_root()? - .display() - .to_string(), + rel = self.tab.tree.selected_path_relative_to_root()?.display() )), _ => { if let Some(fileinfo) = self.tab.path_content.selected() { From a2f1aea562571defe922f0d6ce13220e0e4b9c3f Mon Sep 17 00:00:00 2001 From: qkzk Date: Mon, 6 Nov 2023 21:44:04 +0100 Subject: [PATCH 142/168] refactoring tree --- src/fileinfo.rs | 11 +++++ src/preview.rs | 28 ++++++++++- src/tab.rs | 2 +- src/tree.rs | 127 ++++++++++++++++++++++++++++-------------------- 4 files changed, 112 insertions(+), 56 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index b378bd12..9821adf2 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -16,6 +16,7 @@ use crate::filter::FilterKind; use crate::git::git; use crate::impl_selectable_content; use crate::sort::SortKind; +use crate::tree::Node; use crate::users::Users; use crate::utils::filename_from_path; @@ -564,6 +565,7 @@ pub struct ColorEffect { impl ColorEffect { /// Calculates a color and an effect from `fm::file_info::FileInfo`. + #[inline] pub fn new(fileinfo: &FileInfo) -> ColorEffect { let color = fileinfo_color(fileinfo); @@ -576,6 +578,15 @@ impl ColorEffect { Self { color, effect } } + #[inline] + pub fn node(fileinfo: &FileInfo, current_node: &Node) -> Self { + let mut color_effect = Self::new(fileinfo); + if current_node.selected { + color_effect.effect |= Effect::REVERSE; + } + color_effect + } + /// Makes a new `tuikit::attr::Attr` where `bg` is default. pub fn attr(&self) -> Attr { Attr { diff --git a/src/preview.rs b/src/preview.rs index e0f899db..e82a541f 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -21,7 +21,7 @@ use crate::constant_strings_paths::{ }; use crate::content_window::ContentWindow; use crate::decompress::{list_files_tar, list_files_zip}; -use crate::fileinfo::{FileInfo, FileKind}; +use crate::fileinfo::{ColorEffect, FileInfo, FileKind}; use crate::filter::FilterKind; use crate::opener::execute_and_capture_output_without_check; use crate::sort::SortKind; @@ -1159,6 +1159,32 @@ macro_rules! impl_window { /// Used to iter and impl window trait in tree mode. pub type ColoredTriplet = (String, String, ColoredString); +pub trait MakeTriplet { + fn make( + fileinfo: &FileInfo, + prefix: &str, + filename_text: String, + color_effect: ColorEffect, + current_path: &std::path::Path, + ) -> ColoredTriplet; +} + +impl MakeTriplet for ColoredTriplet { + #[inline] + fn make( + fileinfo: &FileInfo, + prefix: &str, + filename_text: String, + color_effect: ColorEffect, + current_path: &std::path::Path, + ) -> ColoredTriplet { + ( + fileinfo.format_no_filename().unwrap_or_default(), + prefix.to_owned(), + ColoredString::new(filename_text, color_effect, current_path.to_owned()), + ) + } +} /// A vector of highlighted strings pub type VecSyntaxedString = Vec; diff --git a/src/tab.rs b/src/tab.rs index 15805074..2861f73b 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -131,7 +131,7 @@ impl Tab { if matches!(self.previous_mode, Mode::Tree) => { self.completion - .search_from_tree(&self.input.string(), &self.tree.paths()) + .search_from_tree(&self.input.string(), &self.tree.filenames()) } Mode::InputCompleted(InputCompleted::Command) => { self.completion.command(&self.input.string()) diff --git a/src/tree.rs b/src/tree.rs index 34e97824..5d9d5fd4 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -9,7 +9,7 @@ use crate::{ content_window::ContentWindow, fileinfo::{files_collection, ColorEffect, FileInfo}, filter::FilterKind, - preview::ColoredTriplet, + preview::{ColoredTriplet, MakeTriplet}, sort::SortKind, users::Users, utils::filename_from_path, @@ -108,21 +108,21 @@ impl Tree { filter_kind: &FilterKind, ) -> Self { // keep track of the depth - let start_depth = root_path.components().collect::>().len(); + let root_depth = root_path.components().collect::>().len(); let mut stack = vec![root_path.to_owned()]; let mut nodes: HashMap = HashMap::new(); let mut last_path = root_path.to_owned(); - while let Some(path) = stack.pop() { - let reached_depth = path.components().collect::>().len(); - if reached_depth >= depth + start_depth { + while let Some(current_path) = stack.pop() { + let reached_depth = current_path.components().collect::>().len(); + if reached_depth >= depth + root_depth { continue; } - let children_will_be_added = depth + start_depth - reached_depth > 1; - let mut node = Node::new(&path, None); - if path.is_dir() && !path.is_symlink() && children_will_be_added { + let children_will_be_added = depth + root_depth - reached_depth > 1; + let mut node = Node::new(¤t_path, None); + if current_path.is_dir() && !current_path.is_symlink() && children_will_be_added { if let Some(mut files) = - files_collection(&path, users, show_hidden, filter_kind, true) + files_collection(¤t_path, users, show_hidden, filter_kind, true) { sort_kind.sort(&mut files); let children = Self::make_children_and_stack_them(&mut stack, &files); @@ -133,10 +133,10 @@ impl Tree { nodes.insert(node.path.to_owned(), node); } - let Some(node) = nodes.get_mut(&root_path) else { + let Some(root_node) = nodes.get_mut(&root_path) else { unreachable!("root path should be in nodes"); }; - node.select(); + root_node.select(); Self { selected: root_path.clone(), @@ -433,78 +433,79 @@ impl Tree { } pub fn into_navigable_content(&self, users: &Users) -> (usize, Vec) { - let required_height = self.required_height; let mut stack = vec![("".to_owned(), self.root_path.as_path())]; let mut content = vec![]; let mut selected_index = 0; - while let Some((prefix, current_path)) = stack.pop() { - let Some(current_node) = &self.nodes.get(current_path) else { + while let Some((prefix, path)) = stack.pop() { + let Some(node) = self.nodes.get(path) else { continue; }; - if current_node.selected { + if node.selected { selected_index = content.len(); } - let Ok(fileinfo) = FileInfo::new(current_path, users) else { + let Ok(fileinfo) = FileInfo::new(path, users) else { continue; }; - let filename = filename_from_path(current_path) - .unwrap_or_default() - .to_owned(); - let mut color_effect = ColorEffect::new(&fileinfo); - if current_node.selected { - color_effect.effect |= tuikit::attr::Effect::REVERSE; - } - let filename_text = if current_path.is_dir() && !current_path.is_symlink() { - if current_node.folded { - format!("▸ {}", filename) - } else { - format!("▾ {}", filename) - } - } else { - filename - }; - content.push(( - fileinfo.format_no_filename().unwrap_or_default(), - prefix.to_owned(), - ColoredString::new(filename_text, color_effect, current_path.to_owned()), + content.push(::make( + &fileinfo, + &prefix, + filename_format(path, node), + ColorEffect::node(&fileinfo, node), + path, )); - if current_path.is_dir() && !current_path.is_symlink() && !current_node.folded { - let first_prefix = first_prefix(prefix.clone()); - let other_prefix = other_prefix(prefix); - - if let Some(children) = ¤t_node.children { - let mut leaves = children.iter(); - let Some(first_leaf) = leaves.next() else { - continue; - }; - stack.push((first_prefix.clone(), first_leaf)); - - for leaf in leaves { - stack.push((other_prefix.clone(), leaf)); - } - } + if Self::have_interesting_children(path, node) { + Self::stack_children(&mut stack, prefix, node); } - if content.len() > required_height { + if content.len() > self.required_height { break; } } (selected_index, content) } - pub fn paths(&self) -> Vec<&std::ffi::OsStr> { + pub fn filenames(&self) -> Vec<&std::ffi::OsStr> { self.nodes .keys() .filter_map(|path| path.file_name()) .collect() } + + #[inline] + fn stack_children<'a>( + stack: &mut Vec<(String, &'a Path)>, + prefix: String, + current_node: &'a Node, + ) { + let first_prefix = first_prefix(prefix.clone()); + let other_prefix = other_prefix(prefix); + + let Some(children) = ¤t_node.children else { + return; + }; + let mut children = children.iter(); + let Some(first_leaf) = children.next() else { + return; + }; + stack.push((first_prefix.clone(), first_leaf)); + + for leaf in children { + stack.push((other_prefix.clone(), leaf)); + } + } + + #[inline] + fn have_interesting_children(current_path: &Path, current_node: &Node) -> bool { + current_path.is_dir() && !current_path.is_symlink() && !current_node.folded + } } +#[inline] fn first_prefix(mut prefix: String) -> String { prefix.push(' '); prefix = prefix.replace("└──", " "); @@ -513,6 +514,7 @@ fn first_prefix(mut prefix: String) -> String { prefix } +#[inline] fn other_prefix(mut prefix: String) -> String { prefix.push(' '); prefix = prefix.replace("└──", " "); @@ -521,6 +523,23 @@ fn other_prefix(mut prefix: String) -> String { prefix } +#[inline] +fn filename_format(current_path: &Path, current_node: &Node) -> String { + let filename = filename_from_path(current_path) + .unwrap_or_default() + .to_owned(); + + if current_path.is_dir() && !current_path.is_symlink() { + if current_node.folded { + format!("▸ {}", filename) + } else { + format!("▾ {}", filename) + } + } else { + filename + } +} + pub fn calculate_tree_window(selected_index: usize, terminal_height: usize) -> (usize, usize) { let top: usize; let bottom: usize; @@ -529,7 +548,7 @@ pub fn calculate_tree_window(selected_index: usize, terminal_height: usize) -> ( top = 0; bottom = window_height; } else { - let padding = std::cmp::max(10, terminal_height / 2); + let padding = 10.max(terminal_height / 2); top = selected_index - padding; bottom = top + window_height; } From 7047cce927791ca386a87edf289d2fd1a5cbd91b Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 01:05:43 +0100 Subject: [PATCH 143/168] first step in regrouping tree movement into a trait --- src/tab.rs | 9 ++++----- src/tree.rs | 27 ++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 2861f73b..7e54f0f3 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -17,7 +17,7 @@ use crate::preview::Preview; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; use crate::sort::SortKind; -use crate::tree::{calculate_tree_window, Tree}; +use crate::tree::{calculate_tree_window, Go, To, Tree}; use crate::users::Users; use crate::utils::{row_to_window_index, set_clipboard}; @@ -371,7 +371,8 @@ impl Tab { /// Select the next sibling. pub fn tree_select_next(&mut self) -> Result<()> { - self.tree.select_next() + self.tree.go(To::Next); + Ok(()) } /// Select the previous siblging @@ -524,8 +525,6 @@ impl Tab { /// Fold every child node in the tree. /// Recursively explore the tree and fold every node. Reset the display. pub fn tree_go_to_root(&mut self) -> Result<()> { - // self.directory.tree.reset_required_height(); - // self.tree_select_root() self.tree.select_root(); Ok(()) } @@ -653,7 +652,7 @@ impl Tab { let (top, _) = calculate_tree_window(selected_index, term_height - 2); let index = screen_index + top; let (_, _, colored_path) = content.get(index).context("no selected file")?; - self.tree.select_from_path(&colored_path.path); + self.tree.select_path(&colored_path.path); Ok(()) } diff --git a/src/tree.rs b/src/tree.rs index 5d9d5fd4..68229e75 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -86,6 +86,31 @@ impl Node { self.children = children } } +pub trait Go { + fn go(&mut self, direction: To); +} + +pub enum To<'a> { + Next, + Prev, + Root, + Last, + Parent, + Path(&'a Path), +} + +impl Go for Tree { + fn go(&mut self, direction: To) { + match direction { + To::Next => self.select_next().unwrap_or_else(|_| ()), + To::Prev => self.select_prev(), + To::Root => self.select_root(), + To::Last => self.select_last(), + To::Parent => self.select_parent(), + To::Path(path) => self.select_path(path), + } + } +} #[derive(Debug, Clone)] pub struct Tree { @@ -361,7 +386,7 @@ impl Tree { } } - pub fn select_from_path(&mut self, clicked_path: &Path) { + pub fn select_path(&mut self, clicked_path: &Path) { let Some(new_node) = self.nodes.get_mut(clicked_path) else { return; }; From e1628f5e2e6b540c8680a76931d34ea0aff189ff Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 09:42:15 +0100 Subject: [PATCH 144/168] use Go trait instead of selections --- src/event_exec.rs | 2 +- src/tab.rs | 17 ++++++++--------- src/tree.rs | 10 ++++++---- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/event_exec.rs b/src/event_exec.rs index 1dc4cc58..dd467ddd 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -622,7 +622,7 @@ impl EventAction { } Mode::Preview => tab.page_up(), Mode::Tree => { - tab.tree_page_up()?; + tab.tree_page_up(); status.update_second_pane_for_preview()?; } _ => (), diff --git a/src/tab.rs b/src/tab.rs index 7e54f0f3..ea2eab3e 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -342,7 +342,7 @@ impl Tab { self.set_pathcontent(parent.to_owned().as_ref())?; self.make_tree(Some(self.path_content.sort_kind.clone())) } else { - self.tree.select_parent(); + self.tree.go(To::Parent); Ok(()) } } @@ -350,7 +350,7 @@ impl Tab { /// Move down 10 times in the tree pub fn tree_page_down(&mut self) -> Result<()> { for _ in 1..10 { - self.tree.select_next()?; + self.tree.go(To::Next); if self.tree.is_on_last() { break; } @@ -359,14 +359,13 @@ impl Tab { } /// Move up 10 times in the tree - pub fn tree_page_up(&mut self) -> Result<()> { + pub fn tree_page_up(&mut self) { for _ in 1..10 { - self.tree.select_prev(); + self.tree.go(To::Prev); if self.tree.is_on_root() { break; } } - Ok(()) } /// Select the next sibling. @@ -377,13 +376,13 @@ impl Tab { /// Select the previous siblging pub fn tree_select_prev(&mut self) -> Result<()> { - self.tree.select_prev(); + self.tree.go(To::Prev); Ok(()) } /// Go to the last leaf. pub fn tree_go_to_bottom_leaf(&mut self) -> Result<()> { - self.tree.select_last(); + self.tree.go(To::Last); Ok(()) } @@ -525,7 +524,7 @@ impl Tab { /// Fold every child node in the tree. /// Recursively explore the tree and fold every node. Reset the display. pub fn tree_go_to_root(&mut self) -> Result<()> { - self.tree.select_root(); + self.tree.go(To::Root); Ok(()) } @@ -652,7 +651,7 @@ impl Tab { let (top, _) = calculate_tree_window(selected_index, term_height - 2); let index = screen_index + top; let (_, _, colored_path) = content.get(index).context("no selected file")?; - self.tree.select_path(&colored_path.path); + self.tree.go(To::Path(&colored_path.path)); Ok(()) } diff --git a/src/tree.rs b/src/tree.rs index 68229e75..aee8312c 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -86,6 +86,8 @@ impl Node { self.children = children } } + +/// Describe a movement in a navigable structure pub trait Go { fn go(&mut self, direction: To); } @@ -345,7 +347,7 @@ impl Tree { } } - pub fn select_root(&mut self) { + fn select_root(&mut self) { let Some(selected_node) = self.nodes.get_mut(&self.selected) else { unreachable!("selected path should be in node") }; @@ -358,7 +360,7 @@ impl Tree { self.reset_required_height() } - pub fn select_last(&mut self) { + fn select_last(&mut self) { let Some(selected_node) = self.nodes.get_mut(&self.selected) else { unreachable!("selected path should be in node") }; @@ -371,7 +373,7 @@ impl Tree { self.set_required_height_to_max() } - pub fn select_parent(&mut self) { + fn select_parent(&mut self) { if let Some(parent_path) = self.selected.parent() { let Some(parent_node) = self.nodes.get_mut(parent_path) else { return; @@ -386,7 +388,7 @@ impl Tree { } } - pub fn select_path(&mut self, clicked_path: &Path) { + fn select_path(&mut self, clicked_path: &Path) { let Some(new_node) = self.nodes.get_mut(clicked_path) else { return; }; From 17b59cce5ecddf6f7c6609a5680f3486649932d6 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 09:45:39 +0100 Subject: [PATCH 145/168] dev --- development.md | 1 + 1 file changed, 1 insertion(+) diff --git a/development.md b/development.md index e0cab53e..55ae57ab 100644 --- a/development.md +++ b/development.md @@ -615,6 +615,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: move back from root should redo the parent tree - [x] FIX: move up from to go to last and vice versa - [x] FIX: enter a dir from normal mode shouldn't set mode tree + - [x] Use a generic trait for movements - [ ] FIX: tab.selected() should return Option<&FileInfo> - [ ] search can only find the first match - [ ] test everything From 64b3564b51d359010fcd339d762290f08ee91dba Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 09:45:52 +0100 Subject: [PATCH 146/168] don't use public attributes in nodes --- src/fileinfo.rs | 2 +- src/tree.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/fileinfo.rs b/src/fileinfo.rs index 9821adf2..27ad61d2 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -581,7 +581,7 @@ impl ColorEffect { #[inline] pub fn node(fileinfo: &FileInfo, current_node: &Node) -> Self { let mut color_effect = Self::new(fileinfo); - if current_node.selected { + if current_node.selected() { color_effect.effect |= Effect::REVERSE; } color_effect diff --git a/src/tree.rs b/src/tree.rs index aee8312c..1c58f8db 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -38,10 +38,10 @@ impl ColoredString { #[derive(Debug, Clone)] pub struct Node { - pub path: PathBuf, - pub children: Option>, - pub folded: bool, - pub selected: bool, + path: PathBuf, + children: Option>, + folded: bool, + selected: bool, } impl Node { @@ -74,6 +74,10 @@ impl Node { self.selected = false } + pub fn selected(&self) -> bool { + self.selected + } + pub fn fileinfo(&self, users: &Users) -> Result { FileInfo::new(&self.path, users) } From 0c617c271b229d3c1fa3cdf690a85d3525fc5431 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 10:05:16 +0100 Subject: [PATCH 147/168] FIX: first line position for tree --- development.md | 2 ++ src/term_manager.rs | 74 ++++++++++++++++++++++++++++----------------- src/tree.rs | 8 +++++ 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/development.md b/development.md index 55ae57ab..b7ffdafa 100644 --- a/development.md +++ b/development.md @@ -616,6 +616,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: move up from to go to last and vice versa - [x] FIX: enter a dir from normal mode shouldn't set mode tree - [x] Use a generic trait for movements + - [x] FIX: first line position for tree - [ ] FIX: tab.selected() should return Option<&FileInfo> - [ ] search can only find the first match - [ ] test everything @@ -624,6 +625,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. ## TODO +- [ ] use widget for every drawable element. First line should be a collection of widget which are drawned - [ ] while second window is opened, if the selection is below half screen, it's not shown anymore. Scroll to second element if needed - [ ] remote control diff --git a/src/term_manager.rs b/src/term_manager.rs index 27257efa..035cbccf 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -137,16 +137,8 @@ impl<'a> Draw for WinMain<'a> { self.preview_as_second_pane(canvas)?; return Ok(()); } - match self.tab.mode { - Mode::Preview => self.preview(self.tab, &self.tab.window, canvas), - Mode::Tree => self.trees(self.status, self.tab, canvas), - Mode::Normal => self.files(self.status, self.tab, canvas), - _ => match self.tab.previous_mode { - Mode::Tree => self.trees(self.status, self.tab, canvas), - _ => self.files(self.status, self.tab, canvas), - }, - }?; - self.first_line(self.disk_space, canvas)?; + let opt_index = self.draw_content(canvas)?; + self.first_line(self.disk_space, canvas, opt_index)?; Ok(()) } } @@ -177,6 +169,18 @@ impl<'a> WinMain<'a> { Ok(()) } + fn draw_content(&self, canvas: &mut dyn Canvas) -> Result> { + match self.tab.mode { + Mode::Preview => self.preview(self.tab, &self.tab.window, canvas), + Mode::Tree => self.trees(self.status, self.tab, canvas), + Mode::Normal => self.files(self.status, self.tab, canvas), + _ => match self.tab.previous_mode { + Mode::Tree => self.trees(self.status, self.tab, canvas), + _ => self.files(self.status, self.tab, canvas), + }, + } + } + /// Display the top line on terminal. /// Its content depends on the mode. /// In normal mode we display the path and number of files. @@ -184,11 +188,16 @@ impl<'a> WinMain<'a> { /// something else. /// Returns the result of the number of printed chars. /// The colors are reversed when the tab is selected. It gives a visual indication of where he is. - fn first_line(&self, disk_space: &str, canvas: &mut dyn Canvas) -> Result<()> { + fn first_line( + &self, + disk_space: &str, + canvas: &mut dyn Canvas, + opt_index: Option, + ) -> Result<()> { draw_colored_strings( 0, 0, - self.create_first_row(disk_space)?, + self.create_first_row(disk_space, opt_index)?, canvas, self.attributes.is_selected, ) @@ -237,11 +246,11 @@ impl<'a> WinMain<'a> { )?) } - fn normal_first_row(&self, disk_space: &str) -> Result> { + fn normal_first_row(&self, disk_space: &str, opt_index: Option) -> Result> { Ok(vec![ format!(" {}", shorten_path(&self.tab.path_content.path, None)?), self.first_row_filename()?, - self.first_row_position(), + self.first_row_position(opt_index), format!("{} ", self.tab.path_content.used_space()), format!(" Avail: {disk_space} "), format!(" {} ", self.tab.path_content.git_string()?), @@ -266,11 +275,17 @@ impl<'a> WinMain<'a> { } } - fn first_row_position(&self) -> String { + fn first_row_position(&self, opt_index: Option) -> String { + if matches!(self.tab.mode, Mode::Tree) { + let Some(selected_index) = opt_index else { + return "".to_owned(); + }; + return format!(" {selected_index} / {len} ", len = self.tab.tree.len()); + }; format!( - " {} / {} ", - self.tab.path_content.index + 1, - self.tab.path_content.true_len() + 2 + " {index} / {len} ", + index = self.tab.path_content.index + 1, + len = self.tab.path_content.true_len() + 2 ) } @@ -324,10 +339,10 @@ impl<'a> WinMain<'a> { } } - fn create_first_row(&self, disk_space: &str) -> Result> { + fn create_first_row(&self, disk_space: &str, opt_index: Option) -> Result> { let tab = self.status.selected_non_mut(); let first_row = match tab.mode { - Mode::Normal | Mode::Tree => self.normal_first_row(disk_space)?, + Mode::Normal | Mode::Tree => self.normal_first_row(disk_space, opt_index)?, Mode::Preview => match &tab.preview { Preview::Text(text_content) => match text_content.kind { TextKind::HELP => Self::help_first_row(), @@ -337,7 +352,7 @@ impl<'a> WinMain<'a> { _ => self.default_preview_first_line(tab), }, _ => match self.tab.previous_mode { - Mode::Normal | Mode::Tree => self.normal_first_row(disk_space)?, + Mode::Normal | Mode::Tree => self.normal_first_row(disk_space, opt_index)?, _ => vec![], }, }; @@ -351,7 +366,7 @@ impl<'a> WinMain<'a> { /// We reverse the attributes of the selected one, underline the flagged files. /// When we display a simpler version, the second line is used to display the /// metadata of the selected file. - fn files(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + fn files(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result> { let len = tab.path_content.content.len(); let group_size: usize; let owner_size: usize; @@ -388,7 +403,7 @@ impl<'a> WinMain<'a> { if !self.attributes.has_window_below { self.log_line(canvas)?; } - Ok(()) + Ok(None) } fn log_line(&self, canvas: &mut dyn Canvas) -> Result<()> { @@ -397,7 +412,7 @@ impl<'a> WinMain<'a> { Ok(()) } - fn trees(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + fn trees(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result> { let left_margin = if status.display_full { 1 } else { 3 }; let (_, height) = canvas.size()?; let (selected_index, content) = tab.tree.into_navigable_content(&tab.users); @@ -433,7 +448,7 @@ impl<'a> WinMain<'a> { )?; } self.second_line(status, tab, canvas)?; - Ok(()) + Ok(Some(selected_index)) } fn print_line_number( row_position_in_canvas: usize, @@ -455,7 +470,12 @@ impl<'a> WinMain<'a> { /// else the content is supposed to be text and shown as such. /// It may fail to recognize some usual extensions, notably `.toml`. /// It may fail to recognize small files (< 1024 bytes). - fn preview(&self, tab: &Tab, window: &ContentWindow, canvas: &mut dyn Canvas) -> Result<()> { + fn preview( + &self, + tab: &Tab, + window: &ContentWindow, + canvas: &mut dyn Canvas, + ) -> Result> { let length = tab.preview.len(); let line_number_width = length.to_string().len(); match &tab.preview { @@ -545,7 +565,7 @@ impl<'a> WinMain<'a> { Preview::Empty => (), } - Ok(()) + Ok(None) } } diff --git a/src/tree.rs b/src/tree.rs index 1c58f8db..a3d53754 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -223,6 +223,14 @@ impl Tree { Ok(self.selected.strip_prefix(&self.root_path)?) } + pub fn len(&self) -> usize { + self.nodes.len() + } + + pub fn is_empty(&self) -> bool { + self.nodes.is_empty() + } + pub fn is_on_root(&self) -> bool { self.selected == self.root_path } From 77714cbf215de076a89f4d792b5693b1db48ef76 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 10:05:33 +0100 Subject: [PATCH 148/168] dev --- development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/development.md b/development.md index b7ffdafa..9bbccb18 100644 --- a/development.md +++ b/development.md @@ -618,7 +618,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] Use a generic trait for movements - [x] FIX: first line position for tree - [ ] FIX: tab.selected() should return Option<&FileInfo> - - [ ] search can only find the first match + - [ ] FIX: search can only find the first match - [ ] test everything - [ ] refactor - [ ] document From c449f53b02bdc2392f33a1303e85b50eaa72128f Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 19:26:27 +0100 Subject: [PATCH 149/168] refactor tree and users of tree --- src/completion.rs | 20 +++++++-------- src/event_exec.rs | 2 +- src/status.rs | 6 +---- src/tab.rs | 2 +- src/tree.rs | 63 ++++++++++++++++++++++++++--------------------- 5 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 38ed6150..e2da5147 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -3,7 +3,7 @@ use std::fs::{self, ReadDir}; use anyhow::Result; use strum::IntoEnumIterator; -use crate::fileinfo::PathContent; +use crate::{fileinfo::PathContent, tree::Node}; /// Different kind of completions #[derive(Clone, Default, Copy)] @@ -215,18 +215,18 @@ impl Completion { pub fn search_from_tree( &mut self, input_string: &str, - content: &[&std::ffi::OsStr], + content: std::iter::FilterMap< + std::iter::FilterMap< + std::collections::hash_map::Keys<'_, std::path::PathBuf, Node>, + fn(&std::path::PathBuf) -> Option<&std::ffi::OsStr>, + >, + fn(&std::ffi::OsStr) -> Option<&str>, + >, ) -> Result<()> { self.update( content - .iter() - .filter(|&p| p.to_string_lossy().contains(input_string)) - .map(|p| { - p.to_string_lossy() - .into_owned() - .replace("▸ ", "") - .replace("▾ ", "") - }) + .filter(|&p| p.contains(input_string)) + .map(|p| p.replace("▸ ", "").replace("▾ ", "")) .collect(), ); diff --git a/src/event_exec.rs b/src/event_exec.rs index dd467ddd..c1fcbe44 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -1450,7 +1450,7 @@ impl LeaveMode { /// Execute the selected node if it's a file else enter the directory. pub fn tree(status: &mut Status) -> Result<()> { - let path = status.selected_fileinfo()?.path; + let path = status.selected_non_mut().selected()?.path; let is_dir = path.is_dir(); if is_dir { status.selected().set_pathcontent(&path)?; diff --git a/src/status.rs b/src/status.rs index f0b9f016..68e940df 100644 --- a/src/status.rs +++ b/src/status.rs @@ -20,7 +20,7 @@ use crate::config::Settings; use crate::constant_strings_paths::{NVIM, SS, TUIS_PATH}; use crate::copy_move::{copy_move, CopyMove}; use crate::cryptsetup::{BlockDeviceAction, CryptoDeviceOpener}; -use crate::fileinfo::{FileInfo, FileKind}; +use crate::fileinfo::FileKind; use crate::flagged::Flagged; use crate::iso::IsoDevice; use crate::log_line; @@ -258,10 +258,6 @@ impl Status { &self.tabs[self.index] } - pub fn selected_fileinfo(&self) -> Result { - self.selected_non_mut().selected() - } - /// Reset the view of every tab. pub fn reset_tabs_view(&mut self) -> Result<()> { for tab in self.tabs.iter_mut() { diff --git a/src/tab.rs b/src/tab.rs index ea2eab3e..e2010913 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -131,7 +131,7 @@ impl Tab { if matches!(self.previous_mode, Mode::Tree) => { self.completion - .search_from_tree(&self.input.string(), &self.tree.filenames()) + .search_from_tree(&self.input.string(), self.tree.filenames()) } Mode::InputCompleted(InputCompleted::Command) => { self.completion.command(&self.input.string()) diff --git a/src/tree.rs b/src/tree.rs index a3d53754..a44eea10 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,5 +1,8 @@ use std::{ + collections::hash_map, collections::HashMap, + ffi::OsStr, + iter::FilterMap, path::{Path, PathBuf}, }; @@ -45,7 +48,7 @@ pub struct Node { } impl Node { - pub fn new(path: &Path, children: Option>) -> Self { + fn new(path: &Path, children: Option>) -> Self { Self { path: path.to_owned(), children, @@ -54,23 +57,23 @@ impl Node { } } - pub fn fold(&mut self) { + fn fold(&mut self) { self.folded = true } - pub fn unfold(&mut self) { + fn unfold(&mut self) { self.folded = false } - pub fn toggle_fold(&mut self) { + fn toggle_fold(&mut self) { self.folded = !self.folded } - pub fn select(&mut self) { + fn select(&mut self) { self.selected = true } - pub fn unselect(&mut self) { + fn unselect(&mut self) { self.selected = false } @@ -82,12 +85,13 @@ impl Node { FileInfo::new(&self.path, users) } - pub fn color_effect(&self, users: &Users) -> Result { - Ok(ColorEffect::new(&self.fileinfo(users)?)) + fn set_children(&mut self, children: Option>) { + self.children = children } - pub fn set_children(&mut self, children: Option>) { - self.children = children + #[inline] + fn have_children(self: &Node) -> bool { + !self.folded && self.children.is_some() } } @@ -108,7 +112,7 @@ pub enum To<'a> { impl Go for Tree { fn go(&mut self, direction: To) { match direction { - To::Next => self.select_next().unwrap_or_else(|_| ()), + To::Next => self.select_next(), To::Prev => self.select_prev(), To::Root => self.select_root(), To::Last => self.select_last(), @@ -157,7 +161,9 @@ impl Tree { { sort_kind.sort(&mut files); let children = Self::make_children_and_stack_them(&mut stack, &files); - node.set_children(Some(children)); + if !children.is_empty() { + node.set_children(Some(children)); + } }; } last_path = node.path.to_owned(); @@ -240,17 +246,16 @@ impl Tree { } /// Select next sibling or the next sibling of the parent - pub fn select_next(&mut self) -> Result<()> { + pub fn select_next(&mut self) { if self.is_on_last() { self.select_root(); - return Ok(()); + return; } if let Some(next_path) = self.find_next_path() { let Some(next_node) = self.nodes.get_mut(&next_path) else { - return Ok(()); + return; }; - log::info!("selecting {next_node:?}"); next_node.select(); let Some(selected_node) = self.nodes.get_mut(&self.selected) else { unreachable!("current_node should be in nodes"); @@ -259,7 +264,6 @@ impl Tree { self.selected = next_path; self.increment_required_height() } - Ok(()) } // FIX: Still a problem when reaching max depth of tree, @@ -497,7 +501,7 @@ impl Tree { path, )); - if Self::have_interesting_children(path, node) { + if node.have_children() { Self::stack_children(&mut stack, prefix, node); } @@ -508,11 +512,19 @@ impl Tree { (selected_index, content) } - pub fn filenames(&self) -> Vec<&std::ffi::OsStr> { - self.nodes - .keys() - .filter_map(|path| path.file_name()) - .collect() + /// An (ugly) iterator over filenames. + /// It allows us to iter explicitely over filenames + /// while avoiding another allocation by collecting into a `Vec` + #[inline] + pub fn filenames( + &self, + ) -> FilterMap< + FilterMap, fn(&PathBuf) -> Option<&OsStr>>, + fn(&OsStr) -> Option<&str>, + > { + let to_filename: fn(&PathBuf) -> Option<&OsStr> = |path| path.file_name(); + let to_str: fn(&OsStr) -> Option<&str> = |filename| filename.to_str(); + self.nodes.keys().filter_map(to_filename).filter_map(to_str) } #[inline] @@ -537,11 +549,6 @@ impl Tree { stack.push((other_prefix.clone(), leaf)); } } - - #[inline] - fn have_interesting_children(current_path: &Path, current_node: &Node) -> bool { - current_path.is_dir() && !current_path.is_symlink() && !current_node.folded - } } #[inline] From c6fd22dbde9b02869867161279fe8df3004f8642 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 19:48:13 +0100 Subject: [PATCH 150/168] tree refactor --- development.md | 5 +++-- src/tree.rs | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/development.md b/development.md index 9bbccb18..8e554624 100644 --- a/development.md +++ b/development.md @@ -617,14 +617,15 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: enter a dir from normal mode shouldn't set mode tree - [x] Use a generic trait for movements - [x] FIX: first line position for tree - - [ ] FIX: tab.selected() should return Option<&FileInfo> - - [ ] FIX: search can only find the first match + - [ ] FIX: copying/moving 0B files does nothing - [ ] test everything - [ ] refactor - [ ] document ## TODO +- [ ] FIX: tab.selected() should return Option<&FileInfo> +- [ ] FIX: search can only find the first match - [ ] use widget for every drawable element. First line should be a collection of widget which are drawned - [ ] while second window is opened, if the selection is below half screen, it's not shown anymore. Scroll to second element if needed diff --git a/src/tree.rs b/src/tree.rs index a44eea10..828c3324 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -307,7 +307,6 @@ impl Tree { continue; }; if self.nodes.contains_key(next_sibling_path) { - log::info!("returning {next_sibling_path:?}"); return Some(next_sibling_path.to_owned()); } else { current_path = parent_path.to_owned(); From ed9c1484827f15c55a6a2baf70d9af5c05129928 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 21:33:16 +0100 Subject: [PATCH 151/168] tree and users refactor --- development.md | 2 +- src/event_exec.rs | 25 ++++++----- src/opener.rs | 31 +++++++++++++ src/status.rs | 4 +- src/tab.rs | 16 +++---- src/term_manager.rs | 4 +- src/tree.rs | 105 +++++++++++++++++++++++++------------------- 7 files changed, 117 insertions(+), 70 deletions(-) diff --git a/development.md b/development.md index 8e554624..ae340f8a 100644 --- a/development.md +++ b/development.md @@ -620,7 +620,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] FIX: copying/moving 0B files does nothing - [ ] test everything - [ ] refactor - - [ ] document + - [x] document ## TODO diff --git a/src/event_exec.rs b/src/event_exec.rs index c1fcbe44..5514ccf1 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -19,8 +19,9 @@ use crate::log_line; use crate::mocp::Mocp; use crate::mocp::MOCP; use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation}; +use crate::opener::execute_and_capture_output_with_path; use crate::opener::{ - execute_and_capture_output, execute_and_capture_output_without_check, execute_in_child, + execute_and_capture_output_without_check, execute_in_child, execute_in_child_without_output_with_path, }; use crate::password::{PasswordKind, PasswordUsage}; @@ -374,7 +375,7 @@ impl EventAction { /// Basic folders (/, /dev... $HOME) and mount points (even impossible to /// visit ones) are proposed. pub fn shortcut(tab: &mut Tab) -> Result<()> { - std::env::set_current_dir(tab.current_directory_path().context("no parent")?)?; + std::env::set_current_dir(tab.directory_of_selected()?)?; tab.shortcut.update_git_root(); tab.set_mode(Mode::Navigate(Navigate::Shortcut)); Ok(()) @@ -1557,16 +1558,18 @@ impl LeaveMode { }; let (username, hostname, remote_path) = (strings[0], strings[1], strings[2]); - let current_path: &str = tab - .current_directory_path() - .context("no parent")? - .to_str() - .context("couldn't parse the path")?; + let current_path: &str = &tab + .directory_of_selected_previous_mode()? + .display() + .to_string(); let first_arg = &format!("{username}@{hostname}:{remote_path}"); - let command_output = - execute_and_capture_output(SSHFS_EXECUTABLE, &[first_arg, current_path]); - info!("{SSHFS_EXECUTABLE} output {command_output:?}"); - log_line!("{SSHFS_EXECUTABLE} output {command_output:?}"); + let command_output = execute_and_capture_output_with_path( + SSHFS_EXECUTABLE, + current_path, + &[first_arg, current_path], + ); + info!("{SSHFS_EXECUTABLE} {strings:?} output {command_output:?}"); + log_line!("{SSHFS_EXECUTABLE} {strings:?} output {command_output:?}"); Ok(()) } } diff --git a/src/opener.rs b/src/opener.rs index 1b77ad74..7e1e186f 100644 --- a/src/opener.rs +++ b/src/opener.rs @@ -498,6 +498,37 @@ pub fn execute_and_capture_output + fmt::Debug>( } } +/// Execute a command with options in a fork. +/// Wait for termination and return either : +/// `Ok(stdout)` if the status code is 0 +/// an Error otherwise +/// Branch stdin and stderr to /dev/null +pub fn execute_and_capture_output_with_path< + S: AsRef + fmt::Debug, + P: AsRef, +>( + exe: S, + path: P, + args: &[&str], +) -> Result { + info!("execute_and_capture_output. executable: {exe:?}, arguments: {args:?}",); + let child = Command::new(exe) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .current_dir(path) + .spawn()?; + let output = child.wait_with_output()?; + if output.status.success() { + Ok(String::from_utf8(output.stdout)?) + } else { + Err(anyhow!( + "execute_and_capture_output: command didn't finish properly", + )) + } +} + /// Execute a command with options in a fork. /// Wait for termination and return either `Ok(stdout)`. /// Branch stdin and stderr to /dev/null diff --git a/src/status.rs b/src/status.rs index 68e940df..e5d91833 100644 --- a/src/status.rs +++ b/src/status.rs @@ -582,7 +582,7 @@ impl Status { /// Drop the current tree, replace it with an empty one. pub fn remove_tree(&mut self) -> Result<()> { - self.selected().tree = Tree::empty(); + self.selected().tree = Tree::default(); Ok(()) } @@ -617,7 +617,7 @@ impl Status { if let Mode::Tree = self.selected_non_mut().mode { { let tab = self.selected(); - tab.tree = Tree::empty(); + tab.tree = Tree::default(); tab.refresh_view() }?; self.selected().set_mode(Mode::Normal) diff --git a/src/tab.rs b/src/tab.rs index e2010913..a8edba21 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -17,7 +17,7 @@ use crate::preview::Preview; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; use crate::sort::SortKind; -use crate::tree::{calculate_tree_window, Go, To, Tree}; +use crate::tree::{calculate_top_bottom, Go, To, Tree}; use crate::users::Users; use crate::utils::{row_to_window_index, set_clipboard}; @@ -89,7 +89,7 @@ impl Tab { shortcut.extend_with_mount_points(mount_points); let searched = None; let index = path_content.select_file(&path); - let tree = Tree::empty(); + let tree = Tree::default(); window.scroll_to(index); Ok(Self { mode, @@ -146,7 +146,7 @@ impl Tab { self.input.reset(); self.preview = Preview::empty(); self.completion.reset(); - self.tree = Tree::empty(); + self.tree = Tree::default(); Ok(()) } @@ -391,10 +391,10 @@ impl Tab { /// if the selected node is a directory, that's it. /// else, it is the parent of the selected node. /// In other modes, it's the current path of pathcontent. - pub fn current_directory_path(&mut self) -> Option<&path::Path> { - match self.mode { - Mode::Tree => return self.tree.directory_of_selected(), - _ => Some(&self.path_content.path), + pub fn directory_of_selected_previous_mode(&mut self) -> Result<&path::Path> { + match self.previous_mode { + Mode::Tree => return self.tree.directory_of_selected().context("no parent"), + _ => Ok(&self.path_content.path), } } @@ -648,7 +648,7 @@ impl Tab { fn tree_select_row(&mut self, row: u16, term_height: usize) -> Result<()> { let screen_index = row_to_window_index(row); let (selected_index, content) = self.tree.into_navigable_content(&self.users); - let (top, _) = calculate_tree_window(selected_index, term_height - 2); + let (top, _) = calculate_top_bottom(selected_index, term_height - 2); let index = screen_index + top; let (_, _, colored_path) = content.get(index).context("no selected file")?; self.tree.go(To::Path(&colored_path.path)); diff --git a/src/term_manager.rs b/src/term_manager.rs index 035cbccf..06ec467e 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -25,7 +25,7 @@ use crate::selectable_content::SelectableContent; use crate::status::Status; use crate::tab::Tab; use crate::trash::TrashInfo; -use crate::tree::calculate_tree_window; +use crate::tree::calculate_top_bottom; /// Iter over the content, returning a triplet of `(index, line, attr)`. macro_rules! enumerated_colored_iter { @@ -416,7 +416,7 @@ impl<'a> WinMain<'a> { let left_margin = if status.display_full { 1 } else { 3 }; let (_, height) = canvas.size()?; let (selected_index, content) = tab.tree.into_navigable_content(&tab.users); - let (top, bottom) = calculate_tree_window(selected_index, height); + let (top, bottom) = calculate_top_bottom(selected_index, height); let length = content.len(); for (i, (metadata, prefix, colored_string)) in content diff --git a/src/tree.rs b/src/tree.rs index 828c3324..2feebcdf 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,24 +1,20 @@ -use std::{ - collections::hash_map, - collections::HashMap, - ffi::OsStr, - iter::FilterMap, - path::{Path, PathBuf}, -}; +use std::collections::hash_map; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::iter::FilterMap; +use std::path::{Path, PathBuf}; use anyhow::Result; -use crate::{ - content_window::ContentWindow, - fileinfo::{files_collection, ColorEffect, FileInfo}, - filter::FilterKind, - preview::{ColoredTriplet, MakeTriplet}, - sort::SortKind, - users::Users, - utils::filename_from_path, -}; - -/// Holds a string and its display attributes. +use crate::content_window::ContentWindow; +use crate::fileinfo::{files_collection, ColorEffect, FileInfo}; +use crate::filter::FilterKind; +use crate::preview::{ColoredTriplet, MakeTriplet}; +use crate::sort::SortKind; +use crate::users::Users; +use crate::utils::filename_from_path; + +/// Holds a string, its display attributes and the associated pathbuf. #[derive(Clone, Debug)] pub struct ColoredString { /// A text to be printed. In most case, it should be a filename. @@ -30,6 +26,7 @@ pub struct ColoredString { } impl ColoredString { + /// Creates a new colored string. pub fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { Self { text, @@ -39,6 +36,9 @@ impl ColoredString { } } +/// An element of a tree. +/// It's a file/directory, some optional children. +/// A Node knows if it's folded or selected. #[derive(Debug, Clone)] pub struct Node { path: PathBuf, @@ -48,6 +48,8 @@ pub struct Node { } impl Node { + /// Creates a new Node from a path and its children. + /// By default it's not selected nor folded. fn new(path: &Path, children: Option>) -> Self { Self { path: path.to_owned(), @@ -77,10 +79,12 @@ impl Node { self.selected = false } + /// Is the node selected ? pub fn selected(&self) -> bool { self.selected } + /// Creates a new fileinfo from the node. pub fn fileinfo(&self, users: &Users) -> Result { FileInfo::new(&self.path, users) } @@ -97,9 +101,10 @@ impl Node { /// Describe a movement in a navigable structure pub trait Go { - fn go(&mut self, direction: To); + fn go(&mut self, to: To); } +/// Describes a direction for the next selected tree element. pub enum To<'a> { Next, Prev, @@ -110,8 +115,9 @@ pub enum To<'a> { } impl Go for Tree { - fn go(&mut self, direction: To) { - match direction { + /// Select another element from a tree. + fn go(&mut self, to: To) { + match to { To::Next => self.select_next(), To::Prev => self.select_prev(), To::Root => self.select_root(), @@ -122,7 +128,10 @@ impl Go for Tree { } } -#[derive(Debug, Clone)] +/// A FileSystem tree of nodes. +/// Internally it's a wrapper around an `std::collections::HashMap` +/// It also holds informations about the required height of the tree. +#[derive(Debug, Clone, Default)] pub struct Tree { root_path: PathBuf, selected: PathBuf, @@ -134,6 +143,7 @@ pub struct Tree { impl Tree { pub const DEFAULT_REQUIRED_HEIGHT: usize = 80; + /// Creates a new tree, exploring every node untill depth is reached. pub fn new( root_path: PathBuf, depth: usize, @@ -195,58 +205,60 @@ impl Tree { .collect() } - pub fn empty() -> Self { - Self { - root_path: PathBuf::default(), - selected: PathBuf::default(), - last_path: PathBuf::default(), - nodes: HashMap::new(), - required_height: 0, - } - } - + /// Root path of the tree. pub fn root_path(&self) -> &Path { self.root_path.as_path() } + /// Selected path pub fn selected_path(&self) -> &Path { self.selected.as_path() } + /// Selected node pub fn selected_node(&self) -> Option<&Node> { self.nodes.get(&self.selected) } + /// The folder containing the selected node. + /// Itself if selected is a directory. pub fn directory_of_selected(&self) -> Option<&Path> { - if self.selected.is_dir() && !self.selected.is_symlink() { + let ret = if self.selected.is_dir() && !self.selected.is_symlink() { Some(self.selected.as_path()) } else { self.selected.parent() - } + }; + log::info!("directory_of_selected {ret:?}"); + ret } + /// Relative path of selected from rootpath. pub fn selected_path_relative_to_root(&self) -> Result<&Path> { Ok(self.selected.strip_prefix(&self.root_path)?) } + /// Number of nodes pub fn len(&self) -> usize { self.nodes.len() } + /// True if there's no node. pub fn is_empty(&self) -> bool { self.nodes.is_empty() } + /// True if selected is root. pub fn is_on_root(&self) -> bool { self.selected == self.root_path } + /// True if selected is the last file pub fn is_on_last(&self) -> bool { self.selected == self.last_path } /// Select next sibling or the next sibling of the parent - pub fn select_next(&mut self) { + fn select_next(&mut self) { if self.is_on_last() { self.select_root(); return; @@ -317,7 +329,7 @@ impl Tree { } // TODO! find the bottom child of parent instead of jumping back 1 level /// Select previous sibling or the parent - pub fn select_prev(&mut self) { + fn select_prev(&mut self) { if self.is_on_root() { self.select_last(); return; @@ -443,12 +455,14 @@ impl Tree { } } + /// Fold all node from root to end pub fn fold_all(&mut self) { for (_, node) in self.nodes.iter_mut() { node.fold() } } + /// Unfold all node from root to end pub fn unfold_all(&mut self) { for (_, node) in self.nodes.iter_mut() { node.unfold() @@ -456,6 +470,7 @@ impl Tree { } // FIX: can only find the first match and nothing else + /// Select the first node whose filename match a pattern. pub fn search_first_match(&mut self, pattern: &str) { let initial_selected = self.selected.to_owned(); let Some((found_path, found_node)) = self.nodes.iter_mut().find(|(path, _)| { @@ -474,6 +489,7 @@ impl Tree { current_node.unselect(); } + /// Returns a navigable vector of `ColoredTriplet` and the index of selected file pub fn into_navigable_content(&self, users: &Users) -> (usize, Vec) { let mut stack = vec![("".to_owned(), self.root_path.as_path())]; let mut content = vec![]; @@ -585,18 +601,15 @@ fn filename_format(current_path: &Path, current_node: &Node) -> String { } } -pub fn calculate_tree_window(selected_index: usize, terminal_height: usize) -> (usize, usize) { - let top: usize; - let bottom: usize; +/// Emulate a `ContentWindow`, returning the top and bottom index of displayable files. +pub fn calculate_top_bottom(selected_index: usize, terminal_height: usize) -> (usize, usize) { let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP; - if selected_index < window_height { - top = 0; - bottom = window_height; + let top = if selected_index < window_height { + 0 } else { - let padding = 10.max(terminal_height / 2); - top = selected_index - padding; - bottom = top + window_height; - } + selected_index - 10.max(terminal_height / 2) + }; + let bottom = top + window_height; (top, bottom) } From fa0f1190f65391b4c03f2907c14e4efa33b293d2 Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 21:55:23 +0100 Subject: [PATCH 152/168] tree users refactor --- src/status.rs | 19 +++++-------------- src/tab.rs | 12 ++++++++---- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/status.rs b/src/status.rs index e5d91833..59988df1 100644 --- a/src/status.rs +++ b/src/status.rs @@ -405,20 +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 = match self.selected_non_mut().previous_mode { - Mode::Tree => self - .selected_non_mut() - .tree - .directory_of_selected() - .context("no parent")? - .display() - .to_string(), - _ => self - .selected_non_mut() - .path_content_str() - .context("cut or copy: unreadable path")? - .to_owned(), - }; + let dest = self + .selected_non_mut() + .directory_of_selected_previous_mode()? + .display() + .to_string(); copy_move(cut_or_copy, sources, &dest, Arc::clone(&self.term))?; self.clear_flags_and_reset_view() diff --git a/src/tab.rs b/src/tab.rs index a8edba21..5e7ad3ea 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -146,7 +146,11 @@ impl Tab { self.input.reset(); self.preview = Preview::empty(); self.completion.reset(); - self.tree = Tree::default(); + if matches!(self.mode, Mode::Tree) { + self.tree = Tree::default() + } else { + self.make_tree(None)?; + }; Ok(()) } @@ -387,11 +391,11 @@ impl Tab { } /// Returns the current path. - /// In tree mode : + /// If previous mode was tree mode : /// if the selected node is a directory, that's it. /// else, it is the parent of the selected node. /// In other modes, it's the current path of pathcontent. - pub fn directory_of_selected_previous_mode(&mut self) -> Result<&path::Path> { + pub fn directory_of_selected_previous_mode(&self) -> Result<&path::Path> { match self.previous_mode { Mode::Tree => return self.tree.directory_of_selected().context("no parent"), _ => Ok(&self.path_content.path), @@ -409,7 +413,7 @@ impl Tab { } } - /// Optional Fileinfo of the selected element. + /// Fileinfo of the selected element. pub fn selected(&self) -> Result { match self.mode { Mode::Tree => { From ab4a400885593bd1fd086bb89eac24d6f494589d Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 22:02:23 +0100 Subject: [PATCH 153/168] FIX: poor aligment of prefixes in tree --- src/tree.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index 2feebcdf..f495acb0 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -569,8 +569,8 @@ impl Tree { #[inline] fn first_prefix(mut prefix: String) -> String { prefix.push(' '); - prefix = prefix.replace("└──", " "); - prefix = prefix.replace("├──", "│ "); + prefix = prefix.replace("└──", " "); + prefix = prefix.replace("├──", "│ "); prefix.push_str("└──"); prefix } @@ -578,8 +578,8 @@ fn first_prefix(mut prefix: String) -> String { #[inline] fn other_prefix(mut prefix: String) -> String { prefix.push(' '); - prefix = prefix.replace("└──", " "); - prefix = prefix.replace("├──", "│ "); + prefix = prefix.replace("└──", " "); + prefix = prefix.replace("├──", "│ "); prefix.push_str("├──"); prefix } From 0d2e2c8a740ca944d432937643008f6220db37ab Mon Sep 17 00:00:00 2001 From: qkzk Date: Tue, 7 Nov 2023 22:09:46 +0100 Subject: [PATCH 154/168] dev --- development.md | 1 + src/tab.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index ae340f8a..32a60119 100644 --- a/development.md +++ b/development.md @@ -617,6 +617,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: enter a dir from normal mode shouldn't set mode tree - [x] Use a generic trait for movements - [x] FIX: first line position for tree + - [ ] FIX: changing sortkind in tree mode dosn't change first line - [ ] FIX: copying/moving 0B files does nothing - [ ] test everything - [ ] refactor diff --git a/src/tab.rs b/src/tab.rs index 5e7ad3ea..6bc39e07 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -354,10 +354,10 @@ impl Tab { /// Move down 10 times in the tree pub fn tree_page_down(&mut self) -> Result<()> { for _ in 1..10 { - self.tree.go(To::Next); if self.tree.is_on_last() { break; } + self.tree.go(To::Next); } Ok(()) } @@ -365,10 +365,10 @@ impl Tab { /// Move up 10 times in the tree pub fn tree_page_up(&mut self) { for _ in 1..10 { - self.tree.go(To::Prev); if self.tree.is_on_root() { break; } + self.tree.go(To::Prev); } } From f7bf9e6577249bc567808700fcd400c1a9745d18 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 8 Nov 2023 08:17:16 +0100 Subject: [PATCH 155/168] FIX: encrypted are never shown as mounted --- development.md | 3 +-- src/cryptsetup.rs | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/development.md b/development.md index 32a60119..87310f56 100644 --- a/development.md +++ b/development.md @@ -605,6 +605,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself +- [x] FIX: encrypted are never shown as mounted - [ ] Tree remade without recursion. Improve ram usage - [x] FIX: folders are max depth hangs the app - [x] FIX: rename renames the root path @@ -617,8 +618,6 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: enter a dir from normal mode shouldn't set mode tree - [x] Use a generic trait for movements - [x] FIX: first line position for tree - - [ ] FIX: changing sortkind in tree mode dosn't change first line - - [ ] FIX: copying/moving 0B files does nothing - [ ] test everything - [ ] refactor - [x] document diff --git a/src/cryptsetup.rs b/src/cryptsetup.rs index 59b37c4e..261fa2a8 100644 --- a/src/cryptsetup.rs +++ b/src/cryptsetup.rs @@ -102,7 +102,9 @@ impl CryptoDevice { } pub fn mount_point(&self) -> Option { - System::new_with_specifics(RefreshKind::new().with_disks()) + let mut system = System::new_with_specifics(RefreshKind::new().with_disks()); + system.refresh_disks_list(); + system .disks() .iter() .map(|d| d.mount_point()) From 8e53337b880c6e3080b3066944567bee3365a6d4 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 8 Nov 2023 08:23:29 +0100 Subject: [PATCH 156/168] fix first line position for tree (off by one) --- src/term_manager.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/term_manager.rs b/src/term_manager.rs index 06ec467e..44d99479 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -280,7 +280,11 @@ impl<'a> WinMain<'a> { let Some(selected_index) = opt_index else { return "".to_owned(); }; - return format!(" {selected_index} / {len} ", len = self.tab.tree.len()); + return format!( + " {position} / {len} ", + position = selected_index + 1, + len = self.tab.tree.len() + ); }; format!( " {index} / {len} ", From 9b847e37543f4e610fe238ff90a23799287e84c8 Mon Sep 17 00:00:00 2001 From: qkzk Date: Wed, 8 Nov 2023 08:23:39 +0100 Subject: [PATCH 157/168] rename a variable --- development.md | 2 +- src/tree.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/development.md b/development.md index 87310f56..1b324172 100644 --- a/development.md +++ b/development.md @@ -602,10 +602,10 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: exploring root folder leads to wrong first line display. - [x] allow seveval palettes for normal file colors - [x] move every lazy_static configuration into config. +- [x] FIX: encrypted are never shown as mounted - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself -- [x] FIX: encrypted are never shown as mounted - [ ] Tree remade without recursion. Improve ram usage - [x] FIX: folders are max depth hangs the app - [x] FIX: rename renames the root path diff --git a/src/tree.rs b/src/tree.rs index f495acb0..e78592fc 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -156,15 +156,15 @@ impl Tree { let root_depth = root_path.components().collect::>().len(); let mut stack = vec![root_path.to_owned()]; let mut nodes: HashMap = HashMap::new(); - let mut last_path = root_path.to_owned(); + while let Some(current_path) = stack.pop() { let reached_depth = current_path.components().collect::>().len(); if reached_depth >= depth + root_depth { continue; } let children_will_be_added = depth + root_depth - reached_depth > 1; - let mut node = Node::new(¤t_path, None); + let mut current_node = Node::new(¤t_path, None); if current_path.is_dir() && !current_path.is_symlink() && children_will_be_added { if let Some(mut files) = files_collection(¤t_path, users, show_hidden, filter_kind, true) @@ -172,12 +172,12 @@ impl Tree { sort_kind.sort(&mut files); let children = Self::make_children_and_stack_them(&mut stack, &files); if !children.is_empty() { - node.set_children(Some(children)); + current_node.set_children(Some(children)); } }; } - last_path = node.path.to_owned(); - nodes.insert(node.path.to_owned(), node); + last_path = current_path.to_owned(); + nodes.insert(current_path.to_owned(), current_node); } let Some(root_node) = nodes.get_mut(&root_path) else { From 77dc7c93cc17b68e87c02e9d4ea3c164f2bd6780 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 9 Nov 2023 21:49:35 +0100 Subject: [PATCH 158/168] improve search in tree. Far from perfect --- development.md | 3 ++- src/event_exec.rs | 18 +++++------------- src/tree.rs | 39 +++++++++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/development.md b/development.md index 1b324172..3a74d9a3 100644 --- a/development.md +++ b/development.md @@ -618,6 +618,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: enter a dir from normal mode shouldn't set mode tree - [x] Use a generic trait for movements - [x] FIX: first line position for tree + - [x] FIX: searching for file very low don't scroll there + - [x] FIX: search can only find the first match - [ ] test everything - [ ] refactor - [x] document @@ -625,7 +627,6 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. ## TODO - [ ] FIX: tab.selected() should return Option<&FileInfo> -- [ ] FIX: search can only find the first match - [ ] use widget for every drawable element. First line should be a collection of widget which are drawned - [ ] while second window is opened, if the selection is below half screen, it's not shown anymore. Scroll to second element if needed diff --git a/src/event_exec.rs b/src/event_exec.rs index 5514ccf1..4ca457e0 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -471,12 +471,12 @@ impl EventAction { pub fn search_next(status: &mut Status) -> Result<()> { let tab = status.selected(); + let Some(searched) = tab.searched.clone() else { + return Ok(()); + }; match tab.mode { - Mode::Tree => (), + Mode::Tree => tab.tree.search_first_match(&searched), _ => { - let Some(searched) = tab.searched.clone() else { - return Ok(()); - }; let next_index = (tab.path_content.index + 1) % tab.path_content.content.len(); tab.search_from(&searched, next_index); status.update_second_pane_for_preview()?; @@ -1381,16 +1381,8 @@ impl LeaveMode { tab.searched = Some(searched.clone()); match tab.previous_mode { Mode::Tree => { + log::info!("searching in tree"); tab.tree.search_first_match(searched); - // tab.directory.tree.unselect_children(); - // if let Some(position) = tab.directory.tree.select_first_match(&searched) { - // tab.directory.tree.position = position; - // (_, _, tab.directory.tree.current_node) = - // tab.directory.tree.select_from_position()?; - // } else { - // tab.directory.tree.select_root() - // }; - // tab.directory.make_preview(); } _ => { let next_index = tab.path_content.index; diff --git a/src/tree.rs b/src/tree.rs index e78592fc..b00c37ae 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -416,6 +416,9 @@ impl Tree { } fn select_path(&mut self, clicked_path: &Path) { + if clicked_path == self.selected { + return; + } let Some(new_node) = self.nodes.get_mut(clicked_path) else { return; }; @@ -469,24 +472,25 @@ impl Tree { } } - // FIX: can only find the first match and nothing else /// Select the first node whose filename match a pattern. pub fn search_first_match(&mut self, pattern: &str) { - let initial_selected = self.selected.to_owned(); - let Some((found_path, found_node)) = self.nodes.iter_mut().find(|(path, _)| { - path.file_name() - .unwrap_or_default() - .to_string_lossy() - .contains(pattern) - }) else { - return; + let Some(current_index) = self.nodes.keys().position(|path| path == &self.selected) else { + unreachable!("selected should be in pos"); }; - self.selected = found_path.to_owned(); - found_node.select(); - let Some(current_node) = self.nodes.get_mut(&initial_selected) else { - unreachable!("selected path should be in nodes"); + if let Some(found_path) = self + .nodes + .keys() + .skip(current_index + 1) + .find(|path| path_filename_contains(path, pattern)) + { + self.go(To::Path(found_path.to_owned().as_path())); + } else if let Some(found_path) = self + .nodes + .keys() + .find(|path| path_filename_contains(path, pattern)) + { + self.go(To::Path(found_path.to_owned().as_path())); }; - current_node.unselect(); } /// Returns a navigable vector of `ColoredTriplet` and the index of selected file @@ -613,3 +617,10 @@ pub fn calculate_top_bottom(selected_index: usize, terminal_height: usize) -> (u (top, bottom) } + +fn path_filename_contains(path: &Path, pattern: &str) -> bool { + path.file_name() + .unwrap_or_default() + .to_string_lossy() + .contains(pattern) +} From df22bbdd3e5e176d97cfa742ca5c4667fd53af89 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 9 Nov 2023 22:00:17 +0100 Subject: [PATCH 159/168] refactor search --- src/tree.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index b00c37ae..6aa0bd6c 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -477,20 +477,33 @@ impl Tree { let Some(current_index) = self.nodes.keys().position(|path| path == &self.selected) else { unreachable!("selected should be in pos"); }; - if let Some(found_path) = self - .nodes + let Some(found_path) = self.find_pattern(pattern, current_index) else { + return; + }; + self.go(To::Path(found_path.to_owned().as_path())); + } + + fn find_pattern(&self, pattern: &str, current_index: usize) -> Option<&PathBuf> { + if let Some(found_path) = self.find_path_from_index(current_index, pattern) { + Some(found_path) + } else if let Some(found_path) = self.find_path_from_top(pattern) { + Some(found_path) + } else { + None + } + } + + fn find_path_from_top(&self, pattern: &str) -> Option<&PathBuf> { + self.nodes .keys() - .skip(current_index + 1) .find(|path| path_filename_contains(path, pattern)) - { - self.go(To::Path(found_path.to_owned().as_path())); - } else if let Some(found_path) = self - .nodes + } + + fn find_path_from_index(&self, current_index: usize, pattern: &str) -> Option<&PathBuf> { + self.nodes .keys() + .skip(current_index + 1) .find(|path| path_filename_contains(path, pattern)) - { - self.go(To::Path(found_path.to_owned().as_path())); - }; } /// Returns a navigable vector of `ColoredTriplet` and the index of selected file From cce328c74e476096cae71fd860900f1c26458743 Mon Sep 17 00:00:00 2001 From: qkzk Date: Thu, 9 Nov 2023 22:40:25 +0100 Subject: [PATCH 160/168] use DFS to search in tree mode. Type aliasing for filenames --- src/completion.rs | 14 ++------ src/tree.rs | 82 ++++++++++++++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index e2da5147..5e5f4691 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -3,7 +3,7 @@ use std::fs::{self, ReadDir}; use anyhow::Result; use strum::IntoEnumIterator; -use crate::{fileinfo::PathContent, tree::Node}; +use crate::{fileinfo::PathContent, tree::Filenames}; /// Different kind of completions #[derive(Clone, Default, Copy)] @@ -212,17 +212,7 @@ impl Completion { } /// Looks for file within tree completing what the user typed. - pub fn search_from_tree( - &mut self, - input_string: &str, - content: std::iter::FilterMap< - std::iter::FilterMap< - std::collections::hash_map::Keys<'_, std::path::PathBuf, Node>, - fn(&std::path::PathBuf) -> Option<&std::ffi::OsStr>, - >, - fn(&std::ffi::OsStr) -> Option<&str>, - >, - ) -> Result<()> { + pub fn search_from_tree(&mut self, input_string: &str, content: Filenames) -> Result<()> { self.update( content .filter(|&p| p.contains(input_string)) diff --git a/src/tree.rs b/src/tree.rs index 6aa0bd6c..a9153b93 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -472,38 +472,62 @@ impl Tree { } } + // BUG: won't respect display order. + // ATM .keys() and display don't respect the same order. + // keys -> fileinfo -> sortkind won't work since sortkind is written for files from the same directory. + // We could traverse the tree with `into_navigable_content`, search there and then... /// Select the first node whose filename match a pattern. pub fn search_first_match(&mut self, pattern: &str) { - let Some(current_index) = self.nodes.keys().position(|path| path == &self.selected) else { - unreachable!("selected should be in pos"); - }; - let Some(found_path) = self.find_pattern(pattern, current_index) else { + // let Some(current_index) = self.nodes.keys().position(|path| path == &self.selected) else { + // unreachable!("selected should be in pos"); + // }; + let Some(found_path) = self.deep_first_search(pattern) else { return; }; self.go(To::Path(found_path.to_owned().as_path())); } - fn find_pattern(&self, pattern: &str, current_index: usize) -> Option<&PathBuf> { - if let Some(found_path) = self.find_path_from_index(current_index, pattern) { - Some(found_path) - } else if let Some(found_path) = self.find_path_from_top(pattern) { - Some(found_path) - } else { - None - } - } + fn deep_first_search(&self, pattern: &str) -> Option { + let mut stack = vec![self.root_path.as_path()]; + let mut found = vec![]; + + while let Some(path) = stack.pop() { + if path_filename_contains(path, pattern) { + found.push(path.to_path_buf()); + } + let Some(current_node) = self.nodes.get(path) else { + continue; + }; - fn find_path_from_top(&self, pattern: &str) -> Option<&PathBuf> { - self.nodes - .keys() - .find(|path| path_filename_contains(path, pattern)) + if current_node.have_children() { + let Some(children) = ¤t_node.children else { + continue; + }; + for leaf in children.iter() { + stack.push(leaf); + } + } + } + self.pick_best_match(&found) } - fn find_path_from_index(&self, current_index: usize, pattern: &str) -> Option<&PathBuf> { - self.nodes - .keys() - .skip(current_index + 1) - .find(|path| path_filename_contains(path, pattern)) + fn pick_best_match(&self, found: &[PathBuf]) -> Option { + if found.is_empty() { + return None; + } + if let Some(position) = found.iter().position(|path| path == &self.selected) { + // selected is in found + if position + 1 < found.len() { + // selected isn't last, use next elem + Some(found[position + 1].to_owned()) + } else { + // selected is last + Some(found[0].to_owned()) + } + } else { + // selected isn't in found, use first match + Some(found[0].to_owned()) + } } /// Returns a navigable vector of `ColoredTriplet` and the index of selected file @@ -544,16 +568,11 @@ impl Tree { (selected_index, content) } - /// An (ugly) iterator over filenames. + /// An iterator over filenames. /// It allows us to iter explicitely over filenames /// while avoiding another allocation by collecting into a `Vec` #[inline] - pub fn filenames( - &self, - ) -> FilterMap< - FilterMap, fn(&PathBuf) -> Option<&OsStr>>, - fn(&OsStr) -> Option<&str>, - > { + pub fn filenames(&self) -> Filenames<'_> { let to_filename: fn(&PathBuf) -> Option<&OsStr> = |path| path.file_name(); let to_str: fn(&OsStr) -> Option<&str> = |filename| filename.to_str(); self.nodes.keys().filter_map(to_filename).filter_map(to_str) @@ -637,3 +656,8 @@ fn path_filename_contains(path: &Path, pattern: &str) -> bool { .to_string_lossy() .contains(pattern) } + +type FnPbOsstr = fn(&PathBuf) -> Option<&OsStr>; +type FilterHashMap<'a> = FilterMap, FnPbOsstr>; +/// An iterator over filenames of a HashMap +pub type Filenames<'a> = FilterMap, fn(&OsStr) -> Option<&str>>; From 496ac3f1a144bd95651bf84311dd3df6ed992450 Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 10 Nov 2023 22:21:23 +0100 Subject: [PATCH 161/168] FIX: leaving preview doesn't reset tree --- development.md | 1 + src/event_exec.rs | 16 +++++----------- src/tab.rs | 6 +++--- src/tree.rs | 7 +++++-- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/development.md b/development.md index 3a74d9a3..2ac421ee 100644 --- a/development.md +++ b/development.md @@ -620,6 +620,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: first line position for tree - [x] FIX: searching for file very low don't scroll there - [x] FIX: search can only find the first match + - [x] FIX: leaving preview doesn't reset tree - [ ] test everything - [ ] refactor - [x] document diff --git a/src/event_exec.rs b/src/event_exec.rs index 4ca457e0..95e95e77 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -238,11 +238,10 @@ impl EventAction { /// Display the help which can be navigated and displays the configrable /// binds. pub fn help(status: &mut Status) -> Result<()> { - let help = status.help.clone(); - let tab = status.selected(); - tab.set_mode(Mode::Preview); - tab.preview = Preview::help(&help); - tab.window.reset(tab.preview.len()); + status.selected().set_mode(Mode::Preview); + status.selected().preview = Preview::help(&status.help); + let len = status.selected_non_mut().preview.len(); + status.selected().window.reset(len); Ok(()) } @@ -283,12 +282,7 @@ impl EventAction { /// Once a quit event is received, we change a flag and break the main loop. /// It's usefull to reset the cursor before leaving the application. pub fn quit(tab: &mut Tab) -> Result<()> { - if let Mode::Tree = tab.mode { - tab.refresh_view()?; - tab.set_mode(Mode::Normal) - } else { - tab.must_quit = true; - } + tab.must_quit = true; Ok(()) } /// Toggle the display of hidden files. diff --git a/src/tab.rs b/src/tab.rs index 6bc39e07..fbffc080 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -147,9 +147,9 @@ impl Tab { self.preview = Preview::empty(); self.completion.reset(); if matches!(self.mode, Mode::Tree) { - self.tree = Tree::default() - } else { self.make_tree(None)?; + } else { + self.tree = Tree::default() }; Ok(()) } @@ -159,10 +159,10 @@ impl Tab { /// displayed files is reset. /// The first file is selected. pub fn refresh_view(&mut self) -> Result<()> { - self.refresh_params()?; self.path_content .reset_files(&self.filter, self.show_hidden, &self.users)?; self.window.reset(self.path_content.content.len()); + self.refresh_params()?; Ok(()) } diff --git a/src/tree.rs b/src/tree.rs index a9153b93..c9408097 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -117,6 +117,9 @@ pub enum To<'a> { impl Go for Tree { /// Select another element from a tree. fn go(&mut self, to: To) { + if self.is_empty() { + return; + } match to { To::Next => self.select_next(), To::Prev => self.select_prev(), @@ -260,7 +263,7 @@ impl Tree { /// Select next sibling or the next sibling of the parent fn select_next(&mut self) { if self.is_on_last() { - self.select_root(); + self.go(To::Root); return; } @@ -331,7 +334,7 @@ impl Tree { /// Select previous sibling or the parent fn select_prev(&mut self) { if self.is_on_root() { - self.select_last(); + self.go(To::Last); return; } From c1406a2ed106744e8ad433055dc5f9fa2973db7d Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 10 Nov 2023 22:47:39 +0100 Subject: [PATCH 162/168] refactor tree --- src/tree.rs | 56 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index c9408097..ad9431ca 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -155,6 +155,36 @@ impl Tree { show_hidden: bool, filter_kind: &FilterKind, ) -> Self { + let (mut nodes, last_path) = Self::make_nodes( + &root_path, + depth, + sort_kind, + users, + show_hidden, + filter_kind, + ); + let Some(root_node) = nodes.get_mut(&root_path) else { + unreachable!("root path should be in nodes"); + }; + root_node.select(); + + Self { + selected: root_path.clone(), + root_path, + last_path, + nodes, + required_height: Self::DEFAULT_REQUIRED_HEIGHT, + } + } + + fn make_nodes( + root_path: &PathBuf, + depth: usize, + sort_kind: SortKind, + users: &Users, + show_hidden: bool, + filter_kind: &FilterKind, + ) -> (HashMap, PathBuf) { // keep track of the depth let root_depth = root_path.components().collect::>().len(); let mut stack = vec![root_path.to_owned()]; @@ -166,9 +196,9 @@ impl Tree { if reached_depth >= depth + root_depth { continue; } - let children_will_be_added = depth + root_depth - reached_depth > 1; + let children_will_be_added = depth + root_depth > 1 + reached_depth; let mut current_node = Node::new(¤t_path, None); - if current_path.is_dir() && !current_path.is_symlink() && children_will_be_added { + if children_will_be_added && current_path.is_dir() && !current_path.is_symlink() { if let Some(mut files) = files_collection(¤t_path, users, show_hidden, filter_kind, true) { @@ -182,19 +212,7 @@ impl Tree { last_path = current_path.to_owned(); nodes.insert(current_path.to_owned(), current_node); } - - let Some(root_node) = nodes.get_mut(&root_path) else { - unreachable!("root path should be in nodes"); - }; - root_node.select(); - - Self { - selected: root_path.clone(), - root_path, - last_path, - nodes, - required_height: Self::DEFAULT_REQUIRED_HEIGHT, - } + (nodes, last_path) } fn make_children_and_stack_them(stack: &mut Vec, files: &[FileInfo]) -> Vec { @@ -475,15 +493,9 @@ impl Tree { } } - // BUG: won't respect display order. - // ATM .keys() and display don't respect the same order. - // keys -> fileinfo -> sortkind won't work since sortkind is written for files from the same directory. - // We could traverse the tree with `into_navigable_content`, search there and then... /// Select the first node whose filename match a pattern. + /// If the selected file match, the next match will be selected. pub fn search_first_match(&mut self, pattern: &str) { - // let Some(current_index) = self.nodes.keys().position(|path| path == &self.selected) else { - // unreachable!("selected should be in pos"); - // }; let Some(found_path) = self.deep_first_search(pattern) else { return; }; From 5e70534a4d801c4bf85492bfdb4466d620a97a3f Mon Sep 17 00:00:00 2001 From: qkzk Date: Fri, 10 Nov 2023 23:53:48 +0100 Subject: [PATCH 163/168] Add a link to previous and next node in Node. Simplify navigation, increase ram usage :/ --- development.md | 3 +- src/tree.rs | 104 +++++++++++++++---------------------------------- 2 files changed, 34 insertions(+), 73 deletions(-) diff --git a/development.md b/development.md index 2ac421ee..3ef22879 100644 --- a/development.md +++ b/development.md @@ -606,7 +606,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] mount usb key - should be merged with mtp - [ ] document filepicking (from my config etc.). - [ ] avoid multiple refreshs if we edit files ourself -- [ ] Tree remade without recursion. Improve ram usage +- [ ] Tree remade without recursion. Use an `HashMap` - [x] FIX: folders are max depth hangs the app - [x] FIX: rename renames the root path - [x] FIX: scrolling to bottom of tree is bugged @@ -621,6 +621,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: searching for file very low don't scroll there - [x] FIX: search can only find the first match - [x] FIX: leaving preview doesn't reset tree + - [x] Add a link to previous and next node in Node. Simplify navigation, increase ram usage :/ - [ ] test everything - [ ] refactor - [x] document diff --git a/src/tree.rs b/src/tree.rs index ad9431ca..940252fd 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -45,6 +45,8 @@ pub struct Node { children: Option>, folded: bool, selected: bool, + prev: Option, + next: Option, } impl Node { @@ -56,6 +58,8 @@ impl Node { children, folded: false, selected: false, + prev: None, + next: None, } } @@ -177,6 +181,7 @@ impl Tree { } } + #[inline] fn make_nodes( root_path: &PathBuf, depth: usize, @@ -209,12 +214,26 @@ impl Tree { } }; } - last_path = current_path.to_owned(); + + if let Some(last_node) = nodes.get_mut(&last_path) { + last_node.next = Some(current_path.to_owned()); + } + current_node.prev = Some(last_path); nodes.insert(current_path.to_owned(), current_node); + last_path = current_path.to_owned(); } + let Some(root_node) = nodes.get_mut(root_path) else { + unreachable!("root_path should be in nodes"); + }; + root_node.prev = Some(last_path.to_owned()); + let Some(last_node) = nodes.get_mut(&last_path) else { + unreachable!("last_path should be in nodes"); + }; + last_node.next = Some(root_path.to_owned()); (nodes, last_path) } + #[inline] fn make_children_and_stack_them(stack: &mut Vec, files: &[FileInfo]) -> Vec { files .iter() @@ -244,13 +263,11 @@ impl Tree { /// The folder containing the selected node. /// Itself if selected is a directory. pub fn directory_of_selected(&self) -> Option<&Path> { - let ret = if self.selected.is_dir() && !self.selected.is_symlink() { + if self.selected.is_dir() && !self.selected.is_symlink() { Some(self.selected.as_path()) } else { self.selected.parent() - }; - log::info!("directory_of_selected {ret:?}"); - ret + } } /// Relative path of selected from rootpath. @@ -299,56 +316,15 @@ impl Tree { } } - // FIX: Still a problem when reaching max depth of tree, - // can't find next sibling since we look for children which exists but aren't in tree. - // Check if the children are listed (they shouldn't be) in node.children and are in self.nodes. fn find_next_path(&self) -> Option { - let Some(current_node) = self.nodes.get(&self.selected) else { - unreachable!("selected path should be in nodes") - }; - if !self.selected.is_dir() || !current_node.folded { - if let Some(children_paths) = ¤t_node.children { - if let Some(child_path) = children_paths.last() { - let child_path = child_path.to_owned(); - return Some(child_path.to_owned()); - } + if let Some(selected_node) = self.nodes.get(&self.selected) { + if let Some(next_path) = &selected_node.next { + return Some(next_path.to_owned()); } } - let mut current_path = self.selected.to_owned(); - - // TODO: refactor using ancestors. Not so easy since we keep track of parent and current - while let Some(parent_path) = current_path.parent() { - let Some(parent_node) = self.nodes.get(parent_path) else { - current_path = parent_path.to_owned(); - continue; - }; - let Some(siblings_paths) = &parent_node.children else { - current_path = parent_path.to_owned(); - continue; - }; - let Some(index_current) = siblings_paths.iter().position(|path| path == ¤t_path) - else { - current_path = parent_path.to_owned(); - continue; - }; - if index_current == 0 { - current_path = parent_path.to_owned(); - continue; - } - let Some(next_sibling_path) = siblings_paths.get(index_current - 1) else { - current_path = parent_path.to_owned(); - continue; - }; - if self.nodes.contains_key(next_sibling_path) { - return Some(next_sibling_path.to_owned()); - } else { - current_path = parent_path.to_owned(); - continue; - }; - } - None + unreachable!("every node should have a next"); } - // TODO! find the bottom child of parent instead of jumping back 1 level + /// Select previous sibling or the parent fn select_prev(&mut self) { if self.is_on_root() { @@ -371,28 +347,12 @@ impl Tree { } fn find_prev_path(&self) -> Option { - let current_path = self.selected.to_owned(); - let Some(parent_path) = current_path.parent() else { - return None; - }; - let Some(parent_node) = self.nodes.get(parent_path) else { - return None; - }; - let Some(siblings_paths) = &parent_node.children else { - return None; - }; - let Some(index_current) = siblings_paths.iter().position(|path| path == ¤t_path) - else { - return None; - }; - if index_current + 1 < siblings_paths.len() { - Some(siblings_paths[index_current + 1].to_owned()) - } else { - let Some(_node) = self.nodes.get(parent_path) else { - return None; - }; - Some(parent_path.to_owned()) + if let Some(selected_node) = self.nodes.get(&self.selected) { + if let Some(prev_path) = &selected_node.prev { + return Some(prev_path.to_owned()); + } } + unreachable!("every node should have a prev"); } fn select_root(&mut self) { From 317866ff2e23f3608abfe39db9c8479ca00e8d6a Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 11 Nov 2023 13:33:52 +0100 Subject: [PATCH 164/168] use select path for every select_something --- src/tree.rs | 83 +++++++++++++---------------------------------------- 1 file changed, 20 insertions(+), 63 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index 940252fd..d5839f83 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -93,6 +93,7 @@ impl Node { FileInfo::new(&self.path, users) } + #[inline] fn set_children(&mut self, children: Option>) { self.children = children } @@ -130,7 +131,7 @@ impl Go for Tree { To::Root => self.select_root(), To::Last => self.select_last(), To::Parent => self.select_parent(), - To::Path(path) => self.select_path(path), + To::Path(path) => self.select_path(path, true), } } } @@ -297,21 +298,8 @@ impl Tree { /// Select next sibling or the next sibling of the parent fn select_next(&mut self) { - if self.is_on_last() { - self.go(To::Root); - return; - } - if let Some(next_path) = self.find_next_path() { - let Some(next_node) = self.nodes.get_mut(&next_path) else { - return; - }; - next_node.select(); - let Some(selected_node) = self.nodes.get_mut(&self.selected) else { - unreachable!("current_node should be in nodes"); - }; - selected_node.unselect(); - self.selected = next_path; + self.select_path(&next_path, false); self.increment_required_height() } } @@ -328,20 +316,9 @@ impl Tree { /// Select previous sibling or the parent fn select_prev(&mut self) { if self.is_on_root() { - self.go(To::Last); - return; - } - - if let Some(previous_path) = self.find_prev_path() { - let Some(previous_node) = self.nodes.get_mut(&previous_path) else { - return; - }; - previous_node.select(); - let Some(selected_node) = self.nodes.get_mut(&self.selected) else { - unreachable!("current_node should be in nodes"); - }; - selected_node.unselect(); - self.selected = previous_path; + self.select_last(); + } else if let Some(previous_path) = self.find_prev_path() { + self.select_path(&previous_path, false); self.decrement_required_height() } } @@ -356,60 +333,40 @@ impl Tree { } fn select_root(&mut self) { - let Some(selected_node) = self.nodes.get_mut(&self.selected) else { - unreachable!("selected path should be in node") - }; - selected_node.unselect(); - let Some(root_node) = self.nodes.get_mut(&self.root_path) else { - unreachable!("root path should be in nodes") - }; - root_node.select(); - self.selected = self.root_path.to_owned(); + let root_path = self.root_path.to_owned(); + self.select_path(&root_path, false); self.reset_required_height() } fn select_last(&mut self) { - let Some(selected_node) = self.nodes.get_mut(&self.selected) else { - unreachable!("selected path should be in node") - }; - selected_node.unselect(); - let Some(last_node) = self.nodes.get_mut(&self.last_path) else { - unreachable!("root path should be in nodes") - }; - last_node.select(); - self.selected = self.last_path.to_owned(); + let last_path = self.last_path.to_owned(); + self.select_path(&last_path, false); self.set_required_height_to_max() } fn select_parent(&mut self) { if let Some(parent_path) = self.selected.parent() { - let Some(parent_node) = self.nodes.get_mut(parent_path) else { - return; - }; - parent_node.select(); - let Some(selected_node) = self.nodes.get_mut(&self.selected) else { - unreachable!("current_node should be in nodes"); - }; - selected_node.unselect(); - self.selected = parent_path.to_owned(); + self.select_path(parent_path.to_owned().as_path(), false); self.decrement_required_height() } } - fn select_path(&mut self, clicked_path: &Path) { - if clicked_path == self.selected { + fn select_path(&mut self, dest_path: &Path, set_height: bool) { + if dest_path == self.selected { return; } - let Some(new_node) = self.nodes.get_mut(clicked_path) else { + let Some(dest_node) = self.nodes.get_mut(dest_path) else { return; }; - new_node.select(); + dest_node.select(); let Some(selected_node) = self.nodes.get_mut(&self.selected) else { unreachable!("current_node should be in nodes"); }; selected_node.unselect(); - self.selected = clicked_path.to_owned(); - self.set_required_height_to_max() + self.selected = dest_path.to_owned(); + if set_height { + self.set_required_height_to_max() + } } fn increment_required_height(&mut self) { @@ -459,7 +416,7 @@ impl Tree { let Some(found_path) = self.deep_first_search(pattern) else { return; }; - self.go(To::Path(found_path.to_owned().as_path())); + self.select_path(found_path.to_owned().as_path(), true); } fn deep_first_search(&self, pattern: &str) -> Option { From 22ae92fb93de3e31803708a4adc511df38f58f5c Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 11 Nov 2023 15:18:22 +0100 Subject: [PATCH 165/168] dev - tree is redone completely --- development.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/development.md b/development.md index 3ef22879..abb1ce5e 100644 --- a/development.md +++ b/development.md @@ -622,8 +622,8 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] FIX: search can only find the first match - [x] FIX: leaving preview doesn't reset tree - [x] Add a link to previous and next node in Node. Simplify navigation, increase ram usage :/ - - [ ] test everything - - [ ] refactor + - [x] test everything + - [x] refactor - [x] document ## TODO From 4c61e69e392497689f61380504a0656b8b382510 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 11 Nov 2023 15:22:02 +0100 Subject: [PATCH 166/168] dev --- development.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/development.md b/development.md index abb1ce5e..57632c79 100644 --- a/development.md +++ b/development.md @@ -603,10 +603,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] allow seveval palettes for normal file colors - [x] move every lazy_static configuration into config. - [x] FIX: encrypted are never shown as mounted -- [ ] mount usb key - should be merged with mtp -- [ ] document filepicking (from my config etc.). -- [ ] avoid multiple refreshs if we edit files ourself -- [ ] Tree remade without recursion. Use an `HashMap` +- [x] Tree remade without recursion. Use an `HashMap` - [x] FIX: folders are max depth hangs the app - [x] FIX: rename renames the root path - [x] FIX: scrolling to bottom of tree is bugged @@ -625,6 +622,9 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] test everything - [x] refactor - [x] document +- [ ] mount usb key - should be merged with mtp +- [ ] document filepicking (from my config etc.). +- [ ] avoid multiple refreshs if we edit files ourself ## TODO From 1b4a4eb6826c9fa84c3c8afbbfa0ed326e12c907 Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 11 Nov 2023 15:50:55 +0100 Subject: [PATCH 167/168] readme --- readme.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index b9e5fd97..a1c03849 100644 --- a/readme.md +++ b/readme.md @@ -16,9 +16,12 @@ A TUI file manager inspired by dired and ranger Usage: fm [OPTIONS] Options: - -p, --path Starting path [default: .] + -p, --path Starting path. directory or file [default: .] -s, --server Nvim server [default: ] - -f, --file Selected file [default: .] + -D, --dual Dual pane ? [possible values: true, false] + -S, --simple Display files metadata ? [possible values: true, false] + -P, --preview Use second pane as preview ? default to false + -A, --all Display all files (hidden) -h, --help Print help -V, --version Print version ``` @@ -59,6 +62,7 @@ If you added the [recommanded function](#cd-on-quit) to your bashrc/zshrc, simpl ## Features Some features depends on external programs to keep fm from being really bloated. +I try to implement every feature I can think of. ### Navigation @@ -96,28 +100,30 @@ Many ways to jump somewhere : ### Display - Change display, removing details with Alt+e or display a single pane with Alt+d -- Preview most of files (text, highlighted code, binary, pdf, exif details, video/audio details, archives) with P +- Preview most of files (text, highlighted code, binary, pdf, exif details, image/video, audio details, archives, MS-office & OpenOffice documents) with P - Toggle the tree view with t. Fold selected folder with z. Unfold every folder with Z, fold every folder with Alt+z. - Enter preview mode with Alt+P. Every file is previewed in the second pane. - Filter the view (by extension, name, directory only, all files) with F - Find files with / (with completion: Tab, enter to search), - flag files matching a regex with w -### Fuzzy finder +### Fuzzy finders -- Use the integrated fuzzy finder (forked version of skim, an fzf clone) with Ctrl+f to navigate quickly -- The same fuzzy finder can find specific lines in files with Ctrl+s -- Another fuzzy finder is available for your keybinds with Alt+h +- Ctrl-f : search in filenames and move there, +- Ctrl-s : search for a line in file content and move there, +- Alt-h : search for a keybinding and execute the action. + +We use a fork of [skim](https://github.com/lotabout/skim), an fzf clone written in rust. ### Neovim filepicker When you open a file with i, it will send an event to Neovim and open it in a new buffer. -Recent versions of neovim export the RPC server address to an environment variable which is read if no argument -is provided. It should always work, even outside of neovim. -It's also possible to pass the RPC server address with `fm -s address`. +The RPC server address is found by looking for neovim in /proc. If it fails, we can still look for an +environment variable set by neovim itself. +Finally, it's also possible to pass the RPC server address with `fm -s address`. ### cd on quit @@ -177,6 +183,8 @@ Expansions : - Contol MOCP with Ctrl+arrows. Ctrl+Left, Ctrl+Right: previous or next song. Ctrl+Down: Toggle pause. Ctrl+Up: add current folder to playlist - Set the selected image as wallpaper with W. - _Experimental_ enter "command mode" with ':'. Type the name of a command and it will be executed. +- Mount a remote filesystem using ssfhs with Alt-r. +- Mount a MTP device with Alt-R. Most of those features are inspired by ranger and alternatives (Midnight commander, nnn, lf etc.), the look and feel by dired. @@ -308,8 +316,11 @@ You can configure : - **TUI applications**. Some classic TUI applications like htop, glances, btop, lazygit are already there. Open the menu with `S` and pick the desired one. It will only work with a TUI application like HTOP, not a CLI application like bat. - -Before version 0.1.23, colors used to be configurable. Configuration wasn't a problem but passing it everywhere was a burden. +- **Colors** of files. + Non standard files (directory, char devices, block devices, symlinks, sockets, fifo) have their own configurable colors. + You can use ansi colors or rgb values. + Standard files are colored by their extension and you can use 3 differents palettes (red-green, red-blue or green-blue). + Every extension has its own random color. ## External dependencies From d6833e662c48d28e5636fa0d85a373c9572541ea Mon Sep 17 00:00:00 2001 From: qkzk Date: Sat, 11 Nov 2023 15:51:46 +0100 Subject: [PATCH 168/168] dev --- development.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/development.md b/development.md index 57632c79..018b608d 100644 --- a/development.md +++ b/development.md @@ -622,12 +622,12 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [x] test everything - [x] refactor - [x] document -- [ ] mount usb key - should be merged with mtp -- [ ] document filepicking (from my config etc.). -- [ ] avoid multiple refreshs if we edit files ourself ## TODO +- [ ] mount usb key - should be merged with mtp +- [ ] document filepicking (from my config etc.). +- [ ] avoid multiple refreshs if we edit files ourself - [ ] FIX: tab.selected() should return Option<&FileInfo> - [ ] use widget for every drawable element. First line should be a collection of widget which are drawned - [ ] while second window is opened, if the selection is below half screen, it's not shown anymore.