feat: expand the types supported by the event system (#923)

This commit is contained in:
三咲雅 · Misaki Masa 2024-04-19 13:45:01 +08:00 committed by GitHub
parent 2975b999bf
commit 09bc9aa371
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
99 changed files with 605 additions and 502 deletions

View file

@ -15,9 +15,7 @@ pub struct Control {
impl Control { impl Control {
#[inline] #[inline]
pub fn to_seq(&self) -> VecDeque<Cmd> { pub fn to_seq(&self) -> VecDeque<Cmd> { self.run.iter().map(|c| c.shallow_clone()).collect() }
self.run.iter().map(|e| e.clone_without_data()).collect()
}
} }
impl Control { impl Control {

View file

@ -1,8 +1,8 @@
use std::fmt; use std::{fmt, mem};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use serde::{de::{self, Visitor}, Deserializer}; use serde::{de::{self, Visitor}, Deserializer};
use yazi_shared::event::Cmd; use yazi_shared::event::{Cmd, Data};
pub(super) fn run_deserialize<'de, D>(deserializer: D) -> Result<Vec<Cmd>, D::Error> pub(super) fn run_deserialize<'de, D>(deserializer: D) -> Result<Vec<Cmd>, D::Error>
where where
@ -10,21 +10,28 @@ where
{ {
struct RunVisitor; struct RunVisitor;
#[allow(clippy::explicit_counter_loop)]
fn parse(s: &str) -> Result<Cmd> { fn parse(s: &str) -> Result<Cmd> {
let s = shell_words::split(s)?; let mut args = shell_words::split(s)?;
if s.is_empty() { let mut cmd = Cmd { name: mem::take(&mut args[0]), ..Default::default() };
bail!("`run` cannot be empty");
}
let mut cmd = Cmd { name: s[0].clone(), ..Default::default() }; let mut i = 0usize;
for arg in s.into_iter().skip(1) { for arg in args.into_iter().skip(1) {
if arg.starts_with("--") { let Some(arg) = arg.strip_prefix("--") else {
let mut arg = arg.splitn(2, '='); cmd.args.insert(i.to_string(), Data::String(arg));
let key = arg.next().unwrap().trim_start_matches('-'); i += 1;
let val = arg.next().unwrap_or("").to_string(); continue;
cmd.named.insert(key.to_string(), val); };
let mut parts = arg.splitn(2, '=');
let Some(key) = parts.next().map(|s| s.to_owned()) else {
bail!("invalid argument: {arg}");
};
if let Some(val) = parts.next() {
cmd.args.insert(key, Data::String(val.to_owned()));
} else { } else {
cmd.args.push(arg); cmd.args.insert(key, Data::Boolean(true));
} }
} }
Ok(cmd) Ok(cmd)

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0) } Self { step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0) }
} }
} }

View file

@ -8,7 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { submit: c.named.contains_key("submit") } } fn from(c: Cmd) -> Self { Self { submit: c.get_bool("submit") } }
} }
impl Completion { impl Completion {

View file

@ -16,10 +16,10 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
cache: mem::take(&mut c.args), cache: c.take_any("cache").unwrap_or_default(),
cache_name: c.take_name("cache-name").unwrap_or_default(), cache_name: c.take_str("cache-name").unwrap_or_default(),
word: c.take_name("word").unwrap_or_default(), word: c.take_str("word").unwrap_or_default(),
ticket: c.take_name("ticket").and_then(|v| v.parse().ok()).unwrap_or(0), ticket: c.take_str("ticket").and_then(|v| v.parse().ok()).unwrap_or(0),
} }
} }
} }
@ -63,7 +63,7 @@ impl Completion {
} }
if !opt.cache.is_empty() { if !opt.cache.is_empty() {
self.caches.insert(opt.cache_name.to_owned(), opt.cache.clone()); self.caches.insert(opt.cache_name.to_owned(), opt.cache);
} }
let Some(cache) = self.caches.get(&opt.cache_name) else { let Some(cache) = self.caches.get(&opt.cache_name) else {
return; return;

View file

@ -19,8 +19,8 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
word: c.take_first().unwrap_or_default(), word: c.take_first_str().unwrap_or_default(),
ticket: c.take_name("ticket").and_then(|s| s.parse().ok()).unwrap_or(0), ticket: c.take_str("ticket").and_then(|s| s.parse().ok()).unwrap_or(0),
} }
} }
} }
@ -57,7 +57,8 @@ impl Completion {
if !cache.is_empty() { if !cache.is_empty() {
emit!(Call( emit!(Call(
Cmd::args("show", cache) Cmd::new("show")
.with_any("cache", cache)
.with("cache-name", parent) .with("cache-name", parent)
.with("word", child) .with("word", child)
.with("ticket", ticket), .with("ticket", ticket),

View file

@ -45,7 +45,7 @@ pub enum FilterCase {
impl From<&Cmd> for FilterCase { impl From<&Cmd> for FilterCase {
fn from(c: &Cmd) -> Self { fn from(c: &Cmd) -> Self {
match (c.named.contains_key("smart"), c.named.contains_key("insensitive")) { match (c.get_bool("smart"), c.get_bool("insensitive")) {
(true, _) => Self::Smart, (true, _) => Self::Smart,
(_, false) => Self::Sensitive, (_, false) => Self::Sensitive,
(_, true) => Self::Insensitive, (_, true) => Self::Insensitive,

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0) } Self { step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0) }
} }
} }
impl From<isize> for Opt { impl From<isize> for Opt {

View file

@ -7,7 +7,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { under: c.named.contains_key("under") } } fn from(c: Cmd) -> Self { Self { under: c.get_bool("under") } }
} }
impl From<bool> for Opt { impl From<bool> for Opt {
fn from(under: bool) -> Self { Self { under } } fn from(under: bool) -> Self { Self { under } }

View file

@ -8,7 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { submit: c.named.contains_key("submit") } } fn from(c: Cmd) -> Self { Self { submit: c.get_bool("submit") } }
} }
impl From<bool> for Opt { impl From<bool> for Opt {
fn from(submit: bool) -> Self { Self { submit } } fn from(submit: bool) -> Self { Self { submit } }

View file

@ -18,8 +18,8 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
word: c.take_first().unwrap_or_default(), word: c.take_first_str().unwrap_or_default(),
ticket: c.take_name("ticket").and_then(|s| s.parse().ok()).unwrap_or(0), ticket: c.take_str("ticket").and_then(|s| s.parse().ok()).unwrap_or(0),
} }
} }
} }

View file

@ -8,9 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { fn from(c: Cmd) -> Self { Self { cut: c.get_bool("cut"), insert: c.get_bool("insert") } }
Self { cut: c.named.contains_key("cut"), insert: c.named.contains_key("insert") }
}
} }
impl Input { impl Input {

View file

@ -7,7 +7,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { end_of_word: c.named.contains_key("end-of-word") } } fn from(c: Cmd) -> Self { Self { end_of_word: c.get_bool("end-of-word") } }
} }
impl Input { impl Input {

View file

@ -7,7 +7,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { append: c.named.contains_key("append") } } fn from(c: Cmd) -> Self { Self { append: c.get_bool("append") } }
} }
impl From<bool> for Opt { impl From<bool> for Opt {
fn from(append: bool) -> Self { Self { append } } fn from(append: bool) -> Self { Self { append } }

View file

@ -9,7 +9,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { Self { kind: c.take_first().unwrap_or_default() } } fn from(mut c: Cmd) -> Self { Self { kind: c.take_first_str().unwrap_or_default() } }
} }
impl Input { impl Input {

View file

@ -11,8 +11,8 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0), step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0),
in_operating: c.named.contains_key("in-operating"), in_operating: c.get_bool("in-operating"),
} }
} }
} }

View file

@ -7,7 +7,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { before: c.named.contains_key("before") } } fn from(c: Cmd) -> Self { Self { before: c.get_bool("before") } }
} }
impl Input { impl Input {

View file

@ -1,10 +1,24 @@
use yazi_proxy::options::InputOpt; use tokio::sync::mpsc;
use yazi_shared::render; use yazi_config::popup::InputCfg;
use yazi_shared::{event::Cmd, render, InputError};
use crate::input::Input; use crate::input::Input;
pub struct Opt {
cfg: InputCfg,
tx: mpsc::UnboundedSender<Result<String, InputError>>,
}
impl TryFrom<Cmd> for Opt {
type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { cfg: c.take_any("cfg").ok_or(())?, tx: c.take_any("tx").ok_or(())? })
}
}
impl Input { impl Input {
pub fn show(&mut self, opt: impl TryInto<InputOpt>) { pub fn show(&mut self, opt: impl TryInto<Opt>) {
let Ok(opt) = opt.try_into() else { return }; let Ok(opt) = opt.try_into() else { return };
self.close(false); self.close(false);

View file

@ -12,7 +12,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { force: c.named.contains_key("force") } } fn from(c: Cmd) -> Self { Self { force: c.get_bool("force") } }
} }
impl Manager { impl Manager {

View file

@ -9,7 +9,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { Self { url: c.take_first().map(Url::from) } } fn from(mut c: Cmd) -> Self { Self { url: c.take_first_str().map(Url::from) } }
} }
impl From<Option<Url>> for Opt { impl From<Option<Url>> for Opt {
fn from(url: Option<Url>) -> Self { Self { url } } fn from(url: Option<Url>) -> Self { Self { url } }

View file

@ -8,9 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { fn from(c: Cmd) -> Self { Self { relative: c.get_bool("relative"), force: c.get_bool("force") } }
Self { relative: c.named.contains_key("relative"), force: c.named.contains_key("force") }
}
} }
impl Manager { impl Manager {

View file

@ -17,10 +17,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { fn from(c: Cmd) -> Self {
Self { Self { interactive: c.get_bool("interactive"), hovered: c.get_bool("hovered") }
interactive: c.named.contains_key("interactive"),
hovered: c.named.contains_key("hovered"),
}
} }
} }

View file

@ -8,9 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { fn from(c: Cmd) -> Self { Self { force: c.get_bool("force"), follow: c.get_bool("follow") } }
Self { force: c.named.contains_key("force"), follow: c.named.contains_key("follow") }
}
} }
impl Manager { impl Manager {

View file

@ -13,10 +13,10 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
skip: c.take_first().and_then(|s| s.parse().ok()), skip: c.take_first_str().and_then(|s| s.parse().ok()),
force: c.named.contains_key("force"), force: c.get_bool("force"),
only_if: c.take_name("only-if").map(Url::from), only_if: c.take_str("only-if").map(Url::from),
upper_bound: c.named.contains_key("upper-bound"), upper_bound: c.get_bool("upper-bound"),
} }
} }
} }

