summaryrefslogtreecommitdiffstats
path: root/src/context/preview_context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/context/preview_context.rs')
-rw-r--r--src/context/preview_context.rs198
1 files changed, 194 insertions, 4 deletions
diff --git a/src/context/preview_context.rs b/src/context/preview_context.rs
index 51e593d..57184a8 100644
--- a/src/context/preview_context.rs
+++ b/src/context/preview_context.rs
@@ -1,25 +1,215 @@
use std::collections::HashMap;
-use std::path;
+use std::error::Error;
+use std::path::{self, PathBuf};
+use std::process::{Command, Stdio};
+use std::sync::mpsc::{self, Sender};
+use std::sync::Mutex;
+use std::{io, thread};
-use crate::preview::preview_file::PreviewFileState;
+use ratatui::layout::Rect;
+use ratatui_image::picker::Picker;
+use ratatui_image::protocol::Protocol;
+use ratatui_image::Resize;
+
+use crate::config::clean::app::AppConfig;
+use crate::event::{AppEvent, PreviewData};
+use crate::lazy_static;
+use crate::preview::preview_file::{FilePreview, PreviewFileState};
+use crate::ui::{views, AppBackend};
+use crate::AppContext;
+
+lazy_static! {
+ static ref GUARD: Mutex<()> = Mutex::new(());
+}
type FilePreviewMetadata = HashMap<path::PathBuf, PreviewFileState>;
pub struct PreviewContext {
previews: FilePreviewMetadata,
+ image_preview: Option<(PathBuf, Box<dyn Protocol>)>,
+ sender_script: Sender<(PathBuf, Rect)>,
+ sender_image: Option<Sender<(PathBuf, Rect)>>,
+ event_ts: Sender<AppEvent>,
}
impl PreviewContext {
- pub fn new() -> Self {
- Self {
+ pub fn new(
+ picker: Option<Picker>,
+ script: Option<PathBuf>,
+ event_ts: Sender<AppEvent>,
+ ) -> PreviewContext {
+ let (sender_script, receiver) = mpsc::channel::<(PathBuf, Rect)>();
+ let thread_script_event_ts = event_ts.clone();
+ thread::spawn(move || {
+ for (path, rect) in receiver {
+ if let Some(ref script) = script {
+ PreviewContext::spawn_command(
+ path.clone(),
+ script.to_path_buf(),
+ rect,
+ thread_script_event_ts.clone(),
+ );
+ }
+ }
+ });
+
+ let (sender_image, receiver) = mpsc::channel::<(PathBuf, Rect)>();
+ let sender_image = picker.map(|mut picker| {
+ let thread_image_event_ts = event_ts.clone();
+ thread::spawn(move || loop {
+ // Get last, or block for next.
+ if let Some((path, rect)) = receiver
+ .try_iter()
+ .last()
+ .or_else(|| receiver.iter().next())
+ {
+ let proto = image::io::Reader::open(path.as_path())
+ .and_then(|reader| reader.decode().map_err(Self::map_io_err))
+ .and_then(|dyn_img| {
+ picker
+ .new_protocol(dyn_img, rect, Resize::Fit)
+ .map_err(|err| {
+ io::Error::new(io::ErrorKind::Other, format!("{err}"))
+ })
+ });
+ if let Ok(proto) = proto {
+ let ev = AppEvent::PreviewFile {
+ path,
+ res: Ok(PreviewData::Image(proto)),
+ };
+ let _ = thread_image_event_ts.send(ev);
+ }
+ } else {
+ // Closed.
+ return;
+ }
+ });
+ sender_image
+ });
+
+ PreviewContext {
previews: HashMap::new(),
+ image_preview: None,
+ sender_script,
+ sender_image,
+ event_ts,
}
}
+ fn spawn_command(
+ path: PathBuf,
+ script: PathBuf,
+ rect: Rect,
+ thread_event_ts: Sender<AppEvent>,
+ ) {
+ let output = Command::new(script)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::null())
+ .arg("--path")
+ .arg(path.as_path())
+ .arg("--preview-width")
+ .arg(rect.width.to_string())
+ .arg("--preview-height")
+ .arg(rect.height.to_string())
+ .output();
+
+ let res = match output {
+ Ok(output) => {
+ if output.status.success() {
+ let preview = FilePreview::from(output);
+ AppEvent::PreviewFile {
+ path,
+ res: Ok(PreviewData::Script(Box::new(preview))),
+ }
+ } else {
+ AppEvent::PreviewFile {
+ path,
+ res: Err(io::Error::new(io::ErrorKind::Other, "nonzero status")),
+ }
+ }
+ }
+ Err(err) => AppEvent::PreviewFile {
+ path,
+ res: Err(io::Error::new(io::ErrorKind::Other, format!("{err}"))),
+ },
+ };
+ let _ = thread_event_ts.send(res);
+ }
+
pub fn previews_ref(&self) -> &FilePreviewMetadata {
&self.previews
}
pub fn previews_mut(&mut self) -> &mut FilePreviewMetadata {
&mut self.previews
}
+ pub fn image_preview_ref(&self, other: &path::Path) -> Option<&dyn Protocol> {
+ match &self.image_preview {
+ Some((path, protocol)) if path == other => Some(protocol.as_ref()),
+ _ => None,
+ }
+ }
+ pub fn set_image_preview(&mut self, preview: Option<(path::PathBuf, Box<dyn Protocol>)>) {
+ self.image_preview = preview;
+ }
+
+ pub fn load_preview_script(
+ &self,
+ context: &AppContext,
+ backend: &AppBackend,
+ path: path::PathBuf,
+ ) {
+ if let Err(err) = Self::backend_rect(context.config_ref(), backend).and_then(|rect| {
+ self.sender_script
+ .send((path.clone(), rect))
+ .map_err(Self::map_io_err)
+ }) {
+ let ev = AppEvent::PreviewFile {
+ path,
+ res: Err(err),
+ };
+ let _ = self.event_ts.send(ev);
+ }
+ }
+
+ pub fn load_preview_image(
+ &self,
+ context: &AppContext,
+ backend: &AppBackend,
+ path: path::PathBuf,
+ ) {
+ if let Some(sender) = &self.sender_image {
+ if let Err(err) = Self::backend_rect(context.config_ref(), backend)
+ .and_then(|rect| sender.send((path.clone(), rect)).map_err(Self::map_io_err))
+ {
+ let ev = AppEvent::PreviewFile {
+ path,
+ res: Err(err),
+ };
+ let _ = self.event_ts.send(ev);
+ }
+ }
+ }
+
+ fn backend_rect(config: &AppConfig, backend: &AppBackend) -> io::Result<Rect> {
+ let area = backend.terminal_ref().size()?;
+ let area = Rect {
+ y: area.top() + 1,
+ height: area.height - 2,
+ ..area
+ };
+
+ let display_options = config.display_options_ref();
+ let constraints = &display_options.default_layout;
+ let layout = if display_options.show_borders() {
+ views::calculate_layout_with_borders(area, constraints)
+ } else {
+ views::calculate_layout(area, constraints)
+ };
+ Ok(layout[2])
+ }
+
+ #[inline]
+ fn map_io_err(err: impl Error) -> io::Error {
+ io::Error::new(io::ErrorKind::Other, format!("{err}"))
+ }
}