fix: one file's mime-type changed multiple times without triggering a preview again (#1682)

This commit is contained in:
三咲雅 · Misaki Masa 2024-09-26 00:22:55 +08:00 committed by GitHub
parent 631afd09ee
commit 5b66192332
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 66 additions and 58 deletions

16
Cargo.lock generated
View file

@ -1774,9 +1774,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]] [[package]]
name = "plist" name = "plist"
@ -2265,9 +2265,9 @@ dependencies = [
[[package]] [[package]]
name = "simdutf8" name = "simdutf8"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "slab" name = "slab"
@ -2400,18 +2400,18 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.63" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.63" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -40,7 +40,7 @@ static SHOWN: RoCell<arc_swap::ArcSwapOption<ratatui::layout::Rect>> = RoCell::n
pub fn init() { pub fn init() {
// Tmux support // Tmux support
TMUX.init(env_exists("TMUX") && env_exists("TMUX_PANE")); TMUX.init(env_exists("TMUX_PANE") && env_exists("TMUX"));
ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" }); ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" });
START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" }); START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" });
CLOSE.init(if *TMUX { "\x1b\\" } else { "" }); CLOSE.init(if *TMUX { "\x1b\\" } else { "" });

View file

@ -23,8 +23,6 @@ tokio = { workspace = true }
toml_edit = "0.22.21" toml_edit = "0.22.21"
[build-dependencies] [build-dependencies]
yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
# External build dependencies # External build dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
clap = { workspace = true } clap = { workspace = true }

View file