View file

@ -12,7 +12,7 @@ impl From<()> for Opt {
fn from(_: ()) -> Self { Self::default() } fn from(_: ()) -> Self { Self::default() }
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { no_cwd_file: c.named.contains_key("no-cwd-file") } } fn from(c: Cmd) -> Self { Self { no_cwd_file: c.get_bool("no-cwd-file") } }
} }
impl Manager { impl Manager {

View file

@ -13,9 +13,9 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
force: c.named.contains_key("force"), force: c.get_bool("force"),
permanently: c.named.contains_key("permanently"), permanently: c.get_bool("permanently"),
targets: c.take_data().unwrap_or_default(), targets: c.take_any("targets").unwrap_or_default(),
} }
} }
} }

View file

@ -18,9 +18,9 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
force: c.named.contains_key("force"), force: c.get_bool("force"),
empty: c.take_name("empty").unwrap_or_default(), empty: c.take_str("empty").unwrap_or_default(),
cursor: c.take_name("cursor").unwrap_or_default(), cursor: c.take_str("cursor").unwrap_or_default(),
} }
} }
} }

View file

@ -11,7 +11,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { units: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0) } Self { units: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0) }
} }
} }

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { idx: c.take_first().and_then(|i| i.parse().ok()).unwrap_or(0) } Self { idx: c.take_first_str().and_then(|i| i.parse().ok()).unwrap_or(0) }
} }
} }

View file

@ -13,11 +13,11 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
if c.named.contains_key("current") { if c.get_bool("current") {
Self { url: Default::default(), current: true } Self { url: Default::default(), current: true }
} else { } else {
Self { Self {
url: c.take_first().map_or_else(|| Url::from(&BOOT.cwd), Url::from), url: c.take_first_str().map_or_else(|| Url::from(&BOOT.cwd), Url::from),
current: false, current: false,
} }
} }

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0) } Self { step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0) }
} }
} }

View file

@ -10,8 +10,8 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0), step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0),
relative: c.named.contains_key("relative"), relative: c.get_bool("relative"),
} }
} }
} }

View file

@ -12,7 +12,9 @@ pub struct Opt {
impl TryFrom<Cmd> for Opt { impl TryFrom<Cmd> for Opt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { Ok(Self { op: c.take_data().ok_or(())? }) } fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { op: c.take_any("op").ok_or(())? })
}
} }
impl Manager { impl Manager {

View file

@ -1,19 +1,18 @@
use std::collections::HashMap; use std::collections::HashMap;
use yazi_dds::ValueSendable;
use yazi_shared::{event::Cmd, fs::Url, render}; use yazi_shared::{event::Cmd, fs::Url, render};
use crate::{manager::{Manager, LINKED}, tasks::Tasks}; use crate::{manager::{Manager, LINKED}, tasks::Tasks};
pub struct Opt { pub struct Opt {
data: ValueSendable, updates: HashMap<String, String>,
} }
impl TryFrom<Cmd> for Opt { impl TryFrom<Cmd> for Opt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { data: c.take_data().ok_or(())? }) Ok(Self { updates: c.take_data("updates").ok_or(())?.into_table_string() })
} }
} }
@ -25,8 +24,7 @@ impl Manager {
let linked = LINKED.read(); let linked = LINKED.read();
let updates = opt let updates = opt
.data .updates
.into_table_string()
.into_iter() .into_iter()
.map(|(url, mime)| (Url::from(url), mime)) .map(|(url, mime)| (Url::from(url), mime))
.filter(|(url, mime)| self.mimetype.get(url) != Some(mime)) .filter(|(url, mime)| self.mimetype.get(url) != Some(mime))

View file

@ -11,8 +11,8 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
page: c.take_first().and_then(|s| s.parse().ok()), page: c.take_first_str().and_then(|s| s.parse().ok()),
only_if: c.take_name("only-if").map(Url::from), only_if: c.take_str("only-if").map(Url::from),
} }
} }
} }

View file

@ -8,7 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { cut: c.named.contains_key("cut") } } fn from(c: Cmd) -> Self { Self { cut: c.get_bool("cut") } }
} }
impl Manager { impl Manager {

View file

@ -13,7 +13,7 @@ impl TryFrom<Cmd> for Opt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
let interval = c.take_first().and_then(|s| s.parse::<f64>().ok()).ok_or(())?; let interval = c.take_first_str().and_then(|s| s.parse::<f64>().ok()).ok_or(())?;
if interval < 0.0 { if interval < 0.0 {
return Err(()); return Err(());
} }

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0) } Self { step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0) }
} }
} }

View file

@ -8,7 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { submit: c.named.contains_key("submit") } } fn from(c: Cmd) -> Self { Self { submit: c.get_bool("submit") } }
} }
impl From<bool> for Opt { impl From<bool> for Opt {
fn from(submit: bool) -> Self { Self { submit } } fn from(submit: bool) -> Self { Self { submit } }

View file

@ -1,10 +1,24 @@
use yazi_proxy::options::SelectOpt; use tokio::sync::oneshot;
use yazi_shared::render; use yazi_config::popup::SelectCfg;
use yazi_shared::{event::Cmd, render};
use crate::select::Select; use crate::select::Select;
pub struct Opt {
cfg: SelectCfg,
tx: oneshot::Sender<anyhow::Result<usize>>,
}
impl TryFrom<Cmd> for Opt {
type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { cfg: c.take_any("cfg").ok_or(())?, tx: c.take_any("tx").ok_or(())? })
}
}
impl Select { impl Select {
pub fn show(&mut self, opt: impl TryInto<SelectOpt>) { pub fn show(&mut self, opt: impl TryInto<Opt>) {
let Ok(opt) = opt.try_into() else { let Ok(opt) = opt.try_into() else {
return; return;
}; };

View file

@ -10,7 +10,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or_default() } Self { step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or_default() }
} }
} }

View file

@ -16,12 +16,12 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
let mut target = Url::from(c.take_first().unwrap_or_default()); let mut target = Url::from(c.take_first_str().unwrap_or_default());
if target.is_regular() { if target.is_regular() {
target.set_path(expand_path(&target)) target.set_path(expand_path(&target))
} }
Self { target, interactive: c.named.contains_key("interactive") } Self { target, interactive: c.get_bool("interactive") }
} }
} }
impl From<Url> for Opt { impl From<Url> for Opt {

View file

@ -9,7 +9,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { Self { type_: c.take_first().unwrap_or_default() } } fn from(mut c: Cmd) -> Self { Self { type_: c.take_first_str().unwrap_or_default() } }
} }
impl Tab { impl Tab {

View file

@ -16,14 +16,16 @@ bitflags! {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { fn from(c: Cmd) -> Self {
c.named.iter().fold(Opt::empty(), |acc, (k, _)| match k.as_str() { c.args.iter().fold(Opt::empty(), |acc, (k, v)| {
"all" => Self::all(), match (k.as_str(), v.as_bool().unwrap_or(false)) {
"find" => acc | Self::FIND, ("all", true) => Self::all(),
"visual" => acc | Self::VISUAL, ("find", true) => acc | Self::FIND,
"select" => acc | Self::SELECT, ("visual", true) => acc | Self::VISUAL,
"filter" => acc | Self::FILTER, ("select", true) => acc | Self::SELECT,
"search" => acc | Self::SEARCH, ("filter", true) => acc | Self::FILTER,
_ => acc, ("search", true) => acc | Self::SEARCH,
_ => acc,
}
}) })
} }
} }

View file

@ -18,9 +18,9 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
query: c.take_first().unwrap_or_default(), query: c.take_first_str().unwrap_or_default(),
case: FilterCase::from(&c), case: FilterCase::from(&c),
done: c.named.contains_key("done"), done: c.get_bool("done"),
} }
} }
} }

View file

