mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: support end of options (--) marker for all commands (#2298)
This commit is contained in:
parent
b1d2be067f
commit
633e68e73e
9 changed files with 150 additions and 25 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -2191,9 +2191,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
|||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
|
|
@ -2310,12 +2310,6 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
@ -2697,9 +2691,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
|
|
@ -3107,9 +3101,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
|
|||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "7.0.1"
|
||||
version = "7.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb4a9e33648339dc1642b0e36e21b3385e6148e289226f657c809dee59df5028"
|
||||
checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283"
|
||||
dependencies = [
|
||||
"either",
|
||||
"env_home",
|
||||
|
|
@ -3587,7 +3581,6 @@ version = "25.2.5"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"mlua",
|
||||
"shell-words",
|
||||
"tokio",
|
||||
"yazi-config",
|
||||
"yazi-macro",
|
||||
|
|
@ -3630,7 +3623,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"shell-words",
|
||||
"tokio",
|
||||
"uzers",
|
||||
"windows-sys 0.59.0",
|
||||
|
|
|
|||
|
|
@ -37,11 +37,10 @@ regex = "1.11.1"
|
|||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.217", features = [ "derive" ] }
|
||||
serde_json = "1.0.138"
|
||||
shell-words = "1.1.0"
|
||||
tokio = { version = "1.43.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.13"
|
||||
toml = { version = "0.8.19" }
|
||||
toml = { version = "0.8.20" }
|
||||
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
twox-hash = { version = "2.1.0", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
unicode-width = "0.2.0"
|
||||
|
|
|
|||
|
|
@ -20,5 +20,4 @@ yazi-shared = { path = "../yazi-shared", version = "25.2.5" }
|
|||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
shell-words = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ impl TryFrom<CmdCow> for PluginOpt {
|
|||
};
|
||||
|
||||
let args = if let Some(s) = c.str("args") {
|
||||
Cmd::parse_args(shell_words::split(s)?.into_iter(), true)?
|
||||
Cmd::parse_args(yazi_shared::shell::split_unix(s)?.into_iter(), true)?
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl TryFrom<CmdCow> for SearchOpt {
|
|||
Ok(Self {
|
||||
via,
|
||||
subject,
|
||||
args: shell_words::split(c.str("args").unwrap_or_default()).map_err(|_| ())?,
|
||||
args: yazi_shared::shell::split_unix(c.str("args").unwrap_or_default()).map_err(|_| ())?,
|
||||
args_raw: c.take_str("args").unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ parking_lot = { workspace = true }
|
|||
percent-encoding = "2.3.1"
|
||||
ratatui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
shell-words = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ impl FromStr for Cmd {
|
|||
|
||||
#[allow(clippy::explicit_counter_loop)]
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut args = shell_words::split(s)?;
|
||||
let mut args = crate::shell::split_unix(s)?;
|
||||
if args.is_empty() || args[0].is_empty() {
|
||||
bail!("command name cannot be empty");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//! Escape characters that may have special meaning in a shell, including
|
||||
//! spaces. This is a modified version of the [`shell-escape`] crate and [`this
|
||||
//! PR`].
|
||||
//! spaces. This is a modified version of the [`shell-escape`], [`shell-words`]
|
||||
//! and [`this PR`].
|
||||
//!
|
||||
//! [`shell-escape`]: https://crates.io/crates/shell-escape
|
||||
//! [`shell-words`]: https://crates.io/crates/shell-words
|
||||
//! [`this PR`]: https://github.com/sfackler/shell-escape/pull/9
|
||||
|
||||
use std::{borrow::Cow, ffi::OsStr};
|
||||
|
|
@ -41,7 +42,9 @@ pub fn escape_os_str(s: &OsStr) -> Cow<OsStr> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split_unix(s: &str) -> anyhow::Result<Vec<String>> { Ok(shell_words::split(s)?) }
|
||||
pub fn split_unix(s: &str) -> anyhow::Result<Vec<String>> {
|
||||
unix::split(s).map_err(|()| anyhow::anyhow!("missing closing quote"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn split_windows(s: &str) -> anyhow::Result<Vec<String>> { Ok(windows::split(s)?) }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, mem};
|
||||
|
||||
pub fn escape_str(s: &str) -> Cow<str> {
|
||||
match escape_slice(s.as_bytes()) {
|
||||
|
|
@ -46,6 +46,139 @@ fn allowed(b: u8) -> bool {
|
|||
matches!(b, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'=' | b'/' | b',' | b'.' | b'+')
|
||||
}
|
||||
|
||||
pub fn split(s: &str) -> Result<Vec<String>, ()> {
|
||||
enum State {
|
||||
/// Within a delimiter.
|
||||
Delimiter,
|
||||
/// After backslash, but before starting word.
|
||||
Backslash,
|
||||
/// Within an unquoted word.
|
||||
Unquoted,
|
||||
/// After backslash in an unquoted word.
|
||||
UnquotedBackslash,
|
||||
/// Within a single quoted word.
|
||||
SingleQuoted,
|
||||
/// Within a double quoted word.
|
||||
DoubleQuoted,
|
||||
/// After backslash inside a double quoted word.
|
||||
DoubleQuotedBackslash,
|
||||
/// Inside a comment.
|
||||
Comment,
|
||||
}
|
||||
use State::*;
|
||||
|
||||
let mut words = Vec::new();
|
||||
let mut word = String::new();
|
||||
let mut chars = s.chars();
|
||||
let mut state = Delimiter;
|
||||
|
||||
macro_rules! flush {
|
||||
() => {
|
||||
if word == "--" {
|
||||
words.push(chars.collect());
|
||||
break;
|
||||
}
|
||||
words.push(mem::take(&mut word));
|
||||
};
|
||||
}
|
||||
|
||||
loop {
|
||||
let c = chars.next();
|
||||
state = match state {
|
||||
Delimiter => match c {
|
||||
None => break,
|
||||
Some('\'') => SingleQuoted,
|
||||
Some('\"') => DoubleQuoted,
|
||||
Some('\\') => Backslash,
|
||||
Some('\t') | Some(' ') | Some('\n') => Delimiter,
|
||||
Some('#') => Comment,
|
||||
Some(c) => {
|
||||
word.push(c);
|
||||
Unquoted
|
||||
}
|
||||
},
|
||||
Backslash => match c {
|
||||
None => {
|
||||
word.push('\\');
|
||||
flush!();
|
||||
break;
|
||||
}
|
||||
Some('\n') => Delimiter,
|
||||
Some(c) => {
|
||||
word.push(c);
|
||||
Unquoted
|
||||
}
|
||||
},
|
||||
Unquoted => match c {
|
||||
None => {
|
||||
flush!();
|
||||
break;
|
||||
}
|
||||
Some('\'') => SingleQuoted,
|
||||
Some('\"') => DoubleQuoted,
|
||||
Some('\\') => UnquotedBackslash,
|
||||
Some('\t') | Some(' ') | Some('\n') => {
|
||||
flush!();
|
||||
Delimiter
|
||||
}
|
||||
Some(c) => {
|
||||
word.push(c);
|
||||
Unquoted
|
||||
}
|
||||
},
|
||||
UnquotedBackslash => match c {
|
||||
None => {
|
||||
word.push('\\');
|
||||
flush!();
|
||||
break;
|
||||
}
|
||||
Some('\n') => Unquoted,
|
||||
Some(c) => {
|
||||
word.push(c);
|
||||
Unquoted
|
||||
}
|
||||
},
|
||||
SingleQuoted => match c {
|
||||
None => return Err(()),
|
||||
Some('\'') => Unquoted,
|
||||
Some(c) => {
|
||||
word.push(c);
|
||||
SingleQuoted
|
||||
}
|
||||
},
|
||||
DoubleQuoted => match c {
|
||||
None => return Err(()),
|
||||
Some('\"') => Unquoted,
|
||||
Some('\\') => DoubleQuotedBackslash,
|
||||
Some(c) => {
|
||||
word.push(c);
|
||||
DoubleQuoted
|
||||
}
|
||||
},
|
||||
DoubleQuotedBackslash => match c {
|
||||
None => return Err(()),
|
||||
Some('\n') => DoubleQuoted,
|
||||
Some(c @ '$') | Some(c @ '`') | Some(c @ '"') | Some(c @ '\\') => {
|
||||
word.push(c);
|
||||
DoubleQuoted
|
||||
}
|
||||
Some(c) => {
|
||||
word.push('\\');
|
||||
word.push(c);
|
||||
DoubleQuoted
|
||||
}
|
||||
},
|
||||
Comment => match c {
|
||||
None => break,
|
||||
Some('\n') => Delimiter,
|
||||
Some(_) => Comment,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(words)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue