summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqkzk <qu3nt1n@gmail.com>2023-10-29 20:53:06 +0100
committerqkzk <qu3nt1n@gmail.com>2023-10-29 20:53:06 +0100
commitedb7d8b36a33bf1cdb63039a14486b91f92430ab (patch)
tree83b69f162f129b55b0c7a4f05b9362a015458338
parent840789072f0671cb5bf0456d7b2e1308f3383278 (diff)
-rw-r--r--src/event_exec.rs42
-rw-r--r--src/main.rs69
-rw-r--r--src/preview.rs133
-rw-r--r--src/status.rs51
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 {