@ -16,11 +16,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self { query: c.take_first_str(), prev: c.get_bool("previous"), case: FilterCase::from(&c) }
query: c.take_first(),
prev: c.named.contains_key("previous"),
case: FilterCase::from(&c),
}
} }
} }
@ -29,7 +25,7 @@ pub struct ArrowOpt {
} }
impl From<Cmd> for ArrowOpt { impl From<Cmd> for ArrowOpt {
fn from(c: Cmd) -> Self { Self { prev: c.named.contains_key("previous") } } fn from(c: Cmd) -> Self { Self { prev: c.get_bool("previous") } }
} }
impl Tab { impl Tab {

View file

@ -4,8 +4,8 @@ use yazi_shared::event::Cmd;
use crate::tab::Tab; use crate::tab::Tab;
impl Tab { impl Tab {
pub fn hidden(&mut self, c: Cmd) { pub fn hidden(&mut self, mut c: Cmd) {
self.conf.show_hidden = match c.args.first().map(|s| s.as_str()) { self.conf.show_hidden = match c.take_first_str().as_deref() {
Some("show") => true, Some("show") => true,
Some("hide") => false, Some("hide") => false,
_ => !self.conf.show_hidden, _ => !self.conf.show_hidden,

View file

@ -17,9 +17,9 @@ pub enum OptType {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
type_: match c.args.first().map(|s| s.as_str()) { type_: match c.take_first_str().as_deref() {
Some("fzf") => OptType::Fzf, Some("fzf") => OptType::Fzf,
Some("zoxide") => OptType::Zoxide, Some("zoxide") => OptType::Zoxide,
_ => OptType::None, _ => OptType::None,

View file

@ -5,7 +5,7 @@ use crate::tab::Tab;
impl Tab { impl Tab {
pub fn linemode(&mut self, mut c: Cmd) { pub fn linemode(&mut self, mut c: Cmd) {
render!(self.conf.patch(|new| { render!(self.conf.patch(|new| {
let Some(mode) = c.take_first() else { let Some(mode) = c.take_first_str() else {
return; return;
}; };
if !mode.is_empty() && mode.len() <= 20 { if !mode.is_empty() && mode.len() <= 20 {

View file

@ -11,7 +11,7 @@ impl TryFrom<Cmd> for Opt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { lock: c.take_data().ok_or(())? }) Ok(Self { lock: c.take_any("lock").ok_or(())? })
} }
} }

View file

@ -9,7 +9,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
let mut target = Url::from(c.take_first().unwrap_or_default()); let mut target = Url::from(c.take_first_str().unwrap_or_default());
if target.is_regular() { if target.is_regular() {
target.set_path(expand_path(&target)) target.set_path(expand_path(&target))
} }

View file

@ -42,7 +42,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { Self { type_: c.take_first().unwrap_or_default().into() } } fn from(mut c: Cmd) -> Self { Self { type_: c.take_first_str().unwrap_or_default().into() } }
} }
impl Tab { impl Tab {

View file

@ -13,8 +13,8 @@ pub struct Opt<'a> {
impl<'a> From<Cmd> for Opt<'a> { impl<'a> From<Cmd> for Opt<'a> {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
url: c.take_name("url").map(|s| Cow::Owned(Url::from(s))), url: c.take_str("url").map(|s| Cow::Owned(Url::from(s))),
state: match c.named.get("state").map(|s| s.as_str()) { state: match c.take_str("state").as_deref() {
Some("true") => Some(true), Some("true") => Some(true),
Some("false") => Some(false), Some("false") => Some(false),
_ => None, _ => None,

View file

@ -8,9 +8,9 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
state: match c.named.get("state").map(|s| s.as_str()) { state: match c.take_str("state").as_deref() {
Some("true") => Some(true), Some("true") => Some(true),
Some("false") => Some(false), Some("false") => Some(false),
_ => None, _ => None,

View file

@ -16,10 +16,10 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { Self {
run: c.take_first().unwrap_or_default(), run: c.take_first_str().unwrap_or_default(),
block: c.named.contains_key("block"), block: c.get_bool("block"),
orphan: c.named.contains_key("orphan"), orphan: c.get_bool("orphan"),
confirm: c.named.contains_key("confirm"), confirm: c.get_bool("confirm"),
} }
} }
} }

View file

@ -7,13 +7,13 @@ use yazi_shared::event::Cmd;
use crate::{tab::Tab, tasks::Tasks}; use crate::{tab::Tab, tasks::Tasks};
impl Tab { impl Tab {
pub fn sort(&mut self, c: Cmd, tasks: &Tasks) { pub fn sort(&mut self, mut c: Cmd, tasks: &Tasks) {
if let Some(by) = c.args.first() { if let Some(by) = c.take_first_str() {
self.conf.sort_by = SortBy::from_str(by).unwrap_or_default(); self.conf.sort_by = SortBy::from_str(&by).unwrap_or_default();
} }
self.conf.sort_sensitive = c.named.contains_key("sensitive"); self.conf.sort_sensitive = c.get_bool("sensitive");
self.conf.sort_reverse = c.named.contains_key("reverse"); self.conf.sort_reverse = c.get_bool("reverse");
self.conf.sort_dir_first = c.named.contains_key("dir-first"); self.conf.sort_dir_first = c.get_bool("dir-first");
self.apply_files_attrs(); self.apply_files_attrs();
ManagerProxy::update_paged(); ManagerProxy::update_paged();

View file

@ -9,7 +9,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self { Self { unset: c.named.contains_key("unset") } } fn from(c: Cmd) -> Self { Self { unset: c.get_bool("unset") } }
} }
impl Tab { impl Tab {

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { fn from(mut c: Cmd) -> Self {
Self { step: c.take_first().and_then(|s| s.parse().ok()).unwrap_or(0) } Self { step: c.take_first_str().and_then(|s| s.parse().ok()).unwrap_or(0) }
} }
} }

View file

@ -1,15 +1,15 @@
use yazi_dds::ValueSendable; use yazi_shared::event::Data;
use super::Tasks; use super::Tasks;
impl Tasks { impl Tasks {
#[inline] #[inline]
pub fn plugin_micro(&self, name: String, args: Vec<ValueSendable>) { pub fn plugin_micro(&self, name: String, args: Vec<Data>) {
self.scheduler.plugin_micro(name, args); self.scheduler.plugin_micro(name, args);
} }
#[inline] #[inline]
pub fn plugin_macro(&self, name: String, args: Vec<ValueSendable>) { pub fn plugin_macro(&self, name: String, args: Vec<Data>) {
self.scheduler.plugin_macro(name, args); self.scheduler.plugin_macro(name, args);
} }
} }

View file

@ -29,7 +29,7 @@ impl Tasks {
let new = TasksProgress::from(&*ongoing.lock()); let new = TasksProgress::from(&*ongoing.lock());
if last != new { if last != new {
last = new; last = new;
emit!(Call(Cmd::new("update_progress").with_data(new), Layer::App)); emit!(Call(Cmd::new("update_progress").with_any("progress", new), Layer::App));
} }
} }
}); });

View file

@ -14,8 +14,8 @@ impl TryFrom<Cmd> for Opt {
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
tx: c.take_data().ok_or(())?, tx: c.take_any("tx").ok_or(())?,
idx: c.take_first().and_then(|s| s.parse().ok()).ok_or(())?, idx: c.take_first_str().and_then(|s| s.parse().ok()).ok_or(())?,
}) })
} }
} }

View file

@ -16,9 +16,9 @@ impl TryFrom<Cmd> for Opt {
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
cands: c.take_data().unwrap_or_default(), cands: c.take_any("candidates").unwrap_or_default(),
layer: Layer::from_str(&c.take_name("layer").unwrap_or_default())?, layer: Layer::from_str(&c.take_str("layer").unwrap_or_default())?,
silent: c.named.contains_key("silent"), silent: c.get_bool("silent"),
}) })
} }
} }

View file

@ -1,24 +1,25 @@
use mlua::{IntoLua, Lua, Value}; use mlua::{IntoLua, Lua, Value};
use serde::Serialize; use serde::Serialize;
use yazi_shared::event::Data;
use super::Body; use super::Body;
use crate::ValueSendable; use crate::Sendable;
#[derive(Debug)] #[derive(Debug)]
pub struct BodyCustom { pub struct BodyCustom {
pub kind: String, pub kind: String,
pub value: ValueSendable, pub data: Data,
} }
impl BodyCustom { impl BodyCustom {
#[inline] #[inline]
pub fn from_str(kind: &str, value: &str) -> anyhow::Result<Body<'static>> { pub fn from_str(kind: &str, data: &str) -> anyhow::Result<Body<'static>> {
Ok(Self { kind: kind.to_owned(), value: serde_json::from_str(value)? }.into()) Ok(Self { kind: kind.to_owned(), data: serde_json::from_str(data)? }.into())
} }
#[inline] #[inline]
pub fn from_lua(kind: &str, value: Value) -> mlua::Result<Body<'static>> { pub fn from_lua(kind: &str, data: Value) -> mlua::Result<Body<'static>> {
Ok(Self { kind: kind.to_owned(), value: value.try_into()? }.into()) Ok(Self { kind: kind.to_owned(), data: Sendable::value_to_data(data)? }.into())
} }
} }
@ -27,11 +28,11 @@ impl From<BodyCustom> for Body<'_> {
} }
impl IntoLua<'_> for BodyCustom { impl IntoLua<'_> for BodyCustom {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { self.value.into_lua(lua) } fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { Sendable::data_to_value(lua, self.data) }
} }
impl Serialize for BodyCustom { impl Serialize for BodyCustom {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serde::Serialize::serialize(&self.value, serializer) serde::Serialize::serialize(&self.data, serializer)
} }
} }

View file

@ -67,7 +67,7 @@ impl Payload<'static> {
pub(super) fn emit(self) { pub(super) fn emit(self) {
self.try_flush(); self.try_flush();
emit!(Call(Cmd::new("accept_payload").with_data(self), Layer::App)); emit!(Call(Cmd::new("accept_payload").with_any("payload", self), Layer::App));
} }
} }

View file

