From 20dc0550bc2266fef04f1b3b0b3ec662431951a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sun, 1 Dec 2024 23:12:49 +0800 Subject: [PATCH] feat: new `ya emit` and `ya emit-to` subcommands to emit commands to a specified instance for execution (#1979) --- cspell.json | 2 +- yazi-boot/src/lib.rs | 2 +- yazi-cli/Cargo.toml | 2 + yazi-cli/src/args.rs | 95 +++++++++++++++++------- yazi-cli/src/main.rs | 49 ++++++++---- yazi-config/preset/keymap.toml | 2 +- yazi-core/src/tab/commands/toggle.rs | 8 +- yazi-core/src/tab/commands/toggle_all.rs | 4 +- yazi-plugin/preset/plugins/dds.lua | 17 ++++- yazi-proxy/src/options/plugin.rs | 2 +- yazi-shared/src/event/cmd.rs | 12 ++- yazi-shared/src/event/data.rs | 29 +++++++- yazi-shared/src/number.rs | 18 ++++- 13 files changed, 184 insertions(+), 58 deletions(-) diff --git a/cspell.json b/cspell.json index 7a8cc8e88..9e3f47328 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","Sysinfo","ffprobe","vframes","luma"],"language":"en"} \ No newline at end of file +{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","Sysinfo","ffprobe","vframes","luma","obase"],"version":"0.2","language":"en"} \ No newline at end of file diff --git a/yazi-boot/src/lib.rs b/yazi-boot/src/lib.rs index e4c1279d1..7d1260bf8 100644 --- a/yazi-boot/src/lib.rs +++ b/yazi-boot/src/lib.rs @@ -10,7 +10,7 @@ pub static BOOT: RoCell = RoCell::new(); pub fn init() { ARGS.with(<_>::parse); - BOOT.init(From::from(&*ARGS)); + BOOT.init(<_>::from(&*ARGS)); actions::Actions::act(&ARGS); } diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index 858081891..34c99095d 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -24,6 +24,8 @@ tokio = { workspace = true } toml_edit = "0.22.22" [build-dependencies] +yazi-shared = { path = "../yazi-shared", version = "0.3.3" } + # External build dependencies anyhow = { workspace = true } clap = { workspace = true } diff --git a/yazi-cli/src/args.rs b/yazi-cli/src/args.rs index 11a3697ce..a71f9c795 100644 --- a/yazi-cli/src/args.rs +++ b/yazi-cli/src/args.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use anyhow::{Result, bail}; use clap::{Parser, Subcommand, command}; +use yazi_shared::event::Cmd; #[derive(Parser)] #[command(name = "Ya", about, long_about = None)] @@ -16,14 +17,55 @@ pub(super) struct Args { #[derive(Subcommand)] pub(super) enum Command { + /// Emit a command to be executed by the current instance. + Emit(CommandEmit), + /// Emit a command to be executed by the specified instance. + EmitTo(CommandEmitTo), + /// Manage packages. + Pack(CommandPack), /// Publish a message to the current instance. Pub(CommandPub), /// Publish a message to the specified instance. PubTo(CommandPubTo), /// Subscribe to messages from all remote instances. Sub(CommandSub), - /// Manage packages. - Pack(CommandPack), +} + +#[derive(clap::Args)] +pub(super) struct CommandEmit { + /// The name of the command. + pub(super) name: String, + /// The arguments of the command. + #[arg(allow_hyphen_values = true, trailing_var_arg = true)] + pub(super) args: Vec, +} + +#[derive(clap::Args)] +pub(super) struct CommandEmitTo { + /// The receiver ID. + pub(super) receiver: u64, + /// The name of the command. + pub(super) name: String, + /// The arguments of the command. + #[arg(allow_hyphen_values = true, trailing_var_arg = true)] + pub(super) args: Vec, +} + +#[derive(clap::Args)] +#[command(arg_required_else_help = true)] +pub(super) struct CommandPack { + /// Add a package. + #[arg(short = 'a', long)] + pub(super) add: Option, + /// Install all packages. + #[arg(short = 'i', long)] + pub(super) install: bool, + /// List all packages. + #[arg(short = 'l', long)] + pub(super) list: bool, + /// Upgrade all packages. + #[arg(short = 'u', long)] + pub(super) upgrade: bool, } #[derive(clap::Args)] @@ -44,7 +86,7 @@ pub(super) struct CommandPub { impl CommandPub { #[allow(dead_code)] - pub(super) fn receiver(&self) -> Result { + pub(super) fn receiver() -> Result { if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) { Ok(s.parse()?) } else { @@ -79,42 +121,39 @@ pub(super) struct CommandSub { pub(super) kinds: String, } -#[derive(clap::Args)] -#[command(arg_required_else_help = true)] -pub(super) struct CommandPack { - /// Add a package. - #[arg(short = 'a', long)] - pub(super) add: Option, - /// Install all packages. - #[arg(short = 'i', long)] - pub(super) install: bool, - /// List all packages. - #[arg(short = 'l', long)] - pub(super) list: bool, - /// Upgrade all packages. - #[arg(short = 'u', long)] - pub(super) upgrade: bool, +// --- Macros +macro_rules! impl_emit_body { + ($name:ident) => { + impl $name { + #[allow(dead_code)] + pub(super) fn body(self) -> Result { + Ok(serde_json::to_string(&(self.name, Cmd::parse_args(self.args.into_iter(), false)?))?) + } + } + }; } -// --- Macros -macro_rules! impl_body { +macro_rules! impl_pub_body { ($name:ident) => { impl $name { #[allow(dead_code)] pub(super) fn body(&self) -> Result> { - if let Some(json) = &self.json { - Ok(json.into()) + Ok(if let Some(json) = &self.json { + json.into() } else if let Some(str) = &self.str { - Ok(serde_json::to_string(str)?.into()) + serde_json::to_string(str)?.into() } else if !self.list.is_empty() { - Ok(serde_json::to_string(&self.list)?.into()) + serde_json::to_string(&self.list)?.into() } else { - Ok("".into()) - } + "".into() + }) } } }; } -impl_body!(CommandPub); -impl_body!(CommandPubTo); +impl_emit_body!(CommandEmit); +impl_emit_body!(CommandEmitTo); + +impl_pub_body!(CommandPub); +impl_pub_body!(CommandPubTo); diff --git a/yazi-cli/src/main.rs b/yazi-cli/src/main.rs index dd8807df2..4d59f8b19 100644 --- a/yazi-cli/src/main.rs +++ b/yazi-cli/src/main.rs @@ -19,32 +19,26 @@ async fn main() -> anyhow::Result<()> { } match Args::parse().command { - Command::Pub(cmd) => { + Command::Emit(cmd) => { yazi_boot::init_default(); yazi_dds::init(); - if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver()?, &cmd.body()?).await { - eprintln!("Cannot send message: {e}"); + if let Err(e) = + yazi_dds::Client::shot("dds-emit", CommandPub::receiver()?, &cmd.body()?).await + { + eprintln!("Cannot emit command: {e}"); std::process::exit(1); } } - Command::PubTo(cmd) => { + Command::EmitTo(cmd) => { yazi_boot::init_default(); yazi_dds::init(); - if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver, &cmd.body()?).await { - eprintln!("Cannot send message: {e}"); + if let Err(e) = yazi_dds::Client::shot("dds-emit", cmd.receiver, &cmd.body()?).await { + eprintln!("Cannot emit command: {e}"); std::process::exit(1); } } - Command::Sub(cmd) => { - yazi_boot::init_default(); - yazi_dds::init(); - yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?; - - tokio::signal::ctrl_c().await?; - } - Command::Pack(cmd) => { package::init()?; if cmd.install { @@ -60,6 +54,33 @@ async fn main() -> anyhow::Result<()> { package::Package::add_to_config(&repo).await?; } } + + Command::Pub(cmd) => { + yazi_boot::init_default(); + yazi_dds::init(); + if let Err(e) = yazi_dds::Client::shot(&cmd.kind, CommandPub::receiver()?, &cmd.body()?).await + { + eprintln!("Cannot send message: {e}"); + std::process::exit(1); + } + } + + Command::PubTo(cmd) => { + yazi_boot::init_default(); + yazi_dds::init(); + if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver, &cmd.body()?).await { + eprintln!("Cannot send message: {e}"); + std::process::exit(1); + } + } + + Command::Sub(cmd) => { + yazi_boot::init_default(); + yazi_dds::init(); + yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?; + + tokio::signal::ctrl_c().await?; + } } Ok(()) diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 393ad20ed..bb4b6a470 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -44,7 +44,7 @@ keymap = [ # Toggle { on = "", run = [ "toggle", "arrow 1" ], desc = "Toggle the current selection state" }, - { on = "", run = "toggle_all on", desc = "Select all files" }, + { on = "", run = "toggle_all --state=on", desc = "Select all files" }, { on = "", run = "toggle_all", desc = "Invert selection of all files" }, # Visual mode diff --git a/yazi-core/src/tab/commands/toggle.rs b/yazi-core/src/tab/commands/toggle.rs index 7c10c42df..f8b8a2883 100644 --- a/yazi-core/src/tab/commands/toggle.rs +++ b/yazi-core/src/tab/commands/toggle.rs @@ -1,17 +1,19 @@ use yazi_macro::render_and; use yazi_proxy::AppProxy; -use yazi_shared::event::CmdCow; +use yazi_shared::{event::CmdCow, fs::Url}; use crate::tab::Tab; struct Opt { + url: Option, state: Option, } impl From for Opt { fn from(mut c: CmdCow) -> Self { Self { - state: match c.take_first_str().as_deref() { + url: c.take_first_url(), + state: match c.str("state") { Some("on") => Some(true), Some("off") => Some(false), _ => None, @@ -23,7 +25,7 @@ impl From for Opt { impl Tab { #[yazi_codegen::command] pub fn toggle(&mut self, opt: Opt) { - let Some(url) = self.current.hovered().map(|h| &h.url) else { + let Some(url) = opt.url.as_ref().or(self.current.hovered().map(|h| &h.url)) else { return; }; diff --git a/yazi-core/src/tab/commands/toggle_all.rs b/yazi-core/src/tab/commands/toggle_all.rs index e56057160..3bf004d39 100644 --- a/yazi-core/src/tab/commands/toggle_all.rs +++ b/yazi-core/src/tab/commands/toggle_all.rs @@ -9,9 +9,9 @@ struct Opt { } impl From for Opt { - fn from(mut c: CmdCow) -> Self { + fn from(c: CmdCow) -> Self { Self { - state: match c.take_first_str().as_deref() { + state: match c.str("state") { Some("on") => Some(true), Some("off") => Some(false), _ => None, diff --git a/yazi-plugin/preset/plugins/dds.lua b/yazi-plugin/preset/plugins/dds.lua index b8a0452fc..b4ae249e9 100644 --- a/yazi-plugin/preset/plugins/dds.lua +++ b/yazi-plugin/preset/plugins/dds.lua @@ -1,7 +1,22 @@ local M = {} function M:setup() - ps.sub_remote("dds-cd", function(url) ya.manager_emit("cd", { url }) end) + -- TODO: remove this + local b = false + ps.sub_remote("dds-cd", function(url) + if not b then + b = true + ya.notify { + title = "Deprecated DDS Event", + content = "The `dds-cd` event is deprecated, please use `ya emit cd /your/path` instead of `ya pub dds-cd --str /your/path`\n\nSee #1979 for details: https://github.com/sxyazi/yazi/pull/1979", + timeout = 20, + level = "warn", + } + end + ya.manager_emit("cd", { url }) + end) + + ps.sub_remote("dds-emit", function(cmd) ya.manager_emit(cmd[1], cmd[2]) end) end return M diff --git a/yazi-proxy/src/options/plugin.rs b/yazi-proxy/src/options/plugin.rs index 3ac624583..d18dd3b73 100644 --- a/yazi-proxy/src/options/plugin.rs +++ b/yazi-proxy/src/options/plugin.rs @@ -31,7 +31,7 @@ impl TryFrom for PluginOpt { let (args, _old_args) = if let Some(s) = c.str("args") { ( - Cmd::parse_args(shell_words::split(s)?.into_iter())?, + Cmd::parse_args(shell_words::split(s)?.into_iter(), true)?, shell_words::split(s)?.into_iter().map(Data::String).collect(), ) } else { diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs index 7c20544ce..7b8234172 100644 --- a/yazi-shared/src/event/cmd.rs +++ b/yazi-shared/src/event/cmd.rs @@ -106,14 +106,17 @@ impl Cmd { } // Parse - pub fn parse_args(words: impl Iterator) -> Result> { + pub fn parse_args( + words: impl Iterator, + obase: bool, + ) -> Result> { let mut i = 0i64; words .into_iter() .map(|word| { let Some(arg) = word.strip_prefix("--") else { i += 1; - return Ok((DataKey::Integer(i - 1), Data::String(word))); + return Ok((DataKey::Integer(i - obase as i64), Data::String(word))); }; let mut parts = arg.splitn(2, '='); @@ -167,7 +170,10 @@ impl FromStr for Cmd { bail!("command name cannot be empty"); } - Ok(Cmd { name: mem::take(&mut args[0]), args: Cmd::parse_args(args.into_iter().skip(1))? }) + Ok(Cmd { + name: mem::take(&mut args[0]), + args: Cmd::parse_args(args.into_iter().skip(1), true)?, + }) } } diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index cdbbc3f15..fc938aab1 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -1,6 +1,6 @@ use std::{any::Any, borrow::Cow, collections::HashMap}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de}; use crate::{OrderedFloat, fs::Url}; @@ -87,6 +87,7 @@ impl Data { pub enum DataKey { Nil, Boolean(bool), + #[serde(deserialize_with = "Self::deserialize_integer")] Integer(i64), Number(OrderedFloat), String(Cow<'static, str>), @@ -105,6 +106,32 @@ impl DataKey { _ => None, } } + + fn deserialize_integer<'de, D>(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl de::Visitor<'_> for Visitor { + type Value = i64; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an integer or a string of an integer") + } + + fn visit_i64(self, value: i64) -> Result { Ok(value) } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(de::Error::custom) + } + } + + deserializer.deserialize_any(Visitor) + } } impl From for DataKey { diff --git a/yazi-shared/src/number.rs b/yazi-shared/src/number.rs index 774af07cf..132397e8f 100644 --- a/yazi-shared/src/number.rs +++ b/yazi-shared/src/number.rs @@ -1,8 +1,8 @@ use std::hash::{Hash, Hasher}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize)] #[serde(transparent)] pub struct OrderedFloat(f64); @@ -26,3 +26,17 @@ impl PartialEq for OrderedFloat { } impl Eq for OrderedFloat {} + +impl<'de> Deserialize<'de> for OrderedFloat { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let f = f64::deserialize(deserializer)?; + if f.is_nan() { + Err(serde::de::Error::custom("NaN is not a valid OrderedFloat")) + } else { + Ok(Self::new(f)) + } + } +}