feat: ya emit and ya emit-to support invalid UTF-8 as command argument values (#3290)

This commit is contained in:
三咲雅 misaki masa 2025-10-28 18:21:57 +08:00 committed by sxyazi
parent 440e67164a
commit 564b885196
No known key found for this signature in database
6 changed files with 64 additions and 38 deletions

View file

@ -19,19 +19,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Shell formatting ([#3232]) - Shell formatting ([#3232])
- Multi-entry support for plugin system ([#3154]) - Multi-entry support for plugin system ([#3154])
- Zoom in or out of the preview image ([#2864]) - Zoom in or out of the preview image ([#2864])
- Improve the UX of the pick and input component ([#2906], [#2935]) - Improve the UX of the pick and input components ([#2906], [#2935])
- Show progress of each task in task manager ([#3121], [#3131], [#3134]) - Show progress of each task in task manager ([#3121], [#3131], [#3134])
- New `bulk_rename` command always renames files with the editor ([#2984]) - New `bulk_rename` command always renames files with the editor ([#2984])
- `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037]) - `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037])
- New `--bg` specifying image background color for the preset `svg` and `magick` previewers ([#3189]) - New `--bg` specifying image background color in the preset SVG and ImageMagick previewers ([#3189])
- `filter` by full path (prefix + filename) in search view instead of just filename ([#2915]) - `filter` by full path (prefix + filename) in search view instead of just filename ([#2915])
- New `casefy` command for case conversion of the input content ([#3235]) - New `casefy` command for case conversion of the input content ([#3235])
- Allow dynamic adjustment of layout ratio via `rt.mgr.ratio` ([#2964]) - Allow dynamic adjustment of layout ratio via `rt.mgr.ratio` ([#2964])
- Support `.deb` packages ([#2807], [#3128], [#3209]) - Support `.deb` packages ([#2807], [#3128], [#3209])
- Port several widespread GUI keys to the input component ([#2849]) - Port several widespread GUI keys to the input component ([#2849])
- Support invalid UTF-8 paths throughout the codebase ([#2884], [#2889], [#2890], [#2895], [#3023]) - Support invalid UTF-8 paths throughout the codebase ([#2884], [#2889], [#2890], [#2895], [#3023], [#3290])
- Allow upgrading only specific packages with `ya pkg` ([#2841]) - Allow upgrading only specific packages with `ya pkg` ([#2841])
- Respect the user's `image_filter` setting for the preset `magick` previewer ([#3286]) - Respect the user's `image_filter` setting in the preset ImageMagick previewer ([#3286])
- Allow custom mouse click behavior for individual files ([#2925]) - Allow custom mouse click behavior for individual files ([#2925])
- Display newlines in input as spaces to improve readability ([#2932]) - Display newlines in input as spaces to improve readability ([#2932])
- Fill in error messages if preview fails ([#2917]) - Fill in error messages if preview fails ([#2917])
@ -49,7 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Deprecated ### Deprecated
- Deprecate `$n`, `$@` (Unix-like) and `%n`, `%*` (Windows) in `shell` command and opener rules in favor of new shell formatting ([#3232]) - Deprecate `$n`, `$@` (\*nix) and `%n`, `%*` (Windows) in `shell` command and opener rules in favor of new shell formatting ([#3232])
- Deprecate `ya.hide`, `ya.render`, and `ya.truncate` in favor of `ui.hide`, `ui.render`, and `ui.truncate` ([#2939]) - Deprecate `ya.hide`, `ya.render`, and `ya.truncate` in favor of `ui.hide`, `ui.render`, and `ui.truncate` ([#2939])
- Deprecate `position` property of `ya.input()` in favor of `pos` to align with `ya.confirm()` and its type `ui.Pos` ([#2921]) - Deprecate `position` property of `ya.input()` in favor of `pos` to align with `ya.confirm()` and its type `ui.Pos` ([#2921])
- Deprecate `cx.tasks.progress` in favor of `cx.tasks.summary` ([#3131]) - Deprecate `cx.tasks.progress` in favor of `cx.tasks.summary` ([#3131])
@ -84,7 +84,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Zero-copy `UrlBuf` to `Url` conversion ([#3117]) - Zero-copy `UrlBuf` to `Url` conversion ([#3117])
- String interning to reduce memory usage of mimetype and URL domain ([#3084], [#3091]) - String interning to reduce memory usage of mimetype and URL domain ([#3084], [#3091])
- Do not pre-allocate memory for Lua tables ([#2879]) - Do not pre-allocate memory for Lua tables ([#2879])
- Copy-on-write on command data & avoid converting primitive types to strings thereby allocating memory ([#2862]) - Copy-on-write on command data, and avoid converting primitive types to strings thereby allocating memory ([#2862])
- Use `AnyUserData::type_id()` to reduce stack pushes ([#2834]) - Use `AnyUserData::type_id()` to reduce stack pushes ([#2834])
- App data instead of Lua registry to reduce stack pushes ([#2880]) - App data instead of Lua registry to reduce stack pushes ([#2880])
@ -140,7 +140,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Fixed ### Fixed
- Respect the user's `max_width` setting for the built-in video previewer ([#2560]) - Respect the user's `max_width` setting in the preset video previewer ([#2560])
- Reverse the mixing order of theme and flavor configuration ([#2594]) - Reverse the mixing order of theme and flavor configuration ([#2594])
- No title is set when starts the first time ([#2700]) - No title is set when starts the first time ([#2700])
- `ya pub-to 0` checks if any peer is able to receive the message ([#2697]) - `ya pub-to 0` checks if any peer is able to receive the message ([#2697])
@ -227,9 +227,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- New `tbl_col` and `tbl_cell` in theme system for spotter table styling ([#2391]) - New `tbl_col` and `tbl_cell` in theme system for spotter table styling ([#2391])
- Allow different separators to be applied individually to the left and right sides of the status bar ([#2313]) - Allow different separators to be applied individually to the left and right sides of the status bar ([#2313])
- `ripgrep-all` support for the `search` command ([#2383]) - `ripgrep-all` support for the `search` command ([#2383])
- Respect the user's `max_width` setting for the built-in PDF preloader ([#2331]) - Respect the user's `max_width` setting in the preset PDF preloader ([#2331])
- Respect the user's `wrap` setting for the built-in JSON previewer ([#2337]) - Respect the user's `wrap` setting in the preset JSON previewer ([#2337])
- Respect the user's `image_alloc` setting for the built-in ImageMagick previewer ([#2403]) - Respect the user's `image_alloc` setting in the preset ImageMagick previewer ([#2403])
- New `external` and `removable` fields in the `fs.partitions()` API ([#2343]) - New `external` and `removable` fields in the `fs.partitions()` API ([#2343])
- CSI-based Vim and Neovim built-in terminal detection for better accuracy ([#2327]) - CSI-based Vim and Neovim built-in terminal detection for better accuracy ([#2327])
@ -1532,3 +1532,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3268]: https://github.com/sxyazi/yazi/pull/3268 [#3268]: https://github.com/sxyazi/yazi/pull/3268
[#3271]: https://github.com/sxyazi/yazi/pull/3271 [#3271]: https://github.com/sxyazi/yazi/pull/3271
[#3286]: https://github.com/sxyazi/yazi/pull/3286 [#3286]: https://github.com/sxyazi/yazi/pull/3286
[#3290]: https://github.com/sxyazi/yazi/pull/3290

View file

@ -1,8 +1,8 @@
use std::borrow::Cow; use std::{borrow::Cow, ffi::OsString};
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use yazi_shared::{Id, event::Cmd}; use yazi_shared::{Either, Id};
#[derive(Parser)] #[derive(Parser)]
#[command(name = "Ya", about, long_about = None)] #[command(name = "Ya", about, long_about = None)]
@ -38,7 +38,7 @@ pub(super) struct CommandEmit {
pub(super) name: String, pub(super) name: String,
/// Arguments of the command. /// Arguments of the command.
#[arg(allow_hyphen_values = true, trailing_var_arg = true)] #[arg(allow_hyphen_values = true, trailing_var_arg = true)]
pub(super) args: Vec<String>, pub(super) args: Vec<OsString>,
} }
#[derive(clap::Args)] #[derive(clap::Args)]
@ -49,7 +49,7 @@ pub(super) struct CommandEmitTo {
pub(super) name: String, pub(super) name: String,
/// Arguments of the command. /// Arguments of the command.
#[arg(allow_hyphen_values = true, trailing_var_arg = true)] #[arg(allow_hyphen_values = true, trailing_var_arg = true)]
pub(super) args: Vec<String>, pub(super) args: Vec<OsString>,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -139,10 +139,11 @@ macro_rules! impl_emit_body {
impl $name { impl $name {
#[allow(dead_code)] #[allow(dead_code)]
pub(super) fn body(self) -> Result<String> { pub(super) fn body(self) -> Result<String> {
Ok(serde_json::to_string(&( let cmd: Vec<_> = [Either::Left(self.name)]
self.name, .into_iter()
Cmd::parse_args(self.args.into_iter(), None, false)?, .chain(self.args.into_iter().map(|s| Either::Right(s.into_encoded_bytes())))
))?) .collect();
Ok(serde_json::to_string(&cmd)?)
} }
} }
}; };

View file

@ -30,7 +30,7 @@ impl TryFrom<CmdCow> for PluginOpt {
let args = if let Ok(s) = c.second() { let args = if let Ok(s) = c.second() {
let (words, last) = yazi_shared::shell::split_unix(s, true)?; let (words, last) = yazi_shared::shell::split_unix(s, true)?;
Cmd::parse_args(words.into_iter(), last, true)? Cmd::parse_args(words, last)?
} else { } else {
Default::default() Default::default()
}; };

View file

@ -1,7 +1,21 @@
local M = {} local M = {}
function M:setup() function M:setup()
ps.sub_remote("dds-emit", function(cmd) ya.emit(cmd[1], cmd[2]) end) ps.sub_remote("dds-emit", function(cmd)
local i, args = 1, {}
for j = 2, #cmd do
local word = string.char(table.unpack(cmd[j]))
local key = word:match("^%-%-([^=]+)")
if not key then
i, args[i] = i + 1, word
elseif #key + 2 == #word then
args[key] = true
else
args[key] = word:sub(#key + 4)
end
end
ya.emit(cmd[1], args)
end)
end end
return M return M

View file

@ -1,3 +1,5 @@
use serde::Serialize;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Either<L, R> { pub enum Either<L, R> {
Left(L), Left(L),
@ -69,3 +71,19 @@ impl<L, R> Either<L, R> {
} }
} }
} }
impl<L, R> Serialize for Either<L, R>
where
L: Serialize,
R: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Left(l) => l.serialize(serializer),
Self::Right(r) => r.serialize(serializer),
}
}
}

View file

@ -178,34 +178,26 @@ impl Cmd {
} }
// Parse // Parse
pub fn parse_args( pub fn parse_args<I>(words: I, last: Option<String>) -> Result<HashMap<DataKey, Data>>
words: impl Iterator<Item = String>, where
last: Option<String>, I: IntoIterator<Item = String>,
obase: bool, {
) -> Result<HashMap<DataKey, Data>> {
let mut i = 0i64; let mut i = 0i64;
words words
.into_iter() .into_iter()
.map(|s| (s, true)) .map(|s| (s, true))
.chain(last.into_iter().map(|s| (s, false))) .chain(last.into_iter().map(|s| (s, false)))
.map(|(word, normal)| { .map(|(word, normal)| {
let Some(arg) = word.strip_prefix("--").filter(|_| normal) else { let Some(arg) = word.strip_prefix("--").filter(|&s| normal && !s.is_empty()) else {
i += 1; i += 1;
return Ok((DataKey::Integer(i - obase as i64), Data::String(word.into()))); return Ok((DataKey::Integer(i - 1), word.into()));
}; };
let mut parts = arg.splitn(2, '='); let mut parts = arg.splitn(2, '=');
let Some(key) = parts.next().map(|s| s.to_owned()) else { let key = parts.next().expect("at least one part");
bail!("invalid argument: {arg}"); let val = parts.next().map_or(Data::Boolean(true), Data::from);
};
let val = if let Some(val) = parts.next() { Ok((DataKey::from(key.to_owned()), val))
Data::String(val.to_owned().into())
} else {
Data::Boolean(true)
};
Ok((DataKey::String(Cow::Owned(key)), val))
}) })
.collect() .collect()
} }
@ -245,7 +237,7 @@ impl FromStr for Cmd {
} }
let mut me = Self::new(mem::take(&mut words[0]), Default::default(), Some(Default::default()))?; let mut me = Self::new(mem::take(&mut words[0]), Default::default(), Some(Default::default()))?;
me.args = Self::parse_args(words.into_iter().skip(1), last, true)?; me.args = Self::parse_args(words.into_iter().skip(1), last)?;
Ok(me) Ok(me)
} }
} }