diff options
author | qkzk <qu3nt1n@gmail.com> | 2023-10-29 20:53:06 +0100 |
---|---|---|
committer | qkzk <qu3nt1n@gmail.com> | 2023-10-29 20:53:06 +0100 |
commit | edb7d8b36a33bf1cdb63039a14486b91f92430ab (patch) | |
tree | 83b69f162f129b55b0c7a4f05b9362a015458338 | |
parent | 840789072f0671cb5bf0456d7b2e1308f3383278 (diff) |
preview mpscv0.1.23-preview-mpsc
-rw-r--r-- | src/event_exec.rs | 42 | ||||
-rw-r--r-- | src/main.rs | 69 | ||||
-rw-r--r-- | src/preview.rs | 133 | ||||
-rw-r--r-- | src/status.rs | 51 |
4 files changed, 224 insertions, 71 deletions
diff --git a/src/event_exec.rs b/src/event_exec.rs index 6950146..aafdf3e 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,31 +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, - &unmutable_tab.path_content.users_cache, - status, - colors, - ) - .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.set_preview(colors) } /// Enter the delete mode. @@ -926,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/main.rs b/src/main.rs index d449030..3fa3601 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::thread; use std::time::Duration; use anyhow::Result; +use fm::preview::PreviewArgs; use log::info; use fm::config::{load_config, Colors}; @@ -37,6 +38,8 @@ struct FM { /// 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, + previewer: Previewer, + rx2: mpsc::Receiver<Option<fm::preview::Preview>>, } impl FM { @@ -72,6 +75,7 @@ impl FM { )?; let colors = config.colors.clone(); let refresher = Refresher::spawn(term); + let (previewer, rx2) = Previewer::spawn(); drop(config); Ok(Self { event_reader, @@ -80,6 +84,8 @@ impl FM { display, colors, refresher, + previewer, + rx2, }) } @@ -186,6 +192,56 @@ impl Refresher { } } +/// Allows refresh if the current path has been modified externally. +struct Previewer { + /// Sender of messages, used to terminate the thread properly + tx: mpsc::Sender<Option<PreviewArgs>>, + /// Handle to the `term::Event` sender thread. + handle: thread::JoinHandle<()>, +} + +impl Previewer { + /// 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() -> (Self, mpsc::Receiver<Option<fm::preview::Preview>>) { + let (tx, rx) = mpsc::channel::<Option<PreviewArgs>>(); + let (tx2, rx2) = mpsc::channel::<Option<fm::preview::Preview>>(); + let handle = thread::spawn(move || loop { + match rx.try_recv() { + Ok(preview_args) => { + let preview = Self::make_preview(preview_args); + tx2.send(preview).unwrap(); + } + Err(TryRecvError::Disconnected) => { + log::info!("terminating previewer"); + return; + } + Err(TryRecvError::Empty) => {} + } + }); + (Self { tx, handle }, rx2) + } + + fn make_preview(preview_args: Option<PreviewArgs>) -> Option<fm::preview::Preview> { + if let Some(preview_args) = preview_args { + let res = fm::preview::Preview::new(&preview_args.fileinfo); + if let Ok(preview) = res { + Some(preview) + } else { + None + } + } else { + None + } + } +} /// Exit the application and log a message. /// Used when the config can't be read. fn exit_wrong_config() -> ! { @@ -205,6 +261,19 @@ fn main() -> Result<()> { while let Ok(event) = fm.poll_event() { fm.update(event)?; + if let Some(fileinfo) = fm.status.tabs[0].selected() { + let preview_args = PreviewArgs::new(fileinfo); + fm.previewer.tx.send(Some(preview_args))?; + match fm.rx2.try_recv() { + Ok(preview) => { + if let Some(preview) = preview { + log::info!("rx2 received {path}", path = preview.path().display()); + fm.status.update_preview(preview); + } + } + _ => (), + } + } fm.display()?; if fm.must_quit() { break; diff --git a/src/preview.rs b/src/preview.rs index 1f8a858..92013b1 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -1,5 +1,5 @@ use std::cmp::min; -use std::fmt::Write as _; +use std::fmt::{Debug, Write as _}; use std::fs::metadata; use std::io::Cursor; use std::io::{BufRead, BufReader, Read}; @@ -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}; @@ -73,7 +72,7 @@ impl ExtensionKind { } } -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub enum TextKind { HELP, LOG, @@ -82,10 +81,21 @@ pub enum TextKind { TEXTFILE, } +pub struct PreviewArgs { + pub fileinfo: FileInfo, +} + +impl PreviewArgs { + pub fn new(fileinfo: &FileInfo) -> Self { + let fileinfo = fileinfo.clone(); + Self { fileinfo } + } +} + /// 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 -#[derive(Default)] +#[derive(Default, Debug)] pub enum Preview { Syntaxed(HLContent), Text(TextContent), @@ -107,6 +117,44 @@ pub enum Preview { impl Preview { const CONTENT_INSPECTOR_MIN_SIZE: usize = 1024; + pub fn into_string(&self) -> &str { + match self { + Self::Empty => "empty", + Self::Syntaxed(_) => "syntaxed", + Self::Text(_) => "text", + Self::Binary(_) => "binary", + Self::Archive(_) => "archive", + Self::Ueberzug(_) => "ueberzug", + Self::Media(_) => "media", + Self::Directory(_) => "directory", + Self::Iso(_) => "iso", + Self::Diff(_) => "diff", + Self::ColoredText(_) => "colored text", + Self::Socket(_) => "socket", + Self::BlockDevice(_) => "block device", + Self::FifoCharDevice(_) => "fifo char device", + } + } + + pub fn path(&self) -> PathBuf { + match self { + Self::Empty => PathBuf::new(), + Self::Syntaxed(p) => p.path.to_owned(), + Self::Text(p) => p.path.to_owned(), + Self::Binary(p) => p.path.to_owned(), + Self::Archive(p) => p.path.to_owned(), + Self::Ueberzug(p) => p.original.to_owned(), + Self::Media(p) => p.path.to_owned(), + Self::Directory(p) => p.path.to_owned(), + Self::Iso(p) => p.path.to_owned(), + Self::Diff(p) => p.path.to_owned(), + Self::ColoredText(p) => p.path.to_owned(), + Self::Socket(p) => p.path.to_owned(), + Self::BlockDevice(p) => p.path.to_owned(), + Self::FifoCharDevice(p) => p.path.to_owned(), + } + } + /// Empty preview, holding nothing. pub fn new_empty() -> Self { clear_tmp_file(); @@ -117,22 +165,10 @@ impl Preview { /// the file. /// Sometimes it reads the content of the file, sometimes it delegates /// it to the display method. - pub fn new( - file_info: &FileInfo, - users_cache: &UsersCache, - status: &Status, - colors: &Colors, - ) -> Result<Self> { - clear_tmp_file(); + pub fn new(file_info: &FileInfo) -> Result<Self> { + // 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 => Ok(Self::new_empty()), FileKind::NormalFile => { let extension = &file_info.extension.to_lowercase(); match ExtensionKind::matcher(extension) { @@ -334,10 +370,11 @@ fn read_nb_lines(path: &Path, size_limit: usize) -> Result<Vec<String>> { } /// Preview a socket file with `ss -lpmepiT` -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct Socket { content: Vec<String>, length: usize, + path: PathBuf, } impl Socket { @@ -358,6 +395,7 @@ impl Socket { Self { length: content.len(), content, + path: fileinfo.path.clone(), } } @@ -367,10 +405,11 @@ impl Socket { } /// Preview a blockdevice file with lsblk -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct BlockDevice { content: Vec<String>, length: usize, + path: PathBuf, } impl BlockDevice { @@ -394,6 +433,7 @@ impl BlockDevice { Self { length: content.len(), content, + path: fileinfo.path.to_owned(), } } @@ -403,10 +443,11 @@ impl BlockDevice { } /// Preview a fifo or a chardevice file with lsof -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct FifoCharDevice { content: Vec<String>, length: usize, + path: PathBuf, } impl FifoCharDevice { @@ -426,6 +467,7 @@ impl FifoCharDevice { Self { length: content.len(), content, + path: fileinfo.path.to_owned(), } } @@ -436,11 +478,12 @@ impl FifoCharDevice { /// Holds a preview of a text content. /// It's a boxed vector of strings (per line) -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct TextContent { pub kind: TextKind, content: Vec<String>, length: usize, + path: PathBuf, } impl TextContent { @@ -452,6 +495,7 @@ impl TextContent { kind: TextKind::HELP, length: content.len(), content, + path: PathBuf::new(), } } @@ -460,6 +504,7 @@ impl TextContent { kind: TextKind::LOG, length: content.len(), content, + path: PathBuf::new(), } } @@ -475,6 +520,7 @@ impl TextContent { kind: TextKind::EPUB, length: content.len(), content, + path: path.into(), }) } @@ -484,6 +530,7 @@ impl TextContent { kind: TextKind::TEXTFILE, length: content.len(), content, + path: path.into(), }) } @@ -494,10 +541,11 @@ impl TextContent { /// Holds a preview of a code text file whose language is supported by `Syntect`. /// The file is colored propery and line numbers are shown. -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct HLContent { content: Vec<Vec<SyntaxedString>>, length: usize, + pub path: PathBuf, } impl HLContent { @@ -513,6 +561,7 @@ impl HLContent { Ok(Self { length: highlighted_content.len(), content: highlighted_content, + path: path.into(), }) } @@ -522,6 +571,7 @@ impl HLContent { Ok(Self { length: highlighted_content.len(), content: highlighted_content, + path: PathBuf::new(), }) } @@ -560,7 +610,7 @@ 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 /// This struct does the parsing. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct SyntaxedString { col: usize, content: String, @@ -594,7 +644,7 @@ impl SyntaxedString { /// It doesn't try to respect endianness. /// The lines are formatted to display 16 bytes. /// The number of lines is truncated to $2^20 = 1048576$. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct BinaryContent { pub path: PathBuf, length: u64, @@ -643,7 +693,7 @@ impl BinaryContent { /// Holds a `Vec` of "bytes" (`u8`). /// It's mostly used to implement a `print` method. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Line { line: Vec<u8>, } @@ -707,10 +757,11 @@ impl Line { /// 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)] +#[derive(Clone, Debug)] pub struct ArchiveContent { length: usize, content: Vec<String>, + path: PathBuf, } impl ArchiveContent { @@ -726,6 +777,7 @@ impl ArchiveContent { Ok(Self { length: content.len(), content, + path: path.into(), }) } @@ -736,11 +788,12 @@ impl ArchiveContent { /// Holds media info about a "media" file (mostly videos and audios). /// Requires the [`mediainfo`](https://mediaarea.net/) executable installed in path. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MediaContent { length: usize, /// The media info details. content: Vec<String>, + path: PathBuf, } impl MediaContent { @@ -755,6 +808,7 @@ impl MediaContent { Ok(Self { length: content.len(), content, + path: path.into(), }) } @@ -763,6 +817,7 @@ impl MediaContent { } } +#[derive(Debug)] pub enum UeberzugKind { Font, Image, @@ -787,6 +842,14 @@ pub struct Ueberzug { pub index: usize, } +impl Debug for Ueberzug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Ueberzug") + .field("orginial", &self.original) + .finish() + } +} + impl Ueberzug { fn thumbnail(original: PathBuf, kind: UeberzugKind) -> Self { Self { @@ -866,6 +929,7 @@ impl Ueberzug { 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; + thumbnail.original = calc_path.into(); Ok(thumbnail) } @@ -1011,6 +1075,7 @@ pub struct ColoredText { pub content: Vec<String>, len: usize, pub selected_index: usize, + path: PathBuf, } impl ColoredText { @@ -1031,6 +1096,7 @@ impl ColoredText { content, len, selected_index, + path: PathBuf::new(), } } } @@ -1044,6 +1110,7 @@ pub struct Directory { pub tree: Tree, len: usize, pub selected_index: usize, + path: PathBuf, } impl Directory { @@ -1077,6 +1144,7 @@ impl Directory { len: content.len(), content, selected_index, + path: path.into(), }) } @@ -1087,6 +1155,7 @@ impl Directory { len: 0, content: vec![], selected_index: 0, + path: PathBuf::new(), }) } @@ -1227,9 +1296,11 @@ impl Directory { } } +#[derive(Debug)] pub struct Diff { pub content: Vec<String>, length: usize, + path: PathBuf, } impl Diff { @@ -1244,6 +1315,7 @@ impl Diff { Ok(Self { length: content.len(), content, + path: PathBuf::from(first_path), }) } @@ -1252,9 +1324,11 @@ impl Diff { } } +#[derive(Debug)] pub struct Iso { pub content: Vec<String>, length: usize, + path: PathBuf, } impl Iso { @@ -1270,6 +1344,7 @@ impl Iso { Ok(Self { length: content.len(), content, + path: path.into(), }) } diff --git a/src/status.rs b/src/status.rs index 128a76c..bf36b0e 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; @@ -583,6 +584,15 @@ impl Status { Ok(()) } + pub fn update_preview(&mut self, preview: Preview) { + log::info!("update_preview {kind}", kind = preview.into_string()); + if let Some(fileinfo) = self.tabs[0].selected() { + if preview.path() == fileinfo.path { + self.tabs[1].preview = preview; + } + } + } + /// 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<()> { @@ -597,13 +607,50 @@ impl Status { .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(); + self.tabs[1].preview = Preview::new(fileinfo).unwrap_or_default(); self.tabs[1].window.reset(self.tabs[1].preview.len()); Ok(()) } + pub fn set_preview(&mut self, colors: &Colors) -> Result<()> { + if self.selected_non_mut().path_content.is_empty() { + return Ok(()); + } + let unmutable_tab = self.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(); + self.selected().set_mode(Mode::Preview); + self.selected().window.reset(preview.len()); + self.selected().preview = preview; + } + FileKind::Directory => Self::tree(self, 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(()) + } + /// Set dual pane if the term is big enough pub fn set_dual_pane_if_wide_enough(&mut self, width: usize) -> Result<()> { if width < MIN_WIDTH_FOR_DUAL_PANE { |