@ -1,129 +1,122 @@
use std::collections::HashMap; use std::collections::HashMap;
use mlua::{ExternalError, IntoLua, Lua, Value, Variadic}; use mlua::{ExternalError, Lua, Table, Value, Variadic};
use serde::{Deserialize, Serialize}; use yazi_shared::{event::{Data, DataKey}, OrderedFloat};
use yazi_shared::OrderedFloat;
#[derive(Debug, Serialize, Deserialize)] pub struct Sendable;
#[serde(untagged)]
pub enum ValueSendable {
Nil,
Boolean(bool),
Integer(i64),
Number(f64),
String(String),
Table(HashMap<ValueSendableKey, ValueSendable>),
}
impl ValueSendable { impl Sendable {
pub fn try_from_variadic(values: Variadic<Value>) -> mlua::Result<Vec<Self>> { pub fn value_to_data(value: Value) -> mlua::Result<Data> {
Ok(match value {
Value::Nil => Data::Nil,
Value::Boolean(b) => Data::Boolean(b),
Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?,
Value::Integer(n) => Data::Integer(n),
Value::Number(n) => Data::Number(n),
Value::String(s) => Data::String(s.to_str()?.to_owned()),
Value::Table(t) => {
let mut map = HashMap::with_capacity(t.raw_len());
for result in t.pairs::<Value, Value>() {
let (k, v) = result?;
map.insert(Self::value_to_key(k)?, Self::value_to_data(v)?);
}
Data::Table(map)
}
Value::Function(_) => Err("function is not supported".into_lua_err())?,
Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
Value::UserData(ud) => {
if let Ok(t) = ud.take::<yazi_shared::fs::Url>() {
Data::Url(t)
} else if let Ok(t) = ud.take::<super::body::BodyYankIter>() {
Data::Any(Box::new(t))
} else {
Err("unsupported userdata included".into_lua_err())?
}
}
Value::Error(_) => Err("error is not supported".into_lua_err())?,
})
}
pub fn data_to_value(lua: &Lua, data: Data) -> mlua::Result<Value> {
Ok(match data {
Data::Nil => Value::Nil,
Data::Boolean(v) => Value::Boolean(v),
Data::Integer(v) => Value::Integer(v),
Data::Number(v) => Value::Number(v),
Data::String(v) => Value::String(lua.create_string(v)?),
Data::Table(t) => {
let seq_len = t.keys().filter(|&k| !k.is_numeric()).count();
let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?;
for (k, v) in t {
table.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?;
}
Value::Table(table)
}
Data::Url(v) => Value::UserData(lua.create_any_userdata(v)?),
Data::Any(v) => {
if let Ok(t) = v.downcast::<super::body::BodyYankIter>() {
Value::UserData(lua.create_userdata(*t)?)
} else {
Err("unsupported userdata included".into_lua_err())?
}
}
})
}
pub fn vec_to_table(lua: &Lua, data: Vec<Data>) -> mlua::Result<Table> {
let mut vec = Vec::with_capacity(data.len());
for v in data.into_iter() {
vec.push(Self::data_to_value(lua, v)?);
}
lua.create_sequence_from(vec)
}
pub fn vec_to_variadic(lua: &Lua, data: Vec<Data>) -> mlua::Result<Variadic<Value>> {
let mut vec = Vec::with_capacity(data.len());
for v in data {
vec.push(Self::data_to_value(lua, v)?);
}
Ok(Variadic::from_iter(vec))
}
pub fn variadic_to_vec(values: Variadic<Value>) -> mlua::Result<Vec<Data>> {
let mut vec = Vec::with_capacity(values.len()); let mut vec = Vec::with_capacity(values.len());
for value in values { for value in values {
vec.push(Self::try_from(value)?); vec.push(Self::value_to_data(value)?);
} }
Ok(vec) Ok(vec)
} }
pub fn into_table_string(self) -> HashMap<String, String> { fn value_to_key(value: Value) -> mlua::Result<DataKey> {
let Self::Table(table) = self else {
return Default::default();
};
let mut map = HashMap::with_capacity(table.len());
for pair in table {
if let (ValueSendableKey::String(k), Self::String(v)) = pair {
map.insert(k, v);
}
}
map
}
}
impl<'a> TryFrom<Value<'a>> for ValueSendable {
type Error = mlua::Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Ok(match value { Ok(match value {
Value::Nil => Self::Nil, Value::Nil => DataKey::Nil,
Value::Boolean(b) => Self::Boolean(b), Value::Boolean(v) => DataKey::Boolean(v),
Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?, Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?,
Value::Integer(n) => Self::Integer(n), Value::Integer(v) => DataKey::Integer(v),
Value::Number(n) => Self::Number(n), Value::Number(v) => DataKey::Number(OrderedFloat::new(v)),
Value::String(s) => Self::String(s.to_str()?.to_owned()), Value::String(v) => DataKey::String(v.to_str()?.to_owned()),
Value::Table(t) => { Value::Table(_) => Err("table is not supported".into_lua_err())?,
let mut map = HashMap::with_capacity(t.len().map(|l| l as usize)?);
for result in t.pairs::<Value, Value>() {
let (k, v) = result?;
map.insert(Self::try_from(k)?.try_into()?, v.try_into()?);
}
Self::Table(map)
}
Value::Function(_) => Err("function is not supported".into_lua_err())?, Value::Function(_) => Err("function is not supported".into_lua_err())?,
Value::Thread(_) => Err("thread is not supported".into_lua_err())?, Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
Value::UserData(_) => Err("userdata is not supported".into_lua_err())?, Value::UserData(ud) => {
if let Ok(t) = ud.take::<yazi_shared::fs::Url>() {
DataKey::Url(t)
} else {
Err("unsupported userdata included".into_lua_err())?
}
}
Value::Error(_) => Err("error is not supported".into_lua_err())?, Value::Error(_) => Err("error is not supported".into_lua_err())?,
}) })
} }
}
impl IntoLua<'_> for ValueSendable { fn key_to_value(lua: &Lua, key: DataKey) -> mlua::Result<Value> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { Ok(match key {
match self { DataKey::Nil => Value::Nil,
Self::Nil => Ok(Value::Nil), DataKey::Boolean(k) => Value::Boolean(k),
Self::Boolean(v) => Ok(Value::Boolean(v)), DataKey::Integer(k) => Value::Integer(k),
Self::Integer(v) => Ok(Value::Integer(v)), DataKey::Number(k) => Value::Number(k.get()),
Self::Number(v) => Ok(Value::Number(v)), DataKey::String(k) => Value::String(lua.create_string(k)?),
Self::String(v) => Ok(Value::String(lua.create_string(v)?)), DataKey::Url(k) => Value::UserData(lua.create_any_userdata(k)?),
Self::Table(v) => {
let seq_len = v.keys().filter(|&k| !k.is_numeric()).count();
let table = lua.create_table_with_capacity(seq_len, v.len() - seq_len)?;
for (k, v) in v {
table.raw_set(k, v)?;
}
Ok(Value::Table(table))
}
}
}
}
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ValueSendableKey {
Nil,
Boolean(bool),
Integer(i64),
Number(OrderedFloat),
String(String),
}
impl ValueSendableKey {
#[inline]
fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) }
}
impl TryInto<ValueSendableKey> for ValueSendable {
type Error = mlua::Error;
fn try_into(self) -> Result<ValueSendableKey, Self::Error> {
Ok(match self {
Self::Nil => ValueSendableKey::Nil,
Self::Boolean(v) => ValueSendableKey::Boolean(v),
Self::Integer(v) => ValueSendableKey::Integer(v),
Self::Number(v) => ValueSendableKey::Number(OrderedFloat::new(v)),
Self::String(v) => ValueSendableKey::String(v),
Self::Table(_) => Err("table is not supported".into_lua_err())?,
}) })
} }
} }
impl IntoLua<'_> for ValueSendableKey {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
match self {
Self::Nil => Ok(Value::Nil),
Self::Boolean(k) => Ok(Value::Boolean(k)),
Self::Integer(k) => Ok(Value::Integer(k)),
Self::Number(k) => Ok(Value::Number(k.get())),
Self::String(k) => Ok(Value::String(lua.create_string(k)?)),
}
}
}

View file

@ -8,7 +8,7 @@ use crate::{app::App, lives::Lives};
impl App { impl App {
pub(crate) fn accept_payload(&mut self, mut cmd: Cmd) { pub(crate) fn accept_payload(&mut self, mut cmd: Cmd) {
let Some(payload) = cmd.take_data::<Payload>() else { let Some(payload) = cmd.take_any::<Payload>("payload") else {
return; return;
}; };

View file

@ -3,7 +3,8 @@ use std::fmt::Display;
use mlua::TableExt; use mlua::TableExt;
use scopeguard::defer; use scopeguard::defer;
use tracing::warn; use tracing::warn;
use yazi_plugin::{loader::LOADER, OptData, RtRef, LUA}; use yazi_dds::Sendable;
use yazi_plugin::{loader::LOADER, RtRef, LUA};
use yazi_shared::{emit, event::Cmd, Layer}; use yazi_shared::{emit, event::Cmd, Layer};
use crate::{app::App, lives::Lives}; use crate::{app::App, lives::Lives};
@ -16,7 +17,7 @@ impl App {
}; };
if !opt.sync { if !opt.sync {
return self.cx.tasks.plugin_micro(opt.name, opt.data.args); return self.cx.tasks.plugin_micro(opt.name, opt.args);
} }
if LOADER.read().contains_key(&opt.name) { if LOADER.read().contains_key(&opt.name) {
@ -25,14 +26,15 @@ impl App {
tokio::spawn(async move { tokio::spawn(async move {
if LOADER.ensure(&opt.name).await.is_ok() { if LOADER.ensure(&opt.name).await.is_ok() {
Self::_plugin_do(opt.name, opt.data); Self::_plugin_do(opt);
} }
}); });
} }
#[inline] #[inline]
pub(crate) fn _plugin_do(name: String, data: OptData) { pub(crate) fn _plugin_do(opt: yazi_plugin::Opt) {
emit!(Call(Cmd::args("plugin_do", vec![name]).with_data(data), Layer::App)); let cmd: Cmd = opt.into();
emit!(Call(cmd.with_name("plugin_do"), Layer::App));
} }
pub(crate) fn plugin_do(&mut self, opt: impl TryInto<yazi_plugin::Opt, Error = impl Display>) { pub(crate) fn plugin_do(&mut self, opt: impl TryInto<yazi_plugin::Opt, Error = impl Display>) {
@ -53,10 +55,10 @@ impl App {
}; };
_ = Lives::scope(&self.cx, |_| { _ = Lives::scope(&self.cx, |_| {
if let Some(cb) = opt.data.cb { if let Some(cb) = opt.cb {
cb(&LUA, plugin) cb(&LUA, plugin)
} else { } else {
plugin.call_method("entry", opt.data.args) plugin.call_method("entry", Sendable::vec_to_table(&LUA, opt.args)?)
} }
}); });
} }

View file

@ -8,7 +8,7 @@ pub struct Opt {
} }
impl From<Cmd> for Opt { impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self { Self { tx: c.take_data() } } fn from(mut c: Cmd) -> Self { Self { tx: c.take_any("tx") } }
} }
impl App { impl App {

View file

@ -12,7 +12,7 @@ impl TryFrom<Cmd> for Opt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
Ok(Self { progress: c.take_data().ok_or(())? }) Ok(Self { progress: c.take_any("progress").ok_or(())? })
} }
} }

View file

