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 {
#[inline]
pub fn to_seq(&self) -> VecDeque<Cmd> {
self.run.iter().map(|e| e.clone_without_data()).collect()
}
pub fn to_seq(&self) -> VecDeque<Cmd> { self.run.iter().map(|c| c.shallow_clone()).collect() }
}
impl Control {

View file

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

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt {
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 {
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 {

View file

@ -16,10 +16,10 @@ pub struct Opt {
impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self {
Self {
cache: mem::take(&mut c.args),
cache_name: c.take_name("cache-name").unwrap_or_default(),
word: c.take_name("word").unwrap_or_default(),
ticket: c.take_name("ticket").and_then(|v| v.parse().ok()).unwrap_or(0),
cache: c.take_any("cache").unwrap_or_default(),
cache_name: c.take_str("cache-name").unwrap_or_default(),
word: c.take_str("word").unwrap_or_default(),
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() {
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 {
return;

View file

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

View file

@ -45,7 +45,7 @@ pub enum FilterCase {
impl From<&Cmd> for FilterCase {
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,
(_, false) => Self::Sensitive,
(_, true) => Self::Insensitive,

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt {
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 {

View file

@ -7,7 +7,7 @@ pub struct 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 {
fn from(under: bool) -> Self { Self { under } }

View file

@ -8,7 +8,7 @@ pub struct 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 {
fn from(submit: bool) -> Self { Self { submit } }

View file

@ -18,8 +18,8 @@ pub struct Opt {
impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self {
Self {
word: c.take_first().unwrap_or_default(),
ticket: c.take_name("ticket").and_then(|s| s.parse().ok()).unwrap_or(0),
word: c.take_first_str().unwrap_or_default(),
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 {
fn from(c: Cmd) -> Self {
Self { cut: c.named.contains_key("cut"), insert: c.named.contains_key("insert") }
}
fn from(c: Cmd) -> Self { Self { cut: c.get_bool("cut"), insert: c.get_bool("insert") } }
}
impl Input {

View file

@ -7,7 +7,7 @@ pub struct 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 {

View file

@ -7,7 +7,7 @@ pub struct 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 {
fn from(append: bool) -> Self { Self { append } }

View file

@ -9,7 +9,7 @@ pub struct 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 {

View file

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

View file

@ -7,7 +7,7 @@ pub struct 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 {

View file

@ -1,10 +1,24 @@
use yazi_proxy::options::InputOpt;
use yazi_shared::render;
use tokio::sync::mpsc;
use yazi_config::popup::InputCfg;
use yazi_shared::{event::Cmd, render, InputError};
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 {
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 };
self.close(false);

View file

@ -12,7 +12,7 @@ pub struct 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 {

View file

@ -9,7 +9,7 @@ pub struct 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 {
fn from(url: Option<Url>) -> Self { Self { url } }

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ impl From<()> for Opt {
fn from(_: ()) -> Self { Self::default() }
}
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 {

View file

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

View file

@ -18,9 +18,9 @@ pub struct Opt {
impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self {
Self {
force: c.named.contains_key("force"),
empty: c.take_name("empty").unwrap_or_default(),
cursor: c.take_name("cursor").unwrap_or_default(),
force: c.get_bool("force"),
empty: c.take_str("empty").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 {
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 {
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 {
fn from(mut c: Cmd) -> Self {
if c.named.contains_key("current") {
if c.get_bool("current") {
Self { url: Default::default(), current: true }
} else {
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,
}
}

View file

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

View file

@ -12,7 +12,9 @@ pub struct Opt {
impl TryFrom<Cmd> for Opt {
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 {

View file

@ -1,19 +1,18 @@
use std::collections::HashMap;
use yazi_dds::ValueSendable;
use yazi_shared::{event::Cmd, fs::Url, render};
use crate::{manager::{Manager, LINKED}, tasks::Tasks};
pub struct Opt {
data: ValueSendable,
updates: HashMap<String, String>,
}
impl TryFrom<Cmd> for Opt {
type 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 updates = opt
.data
.into_table_string()
.updates
.into_iter()
.map(|(url, mime)| (Url::from(url), mime))
.filter(|(url, mime)| self.mimetype.get(url) != Some(mime))

View file

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

View file

@ -8,7 +8,7 @@ pub struct 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 {

View file

@ -13,7 +13,7 @@ impl TryFrom<Cmd> for Opt {
type 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 {
return Err(());
}

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt {
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 {
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 {
fn from(submit: bool) -> Self { Self { submit } }

View file

@ -1,10 +1,24 @@
use yazi_proxy::options::SelectOpt;
use yazi_shared::render;
use tokio::sync::oneshot;
use yazi_config::popup::SelectCfg;
use yazi_shared::{event::Cmd, render};
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 {
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 {
return;
};

View file

@ -10,7 +10,7 @@ pub struct Opt {
impl From<Cmd> for Opt {
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 {
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() {
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 {

View file

@ -9,7 +9,7 @@ pub struct 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 {

View file

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

View file

@ -18,9 +18,9 @@ pub struct Opt {
impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self {
Self {
query: c.take_first().unwrap_or_default(),
query: c.take_first_str().unwrap_or_default(),
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 {
fn from(mut c: Cmd) -> Self {
Self {
query: c.take_first(),
prev: c.named.contains_key("previous"),
case: FilterCase::from(&c),
}
Self { query: c.take_first_str(), prev: c.get_bool("previous"), case: FilterCase::from(&c) }
}
}
@ -29,7 +25,7 @@ pub struct 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 {

View file

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

View file

@ -17,9 +17,9 @@ pub enum OptType {
}
impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self {
fn from(mut c: Cmd) -> 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("zoxide") => OptType::Zoxide,
_ => OptType::None,

View file

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

View file

@ -11,7 +11,7 @@ impl TryFrom<Cmd> for Opt {
type 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 {
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() {
target.set_path(expand_path(&target))
}

View file

@ -42,7 +42,7 @@ pub struct 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 {

View file

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

View file

@ -8,9 +8,9 @@ pub struct Opt {
}
impl From<Cmd> for Opt {
fn from(c: Cmd) -> Self {
fn from(mut c: Cmd) -> 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("false") => Some(false),
_ => None,

View file

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

View file

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

View file

@ -9,7 +9,7 @@ pub struct 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 {

View file

@ -8,7 +8,7 @@ pub struct Opt {
impl From<Cmd> for Opt {
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;
impl Tasks {
#[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);
}
#[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);
}
}

View file

@ -29,7 +29,7 @@ impl Tasks {
let new = TasksProgress::from(&*ongoing.lock());
if 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> {
Ok(Self {
tx: c.take_data().ok_or(())?,
idx: c.take_first().and_then(|s| s.parse().ok()).ok_or(())?,
tx: c.take_any("tx").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> {
Ok(Self {
cands: c.take_data().unwrap_or_default(),
layer: Layer::from_str(&c.take_name("layer").unwrap_or_default())?,
silent: c.named.contains_key("silent"),
cands: c.take_any("candidates").unwrap_or_default(),
layer: Layer::from_str(&c.take_str("layer").unwrap_or_default())?,
silent: c.get_bool("silent"),
})
}
}

View file

@ -1,24 +1,25 @@
use mlua::{IntoLua, Lua, Value};
use serde::Serialize;
use yazi_shared::event::Data;
use super::Body;
use crate::ValueSendable;
use crate::Sendable;
#[derive(Debug)]
pub struct BodyCustom {
pub kind: String,
pub value: ValueSendable,
pub kind: String,
pub data: Data,
}
impl BodyCustom {
#[inline]
pub fn from_str(kind: &str, value: &str) -> anyhow::Result<Body<'static>> {
Ok(Self { kind: kind.to_owned(), value: serde_json::from_str(value)? }.into())
pub fn from_str(kind: &str, data: &str) -> anyhow::Result<Body<'static>> {
Ok(Self { kind: kind.to_owned(), data: serde_json::from_str(data)? }.into())
}
#[inline]
pub fn from_lua(kind: &str, value: Value) -> mlua::Result<Body<'static>> {
Ok(Self { kind: kind.to_owned(), value: value.try_into()? }.into())
pub fn from_lua(kind: &str, data: Value) -> mlua::Result<Body<'static>> {
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 {
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 {
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) {
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 mlua::{ExternalError, IntoLua, Lua, Value, Variadic};
use serde::{Deserialize, Serialize};
use yazi_shared::OrderedFloat;
use mlua::{ExternalError, Lua, Table, Value, Variadic};
use yazi_shared::{event::{Data, DataKey}, OrderedFloat};
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ValueSendable {
Nil,
Boolean(bool),
Integer(i64),
Number(f64),
String(String),
Table(HashMap<ValueSendableKey, ValueSendable>),
}
pub struct Sendable;
impl ValueSendable {
pub fn try_from_variadic(values: Variadic<Value>) -> mlua::Result<Vec<Self>> {
impl Sendable {
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());
for value in values {
vec.push(Self::try_from(value)?);
vec.push(Self::value_to_data(value)?);
}
Ok(vec)
}
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 (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> {
fn value_to_key(value: Value) -> mlua::Result<DataKey> {
Ok(match value {
Value::Nil => Self::Nil,
Value::Boolean(b) => Self::Boolean(b),
Value::Nil => DataKey::Nil,
Value::Boolean(v) => DataKey::Boolean(v),
Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?,
Value::Integer(n) => Self::Integer(n),
Value::Number(n) => Self::Number(n),
Value::String(s) => Self::String(s.to_str()?.to_owned()),
Value::Table(t) => {
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::Integer(v) => DataKey::Integer(v),
Value::Number(v) => DataKey::Number(OrderedFloat::new(v)),
Value::String(v) => DataKey::String(v.to_str()?.to_owned()),
Value::Table(_) => Err("table 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::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())?,
})
}
}
impl IntoLua<'_> for ValueSendable {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
match self {
Self::Nil => Ok(Value::Nil),
Self::Boolean(v) => Ok(Value::Boolean(v)),
Self::Integer(v) => Ok(Value::Integer(v)),
Self::Number(v) => Ok(Value::Number(v)),
Self::String(v) => Ok(Value::String(lua.create_string(v)?)),
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())?,
fn key_to_value(lua: &Lua, key: DataKey) -> mlua::Result<Value> {
Ok(match key {
DataKey::Nil => Value::Nil,
DataKey::Boolean(k) => Value::Boolean(k),
DataKey::Integer(k) => Value::Integer(k),
DataKey::Number(k) => Value::Number(k.get()),
DataKey::String(k) => Value::String(lua.create_string(k)?),
DataKey::Url(k) => Value::UserData(lua.create_any_userdata(k)?),
})
}
}
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 {
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;
};

View file

@ -3,7 +3,8 @@ use std::fmt::Display;
use mlua::TableExt;
use scopeguard::defer;
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 crate::{app::App, lives::Lives};
@ -16,7 +17,7 @@ impl App {
};
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) {
@ -25,14 +26,15 @@ impl App {
tokio::spawn(async move {
if LOADER.ensure(&opt.name).await.is_ok() {
Self::_plugin_do(opt.name, opt.data);
Self::_plugin_do(opt);
}
});
}
#[inline]
pub(crate) fn _plugin_do(name: String, data: OptData) {
emit!(Call(Cmd::args("plugin_do", vec![name]).with_data(data), Layer::App));
pub(crate) fn _plugin_do(opt: yazi_plugin::Opt) {
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>) {
@ -53,10 +55,10 @@ impl App {
};
_ = Lives::scope(&self.cx, |_| {
if let Some(cb) = opt.data.cb {
if let Some(cb) = opt.cb {
cb(&LUA, plugin)
} 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 {
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 {

View file

@ -12,7 +12,7 @@ impl TryFrom<Cmd> for Opt {
type 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);
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)
} else {
self.app.cx.input.complete(cmd)

View file

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

View file

@ -1,11 +1,12 @@
use mlua::{ExternalError, ExternalResult, Table, TableExt};
use tokio::runtime::Handle;
use yazi_dds::ValueSendable;
use yazi_dds::Sendable;
use yazi_shared::event::Data;
use super::slim_lua;
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()?;
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());
};
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
.into_lua_err()?

View file

@ -6,7 +6,7 @@ use yazi_config::LAYOUT;
use yazi_shared::{emit, event::Cmd, Layer};
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 {
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) {
let data = OptData {
cb: Some(Box::new(move |_, plugin| {
plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("skip", skip)?;
plugin.raw_set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?;
plugin.raw_set("window", Window::default())?;
plugin.call_method("peek", ())
})),
..Default::default()
};
emit!(Call(
Cmd::args("plugin", vec![cmd.name.to_owned()]).with_bool("sync", true).with_data(data),
Layer::App
));
let cb: OptCallback = Box::new(move |_, plugin| {
plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("skip", skip)?;
plugin.raw_set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?;
plugin.raw_set("window", Window::default())?;
plugin.call_method("peek", ())
});
let cmd: Cmd =
Opt { name: cmd.name.to_owned(), sync: true, cb: Some(cb), ..Default::default() }.into();
emit!(Call(cmd.with_name("plugin"), Layer::App));
}

View file

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

View file

@ -1,34 +1,43 @@
use anyhow::bail;
use mlua::{Lua, Table};
use yazi_dds::ValueSendable;
use yazi_shared::event::Cmd;
use yazi_shared::event::{Cmd, Data};
pub(super) type OptCallback = Box<dyn FnOnce(&Lua, Table) -> mlua::Result<()> + Send>;
#[derive(Default)]
pub struct Opt {
pub name: String,
pub sync: bool,
pub data: OptData,
}
#[derive(Default)]
pub struct OptData {
pub args: Vec<ValueSendable>,
pub cb: Option<Box<dyn FnOnce(&Lua, Table) -> mlua::Result<()> + Send>>,
pub args: Vec<Data>,
pub cb: Option<OptCallback>,
}
impl TryFrom<Cmd> for Opt {
type Error = anyhow::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");
};
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") {
data.args = shell_words::split(args)?.into_iter().map(ValueSendable::String).collect();
}
Ok(Self { name, sync: c.named.contains_key("sync"), data })
Ok(Self { name, sync: c.get_bool("sync"), args, cb: c.take_any("callback") })
}
}
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 mlua::{ExternalError, Lua, Table, Value};
use yazi_dds::ValueSendable;
use yazi_shared::{emit, event::Cmd, render, Layer};
use yazi_dds::Sendable;
use yazi_shared::{emit, event::{Cmd, Data}, render, Layer};
use super::Utils;
impl Utils {
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, HashMap<String, String>)> {
let mut args = vec![];
let mut named = HashMap::new();
for result in t.pairs::<Value, Value>() {
let (k, v) = result?;
fn parse_args(t: Table) -> mlua::Result<HashMap<String, Data>> {
let mut args = HashMap::with_capacity(t.raw_len());
for pair in t.pairs::<Value, Value>() {
let (k, v) = pair?;
match k {
Value::Integer(_) => {
args.push(match 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::Integer(i) if i > 0 => {
args.insert((i - 1).to_string(), Sendable::value_to_data(v)?);
}
Value::String(s) => {
let v = match 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);
args.insert(s.to_str()?.replace('_', "-"), Sendable::value_to_data(v)?);
}
_ => return Err("invalid key in cmd".into_lua_err()),
}
}
Ok((args, named))
}
#[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)
Ok(args)
}
pub(super) fn call(lua: &Lua, ya: &Table) -> mlua::Result<()> {
@ -60,16 +35,16 @@ impl Utils {
ya.raw_set(
"app_emit",
lua.create_function(|_, (name, table, data): (String, Table, Option<Value>)| {
emit!(Call(Self::create_cmd(name, table, data)?, Layer::App));
lua.create_function(|_, (name, args): (String, Table)| {
emit!(Call(Cmd { name, args: Self::parse_args(args)? }, Layer::App));
Ok(())
})?,
)?;
ya.raw_set(
"manager_emit",
lua.create_function(|_, (name, table, data): (String, Table, Option<Value>)| {
emit!(Call(Self::create_cmd(name, table, data)?, Layer::Manager));
lua.create_function(|_, (name, args): (String, Table)| {
emit!(Call(Cmd { name, args: Self::parse_args(args)? }, Layer::Manager));
Ok(())
})?,
)?;

View file

@ -37,7 +37,7 @@ impl Utils {
let cand = cand?;
cands.push(Control {
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(),
});
}
@ -46,8 +46,8 @@ impl Utils {
emit!(Call(
Cmd::new("show")
.with("layer", Layer::Which)
.with_bool("silent", t.raw_get("silent").unwrap_or_default())
.with_data(cands),
.with_any("candidates", cands)
.with_bool("silent", t.raw_get("silent").unwrap_or_default()),
Layer::Which
));

View file

@ -44,7 +44,7 @@ impl Utils {
};
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)
})?,
)?;
@ -67,7 +67,7 @@ impl Utils {
..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)
})?,
)?;
@ -78,7 +78,7 @@ impl Utils {
let mut lock = PreviewLock::try_from(t)?;
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(())
})?,
)?;

View file

@ -1,10 +1,10 @@
use mlua::{ExternalError, ExternalResult, Function, IntoLua, Lua, Table, Value, Variadic};
use tokio::sync::oneshot;
use yazi_dds::ValueSendable;
use yazi_shared::{emit, event::Cmd, Layer};
use yazi_dds::Sendable;
use yazi_shared::{emit, event::{Cmd, Data}, Layer};
use super::Utils;
use crate::{loader::LOADER, runtime::RtRef, OptData};
use crate::{loader::LOADER, runtime::RtRef, OptCallback};
impl Utils {
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();
};
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,
calls: usize,
args: Variadic<Value<'_>>,
) -> mlua::Result<mlua::Variadic<ValueSendable>> {
let args = ValueSendable::try_from_variadic(args)?;
let (tx, rx) = oneshot::channel::<Vec<ValueSendable>>();
) -> mlua::Result<Vec<Data>> {
let args = Sendable::variadic_to_vec(args)?;
let (tx, rx) = oneshot::channel::<Vec<Data>>();
let data = OptData {
cb: Some({
let name = name.clone();
Box::new(move |lua, plugin| {
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else {
return Err("sync block not found".into_lua_err());
};
let callback: OptCallback = {
let name = name.clone();
Box::new(move |lua, plugin| {
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else {
return Err("sync block not found".into_lua_err());
};
let mut self_args = Vec::with_capacity(args.len() + 1);
self_args.push(Value::Table(plugin));
for arg in args {
self_args.push(arg.into_lua(lua)?);
}
let mut self_args = Vec::with_capacity(args.len() + 1);
self_args.push(Value::Table(plugin));
for arg in args {
self_args.push(Sendable::data_to_value(lua, arg)?);
}
let values =
ValueSendable::try_from_variadic(block.call(Variadic::from_iter(self_args))?)?;
tx.send(values).map_err(|_| "send failed".into_lua_err())
})
}),
..Default::default()
let values = Sendable::variadic_to_vec(block.call(Variadic::from_iter(self_args))?)?;
tx.send(values).map_err(|_| "send failed".into_lua_err())
})
};
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
));
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()
})?))
})
}
}

View file

@ -11,7 +11,7 @@ impl AppProxy {
#[inline]
pub async fn stop() {
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();
}
@ -22,13 +22,13 @@ impl AppProxy {
#[inline]
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]
pub fn notify_warn(title: &str, content: &str) {
emit!(Call(
Cmd::new("notify").with_data(NotifyOpt {
Cmd::new("notify").with_any("option", NotifyOpt {
title: title.to_owned(),
content: content.to_owned(),
level: NotifyLevel::Warn,

View file

@ -2,15 +2,13 @@ use tokio::sync::mpsc;
use yazi_config::popup::InputCfg;
use yazi_shared::{emit, event::Cmd, InputError, Layer};
use crate::options::InputOpt;
pub struct InputProxy;
impl InputProxy {
#[inline]
pub fn show(cfg: InputCfg) -> mpsc::UnboundedReceiver<Result<String, InputError>> {
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
}

View file

@ -25,13 +25,13 @@ impl ManagerProxy {
#[inline]
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]
pub fn remove_do(targets: Vec<Url>, permanently: bool) {
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
));
}

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 open;
mod process;
mod select;
pub use input::*;
pub use notify::*;
pub use open::*;
pub use process::*;
pub use select::*;

View file

@ -14,7 +14,7 @@ pub struct NotifyOpt {
impl TryFrom<Cmd> for NotifyOpt {
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 {

View file

@ -12,7 +12,7 @@ pub struct 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
@ -24,5 +24,5 @@ pub struct OpenWithOpt {
impl TryFrom<Cmd> for OpenWithOpt {
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 {
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_shared::{emit, event::Cmd, Layer};
use crate::options::SelectOpt;
pub struct SelectProxy;
impl SelectProxy {
#[inline]
pub async fn show(cfg: SelectCfg) -> anyhow::Result<usize> {
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?
}
}

View file

@ -11,14 +11,17 @@ pub struct TasksProxy;
impl TasksProxy {
#[inline]
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]
pub async fn process_exec(args: Vec<OsString>, opener: Cow<'static, Opener>) {
let (tx, rx) = oneshot::channel();
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
));
rx.await.ok();

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::VecDeque, fs::Metadata, path::{Path, PathBuf}};
use anyhow::Result;
use anyhow::{anyhow, Result};
use futures::{future::BoxFuture, FutureExt};
use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
@ -51,9 +51,10 @@ impl File {
if task.retry < TASKS.bizarre_retry
&& matches!(e.raw_os_error(), Some(1) | Some(93)) =>
{
self.log(task.id, format!("Paste task retry: {:?}", task))?;
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)?,
}
@ -147,9 +148,9 @@ impl File {
self.prog.send(TaskProg::New(id, meta.len()))?;
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() {
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);
}
@ -193,9 +194,9 @@ impl File {
self.prog.send(TaskProg::New(task.id, meta.len()))?;
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() {
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.macro_.send(FileOp::Link(task).into(), NORMAL).await?;
self.queue(FileOp::Link(task), NORMAL).await?;
self.succ(id)
}
@ -219,7 +220,7 @@ impl File {
let id = task.id;
task.length = 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);
}
@ -238,7 +239,7 @@ impl File {
task.target = Url::from(entry.path());
task.length = 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)
@ -249,7 +250,7 @@ impl File {
task.length = calculate_size(&task.target).await;
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)
}
@ -296,6 +297,11 @@ impl File {
fn log(&self, id: usize, line: String) -> Result<()> {
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 {

View file

@ -1,4 +1,4 @@
use yazi_dds::ValueSendable;
use yazi_shared::event::Data;
#[derive(Debug)]
pub enum PluginOp {
@ -17,5 +17,5 @@ impl PluginOp {
pub struct PluginOpEntry {
pub id: usize,
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 yazi_plugin::isolate;
@ -43,7 +43,7 @@ impl Plugin {
let id = task.id;
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)
}
}
@ -56,4 +56,9 @@ impl Plugin {
fn fail(&self, id: usize, reason: String) -> Result<()> {
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 anyhow::Result;
use anyhow::{anyhow, Result};
use parking_lot::RwLock;
use tokio::sync::mpsc;
use tracing::error;
@ -73,8 +73,8 @@ impl Preload {
self.prog.send(TaskProg::New(id, 0))?;
match task.plugin.prio {
Priority::Low => self.macro_.send(PreloadOp::Rule(task).into(), NORMAL).await?,
Priority::Normal => self.macro_.send(PreloadOp::Rule(task).into(), HIGH).await?,
Priority::Low => self.queue(PreloadOp::Rule(task), NORMAL).await?,
Priority::Normal => self.queue(PreloadOp::Rule(task), HIGH).await?,
Priority::High => self.work(PreloadOp::Rule(task)).await?,
}
self.succ(id)
@ -97,4 +97,9 @@ impl Preload {
fn fail(&self, id: usize, reason: String) -> Result<()> {
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 tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle};
use yazi_config::{open::Opener, plugin::PluginRule, TASKS};
use yazi_dds::{Pump, ValueSendable};
use yazi_shared::{fs::{unique_path, Url}, Throttle};
use yazi_dds::Pump;
use yazi_shared::{event::Data, fs::{unique_path, Url}, Throttle};
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};
@ -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 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}`"));
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)]
pub struct Cmd {
pub name: String,
pub args: Vec<String>,
pub named: HashMap<String, String>,
pub data: Option<Box<dyn Any + Send>>,
pub name: String,
pub args: HashMap<String, Data>,
}
impl Cmd {
@ -14,49 +14,70 @@ impl Cmd {
#[inline]
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]
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
}
#[inline]
pub fn with_bool(mut self, name: impl ToString, state: bool) -> Self {
if state {
self.named.insert(name.to_string(), Default::default());
}
self.args.insert(name.to_string(), Data::Boolean(state));
self
}
#[inline]
pub fn with_data(mut self, data: impl Any + Send) -> Self {
self.data = Some(Box::new(data));
pub fn with_any(mut self, name: impl ToString, data: impl Any + Send) -> Self {
self.args.insert(name.to_string(), Data::Any(Box::new(data)));
self
}
#[inline]
pub fn take_data<T: 'static>(&mut self) -> Option<T> {
self.data.take().and_then(|d| d.downcast::<T>().ok()).map(|d| *d)
pub fn with_name(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
self
}
#[inline]
pub fn take_first(&mut self) -> Option<String> {
if self.args.is_empty() { None } else { Some(mem::take(&mut self.args[0])) }
pub fn get_str(&self, name: &str) -> Option<&str> { self.args.get(name).and_then(Data::as_str) }
#[inline]
pub fn get_bool(&self, name: &str) -> bool {
self.args.get(name).and_then(Data::as_bool).unwrap_or(false)
}
#[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]
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 {
name: self.name.clone(),
args: self.args.clone(),
named: self.named.clone(),
data: None,
name: self.name.clone(),
args: self
.args
.iter()
.filter_map(|(k, v)| v.shallow_clone().map(|v| (k.clone(), v)))
.collect(),
}
}
}
@ -64,13 +85,15 @@ impl Cmd {
impl Display for Cmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)?;
if !self.args.is_empty() {
write!(f, " {}", self.args.join(" "))?;
}
for (k, v) in &self.named {
write!(f, " --{k}")?;
if !v.is_empty() {
write!(f, "={v}")?;
for (k, v) in &self.args {
if k.as_bytes().first().is_some_and(|b| b.is_ascii_digit()) {
if let Some(s) = v.as_str() {
write!(f, " {s}")?;
}
} else if v.as_bool().is_some_and(|b| b) {
write!(f, " --{k}")?;
} else if let Some(s) = v.as_str() {
write!(f, " --{k}={s}")?;
}
}
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)]
mod cmd;
mod data;
mod event;
mod render;
pub use cmd::*;
pub use data::*;
pub use event::*;
pub use render::*;

View file

@ -38,7 +38,7 @@ impl FilesOp {
#[inline]
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 {