mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feature: Multiple previewers to cycle through
This commit is contained in:
parent
30c0279570
commit
6d7227cd69
9 changed files with 134 additions and 27 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use yazi_config::YAZI;
|
||||||
use yazi_macro::succ;
|
use yazi_macro::succ;
|
||||||
use yazi_parser::mgr::PeekOpt;
|
use yazi_parser::mgr::PeekOpt;
|
||||||
use yazi_proxy::HIDER;
|
use yazi_proxy::HIDER;
|
||||||
|
|
@ -24,30 +25,55 @@ impl Actor for Peek {
|
||||||
let mime = cx.mgr.mimetype.by_file_owned(&hovered).unwrap_or_default();
|
let mime = cx.mgr.mimetype.by_file_owned(&hovered).unwrap_or_default();
|
||||||
let folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha));
|
let folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha));
|
||||||
|
|
||||||
if !cx.tab().preview.same_url(&hovered.url) {
|
let same_url = cx.tab().preview.same_url(&hovered.url);
|
||||||
cx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default();
|
let same_file = cx.tab().preview.same_file(&hovered, &mime);
|
||||||
}
|
|
||||||
if !cx.tab().preview.same_file(&hovered, &mime) {
|
{
|
||||||
cx.tab_mut().preview.reset();
|
let preview = &mut cx.tab_mut().preview;
|
||||||
|
if !same_url {
|
||||||
|
preview.skip = folder.map(|f| f.0).unwrap_or_default();
|
||||||
|
preview.run = 0;
|
||||||
|
}
|
||||||
|
if !same_file {
|
||||||
|
preview.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(step) = opt.cycle {
|
||||||
|
if step == 0 {
|
||||||
|
preview.run = 0;
|
||||||
|
} else if let Some(previewer) = YAZI.plugin.previewer(&hovered.url, &mime) {
|
||||||
|
let len = previewer.len();
|
||||||
|
if len == 0 {
|
||||||
|
preview.run = 0;
|
||||||
|
} else {
|
||||||
|
let len_isize = len as isize;
|
||||||
|
let current = (preview.run % len) as isize;
|
||||||
|
let next = (current + step as isize).rem_euclid(len_isize);
|
||||||
|
preview.run = next as usize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
preview.run = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(skip) = opt.skip {
|
||||||
|
if opt.upper_bound {
|
||||||
|
preview.skip = preview.skip.min(skip);
|
||||||
|
} else {
|
||||||
|
preview.skip = skip;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(opt.only_if, Some(u) if u != hovered.url) {
|
if matches!(opt.only_if, Some(u) if u != hovered.url) {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(skip) = opt.skip {
|
let force = opt.force || opt.cycle.is_some();
|
||||||
let preview = &mut cx.tab_mut().preview;
|
|
||||||
if opt.upper_bound {
|
|
||||||
preview.skip = preview.skip.min(skip);
|
|
||||||
} else {
|
|
||||||
preview.skip = skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hovered.is_dir() {
|
if hovered.is_dir() {
|
||||||
cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), opt.force);
|
cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), force);
|
||||||
} else {
|
} else {
|
||||||
cx.tab_mut().preview.go(hovered, mime, opt.force);
|
cx.tab_mut().preview.go(hovered, mime, force);
|
||||||
}
|
}
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,12 @@ impl Actor for Seek {
|
||||||
let Some(previewer) = YAZI.plugin.previewer(&hovered.url, mime) else {
|
let Some(previewer) = YAZI.plugin.previewer(&hovered.url, mime) else {
|
||||||
succ!(cx.tab_mut().preview.reset());
|
succ!(cx.tab_mut().preview.reset());
|
||||||
};
|
};
|
||||||
|
let run_idx = cx.tab().preview.run;
|
||||||
|
let Some(cmd) = previewer.cmd(run_idx) else {
|
||||||
|
succ!(cx.tab_mut().preview.reset());
|
||||||
|
};
|
||||||
|
|
||||||
isolate::seek_sync(&previewer.run, hovered.clone(), opt.units);
|
isolate::seek_sync(cmd, hovered.clone(), opt.units);
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ impl Actor for UpdatePeeked {
|
||||||
};
|
};
|
||||||
|
|
||||||
if opt.lock.url == *hovered {
|
if opt.lock.url == *hovered {
|
||||||
cx.tab_mut().preview.lock = Some(opt.lock);
|
let preview = &mut cx.tab_mut().preview;
|
||||||
|
preview.lock_run = preview.run;
|
||||||
|
preview.lock = Some(opt.lock);
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ keymap = [
|
||||||
# Seeking
|
# Seeking
|
||||||
{ on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" },
|
{ on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" },
|
||||||
{ on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" },
|
{ on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" },
|
||||||
|
{ on = [ "g", "p" ], run = "peek --cycle=next", desc = "Cycle to next previewer" },
|
||||||
|
{ on = [ "g", "P" ], run = "peek --cycle=prev", desc = "Cycle to previous previewer" },
|
||||||
|
|
||||||
# Spotting
|
# Spotting
|
||||||
{ on = "<Tab>", run = "spot", desc = "Spot hovered file" },
|
{ on = "<Tab>", run = "spot", desc = "Spot hovered file" },
|
||||||
|
|
|
||||||
|
|
@ -124,8 +124,8 @@ preloaders = [
|
||||||
previewers = [
|
previewers = [
|
||||||
{ url = "*/", run = "folder" },
|
{ url = "*/", run = "folder" },
|
||||||
# Code
|
# Code
|
||||||
{ mime = "text/*", run = "code" },
|
{ mime = "text/*", run = [ "code", "file" ] },
|
||||||
{ mime = "application/{mbox,javascript,wine-extension-ini}", run = "code" },
|
{ mime = "application/{mbox,javascript,wine-extension-ini}", run = [ "code", "file" ] },
|
||||||
# JSON
|
# JSON
|
||||||
{ mime = "application/{json,ndjson}", run = "json" },
|
{ mime = "application/{json,ndjson}", run = "json" },
|
||||||
# Image
|
# Image
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Deserializer, de};
|
||||||
use yazi_shared::{MIME_DIR, event::Cmd, url::UrlBuf};
|
use yazi_shared::{MIME_DIR, event::Cmd, url::UrlBuf};
|
||||||
|
|
||||||
use crate::Pattern;
|
use crate::Pattern;
|
||||||
|
|
@ -7,7 +7,8 @@ use crate::Pattern;
|
||||||
pub struct Previewer {
|
pub struct Previewer {
|
||||||
pub url: Option<Pattern>,
|
pub url: Option<Pattern>,
|
||||||
pub mime: Option<Pattern>,
|
pub mime: Option<Pattern>,
|
||||||
pub run: Cmd,
|
#[serde(deserialize_with = "deserialize_run")]
|
||||||
|
pub run: Vec<Cmd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Previewer {
|
impl Previewer {
|
||||||
|
|
@ -17,9 +18,47 @@ impl Previewer {
|
||||||
|| self.url.as_ref().is_some_and(|p| p.match_url(url, mime == MIME_DIR))
|
|| self.url.as_ref().is_some_and(|p| p.match_url(url, mime == MIME_DIR))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn len(&self) -> usize { self.run.len() }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cmd(&self, index: usize) -> Option<&Cmd> {
|
||||||
|
if self.run.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.run.get(index).or_else(|| self.run.first())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn any_file(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_file()) }
|
pub fn any_file(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_file()) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn any_dir(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_dir()) }
|
pub fn any_dir(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_dir()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum RunField {
|
||||||
|
One(String),
|
||||||
|
Many(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_run<'de, D>(deserializer: D) -> Result<Vec<Cmd>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let field = RunField::deserialize(deserializer)?;
|
||||||
|
let items: Vec<String> = match field {
|
||||||
|
RunField::One(s) => vec![s],
|
||||||
|
RunField::Many(list) => list,
|
||||||
|
};
|
||||||
|
|
||||||
|
if items.is_empty() {
|
||||||
|
return Err(de::Error::custom("previewer.run must not be empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.parse::<Cmd>().map_err(de::Error::custom))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,12 @@ use yazi_shared::{MIME_DIR, pool::{InternStr, Symbol}, url::UrlBuf};
|
||||||
pub struct Preview {
|
pub struct Preview {
|
||||||
pub lock: Option<PreviewLock>,
|
pub lock: Option<PreviewLock>,
|
||||||
pub skip: usize,
|
pub skip: usize,
|
||||||
|
pub run: usize,
|
||||||
|
|
||||||
previewer_ct: Option<CancellationToken>,
|
previewer_ct: Option<CancellationToken>,
|
||||||
folder_loader: Option<JoinHandle<()>>,
|
folder_loader: Option<JoinHandle<()>>,
|
||||||
|
|
||||||
|
pub lock_run: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Preview {
|
impl Preview {
|
||||||
|
|
@ -32,8 +35,15 @@ impl Preview {
|
||||||
return self.reset();
|
return self.reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if self.run >= previewer.len() {
|
||||||
|
self.run = 0;
|
||||||
|
}
|
||||||
|
let Some(cmd) = previewer.cmd(self.run) else {
|
||||||
|
return self.reset();
|
||||||
|
};
|
||||||
|
|
||||||
self.abort();
|
self.abort();
|
||||||
self.previewer_ct = isolate::peek(&previewer.run, file, mime, self.skip);
|
self.previewer_ct = isolate::peek(cmd, 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) {
|
||||||
|
|
@ -76,6 +86,7 @@ impl Preview {
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.abort();
|
self.abort();
|
||||||
ADAPTOR.get().image_hide().ok();
|
ADAPTOR.get().image_hide().ok();
|
||||||
|
self.lock_run = 0;
|
||||||
render!(self.lock.take().is_some())
|
render!(self.lock.take().is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +103,10 @@ impl Preview {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn same_lock(&self, file: &File, mime: &str) -> bool {
|
pub fn same_lock(&self, file: &File, mime: &str) -> bool {
|
||||||
self.same_file(file, mime) && matches!(&self.lock, Some(l) if l.skip == self.skip)
|
if let Some(lock) = &self.lock {
|
||||||
|
self.same_file(file, mime) && lock.skip == self.skip && self.lock_run == self.run
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub struct PeekOpt {
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
pub only_if: Option<UrlCow<'static>>,
|
pub only_if: Option<UrlCow<'static>>,
|
||||||
pub upper_bound: bool,
|
pub upper_bound: bool,
|
||||||
|
pub cycle: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CmdCow> for PeekOpt {
|
impl From<CmdCow> for PeekOpt {
|
||||||
|
|
@ -16,6 +17,7 @@ impl From<CmdCow> for PeekOpt {
|
||||||
force: c.bool("force"),
|
force: c.bool("force"),
|
||||||
only_if: c.take_url("only-if"),
|
only_if: c.take_url("only-if"),
|
||||||
upper_bound: c.bool("upper-bound"),
|
upper_bound: c.bool("upper-bound"),
|
||||||
|
cycle: c.take_str("cycle").and_then(|s| parse_cycle(s.as_ref())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -31,3 +33,12 @@ impl FromLua for PeekOpt {
|
||||||
impl IntoLua for PeekOpt {
|
impl IntoLua for PeekOpt {
|
||||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_cycle(value: &str) -> Option<i32> {
|
||||||
|
match value.to_ascii_lowercase().as_str() {
|
||||||
|
"next" | "forward" | "+" => Some(1),
|
||||||
|
"prev" | "previous" | "back" | "-" => Some(-1),
|
||||||
|
"reset" | "first" | "start" | "0" => Some(0),
|
||||||
|
_ => value.parse().ok(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,15 +98,23 @@ impl UserData for Preloader {
|
||||||
struct Previewer {
|
struct Previewer {
|
||||||
inner: &'static yazi_config::plugin::Previewer,
|
inner: &'static yazi_config::plugin::Previewer,
|
||||||
|
|
||||||
v_cmd: Option<Value>,
|
v_cmd: Option<Value>,
|
||||||
|
v_cmds: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Previewer {
|
impl Previewer {
|
||||||
pub fn new(inner: &'static yazi_config::plugin::Previewer) -> Self { Self { inner, v_cmd: None } }
|
pub fn new(inner: &'static yazi_config::plugin::Previewer) -> Self {
|
||||||
|
Self { inner, v_cmd: None, v_cmds: None }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData for Previewer {
|
impl UserData for Previewer {
|
||||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||||
cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref()));
|
cached_field!(fields, cmd, |lua, me| {
|
||||||
|
lua.create_string(me.inner.cmd(0).map(|cmd| cmd.name.as_ref()).unwrap_or(""))
|
||||||
|
});
|
||||||
|
cached_field!(fields, cmds, |lua, me| {
|
||||||
|
lua.create_sequence_from(me.inner.run.iter().map(|cmd| cmd.name.as_ref()))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue