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 yazi_config::YAZI;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::PeekOpt;
|
||||
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 folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha));
|
||||
|
||||
if !cx.tab().preview.same_url(&hovered.url) {
|
||||
cx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default();
|
||||
}
|
||||
if !cx.tab().preview.same_file(&hovered, &mime) {
|
||||
cx.tab_mut().preview.reset();
|
||||
let same_url = cx.tab().preview.same_url(&hovered.url);
|
||||
let same_file = cx.tab().preview.same_file(&hovered, &mime);
|
||||
|
||||
{
|
||||
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) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
if let Some(skip) = opt.skip {
|
||||
let preview = &mut cx.tab_mut().preview;
|
||||
if opt.upper_bound {
|
||||
preview.skip = preview.skip.min(skip);
|
||||
} else {
|
||||
preview.skip = skip;
|
||||
}
|
||||
}
|
||||
|
||||
let force = opt.force || opt.cycle.is_some();
|
||||
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 {
|
||||
cx.tab_mut().preview.go(hovered, mime, opt.force);
|
||||
cx.tab_mut().preview.go(hovered, mime, force);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,12 @@ impl Actor for Seek {
|
|||
let Some(previewer) = YAZI.plugin.previewer(&hovered.url, mime) else {
|
||||
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!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ impl Actor for UpdatePeeked {
|
|||
};
|
||||
|
||||
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!();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ keymap = [
|
|||
# Seeking
|
||||
{ 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 = [ "g", "p" ], run = "peek --cycle=next", desc = "Cycle to next previewer" },
|
||||
{ on = [ "g", "P" ], run = "peek --cycle=prev", desc = "Cycle to previous previewer" },
|
||||
|
||||
# Spotting
|
||||
{ on = "<Tab>", run = "spot", desc = "Spot hovered file" },
|
||||
|
|
|
|||
|
|
@ -124,8 +124,8 @@ preloaders = [
|
|||
previewers = [
|
||||
{ url = "*/", run = "folder" },
|
||||
# Code
|
||||
{ mime = "text/*", run = "code" },
|
||||
{ mime = "application/{mbox,javascript,wine-extension-ini}", run = "code" },
|
||||
{ mime = "text/*", run = [ "code", "file" ] },
|
||||
{ mime = "application/{mbox,javascript,wine-extension-ini}", run = [ "code", "file" ] },
|
||||
# JSON
|
||||
{ mime = "application/{json,ndjson}", run = "json" },
|
||||
# Image
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Deserializer, de};
|
||||
use yazi_shared::{MIME_DIR, event::Cmd, url::UrlBuf};
|
||||
|
||||
use crate::Pattern;
|
||||
|
|
@ -7,7 +7,8 @@ use crate::Pattern;
|
|||
pub struct Previewer {
|
||||
pub url: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
pub run: Cmd,
|
||||
#[serde(deserialize_with = "deserialize_run")]
|
||||
pub run: Vec<Cmd>,
|
||||
}
|
||||
|
||||
impl Previewer {
|
||||
|
|
@ -17,9 +18,47 @@ impl Previewer {
|
|||
|| 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]
|
||||
pub fn any_file(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_file()) }
|
||||
|
||||
#[inline]
|
||||
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 lock: Option<PreviewLock>,
|
||||
pub skip: usize,
|
||||
pub run: usize,
|
||||
|
||||
previewer_ct: Option<CancellationToken>,
|
||||
folder_loader: Option<JoinHandle<()>>,
|
||||
|
||||
pub lock_run: usize,
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
|
|
@ -32,8 +35,15 @@ impl Preview {
|
|||
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.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) {
|
||||
|
|
@ -76,6 +86,7 @@ impl Preview {
|
|||
pub fn reset(&mut self) {
|
||||
self.abort();
|
||||
ADAPTOR.get().image_hide().ok();
|
||||
self.lock_run = 0;
|
||||
render!(self.lock.take().is_some())
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +103,10 @@ impl Preview {
|
|||
}
|
||||
|
||||
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 only_if: Option<UrlCow<'static>>,
|
||||
pub upper_bound: bool,
|
||||
pub cycle: Option<i32>,
|
||||
}
|
||||
|
||||
impl From<CmdCow> for PeekOpt {
|
||||
|
|
@ -16,6 +17,7 @@ impl From<CmdCow> for PeekOpt {
|
|||
force: c.bool("force"),
|
||||
only_if: c.take_url("only-if"),
|
||||
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 {
|
||||
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 {
|
||||
inner: &'static yazi_config::plugin::Previewer,
|
||||
|
||||
v_cmd: Option<Value>,
|
||||
v_cmd: Option<Value>,
|
||||
v_cmds: Option<Value>,
|
||||
}
|
||||
|
||||
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 {
|
||||
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