@ -214,7 +214,7 @@ impl<'a> Executor<'a> {
on!(forward); on!(forward);
if cmd.name.as_str() == "complete" { if cmd.name.as_str() == "complete" {
return if cmd.named.contains_key("trigger") { return if cmd.get_bool("trigger") {
self.app.cx.completion.trigger(cmd) self.app.cx.completion.trigger(cmd)
} else { } else {
self.app.cx.input.complete(cmd) self.app.cx.input.complete(cmd)

View file

@ -22,18 +22,18 @@ function M:preload()
return 0 return 0
end end
local mimes, last = {}, ya.time() local updates, last = {}, ya.time()
local flush = function(force) local flush = function(force)
if not force and ya.time() - last < 0.3 then if not force and ya.time() - last < 0.3 then
return return
end end
if next(mimes) then if next(updates) then
ya.manager_emit("update_mimetype", {}, mimes) ya.manager_emit("update_mimetype", { updates = updates })
mimes, last = {}, ya.time() updates, last = {}, ya.time()
end end
end end
local i, j, mime = 1, 0, nil local i, j, valid = 1, 0, nil
repeat repeat
local line, event = child:read_line_with { timeout = 300 } local line, event = child:read_line_with { timeout = 300 }
if event == 3 then if event == 3 then
@ -43,11 +43,11 @@ function M:preload()
break break
end end
mime = match_mimetype(line) valid = match_mimetype(line)
if mime and string.find(line, mime, 1, true) ~= 1 then if valid and string.find(line, valid, 1, true) ~= 1 then
goto continue goto continue
elseif mime then elseif valid then
j, mimes[urls[i]] = j + 1, mime j, updates[urls[i]] = j + 1, valid
flush(false) flush(false)
end end

View file

@ -1,11 +1,12 @@
use mlua::{ExternalError, ExternalResult, Table, TableExt}; use mlua::{ExternalError, ExternalResult, Table, TableExt};
use tokio::runtime::Handle; use tokio::runtime::Handle;
use yazi_dds::ValueSendable; use yazi_dds::Sendable;
use yazi_shared::event::Data;
use super::slim_lua; use super::slim_lua;
use crate::loader::LOADER; use crate::loader::LOADER;
pub async fn entry(name: String, args: Vec<ValueSendable>) -> mlua::Result<()> { pub async fn entry(name: String, args: Vec<Data>) -> mlua::Result<()> {
LOADER.ensure(&name).await.into_lua_err()?; LOADER.ensure(&name).await.into_lua_err()?;
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
@ -16,7 +17,8 @@ pub async fn entry(name: String, args: Vec<ValueSendable>) -> mlua::Result<()> {
return Err("unloaded plugin".into_lua_err()); return Err("unloaded plugin".into_lua_err());
}; };
Handle::current().block_on(plugin.call_async_method("entry", args)) Handle::current()
.block_on(plugin.call_async_method("entry", Sendable::vec_to_table(&lua, args)))
}) })
.await .await
.into_lua_err()? .into_lua_err()?

View file

@ -6,7 +6,7 @@ use yazi_config::LAYOUT;
use yazi_shared::{emit, event::Cmd, Layer}; use yazi_shared::{emit, event::Cmd, Layer};
use super::slim_lua; use super::slim_lua;
use crate::{bindings::{Cast, File, Window}, elements::Rect, loader::LOADER, OptData, LUA}; use crate::{bindings::{Cast, File, Window}, elements::Rect, loader::LOADER, Opt, OptCallback, LUA};
pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> CancellationToken { pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> CancellationToken {
let ct = CancellationToken::new(); let ct = CancellationToken::new();
@ -56,18 +56,16 @@ pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> Cancellation
} }
pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) { pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) {
let data = OptData { let cb: OptCallback = Box::new(move |_, plugin| {
cb: Some(Box::new(move |_, plugin| { plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("file", File::cast(&LUA, file)?)?; 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())?; plugin.call_method("peek", ())
plugin.call_method("peek", ()) });
})),
..Default::default() let cmd: Cmd =
}; Opt { name: cmd.name.to_owned(), sync: true, cb: Some(cb), ..Default::default() }.into();
emit!(Call(
Cmd::args("plugin", vec![cmd.name.to_owned()]).with_bool("sync", true).with_data(data), emit!(Call(cmd.with_name("plugin"), Layer::App));
Layer::App
));
} }

View file

@ -2,19 +2,17 @@ use mlua::TableExt;
use yazi_config::LAYOUT; use yazi_config::LAYOUT;
use yazi_shared::{emit, event::Cmd, Layer}; use yazi_shared::{emit, event::Cmd, Layer};
use crate::{bindings::{Cast, File}, elements::Rect, OptData, LUA}; use crate::{bindings::{Cast, File}, elements::Rect, Opt, OptCallback, LUA};
pub fn seek_sync(cmd: &Cmd, file: yazi_shared::fs::File, units: i16) { pub fn seek_sync(cmd: &Cmd, file: yazi_shared::fs::File, units: i16) {
let data = OptData { let cb: OptCallback = Box::new(move |_, plugin| {
cb: Some(Box::new(move |_, plugin| { plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("file", File::cast(&LUA, file)?)?; plugin.raw_set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?;
plugin.raw_set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?; plugin.call_method("seek", units)
plugin.call_method("seek", units) });
})),
..Default::default() let cmd: Cmd =
}; Opt { name: cmd.name.to_owned(), sync: true, cb: Some(cb), ..Default::default() }.into();
emit!(Call(
Cmd::args("plugin", vec![cmd.name.to_owned()]).with_bool("sync", true).with_data(data), emit!(Call(cmd.with_name("plugin"), Layer::App));
Layer::App
));
} }

View file

@ -1,34 +1,43 @@
use anyhow::bail; use anyhow::bail;
use mlua::{Lua, Table}; use mlua::{Lua, Table};
use yazi_dds::ValueSendable; use yazi_shared::event::{Cmd, Data};
use yazi_shared::event::Cmd;
pub(super) type OptCallback = Box<dyn FnOnce(&Lua, Table) -> mlua::Result<()> + Send>;
#[derive(Default)]
pub struct Opt { pub struct Opt {
pub name: String, pub name: String,
pub sync: bool, pub sync: bool,
pub data: OptData, pub args: Vec<Data>,
} pub cb: Option<OptCallback>,
#[derive(Default)]
pub struct OptData {
pub args: Vec<ValueSendable>,
pub cb: Option<Box<dyn FnOnce(&Lua, Table) -> mlua::Result<()> + Send>>,
} }
impl TryFrom<Cmd> for Opt { impl TryFrom<Cmd> for Opt {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
let Some(name) = c.take_first().filter(|s| !s.is_empty()) else { let Some(name) = c.take_first_str().filter(|s| !s.is_empty()) else {
bail!("plugin name cannot be empty"); bail!("plugin name cannot be empty");
}; };
let mut data: OptData = c.take_data().unwrap_or_default(); let args = if let Some(s) = c.get_str("args") {
shell_words::split(s)?.into_iter().map(Data::String).collect()
} else {
c.take_any::<Vec<Data>>("args").unwrap_or_default()
};
if let Some(args) = c.named.get("args") { Ok(Self { name, sync: c.get_bool("sync"), args, cb: c.take_any("callback") })
data.args = shell_words::split(args)?.into_iter().map(ValueSendable::String).collect(); }
} }
Ok(Self { name, sync: c.named.contains_key("sync"), data }) impl From<Opt> for Cmd {
fn from(value: Opt) -> Self {
let mut cmd =
Cmd::args("", vec![value.name]).with_bool("sync", value.sync).with_any("args", value.args);
if let Some(cb) = value.cb {
cmd = cmd.with_any("callback", cb);
}
cmd
} }
} }

View file

@ -1,52 +1,27 @@
use std::collections::HashMap; use std::collections::HashMap;
use mlua::{ExternalError, Lua, Table, Value}; use mlua::{ExternalError, Lua, Table, Value};
use yazi_dds::ValueSendable; use yazi_dds::Sendable;
use yazi_shared::{emit, event::Cmd, render, Layer}; use yazi_shared::{emit, event::{Cmd, Data}, render, Layer};
use super::Utils; use super::Utils;
impl Utils { impl Utils {
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, HashMap<String, String>)> { fn parse_args(t: Table) -> mlua::Result<HashMap<String, Data>> {
let mut args = vec![]; let mut args = HashMap::with_capacity(t.raw_len());
let mut named = HashMap::new(); for pair in t.pairs::<Value, Value>() {
for result in t.pairs::<Value, Value>() { let (k, v) = pair?;
let (k, v) = result?;
match k { match k {
Value::Integer(_) => { Value::Integer(i) if i > 0 => {
args.push(match v { args.insert((i - 1).to_string(), Sendable::value_to_data(v)?);
Value::Integer(i) => i.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.to_string_lossy().into_owned(),
_ => return Err("invalid value in cmd".into_lua_err()),
});
} }
Value::String(s) => { Value::String(s) => {
let v = match v { args.insert(s.to_str()?.replace('_', "-"), Sendable::value_to_data(v)?);
Value::Boolean(b) if b => String::new(),
Value::Boolean(b) if !b => continue,
Value::Integer(i) => i.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.to_string_lossy().into_owned(),
_ => return Err("invalid value in cmd".into_lua_err()),
};
named.insert(s.to_str()?.replace('_', "-"), v);
} }
_ => return Err("invalid key in cmd".into_lua_err()), _ => return Err("invalid key in cmd".into_lua_err()),
} }
} }
Ok((args, named)) Ok(args)
}
#[inline]
fn create_cmd(name: String, table: Table, data: Option<Value>) -> mlua::Result<Cmd> {
let (args, named) = Self::parse_args(table)?;
let mut cmd = Cmd { name, args, named, ..Default::default() };
if let Some(data) = data.and_then(|v| ValueSendable::try_from(v).ok()) {
cmd = cmd.with_data(data);
}
Ok(cmd)
} }
pub(super) fn call(lua: &Lua, ya: &Table) -> mlua::Result<()> { pub(super) fn call(lua: &Lua, ya: &Table) -> mlua::Result<()> {
@ -60,16 +35,16 @@ impl Utils {
ya.raw_set( ya.raw_set(
"app_emit", "app_emit",
lua.create_function(|_, (name, table, data): (String, Table, Option<Value>)| { lua.create_function(|_, (name, args): (String, Table)| {
emit!(Call(Self::create_cmd(name, table, data)?, Layer::App)); emit!(Call(Cmd { name, args: Self::parse_args(args)? }, Layer::App));
Ok(()) Ok(())
})?, })?,
)?; )?;
ya.raw_set( ya.raw_set(
"manager_emit", "manager_emit",
lua.create_function(|_, (name, table, data): (String, Table, Option<Value>)| { lua.create_function(|_, (name, args): (String, Table)| {
emit!(Call(Self::create_cmd(name, table, data)?, Layer::Manager)); emit!(Call(Cmd { name, args: Self::parse_args(args)? }, Layer::Manager));
Ok(()) Ok(())
})?, })?,
)?; )?;

View file

@ -37,7 +37,7 @@ impl Utils {
let cand = cand?; let cand = cand?;
cands.push(Control { cands.push(Control {
on: Self::parse_keys(cand.raw_get("on")?)?, on: Self::parse_keys(cand.raw_get("on")?)?,
run: vec![Cmd::args("callback", vec![i.to_string()]).with_data(tx.clone())], run: vec![Cmd::args("callback", vec![i.to_string()]).with_any("tx", tx.clone())],
desc: cand.raw_get("desc").ok(), desc: cand.raw_get("desc").ok(),
}); });
} }
@ -46,8 +46,8 @@ impl Utils {
emit!(Call( emit!(Call(
Cmd::new("show") Cmd::new("show")
.with("layer", Layer::Which) .with("layer", Layer::Which)
.with_bool("silent", t.raw_get("silent").unwrap_or_default()) .with_any("candidates", cands)
.with_data(cands), .with_bool("silent", t.raw_get("silent").unwrap_or_default()),
Layer::Which Layer::Which
)); ));

View file

@ -44,7 +44,7 @@ impl Utils {
}; };
lock.data = vec![Box::new(Paragraph { area: *area, text, ..Default::default() })]; lock.data = vec![Box::new(Paragraph { area: *area, text, ..Default::default() })];
emit!(Call(Cmd::new("preview").with_data(lock), Layer::Manager)); emit!(Call(Cmd::new("preview").with_any("lock", lock), Layer::Manager));
(true, Value::Nil).into_lua_multi(lua) (true, Value::Nil).into_lua_multi(lua)
})?, })?,
)?; )?;
@ -67,7 +67,7 @@ impl Utils {
..Default::default() ..Default::default()
})]; })];
emit!(Call(Cmd::new("preview").with_data(lock), Layer::Manager)); emit!(Call(Cmd::new("preview").with_any("lock", lock), Layer::Manager));
(true, Value::Nil).into_lua_multi(lua) (true, Value::Nil).into_lua_multi(lua)
})?, })?,
)?; )?;
@ -78,7 +78,7 @@ impl Utils {
let mut lock = PreviewLock::try_from(t)?; let mut lock = PreviewLock::try_from(t)?;
lock.data = widgets.into_iter().filter_map(cast_to_renderable).collect(); lock.data = widgets.into_iter().filter_map(cast_to_renderable).collect();
emit!(Call(Cmd::new("preview").with_data(lock), Layer::Manager)); emit!(Call(Cmd::new("preview").with_any("lock", lock), Layer::Manager));
Ok(()) Ok(())
})?, })?,
)?; )?;

