From 6380791acaef1772c1e3ccb990cdf87bd2902367 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Sun, 30 Jul 2023 11:48:05 +0800 Subject: [PATCH] .. --- README.md | 2 +- src/config/preview/adaptor.rs | 4 +-- src/core/adaptor/adaptor.rs | 2 +- src/core/adaptor/ueberzug.rs | 2 +- src/core/event.rs | 6 ++-- src/core/manager/manager.rs | 17 +++++---- src/core/manager/preview.rs | 65 +++++++++++++++++++++++------------ src/core/manager/tab.rs | 2 +- src/core/tasks/tasks.rs | 1 + src/core/which/which.rs | 14 +++++--- src/ui/app.rs | 14 ++++---- src/ui/context.rs | 21 ++++++++--- src/ui/manager/preview.rs | 7 ++-- src/ui/root.rs | 7 ++-- src/ui/which/layout.rs | 4 +-- 15 files changed, 103 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 237937b3..bf025fbb 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ it will automatically use the "Window system protocol" to display images -- this - [x] Integration with fzf, zoxide for fast directory navigation - [x] Integration with fd, rg for fuzzy file searching - [x] Documentation of commands and options -- [ ] Support for Überzug++ for image previews with X11/wayland environment +- [x] Support for Überzug++ for image previews with X11/wayland environment - [ ] Batch renaming support ## License diff --git a/src/config/preview/adaptor.rs b/src/config/preview/adaptor.rs index 25cfb98a..c0662d1c 100644 --- a/src/config/preview/adaptor.rs +++ b/src/config/preview/adaptor.rs @@ -6,9 +6,9 @@ pub enum PreviewAdaptor { Iterm2, // Supported by Überzug++ + Sixel, X11, Wayland, - Sixel, Chafa, } @@ -43,9 +43,9 @@ impl ToString for PreviewAdaptor { match self { PreviewAdaptor::Kitty => "kitty", PreviewAdaptor::Iterm2 => "iterm2", + PreviewAdaptor::Sixel => "sixel", PreviewAdaptor::X11 => "x11", PreviewAdaptor::Wayland => "wayland", - PreviewAdaptor::Sixel => "sixel", PreviewAdaptor::Chafa => "chafa", } .to_string() diff --git a/src/core/adaptor/adaptor.rs b/src/core/adaptor/adaptor.rs index df69eb31..141a4aa2 100644 --- a/src/core/adaptor/adaptor.rs +++ b/src/core/adaptor/adaptor.rs @@ -11,7 +11,7 @@ use crate::config::{preview::PreviewAdaptor, PREVIEW}; static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false); static UEBERZUG: Lazy>>> = - Lazy::new(|| if PREVIEW.adaptor.needs_ueberzug() { Ueberzug::init().ok() } else { None }); + Lazy::new(|| if PREVIEW.adaptor.needs_ueberzug() { Ueberzug::start().ok() } else { None }); pub struct Adaptor; diff --git a/src/core/adaptor/ueberzug.rs b/src/core/adaptor/ueberzug.rs index e08c2f95..bca5c1aa 100644 --- a/src/core/adaptor/ueberzug.rs +++ b/src/core/adaptor/ueberzug.rs @@ -9,7 +9,7 @@ use crate::config::PREVIEW; pub(super) struct Ueberzug; impl Ueberzug { - pub(super) fn init() -> Result>> { + pub(super) fn start() -> Result>> { let mut child = Self::create_demon().ok(); let (tx, mut rx) = mpsc::unbounded_channel(); diff --git a/src/core/event.rs b/src/core/event.rs index 0c50ef80..b9b65f07 100644 --- a/src/core/event.rs +++ b/src/core/event.rs @@ -25,7 +25,7 @@ pub enum Event { Pages(usize), Mimetype(BTreeMap), Hover(Option), - Preview(PathBuf, PreviewData), + Preview(PathBuf, String, PreviewData), // Input Select(SelectOpt, oneshot::Sender>), @@ -94,8 +94,8 @@ macro_rules! emit { (Hover($file:expr)) => { $crate::core::Event::Hover(Some($file)).emit(); }; - (Preview($path:expr, $data:expr)) => { - $crate::core::Event::Preview($path, $data).emit(); + (Preview($path:expr, $mime:expr, $data:expr)) => { + $crate::core::Event::Preview($path, $mime, $data).emit(); }; (Select($opt:expr)) => {{ diff --git a/src/core/manager/manager.rs b/src/core/manager/manager.rs index dba8db8c..434d113e 100644 --- a/src/core/manager/manager.rs +++ b/src/core/manager/manager.rs @@ -49,20 +49,24 @@ impl Manager { self.watcher.watch(to_watch); } - pub fn preview(&mut self) -> bool { + pub fn preview(&mut self, show_image: bool) -> bool { let hovered = if let Some(h) = self.hovered() { h.clone() } else { return self.active_mut().preview.reset(); }; + if !show_image { + self.active_mut().preview.reset_image(); + } + if hovered.meta.is_dir() { - self.active_mut().preview.go(&hovered.path, MIME_DIR); + self.active_mut().preview.go(&hovered.path, MIME_DIR, show_image); if self.active().history(&hovered.path).is_some() { - emit!(Preview(hovered.path, PreviewData::Folder)); + emit!(Preview(hovered.path, MIME_DIR.to_owned(), PreviewData::Folder)); } } else if let Some(mime) = self.mimetype.get(&hovered.path).cloned() { - self.active_mut().preview.go(&hovered.path, &mime); + self.active_mut().preview.go(&hovered.path, &mime, show_image); } else { tokio::spawn(async move { if let Ok(mimes) = external::file(&[hovered.path]).await { @@ -300,11 +304,10 @@ impl Manager { tasks.precache_video(&mimes); self.mimetype.extend(mimes); - self.preview(); true } - pub fn update_preview(&mut self, path: PathBuf, data: PreviewData) -> bool { + pub fn update_preview(&mut self, path: PathBuf, mime: String, data: PreviewData) -> bool { let hovered = if let Some(ref h) = self.current().hovered { h.path() } else { @@ -316,7 +319,7 @@ impl Manager { } let preview = &mut self.active_mut().preview; - preview.path = path; + preview.lock = Some((path, mime)); preview.data = data; true } diff --git a/src/core/manager/preview.rs b/src/core/manager/preview.rs index 2705a32b..8294f209 100644 --- a/src/core/manager/preview.rs +++ b/src/core/manager/preview.rs @@ -1,4 +1,4 @@ -use std::{fs::File, io::{BufRead, BufReader}, path::{Path, PathBuf}, sync::OnceLock}; +use std::{fs::File, io::{BufRead, BufReader}, mem, path::{Path, PathBuf}, sync::OnceLock}; use anyhow::{anyhow, Result}; use ratatui::prelude::Rect; @@ -11,8 +11,9 @@ use crate::{config::{PREVIEW, THEME}, core::{adaptor::Adaptor, external, files:: static SYNTECT_SYNTAX: OnceLock = OnceLock::new(); static SYNTECT_THEME: OnceLock = OnceLock::new(); +#[derive(Default)] pub struct Preview { - pub path: PathBuf, + pub lock: Option<(PathBuf, String)>, pub data: PreviewData, handle: Option>, @@ -24,18 +25,10 @@ pub enum PreviewData { None, Folder, Text(String), + Image, } impl Preview { - pub fn new() -> Self { - Self { - path: Default::default(), - data: Default::default(), - - handle: Default::default(), - } - } - fn rect() -> Rect { let s = tty_size(); @@ -50,12 +43,19 @@ impl Preview { } } - pub fn go(&mut self, path: &Path, mime: &str) { - self.reset(); + pub fn go(&mut self, path: &Path, mime: &str, show_image: bool) { + let kind = MimeKind::new(&mime); + if !show_image && matches!(kind, MimeKind::Image | MimeKind::Video) { + return; + } else if self.same(path, mime) { + return; + } else { + self.reset(); + } let (path, mime) = (path.to_path_buf(), mime.to_owned()); self.handle = Some(tokio::spawn(async move { - let result = match MimeKind::new(&mime) { + let result = match kind { MimeKind::Dir => Self::folder(&path).await, MimeKind::JSON => Self::json(&path).await.map(PreviewData::Text), MimeKind::Text => Self::highlight(&path).await.map(PreviewData::Text), @@ -65,7 +65,7 @@ impl Preview { MimeKind::Others => Err(anyhow!("Unsupported mimetype: {}", mime)), }; - emit!(Preview(path, result.unwrap_or_default())); + emit!(Preview(path, mime, result.unwrap_or_default())); })); } @@ -73,13 +73,22 @@ impl Preview { self.handle.take().map(|h| h.abort()); Adaptor::image_hide(Self::rect()); - if self.path == PathBuf::default() { - return false; - } + self.lock = None; + !matches!( + mem::replace(&mut self.data, PreviewData::None), + PreviewData::None | PreviewData::Image + ) + } - self.path = Default::default(); - self.data = Default::default(); - true + pub fn reset_image(&mut self) -> bool { + self.handle.take().map(|h| h.abort()); + Adaptor::image_hide(Self::rect()); + + if matches!(self.data, PreviewData::Image) { + self.lock = None; + self.data = PreviewData::None; + } + false } pub async fn folder(path: &Path) -> Result { @@ -98,7 +107,7 @@ impl Preview { } Adaptor::image_show(path, Self::rect()).await?; - Ok(PreviewData::None) + Ok(PreviewData::Image) } pub async fn video(path: &Path) -> Result { @@ -166,3 +175,15 @@ impl Preview { .await? } } + +impl Preview { + #[inline] + pub fn same(&self, path: &Path, mime: &str) -> bool { + self.lock.as_ref().map(|(p, m)| p == path && m == mime).unwrap_or(false) + } + + #[inline] + pub fn same_path(&self, path: &Path) -> bool { + self.lock.as_ref().map(|(p, _)| p == path).unwrap_or(false) + } +} diff --git a/src/core/manager/tab.rs b/src/core/manager/tab.rs index d7c9dde9..796d8d07 100644 --- a/src/core/manager/tab.rs +++ b/src/core/manager/tab.rs @@ -27,7 +27,7 @@ impl Tab { search: None, history: Default::default(), - preview: Preview::new(), + preview: Default::default(), } } diff --git a/src/core/tasks/tasks.rs b/src/core/tasks/tasks.rs index 4507008b..13941e94 100644 --- a/src/core/tasks/tasks.rs +++ b/src/core/tasks/tasks.rs @@ -67,6 +67,7 @@ impl Tasks { pub fn toggle(&mut self) -> bool { self.visible = !self.visible; + emit!(Hover); // Show/hide preview for images true } diff --git a/src/core/which/which.rs b/src/core/which/which.rs index dd063d0c..b50add1f 100644 --- a/src/core/which/which.rs +++ b/src/core/which/which.rs @@ -26,7 +26,7 @@ impl Which { .filter(|s| s.on.len() > 1 && s.on[0] == *key) .cloned() .collect(); - self.visible = true; + self.switch(true); true } @@ -37,16 +37,22 @@ impl Which { .collect(); if self.cands.is_empty() { - self.visible = false; + self.switch(false); } else if self.cands.len() == 1 { - self.visible = false; + self.switch(false); emit!(Ctrl(self.cands.remove(0), self.layer)); } else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times + 1) { + self.switch(false); emit!(Ctrl(self.cands.remove(i), self.layer)); - self.visible = false; } self.times += 1; return true; } + + #[inline] + fn switch(&mut self, state: bool) { + self.visible = state; + emit!(Hover); // Show/hide preview for images + } } diff --git a/src/ui/app.rs b/src/ui/app.rs index c9b6b450..3a8ef940 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -55,7 +55,7 @@ impl App { let _ = term.draw(|f| { f.render_widget(Root::new(&mut self.cx), f.size()); - if let Some((x, y)) = self.cx.cursor { + if let Some((x, y)) = self.cx.cursor() { f.set_cursor(x, y); } }); @@ -64,7 +64,7 @@ impl App { fn dispatch_resize(&mut self) { self.cx.manager.current_mut().set_page(true); - self.cx.manager.preview(); + self.cx.manager.preview(self.cx.image_layer()); emit!(Render); } @@ -123,17 +123,17 @@ impl App { Event::Mimetype(mimes) => { if manager.update_mimetype(mimes, tasks) { emit!(Render); + self.cx.manager.preview(self.cx.image_layer()); } } Event::Hover(file) => { - let mut b = file.map(|f| manager.current_mut().hover_force(f)).unwrap_or(false); - b |= manager.preview(); - if b { + if file.map(|f| manager.current_mut().hover_force(f)).unwrap_or(false) { emit!(Render); } + self.cx.manager.preview(self.cx.image_layer()); } - Event::Preview(file, data) => { - manager.update_preview(file, data); + Event::Preview(path, mime, data) => { + manager.update_preview(path, mime, data); emit!(Render); } diff --git a/src/ui/context.rs b/src/ui/context.rs index be8e035b..0c18dace 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -1,8 +1,6 @@ use crate::{config::keymap::KeymapLayer, core::{input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position}, misc::tty_size}; pub struct Ctx { - pub cursor: Option<(u16, u16)>, - pub manager: Manager, pub which: Which, pub select: Select, @@ -13,8 +11,6 @@ pub struct Ctx { impl Ctx { pub fn new() -> Self { Self { - cursor: None, - manager: Manager::new(), which: Default::default(), select: Default::default(), @@ -23,6 +19,14 @@ impl Ctx { } } + #[inline] + pub fn cursor(&self) -> Option<(u16, u16)> { + if self.input.visible { + return Some(self.input.cursor()); + } + None + } + #[inline] pub fn layer(&self) -> KeymapLayer { if self.which.visible { @@ -38,6 +42,15 @@ impl Ctx { } } + #[inline] + pub fn image_layer(&self) -> bool { + match self.layer() { + KeymapLayer::Which => false, + KeymapLayer::Tasks => false, + _ => true, + } + } + pub fn position(&self, pos: Position) -> Position { match pos { Position::Top => Position::Coords((tty_size().ws_col / 2).saturating_sub(25), 2), diff --git a/src/ui/manager/preview.rs b/src/ui/manager/preview.rs index 0915dc2b..137b51ec 100644 --- a/src/ui/manager/preview.rs +++ b/src/ui/manager/preview.rs @@ -24,10 +24,6 @@ impl<'a> Widget for Preview<'a> { buf, ); - // TODO: image - // if self.cx.input.visible || self.cx.select.visible || self.cx.tasks.visible { - // } - let manager = &self.cx.manager; let hovered = if let Some(h) = manager.hovered() { h.path() @@ -36,7 +32,7 @@ impl<'a> Widget for Preview<'a> { }; let preview = manager.active().preview(); - if preview.path != hovered { + if !preview.same_path(&hovered) { return; } @@ -51,6 +47,7 @@ impl<'a> Widget for Preview<'a> { let p = Paragraph::new(s.as_bytes().into_text().unwrap()); p.render(area, buf); } + PreviewData::Image => {} } } } diff --git a/src/ui/root.rs b/src/ui/root.rs index 43227c72..c202aa2f 100644 --- a/src/ui/root.rs +++ b/src/ui/root.rs @@ -3,11 +3,11 @@ use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, wid use super::{header, manager, status, tasks, which::Which, Ctx, Input, Select}; pub struct Root<'a> { - cx: &'a mut Ctx, + cx: &'a Ctx, } impl<'a> Root<'a> { - pub fn new(cx: &'a mut Ctx) -> Self { Self { cx } } + pub fn new(cx: &'a Ctx) -> Self { Self { cx } } } impl<'a> Widget for Root<'a> { @@ -31,9 +31,6 @@ impl<'a> Widget for Root<'a> { if self.cx.input.visible { Input::new(self.cx).render(area, buf); - self.cx.cursor = Some(self.cx.input.cursor()); - } else { - self.cx.cursor = None; } if self.cx.which.visible { diff --git a/src/ui/which/layout.rs b/src/ui/which/layout.rs index 4653564d..285ac8d1 100644 --- a/src/ui/which/layout.rs +++ b/src/ui/which/layout.rs @@ -4,11 +4,11 @@ use super::Side; use crate::ui::Ctx; pub struct Which<'a> { - cx: &'a mut Ctx, + cx: &'a Ctx, } impl<'a> Which<'a> { - pub fn new(cx: &'a mut Ctx) -> Self { Self { cx } } + pub fn new(cx: &'a Ctx) -> Self { Self { cx } } } impl Widget for Which<'_> {