This commit is contained in:
sxyazi 2023-08-26 16:58:09 +08:00
parent a7e8567e81
commit 0fa8df100d
No known key found for this signature in database
12 changed files with 184 additions and 149 deletions

12
Cargo.lock generated
View file

@ -1294,9 +1294,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.12"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
@ -1527,18 +1527,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.186"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.186"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",

View file

@ -87,9 +87,8 @@ impl App {
fn dispatch_resize(&mut self) {
self.cx.manager.current_mut().set_page(true);
self.cx.manager.active_mut().preview_reset_image();
// TODO: use peek
self.cx.manager.preview(self.cx.image_layer());
self.cx.manager.active_mut().preview_reset();
self.cx.manager.peek(true, self.cx.image_layer());
emit!(Render);
}
@ -101,8 +100,6 @@ impl App {
} else {
self.term = Some(Term::start().unwrap());
self.signals.stop_term(false);
// TODO: use peek
self.cx.manager.preview(self.cx.image_layer());
emit!(Render);
emit!(Hover);
}
@ -154,21 +151,24 @@ impl App {
Event::Mimetype(mimes) => {
if manager.update_mimetype(mimes, tasks) {
emit!(Render);
self.cx.manager.preview(self.cx.image_layer());
emit!(Peek);
}
}
Event::Hover(file) => {
if file.map(|f| manager.current_mut().hover_force(f)).unwrap_or(false) {
if manager.update_hover(file) {
emit!(Render);
}
self.cx.manager.preview(self.cx.image_layer());
emit!(Peek);
}
Event::Preview(path, mime, data) => {
manager.update_preview(path, mime, data);
emit!(Render);
Event::Peek(skip, sequent) => {
let b = sequent.is_some();
manager.active_mut().update_peek(skip as isize, sequent);
self.cx.manager.peek(b, self.cx.image_layer());
}
Event::Peek(path, skip) => {
manager.update_peek(path, skip);
Event::Preview(lock) => {
if manager.active_mut().update_preview(lock) {
emit!(Render);
}
}
Event::Select(opt, tx) => {

View file

@ -63,7 +63,8 @@ impl Executor {
}
"peek" => {
let step = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
cx.manager.active_mut().peek(step)
cx.manager.active_mut().update_peek(step, None);
cx.manager.peek(true, cx.image_layer())
}
"leave" => cx.manager.active_mut().leave(),
"enter" => cx.manager.active_mut().enter(),

View file

@ -26,8 +26,7 @@ impl<'a> Widget for Preview<'a> {
return;
}
match &preview.data {
PreviewData::None => {}
match &preview.lock.as_ref().unwrap().data {
PreviewData::Folder => {
if let Some(folder) = manager.active().history(hovered) {
Folder::new(self.cx, folder).with_preview(true).render(area, buf);

View file

@ -6,7 +6,8 @@ use crossterm::event::KeyEvent;
use shared::RoCell;
use tokio::sync::{mpsc::UnboundedSender, oneshot};
use super::{files::{File, FilesOp}, input::InputOpt, manager::PreviewData, select::SelectOpt};
use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt};
use crate::manager::PreviewLock;
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
@ -26,8 +27,8 @@ pub enum Event {
Pages(usize),
Mimetype(BTreeMap<PathBuf, String>),
Hover(Option<File>),
Preview(PathBuf, String, PreviewData),
Peek(PathBuf, usize),
Peek(usize, Option<PathBuf>),
Preview(PreviewLock),
// Input
Select(SelectOpt, oneshot::Sender<Result<usize>>),
@ -88,11 +89,14 @@ macro_rules! emit {
(Hover($file:expr)) => {
$crate::Event::Hover(Some($file)).emit();
};
(Preview($path:expr, $mime:expr, $data:expr)) => {
$crate::Event::Preview($path, $mime, $data).emit();
(Peek) => {
$crate::Event::Peek(0, None).emit();
};
(Peek($path:expr, $skip:expr)) => {
$crate::Event::Peek($path, $skip).emit();
(Peek($skip:expr, $path:expr)) => {
$crate::Event::Peek($skip, Some($path)).emit();
};
(Preview($lock:expr)) => {
$crate::Event::Preview($lock).emit();
};
(Select($opt:expr)) => {{

View file

@ -5,7 +5,7 @@ use config::{BOOT, OPEN};
use shared::{max_common_root, Defer, Term, MIME_DIR};
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use super::{PreviewData, Tab, Tabs, Watcher};
use super::{Tab, Tabs, Watcher};
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Folder, select::SelectOpt, tasks::Tasks, Event, BLOCKER};
pub struct Manager {
@ -52,7 +52,7 @@ impl Manager {
self.watcher.watch(to_watch);
}
pub fn preview(&mut self, show_image: bool) -> bool {
pub fn peek(&mut self, sequent: bool, show_image: bool) -> bool {
let Some(hovered) = self.hovered().cloned() else {
return self.active_mut().preview.reset();
};
@ -61,19 +61,23 @@ impl Manager {
self.active_mut().preview_reset_image();
}
if hovered.meta.is_dir() {
self.active_mut().preview.go(&hovered.path, MIME_DIR, show_image);
if let Some(offset) = self.active().history(&hovered.path).map(|f| f.offset()) {
emit!(Peek(hovered.path, offset));
}
} else if let Some(mime) = self.mimetype.get(&hovered.path).cloned() {
self.active_mut().preview.go(&hovered.path, &mime, show_image);
let mime = if hovered.meta.is_dir() {
MIME_DIR.to_owned()
} else if let Some(m) = self.mimetype.get(&hovered.path).cloned() {
m
} else {
tokio::spawn(async move {
if let Ok(mimes) = external::file(&[hovered.path]).await {
emit!(Mimetype(mimes));
}
});
return false;
};
if sequent {
self.active_mut().preview.sequent(&hovered.path, &mime, show_image);
} else {
self.active_mut().preview.go(&hovered.path, &mime, show_image);
}
false
}
@ -393,39 +397,16 @@ impl Manager {
true
}
pub fn update_preview(&mut self, path: PathBuf, mime: String, data: PreviewData) -> bool {
let Some(hovered) = self.hovered().map(|h| &h.path) else {
return self.active_mut().preview.reset();
pub fn update_hover(&mut self, file: Option<File>) -> bool {
let b = file.map(|f| self.current_mut().hover_force(f)).unwrap_or(false);
let Some(hovered) = self.hovered() else {
return b;
};
if path != *hovered {
return false;
if hovered.meta.is_dir() {
self.watcher.trigger_dirs(&[&hovered.path]);
}
let preview = &mut self.active_mut().preview;
preview.lock = Some((path, mime));
preview.data = data;
true
}
pub fn update_peek(&mut self, path: PathBuf, skip: usize) -> bool {
if !matches!(self.hovered(), Some(f) if f.path == path) {
return false;
}
if self.active().preview.lock.is_some() {
self.active_mut().preview.peek(skip);
return false;
}
if self.active().history(&path).is_some() {
let preview = &mut self.active_mut().preview;
preview.lock = Some((path, MIME_DIR.to_string()));
preview.data = PreviewData::Folder;
}
self.active_mut().preview.peek(skip);
false
b
}
}

View file

@ -1,26 +1,30 @@
use std::{mem, path::{Path, PathBuf}, sync::atomic::Ordering};
use std::{path::{Path, PathBuf}, sync::atomic::Ordering};
use adaptor::Adaptor;
use config::MANAGER;
use shared::{MimeKind, PeekError};
use tokio::task::JoinHandle;
use super::{provider::INCR, Provider};
use super::{Provider, INCR};
use crate::emit;
#[derive(Default)]
pub struct Preview {
pub lock: Option<(PathBuf, String)>,
pub data: PreviewData,
pub lock: Option<PreviewLock>,
skip: usize,
handle: Option<JoinHandle<()>>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct PreviewLock {
pub path: PathBuf,
pub mime: String,
pub skip: usize,
pub data: PreviewData,
}
#[derive(Debug)]
pub enum PreviewData {
#[default]
None,
Folder,
Text(String),
Image,
@ -29,50 +33,67 @@ pub enum PreviewData {
impl Preview {
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) {
if !show_image && kind.show_as_image() {
return;
} else if self.same(path, mime) {
return;
} else {
self.reset();
}
self.reset();
if !self.same_path(path) {
self.skip = 0;
}
let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip);
self.handle = Some(tokio::spawn(async move {
let result = Provider::auto(kind, &path, skip).await;
emit!(Preview(path, mime, result.unwrap_or_default()));
match Provider::auto(kind, &path, skip).await {
Ok(data) => {
emit!(Preview(PreviewLock { path, mime, skip, data }));
}
Err(PeekError::Exceed(max)) => {
emit!(Peek(max, path));
}
_ => {}
}
}));
}
pub fn peek(&mut self, skip: usize) {
let Some((path, kind, mime)) = self.lock.clone().map(|(p, m)| (p, MimeKind::new(&m), m)) else {
pub fn sequent(&mut self, path: &Path, mime: &str, show_image: bool) {
let kind = MimeKind::new(mime);
if !show_image && kind.show_as_image() {
return;
};
} else if self.same(path, mime) {
return;
}
self.handle.take().map(|h| h.abort());
INCR.fetch_add(1, Ordering::Relaxed);
self.skip = skip;
let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip);
self.handle = Some(tokio::spawn(async move {
match Provider::auto(kind, &path, skip).await {
Ok(result) => {
emit!(Preview(path, mime, result));
Ok(data) => {
emit!(Preview(PreviewLock { path, mime, skip, data }));
}
Err(PeekError::Exceed(max)) => {
emit!(Peek(path, max));
emit!(Peek(max, path));
}
_ => {}
};
}
}));
}
#[inline]
pub fn peek_step(&mut self, step: isize) -> usize {
let Some(kind) = self.lock.clone().map(|(_, m)| MimeKind::new(&m)) else {
return 0;
};
pub fn arrow(&mut self, step: isize, absolute: bool) -> bool {
let old = self.skip;
if absolute {
self.skip = step.unsigned_abs();
} else if let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) {
let size = Provider::step_size(kind, step.unsigned_abs());
self.skip = if step < 0 { old.saturating_sub(size) } else { old + size };
}
Provider::step_size(kind, step.unsigned_abs())
self.skip != old
}
pub fn reset(&mut self) -> bool {
@ -80,12 +101,9 @@ impl Preview {
INCR.fetch_add(1, Ordering::Relaxed);
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
let b = matches!(&self.lock, Some(l) if !l.is_image());
self.lock = None;
self.skip = 0;
!matches!(
mem::replace(&mut self.data, PreviewData::None),
PreviewData::None | PreviewData::Image
)
b
}
pub fn reset_image(&mut self) -> bool {
@ -93,26 +111,41 @@ impl Preview {
INCR.fetch_add(1, Ordering::Relaxed);
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
if self.data == PreviewData::Image {
if matches!(&self.lock, Some(l) if l.is_image()) {
self.lock = None;
self.data = PreviewData::None;
self.skip = 0;
}
false
}
}
impl Preview {
#[inline]
pub fn lock(&self) -> &Option<PreviewLock> { &self.lock }
#[inline]
pub fn skip(&self) -> usize { self.skip }
#[inline]
pub fn same(&self, path: &Path, mime: &str) -> bool {
self.lock.as_ref().map(|(p, m)| p == path && m == mime).unwrap_or(false)
if let Some(ref lock) = self.lock {
return lock.path == path && lock.mime == mime && lock.skip == self.skip;
}
false
}
#[inline]
pub fn same_path(&self, path: &Path) -> bool {
self.lock.as_ref().map(|(p, _)| p == path).unwrap_or(false)
if let Some(ref lock) = self.lock {
return lock.path == path;
}
false
}
}
impl PreviewLock {
#[inline]
pub fn is_image(&self) -> bool { matches!(self.data, PreviewData::Image) }
#[inline]
pub fn is_folder(&self) -> bool { matches!(self.data, PreviewData::Folder) }
}

View file

@ -21,7 +21,7 @@ impl Provider {
skip: usize,
) -> Result<PreviewData, PeekError> {
match kind {
MimeKind::Empty => Ok(PreviewData::None),
MimeKind::Empty => Err("Empty file".into()),
MimeKind::Archive => Provider::archive(path, skip).await.map(PreviewData::Text),
MimeKind::Dir => Provider::folder(path).await,
MimeKind::Image => Provider::image(path).await,

View file

@ -3,10 +3,10 @@ use std::{collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem, path::
use anyhow::{Error, Result};
use config::{open::Opener, MANAGER};
use futures::StreamExt;
use shared::{Defer, MIME_DIR};
use shared::Defer;
use tokio::task::JoinHandle;
use super::{Folder, Mode, Preview};
use super::{Folder, Mode, Preview, PreviewLock};
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::InputOpt, Event, BLOCKER};
pub struct Tab {
@ -76,32 +76,6 @@ impl Tab {
true
}
pub fn peek(&mut self, step: isize) -> bool {
let size = self.preview.peek_step(step);
let old = self.preview.skip();
let new = if step < 0 { old.saturating_sub(size) } else { old + size };
if new == old {
return false;
}
if !matches!(&self.preview.lock, Some((_, m)) if m == MIME_DIR) {
self.preview.peek(new);
return false;
};
let path = self.preview.lock.as_ref().unwrap().0.clone();
if let Some(folder) = self.history(&path) {
let max = folder.files.len().saturating_sub(MANAGER.layout.preview_height());
if new > max {
emit!(Peek(path, max));
return false;
}
}
self.preview.peek(new);
false
}
pub async fn cd(&mut self, mut target: PathBuf) -> bool {
let Ok(file) = File::from(&target).await else {
return false;
@ -335,6 +309,42 @@ impl Tab {
false
}
pub fn update_peek(&mut self, step: isize, path: Option<PathBuf>) {
let Some(ref hovered) = self.current.hovered else {
return;
};
if path.as_ref().map(|p| *p != hovered.path).unwrap_or(false) {
return;
} else if !self.preview.lock.as_ref().map(|l| l.path == hovered.path).unwrap_or(false) {
return;
} else if !self.preview.arrow(step, path.is_some()) {
return;
} else if !hovered.meta.is_dir() {
return;
}
if let Some(folder) = self.history(&hovered.path) {
let max = folder.files.len().saturating_sub(MANAGER.layout.preview_height());
if self.preview.skip() > max {
self.preview.arrow(max as isize, true);
}
}
}
pub fn update_preview(&mut self, lock: PreviewLock) -> bool {
let Some(hovered) = self.current.hovered.as_ref().map(|h| &h.path) else {
return self.preview.reset();
};
if lock.path != *hovered {
return false;
}
self.preview.lock = Some(lock);
true
}
}
impl Tab {
@ -388,6 +398,9 @@ impl Tab {
#[inline]
pub fn preview(&self) -> &Preview { &self.preview }
#[inline]
pub fn preview_reset(&mut self) -> bool { self.preview.reset() }
#[inline]
pub fn preview_reset_image(&mut self) -> bool { self.preview.reset_image() }
}

View file

@ -34,7 +34,7 @@ impl Tasks {
pub fn toggle(&mut self) -> bool {
self.visible = !self.visible;
emit!(Hover); // Show/hide preview for images
emit!(Peek); // Show/hide preview for images
true
}

View file

@ -51,6 +51,6 @@ impl Which {
#[inline]
fn switch(&mut self, state: bool) {
self.visible = state;
emit!(Hover); // Show/hide preview for images
emit!(Peek); // Show/hide preview for images
}
}

View file

@ -18,21 +18,6 @@ pub enum MimeKind {
}
impl MimeKind {
pub fn valid(s: &str) -> bool {
if s == "inode/x-empty" {
return true;
}
let parts = s.split('/').collect::<Vec<_>>();
if parts.len() != 2 {
return false;
}
#[rustfmt::skip]
let b = matches!(parts[0], "application" | "audio" | "example" | "font" | "image" | "message" | "model" | "multipart" | "text" | "video");
b && !parts[1].is_empty()
}
pub fn new(s: &str) -> Self {
if s == MIME_DIR {
Self::Dir
@ -61,4 +46,23 @@ impl MimeKind {
Self::Others
}
}
pub fn valid(s: &str) -> bool {
if s == "inode/x-empty" {
return true;
}
let parts = s.split('/').collect::<Vec<_>>();
if parts.len() != 2 {
return false;
}
#[rustfmt::skip]
let b = matches!(parts[0], "application" | "audio" | "example" | "font" | "image" | "message" | "model" | "multipart" | "text" | "video");
b && !parts[1].is_empty()
}
pub fn show_as_image(&self) -> bool {
matches!(self, MimeKind::Image | MimeKind::Video | MimeKind::PDF)
}
}