View file

@ -1,10 +1,10 @@
use mlua::{ExternalError, ExternalResult, Function, IntoLua, Lua, Table, Value, Variadic}; use mlua::{ExternalError, ExternalResult, Function, IntoLua, Lua, Table, Value, Variadic};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use yazi_dds::ValueSendable; use yazi_dds::Sendable;
use yazi_shared::{emit, event::Cmd, Layer}; use yazi_shared::{emit, event::{Cmd, Data}, Layer};
use super::Utils; use super::Utils;
use crate::{loader::LOADER, runtime::RtRef, OptData}; use crate::{loader::LOADER, runtime::RtRef, OptCallback};
impl Utils { impl Utils {
pub(super) fn sync(lua: &'static Lua, ya: &Table) -> mlua::Result<()> { pub(super) fn sync(lua: &'static Lua, ya: &Table) -> mlua::Result<()> {
@ -37,7 +37,7 @@ impl Utils {
return Err("`ya.sync()` must be called in a plugin").into_lua_err(); return Err("`ya.sync()` must be called in a plugin").into_lua_err();
}; };
Self::retrieve(cur, block, args).await Sendable::vec_to_variadic(lua, Self::retrieve(cur, block, args).await?)
}) })
})?, })?,
)?; )?;
@ -49,39 +49,37 @@ impl Utils {
name: String, name: String,
calls: usize, calls: usize,
args: Variadic<Value<'_>>, args: Variadic<Value<'_>>,
) -> mlua::Result<mlua::Variadic<ValueSendable>> { ) -> mlua::Result<Vec<Data>> {
let args = ValueSendable::try_from_variadic(args)?; let args = Sendable::variadic_to_vec(args)?;
let (tx, rx) = oneshot::channel::<Vec<ValueSendable>>(); let (tx, rx) = oneshot::channel::<Vec<Data>>();
let data = OptData { let callback: OptCallback = {
cb: Some({ let name = name.clone();
let name = name.clone(); Box::new(move |lua, plugin| {
Box::new(move |lua, plugin| { let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else {
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else { return Err("sync block not found".into_lua_err());
return Err("sync block not found".into_lua_err()); };
};
let mut self_args = Vec::with_capacity(args.len() + 1); let mut self_args = Vec::with_capacity(args.len() + 1);
self_args.push(Value::Table(plugin)); self_args.push(Value::Table(plugin));
for arg in args { for arg in args {
self_args.push(arg.into_lua(lua)?); self_args.push(Sendable::data_to_value(lua, arg)?);
} }
let values = let values = Sendable::variadic_to_vec(block.call(Variadic::from_iter(self_args))?)?;
ValueSendable::try_from_variadic(block.call(Variadic::from_iter(self_args))?)?; tx.send(values).map_err(|_| "send failed".into_lua_err())
tx.send(values).map_err(|_| "send failed".into_lua_err()) })
})
}),
..Default::default()
}; };
emit!(Call( emit!(Call(
Cmd::args("plugin", vec![name.clone()]).with_bool("sync", true).with_data(data), Cmd::args("plugin", vec![name.clone()])
.with_bool("sync", true)
.with_any("callback", callback),
Layer::App Layer::App
)); ));
Ok(Variadic::from_iter(rx.await.map_err(|_| { rx.await.map_err(|_| {
format!("Failed to execute sync block-{calls} in `{name}` plugin").into_lua_err() format!("Failed to execute sync block-{calls} in `{name}` plugin").into_lua_err()
})?)) })
} }
} }

View file