@ -27,13 +27,17 @@ impl From<bool> for Opt {
impl Manager { impl Manager {
pub fn peek(&mut self, opt: impl Into<Opt>) { pub fn peek(&mut self, opt: impl Into<Opt>) {
let Some(hovered) = self.hovered().cloned() else { let Some(hovered) = self.hovered().cloned() else {
return render!(self.active_mut().preview.reset()); return self.active_mut().preview.reset();
}; };
let mime = self.mimetype.get_owned(&hovered.url).unwrap_or_default();
let folder = self.active().hovered_folder().map(|f| (f.offset, f.cha)); let folder = self.active().hovered_folder().map(|f| (f.offset, f.cha));
if !self.active().preview.same_url(&hovered.url) { if !self.active().preview.same_url(&hovered.url) {
self.active_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default(); self.active_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default();
render!(self.active_mut().preview.reset()); }
if !self.active().preview.same_file(&hovered, &mime) {
self.active_mut().preview.reset();
} }
let opt = opt.into() as Opt; let opt = opt.into() as Opt;
@ -52,13 +56,8 @@ impl Manager {
if hovered.is_dir() { if hovered.is_dir() {
self.active_mut().preview.go_folder(hovered, folder.map(|f| f.1), opt.force); self.active_mut().preview.go_folder(hovered, folder.map(|f| f.1), opt.force);
return; } else {
} self.active_mut().preview.go(hovered, mime.into(), opt.force);
let mime = self.mimetype.get_owned(&hovered.url).unwrap_or_default();
if !mime.is_empty() {
// Wait till mimetype is resolved to avoid flickering
self.active_mut().preview.go(hovered, &mime, opt.force);
} }
} }
} }

View file

@ -16,7 +16,7 @@ impl From<Cmd> for Opt {
impl Manager { impl Manager {
pub fn seek(&mut self, opt: impl Into<Opt>) { pub fn seek(&mut self, opt: impl Into<Opt>) {
let Some(hovered) = self.hovered() else { let Some(hovered) = self.hovered() else {
return render!(self.active_mut().preview.reset()); return self.active_mut().preview.reset();
}; };
let mime = if hovered.is_dir() { let mime = if hovered.is_dir() {
@ -24,11 +24,11 @@ impl Manager {
} else if let Some(s) = self.mimetype.get(&hovered.url) { } else if let Some(s) = self.mimetype.get(&hovered.url) {
s s
} else { } else {
return render!(self.active_mut().preview.reset()); return self.active_mut().preview.reset();
}; };
let Some(previewer) = PLUGIN.previewer(&hovered.url, mime) else { let Some(previewer) = PLUGIN.previewer(&hovered.url, mime) else {
return render!(self.active_mut().preview.reset()); return self.active_mut().preview.reset();
}; };
let opt = opt.into() as Opt; let opt = opt.into() as Opt;

View file

@ -18,7 +18,7 @@ impl TryFrom<Cmd> for Opt {
impl Tab { impl Tab {
pub fn preview(&mut self, opt: impl TryInto<Opt>) { pub fn preview(&mut self, opt: impl TryInto<Opt>) {
let Some(hovered) = self.current.hovered().map(|h| &h.url) else { let Some(hovered) = self.current.hovered().map(|h| &h.url) else {
return render!(self.preview.reset()); return self.preview.reset();
}; };
let Ok(opt) = opt.try_into() else { let Ok(opt) = opt.try_into() else {

View file

@ -1,4 +1,4 @@
use std::time::Duration; use std::{borrow::Cow, ops::Not, time::Duration};
use tokio::{pin, task::JoinHandle}; use tokio::{pin, task::JoinHandle};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
@ -7,7 +7,7 @@ use yazi_adapter::ADAPTOR;
use yazi_config::PLUGIN; use yazi_config::PLUGIN;
use yazi_fs::Files; use yazi_fs::Files;
use yazi_plugin::{external::Highlighter, isolate, utils::PreviewLock}; use yazi_plugin::{external::Highlighter, isolate, utils::PreviewLock};
use yazi_shared::{MIME_DIR, fs::{Cha, File, FilesOp, Url}}; use yazi_shared::{MIME_DIR, fs::{Cha, File, FilesOp, Url}, render};
#[derive(Default)] #[derive(Default)]
pub struct Preview { pub struct Preview {
@ -19,32 +19,30 @@ pub struct Preview {
} }
impl Preview { impl Preview {
pub fn go(&mut self, file: File, mime: &str, force: bool) { pub fn go(&mut self, file: File, mime: Cow<'static, str>, force: bool) {
if !force && self.content_unchanged(&file.url, file.cha) { if mime.is_empty() {
return; // Wait till mimetype is resolved to avoid flickering
} else if !force && self.same_lock(&file, &mime) {
return; return;
} }
let Some(previewer) = PLUGIN.previewer(&file.url, mime) else { let Some(previewer) = PLUGIN.previewer(&file.url, &mime) else {
self.reset(); return self.reset();
return;
}; };
self.abort(); self.abort();
if previewer.sync { if previewer.sync {
isolate::peek_sync(&previewer.run, file, self.skip); isolate::peek_sync(&previewer.run, file, mime, self.skip);
} else { } else {
self.previewer_ct = Some(isolate::peek(&previewer.run, file, self.skip)); self.previewer_ct = Some(isolate::peek(&previewer.run, file, mime, self.skip));
} }
} }
pub fn go_folder(&mut self, file: File, dir: Option<Cha>, force: bool) { pub fn go_folder(&mut self, file: File, dir: Option<Cha>, force: bool) {
let (cha, cwd) = (file.cha, file.url_owned()); let cwd = self.same_file(&file, MIME_DIR).not().then(|| file.url_owned());
self.go(file, MIME_DIR, force); self.go(file, Cow::Borrowed(MIME_DIR), force);
if self.content_unchanged(&cwd, cha) {
return;
}
let Some(cwd) = cwd else { return };
self.folder_loader.take().map(|h| h.abort()); self.folder_loader.take().map(|h| h.abort());
self.folder_loader = Some(tokio::spawn(async move { self.folder_loader = Some(tokio::spawn(async move {
let Some(new) = Files::assert_stale(&cwd, dir.unwrap_or(Cha::dummy())).await else { let Some(new) = Files::assert_stale(&cwd, dir.unwrap_or(Cha::dummy())).await else {
@ -71,10 +69,10 @@ impl Preview {
} }
#[inline] #[inline]
pub fn reset(&mut self) -> bool { pub fn reset(&mut self) {
self.abort(); self.abort();
ADAPTOR.image_hide().ok(); ADAPTOR.image_hide().ok();
self.lock.take().is_some() render!(self.lock.take().is_some())
} }
#[inline] #[inline]
@ -84,15 +82,16 @@ impl Preview {
} }
#[inline] #[inline]
pub fn same_url(&self, url: &Url) -> bool { pub fn same_url(&self, url: &Url) -> bool { self.lock.as_ref().is_some_and(|l| *url == l.url) }
matches!(self.lock, Some(ref lock) if lock.url == *url)
#[inline]
pub fn same_file(&self, file: &File, mime: &str) -> bool {
self.same_url(&file.url)
&& self.lock.as_ref().is_some_and(|l| file.cha.hits(l.cha) && mime == l.mime)
} }
#[inline] #[inline]
fn content_unchanged(&self, url: &Url, cha: Cha) -> bool { pub fn same_lock(&self, file: &File, mime: &str) -> bool {
match &self.lock { self.same_file(file, mime) && self.lock.as_ref().is_some_and(|l| self.skip == l.skip)
Some(l) => *url == l.url && self.skip == l.skip && cha.hits(l.cha),
None => false,
}
} }
} }

View file

@ -4,14 +4,14 @@ use crossterm::{execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchroni
use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer}; use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer};
use scopeguard::defer; use scopeguard::defer;
use yazi_plugin::elements::COLLISION; use yazi_plugin::elements::COLLISION;
use yazi_shared::event::NEED_RENDER;
use crate::{app::App, lives::Lives, root::Root}; use crate::{app::App, lives::Lives, root::Root};
impl App { impl App {
pub(crate) fn render(&mut self) { pub(crate) fn render(&mut self) {
let Some(term) = &mut self.term else { NEED_RENDER.store(false, Ordering::Relaxed);
return; let Some(term) = &mut self.term else { return };
};
queue!(stderr(), BeginSynchronizedUpdate).ok(); queue!(stderr(), BeginSynchronizedUpdate).ok();
defer! { execute!(stderr(), EndSynchronizedUpdate).ok(); } defer! { execute!(stderr(), EndSynchronizedUpdate).ok(); }

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use mlua::{ExternalError, ExternalResult, HookTriggers, Table, TableExt}; use mlua::{ExternalError, ExternalResult, HookTriggers, Table, TableExt};
use tokio::{runtime::Handle, select}; use tokio::{runtime::Handle, select};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@ -8,7 +10,12 @@ use yazi_shared::{Layer, emit, event::Cmd};
use super::slim_lua; use super::slim_lua;
use crate::{LUA, Opt, OptCallback, bindings::{Cast, Window}, elements::Rect, file::File, loader::LOADER}; use crate::{LUA, Opt, OptCallback, bindings::{Cast, Window}, elements::Rect, file::File, loader::LOADER};
pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> CancellationToken { pub fn peek(
cmd: &Cmd,
file: yazi_shared::fs::File,
mime: Cow<'static, str>,
skip: usize,
) -> CancellationToken {
let ct = CancellationToken::new(); let ct = CancellationToken::new();
let name = cmd.name.to_owned(); let name = cmd.name.to_owned();
@ -31,6 +38,7 @@ pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> Cancellation
return Err("unloaded plugin".into_lua_err()); return Err("unloaded plugin".into_lua_err());
}; };
plugin.raw_set("file", File::cast(&lua, file)?)?; plugin.raw_set("file", File::cast(&lua, file)?)?;
plugin.raw_set("_mime", mime)?;
plugin.raw_set("skip", skip)?; plugin.raw_set("skip", skip)?;
plugin.raw_set("area", Rect::cast(&lua, LAYOUT.load().preview)?)?; plugin.raw_set("area", Rect::cast(&lua, LAYOUT.load().preview)?)?;
plugin.raw_set("window", Window::default())?; plugin.raw_set("window", Window::default())?;
@ -55,9 +63,10 @@ pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> Cancellation
ct ct
} }
pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) { pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>, skip: usize) {
let cb: OptCallback = Box::new(move |_, plugin| { let cb: OptCallback = Box::new(move |_, plugin| {
plugin.raw_set("file", File::cast(&LUA, file)?)?; plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("_mime", mime)?;
plugin.raw_set("skip", skip)?; plugin.raw_set("skip", skip)?;
plugin.raw_set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?; plugin.raw_set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?;
plugin.raw_set("window", Window::default())?; plugin.raw_set("window", Window::default())?;

View file

@ -6,8 +6,9 @@ use super::Utils;
use crate::{bindings::Window, cast_to_renderable, elements::{Paragraph, RectRef, Renderable, WRAP, WRAP_NO}, external::Highlighter, file::FileRef}; use crate::{bindings::Window, cast_to_renderable, elements::{Paragraph, RectRef, Renderable, WRAP, WRAP_NO}, external::Highlighter, file::FileRef};
pub struct PreviewLock { pub struct PreviewLock {
pub url: yazi_shared::fs::Url, pub url: yazi_shared::fs::Url,
pub cha: yazi_shared::fs::Cha, pub cha: yazi_shared::fs::Cha,
pub mime: String,
pub skip: usize, pub skip: usize,
pub window: Window, pub window: Window,
@ -18,10 +19,12 @@ impl<'a> TryFrom<Table<'a>> for PreviewLock {
type Error = mlua::Error; type Error = mlua::Error;
fn try_from(t: Table) -> Result<Self, Self::Error> { fn try_from(t: Table) -> Result<Self, Self::Error> {
let file: FileRef = t.raw_get("file")?; let file: FileRef = t.raw_get("file")?; // TODO: use `_file` instead of `file`
Ok(Self { Ok(Self {
cha: file.cha, url: file.url_owned(),
url: file.url_owned(), cha: file.cha,
mime: t.raw_get("_mime")?,
skip: t.raw_get("skip")?, skip: t.raw_get("skip")?,
window: t.raw_get("window")?, window: t.raw_get("window")?,
data: Default::default(), data: Default::default(),