@ -11,7 +11,7 @@ impl AppProxy {
#[inline] #[inline]
pub async fn stop() { pub async fn stop() {
let (tx, rx) = oneshot::channel::<()>(); let (tx, rx) = oneshot::channel::<()>();
emit!(Call(Cmd::new("stop").with_data(tx), Layer::App)); emit!(Call(Cmd::new("stop").with_any("tx", tx), Layer::App));
rx.await.ok(); rx.await.ok();
} }
@ -22,13 +22,13 @@ impl AppProxy {
#[inline] #[inline]
pub fn notify(opt: NotifyOpt) { pub fn notify(opt: NotifyOpt) {
emit!(Call(Cmd::new("notify").with_data(opt), Layer::App)); emit!(Call(Cmd::new("notify").with_any("option", opt), Layer::App));
} }
#[inline] #[inline]
pub fn notify_warn(title: &str, content: &str) { pub fn notify_warn(title: &str, content: &str) {
emit!(Call( emit!(Call(
Cmd::new("notify").with_data(NotifyOpt { Cmd::new("notify").with_any("option", NotifyOpt {
title: title.to_owned(), title: title.to_owned(),
content: content.to_owned(), content: content.to_owned(),
level: NotifyLevel::Warn, level: NotifyLevel::Warn,

View file

@ -2,15 +2,13 @@ use tokio::sync::mpsc;
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_shared::{emit, event::Cmd, InputError, Layer}; use yazi_shared::{emit, event::Cmd, InputError, Layer};
use crate::options::InputOpt;
pub struct InputProxy; pub struct InputProxy;
impl InputProxy { impl InputProxy {
#[inline] #[inline]
pub fn show(cfg: InputCfg) -> mpsc::UnboundedReceiver<Result<String, InputError>> { pub fn show(cfg: InputCfg) -> mpsc::UnboundedReceiver<Result<String, InputError>> {
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
emit!(Call(Cmd::new("show").with_data(InputOpt { cfg, tx }), Layer::Input)); emit!(Call(Cmd::new("show").with_any("tx", tx).with_any("cfg", cfg), Layer::Input));
rx rx
} }

View file

@ -25,13 +25,13 @@ impl ManagerProxy {
#[inline] #[inline]
pub fn open_do(opt: OpenDoOpt) { pub fn open_do(opt: OpenDoOpt) {
emit!(Call(Cmd::new("open_do").with_data(opt), Layer::Manager)); emit!(Call(Cmd::new("open_do").with_any("option", opt), Layer::Manager));
} }
#[inline] #[inline]
pub fn remove_do(targets: Vec<Url>, permanently: bool) { pub fn remove_do(targets: Vec<Url>, permanently: bool) {
emit!(Call( emit!(Call(
Cmd::new("remove_do").with_bool("permanently", permanently).with_data(targets), Cmd::new("remove_do").with_bool("permanently", permanently).with_any("targets", targets),
Layer::Manager Layer::Manager
)); ));
} }

View file

@ -1,14 +0,0 @@
use tokio::sync::mpsc;
use yazi_config::popup::InputCfg;
use yazi_shared::{event::Cmd, InputError};
pub struct InputOpt {
pub cfg: InputCfg,
pub tx: mpsc::UnboundedSender<Result<String, InputError>>,
}
impl TryFrom<Cmd> for InputOpt {
type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_data().ok_or(()) }
}

View file

@ -1,11 +1,7 @@
mod input;
mod notify; mod notify;
mod open; mod open;
mod process; mod process;
mod select;
pub use input::*;
pub use notify::*; pub use notify::*;
pub use open::*; pub use open::*;
pub use process::*; pub use process::*;
pub use select::*;

View file

@ -14,7 +14,7 @@ pub struct NotifyOpt {
impl TryFrom<Cmd> for NotifyOpt { impl TryFrom<Cmd> for NotifyOpt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_data().ok_or(()) } fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_any("option").ok_or(()) }
} }
impl<'a> TryFrom<mlua::Table<'a>> for NotifyOpt { impl<'a> TryFrom<mlua::Table<'a>> for NotifyOpt {

View file

@ -12,7 +12,7 @@ pub struct OpenDoOpt {
} }
impl From<Cmd> for OpenDoOpt { impl From<Cmd> for OpenDoOpt {
fn from(mut c: Cmd) -> Self { c.take_data().unwrap_or_default() } fn from(mut c: Cmd) -> Self { c.take_any("option").unwrap_or_default() }
} }
// --- Open with // --- Open with
@ -24,5 +24,5 @@ pub struct OpenWithOpt {
impl TryFrom<Cmd> for OpenWithOpt { impl TryFrom<Cmd> for OpenWithOpt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_data().ok_or(()) } fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_any("option").ok_or(()) }
} }

View file

@ -14,5 +14,5 @@ pub struct ProcessExecOpt {
impl TryFrom<Cmd> for ProcessExecOpt { impl TryFrom<Cmd> for ProcessExecOpt {
type Error = (); type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_data().ok_or(()) } fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_any("option").ok_or(()) }
} }

View file

@ -1,14 +0,0 @@
use tokio::sync::oneshot;
use yazi_config::popup::SelectCfg;
use yazi_shared::event::Cmd;
pub struct SelectOpt {
pub cfg: SelectCfg,
pub tx: oneshot::Sender<anyhow::Result<usize>>,
}
impl TryFrom<Cmd> for SelectOpt {
type Error = ();
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> { c.take_data().ok_or(()) }
}

View file

@ -2,15 +2,13 @@ use tokio::sync::oneshot;
use yazi_config::popup::SelectCfg; use yazi_config::popup::SelectCfg;
use yazi_shared::{emit, event::Cmd, Layer}; use yazi_shared::{emit, event::Cmd, Layer};
use crate::options::SelectOpt;
pub struct SelectProxy; pub struct SelectProxy;
impl SelectProxy { impl SelectProxy {
#[inline] #[inline]
pub async fn show(cfg: SelectCfg) -> anyhow::Result<usize> { pub async fn show(cfg: SelectCfg) -> anyhow::Result<usize> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
emit!(Call(Cmd::new("show").with_data(SelectOpt { cfg, tx }), Layer::Select)); emit!(Call(Cmd::new("show").with_any("tx", tx).with_any("cfg", cfg), Layer::Select));
rx.await? rx.await?
} }
} }

View file

@ -11,14 +11,17 @@ pub struct TasksProxy;
impl TasksProxy { impl TasksProxy {
#[inline] #[inline]
pub fn open_with(targets: Vec<Url>, opener: Cow<'static, Opener>) { pub fn open_with(targets: Vec<Url>, opener: Cow<'static, Opener>) {
emit!(Call(Cmd::new("open_with").with_data(OpenWithOpt { targets, opener }), Layer::Tasks)); emit!(Call(
Cmd::new("open_with").with_any("option", OpenWithOpt { targets, opener }),
Layer::Tasks
));
} }
#[inline] #[inline]
pub async fn process_exec(args: Vec<OsString>, opener: Cow<'static, Opener>) { pub async fn process_exec(args: Vec<OsString>, opener: Cow<'static, Opener>) {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
emit!(Call( emit!(Call(
Cmd::new("process_exec").with_data(ProcessExecOpt { args, opener, done: tx }), Cmd::new("process_exec").with_any("option", ProcessExecOpt { args, opener, done: tx }),
Layer::Tasks Layer::Tasks
)); ));
rx.await.ok(); rx.await.ok();

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::VecDeque, fs::Metadata, path::{Path, PathBuf}}; use std::{borrow::Cow, collections::VecDeque, fs::Metadata, path::{Path, PathBuf}};
use anyhow::Result; use anyhow::{anyhow, Result};
use futures::{future::BoxFuture, FutureExt}; use futures::{future::BoxFuture, FutureExt};
use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn; use tracing::warn;
@ -51,9 +51,10 @@ impl File {
if task.retry < TASKS.bizarre_retry if task.retry < TASKS.bizarre_retry
&& matches!(e.raw_os_error(), Some(1) | Some(93)) => && matches!(e.raw_os_error(), Some(1) | Some(93)) =>
{ {
self.log(task.id, format!("Paste task retry: {:?}", task))?;
task.retry += 1; task.retry += 1;
return Ok(self.macro_.send(FileOp::Paste(task).into(), LOW).await?); self.log(task.id, format!("Paste task retry: {:?}", task))?;
self.queue(FileOp::Paste(task), LOW).await?;
return Ok(());
} }
Err(e) => Err(e)?, Err(e) => Err(e)?,
} }
@ -147,9 +148,9 @@ impl File {
self.prog.send(TaskProg::New(id, meta.len()))?; self.prog.send(TaskProg::New(id, meta.len()))?;
if meta.is_file() { if meta.is_file() {
self.macro_.send(FileOp::Paste(task).into(), LOW).await?; self.queue(FileOp::Paste(task), LOW).await?;
} else if meta.is_symlink() { } else if meta.is_symlink() {
self.macro_.send(FileOp::Link(task.to_link(meta)).into(), NORMAL).await?; self.queue(FileOp::Link(task.to_link(meta)), NORMAL).await?;
} }
return self.succ(id); return self.succ(id);
} }
@ -193,9 +194,9 @@ impl File {
self.prog.send(TaskProg::New(task.id, meta.len()))?; self.prog.send(TaskProg::New(task.id, meta.len()))?;
if meta.is_file() { if meta.is_file() {
self.macro_.send(FileOp::Paste(task.clone()).into(), LOW).await?; self.queue(FileOp::Paste(task.clone()), LOW).await?;
} else if meta.is_symlink() { } else if meta.is_symlink() {
self.macro_.send(FileOp::Link(task.to_link(meta)).into(), NORMAL).await?; self.queue(FileOp::Link(task.to_link(meta)), NORMAL).await?;
} }
} }
} }
@ -209,7 +210,7 @@ impl File {
} }
self.prog.send(TaskProg::New(id, task.meta.as_ref().unwrap().len()))?; self.prog.send(TaskProg::New(id, task.meta.as_ref().unwrap().len()))?;
self.macro_.send(FileOp::Link(task).into(), NORMAL).await?; self.queue(FileOp::Link(task), NORMAL).await?;
self.succ(id) self.succ(id)
} }
@ -219,7 +220,7 @@ impl File {
let id = task.id; let id = task.id;
task.length = meta.len(); task.length = meta.len();
self.prog.send(TaskProg::New(id, meta.len()))?; self.prog.send(TaskProg::New(id, meta.len()))?;
self.macro_.send(FileOp::Delete(task).into(), NORMAL).await?; self.queue(FileOp::Delete(task), NORMAL).await?;
return self.succ(id); return self.succ(id);
} }
@ -238,7 +239,7 @@ impl File {
task.target = Url::from(entry.path()); task.target = Url::from(entry.path());
task.length = meta.len(); task.length = meta.len();
self.prog.send(TaskProg::New(task.id, meta.len()))?; self.prog.send(TaskProg::New(task.id, meta.len()))?;
self.macro_.send(FileOp::Delete(task.clone()).into(), NORMAL).await?; self.queue(FileOp::Delete(task.clone()), NORMAL).await?;
} }
} }
self.succ(task.id) self.succ(task.id)
@ -249,7 +250,7 @@ impl File {
task.length = calculate_size(&task.target).await; task.length = calculate_size(&task.target).await;
self.prog.send(TaskProg::New(id, task.length))?; self.prog.send(TaskProg::New(id, task.length))?;
self.macro_.send(FileOp::Trash(task).into(), LOW).await?; self.queue(FileOp::Trash(task), LOW).await?;
self.succ(id) self.succ(id)
} }
@ -296,6 +297,11 @@ impl File {
fn log(&self, id: usize, line: String) -> Result<()> { fn log(&self, id: usize, line: String) -> Result<()> {
Ok(self.prog.send(TaskProg::Log(id, line))?) Ok(self.prog.send(TaskProg::Log(id, line))?)
} }
#[inline]
async fn queue(&self, op: impl Into<TaskOp>, priority: u8) -> Result<()> {
self.macro_.send(op.into(), priority).await.map_err(|_| anyhow!("Failed to send task"))
}
} }
impl FileOpPaste { impl FileOpPaste {

View file

@ -1,4 +1,4 @@
use yazi_dds::ValueSendable; use yazi_shared::event::Data;
#[derive(Debug)] #[derive(Debug)]
pub enum PluginOp { pub enum PluginOp {
@ -17,5 +17,5 @@ impl PluginOp {
pub struct PluginOpEntry { pub struct PluginOpEntry {
pub id: usize, pub id: usize,
pub name: String, pub name: String,
pub args: Vec<ValueSendable>, pub args: Vec<Data>,
} }

View file

@ -1,4 +1,4 @@
use anyhow::Result; use anyhow::{anyhow, Result};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_plugin::isolate; use yazi_plugin::isolate;
@ -43,7 +43,7 @@ impl Plugin {
let id = task.id; let id = task.id;
self.prog.send(TaskProg::New(id, 0))?; self.prog.send(TaskProg::New(id, 0))?;
self.macro_.try_send(PluginOp::Entry(task).into(), HIGH)?; self.queue(PluginOp::Entry(task), HIGH)?;
self.succ(id) self.succ(id)
} }
} }
@ -56,4 +56,9 @@ impl Plugin {
fn fail(&self, id: usize, reason: String) -> Result<()> { fn fail(&self, id: usize, reason: String) -> Result<()> {
Ok(self.prog.send(TaskProg::Fail(id, reason))?) Ok(self.prog.send(TaskProg::Fail(id, reason))?)
} }
#[inline]
fn queue(&self, op: impl Into<TaskOp>, priority: u8) -> Result<()> {
self.macro_.try_send(op.into(), priority).map_err(|_| anyhow!("Failed to send task"))
}
} }

View file

@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use anyhow::Result; use anyhow::{anyhow, Result};
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::error; use tracing::error;
@ -73,8 +73,8 @@ impl Preload {
self.prog.send(TaskProg::New(id, 0))?; self.prog.send(TaskProg::New(id, 0))?;
match task.plugin.prio { match task.plugin.prio {
Priority::Low => self.macro_.send(PreloadOp::Rule(task).into(), NORMAL).await?, Priority::Low => self.queue(PreloadOp::Rule(task), NORMAL).await?,
Priority::Normal => self.macro_.send(PreloadOp::Rule(task).into(), HIGH).await?, Priority::Normal => self.queue(PreloadOp::Rule(task), HIGH).await?,
Priority::High => self.work(PreloadOp::Rule(task)).await?, Priority::High => self.work(PreloadOp::Rule(task)).await?,
} }
self.succ(id) self.succ(id)
@ -97,4 +97,9 @@ impl Preload {
fn fail(&self, id: usize, reason: String) -> Result<()> { fn fail(&self, id: usize, reason: String) -> Result<()> {
Ok(self.prog.send(TaskProg::Fail(id, reason))?) Ok(self.prog.send(TaskProg::Fail(id, reason))?)
} }
#[inline]
async fn queue(&self, op: impl Into<TaskOp>, priority: u8) -> Result<()> {
self.macro_.send(op.into(), priority).await.map_err(|_| anyhow!("Failed to send task"))
}
} }

View file

@ -4,8 +4,8 @@ use futures::{future::BoxFuture, FutureExt};
use parking_lot::Mutex; use parking_lot::Mutex;
use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle}; use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle};
use yazi_config::{open::Opener, plugin::PluginRule, TASKS}; use yazi_config::{open::Opener, plugin::PluginRule, TASKS};
use yazi_dds::{Pump, ValueSendable}; use yazi_dds::Pump;
use yazi_shared::{fs::{unique_path, Url}, Throttle}; use yazi_shared::{event::Data, fs::{unique_path, Url}, Throttle};
use super::{Ongoing, TaskProg, TaskStage}; use super::{Ongoing, TaskProg, TaskStage};
use crate::{file::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, preload::{Preload, PreloadOpRule, PreloadOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL}; use crate::{file::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, preload::{Preload, PreloadOpRule, PreloadOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL};
@ -182,7 +182,7 @@ impl Scheduler {
); );
} }
pub fn plugin_micro(&self, name: String, args: Vec<ValueSendable>) { pub fn plugin_micro(&self, name: String, args: Vec<Data>) {
let id = self.ongoing.lock().add(TaskKind::User, format!("Run micro plugin `{name}`")); let id = self.ongoing.lock().add(TaskKind::User, format!("Run micro plugin `{name}`"));
let plugin = self.plugin.clone(); let plugin = self.plugin.clone();
@ -195,7 +195,7 @@ impl Scheduler {
); );
} }
pub fn plugin_macro(&self, name: String, args: Vec<ValueSendable>) { pub fn plugin_macro(&self, name: String, args: Vec<Data>) {
let id = self.ongoing.lock().add(TaskKind::User, format!("Run macro plugin `{name}`")); let id = self.ongoing.lock().add(TaskKind::User, format!("Run macro plugin `{name}`"));
self.plugin.macro_(PluginOpEntry { id, name, args }).ok(); self.plugin.macro_(PluginOpEntry { id, name, args }).ok();

View file

@ -1,11 +1,11 @@
use std::{any::Any, collections::HashMap, fmt::{self, Display}, mem}; use std::{any::Any, collections::HashMap, fmt::{self, Display}};
use super::Data;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Cmd { pub struct Cmd {
pub name: String, pub name: String,
pub args: Vec<String>, pub args: HashMap<String, Data>,
pub named: HashMap<String, String>,
pub data: Option<Box<dyn Any + Send>>,
} }
impl Cmd { impl Cmd {
@ -14,49 +14,70 @@ impl Cmd {
#[inline] #[inline]
pub fn args(name: &str, args: Vec<String>) -> Self { pub fn args(name: &str, args: Vec<String>) -> Self {
Self { name: name.to_owned(), args, ..Default::default() } Self {
name: name.to_owned(),
args: args.into_iter().enumerate().map(|(i, s)| (i.to_string(), Data::String(s))).collect(),
}
} }
#[inline] #[inline]
pub fn with(mut self, name: impl ToString, value: impl ToString) -> Self { pub fn with(mut self, name: impl ToString, value: impl ToString) -> Self {
self.named.insert(name.to_string(), value.to_string()); self.args.insert(name.to_string(), Data::String(value.to_string()));
self self
} }
#[inline] #[inline]
pub fn with_bool(mut self, name: impl ToString, state: bool) -> Self { pub fn with_bool(mut self, name: impl ToString, state: bool) -> Self {
if state { self.args.insert(name.to_string(), Data::Boolean(state));
self.named.insert(name.to_string(), Default::default());
}
self self
} }
#[inline] #[inline]
pub fn with_data(mut self, data: impl Any + Send) -> Self { pub fn with_any(mut self, name: impl ToString, data: impl Any + Send) -> Self {
self.data = Some(Box::new(data)); self.args.insert(name.to_string(), Data::Any(Box::new(data)));
self self
} }
#[inline] #[inline]
pub fn take_data<T: 'static>(&mut self) -> Option<T> { pub fn with_name(mut self, name: impl ToString) -> Self {
self.data.take().and_then(|d| d.downcast::<T>().ok()).map(|d| *d) self.name = name.to_string();
self
} }
#[inline] #[inline]
pub fn take_first(&mut self) -> Option<String> { pub fn get_str(&self, name: &str) -> Option<&str> { self.args.get(name).and_then(Data::as_str) }
if self.args.is_empty() { None } else { Some(mem::take(&mut self.args[0])) }
#[inline]
pub fn get_bool(&self, name: &str) -> bool {
self.args.get(name).and_then(Data::as_bool).unwrap_or(false)
} }
#[inline] #[inline]
pub fn take_name(&mut self, name: &str) -> Option<String> { self.named.remove(name) } pub fn take_data(&mut self, name: &str) -> Option<Data> { self.args.remove(name) }
#[inline] #[inline]
pub fn clone_without_data(&self) -> Self { pub fn take_str(&mut self, name: &str) -> Option<String> {
if let Some(Data::String(s)) = self.args.remove(name) { Some(s) } else { None }
}
#[inline]
pub fn take_first_str(&mut self) -> Option<String> {
if let Some(Data::String(s)) = self.args.remove("0") { Some(s) } else { None }
}
#[inline]
pub fn take_any<T: 'static>(&mut self, name: &str) -> Option<T> {
self.args.remove(name).and_then(|d| d.into_any())
}
pub fn shallow_clone(&self) -> Self {
Self { Self {
name: self.name.clone(), name: self.name.clone(),
args: self.args.clone(), args: self
named: self.named.clone(), .args
data: None, .iter()
.filter_map(|(k, v)| v.shallow_clone().map(|v| (k.clone(), v)))
.collect(),
} }
} }
} }
@ -64,13 +85,15 @@ impl Cmd {
impl Display for Cmd { impl Display for Cmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)?; write!(f, "{}", self.name)?;
if !self.args.is_empty() { for (k, v) in &self.args {
write!(f, " {}", self.args.join(" "))?; if k.as_bytes().first().is_some_and(|b| b.is_ascii_digit()) {
} if let Some(s) = v.as_str() {
for (k, v) in &self.named { write!(f, " {s}")?;
write!(f, " --{k}")?; }
if !v.is_empty() { } else if v.as_bool().is_some_and(|b| b) {
write!(f, "={v}")?; write!(f, " --{k}")?;
} else if let Some(s) = v.as_str() {
write!(f, " --{k}={s}")?;
} }
} }
Ok(()) Ok(())

View file

@ -0,0 +1,96 @@
use std::{any::Any, collections::HashMap};
use serde::{Deserialize, Serialize};
use crate::{fs::Url, OrderedFloat};
// --- Data
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Data {
Nil,
Boolean(bool),
Integer(i64),
Number(f64),
String(String),
Table(HashMap<DataKey, Data>),
#[serde(skip)]
Url(Url),
#[serde(skip)]
Any(Box<dyn Any + Send>),
}
impl Data {
#[inline]
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Boolean(b) => Some(*b),
_ => None,
}
}
#[inline]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
#[inline]
pub fn as_any<T: 'static>(&self) -> Option<&T> {
match self {
Self::Any(b) => b.downcast_ref::<T>(),
_ => None,
}
}
#[inline]
pub fn into_any<T: 'static>(self) -> Option<T> {
match self {
Self::Any(b) => b.downcast::<T>().ok().map(|b| *b),
_ => None,
}
}
pub fn into_table_string(self) -> HashMap<String, String> {
let Self::Table(table) = self else {
return Default::default();
};
let mut map = HashMap::with_capacity(table.len());
for pair in table {
if let (DataKey::String(k), Self::String(v)) = pair {
map.insert(k, v);
}
}
map
}
#[inline]
pub fn shallow_clone(&self) -> Option<Self> {
match self {
Self::Boolean(b) => Some(Self::Boolean(*b)),
Self::String(s) => Some(Self::String(s.clone())),
_ => None,
}
}
}
// --- Key
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DataKey {
Nil,
Boolean(bool),
Integer(i64),
Number(OrderedFloat),
String(String),
#[serde(skip)]
Url(Url),
}
impl DataKey {
#[inline]
pub fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) }
}

View file

@ -1,9 +1,11 @@
#![allow(clippy::module_inception)] #![allow(clippy::module_inception)]
mod cmd; mod cmd;
mod data;
mod event; mod event;
mod render; mod render;
pub use cmd::*; pub use cmd::*;
pub use data::*;
pub use event::*; pub use event::*;
pub use render::*; pub use render::*;

View file

@ -38,7 +38,7 @@ impl FilesOp {
#[inline] #[inline]
pub fn emit(self) { pub fn emit(self) {
emit!(Call(Cmd::new("update_files").with_data(self), Layer::Manager)); emit!(Call(Cmd::new("update_files").with_any("op", self), Layer::Manager));
} }
pub fn prepare(url: &Url) -> u64 { pub fn prepare(url: &Url) -> u64 {