mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41: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]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.0"
|
version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
|
|
@ -2310,12 +2310,6 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shell-words"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -2697,9 +2691,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.19"
|
version = "0.8.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
|
|
@ -3107,9 +3101,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
version = "7.0.1"
|
version = "7.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb4a9e33648339dc1642b0e36e21b3385e6148e289226f657c809dee59df5028"
|
checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"env_home",
|
"env_home",
|
||||||
|
|
@ -3587,7 +3581,6 @@ version = "25.2.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"mlua",
|
"mlua",
|
||||||
"shell-words",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
"yazi-macro",
|
"yazi-macro",
|
||||||
|
|
@ -3630,7 +3623,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
"shell-words",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"uzers",
|
"uzers",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,10 @@ regex = "1.11.1"
|
||||||
scopeguard = "1.2.0"
|
scopeguard = "1.2.0"
|
||||||
serde = { version = "1.0.217", features = [ "derive" ] }
|
serde = { version = "1.0.217", features = [ "derive" ] }
|
||||||
serde_json = "1.0.138"
|
serde_json = "1.0.138"
|
||||||
shell-words = "1.1.0"
|
|
||||||
tokio = { version = "1.43.0", features = [ "full" ] }
|
tokio = { version = "1.43.0", features = [ "full" ] }
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
tokio-util = "0.7.13"
|
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" ] }
|
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" ] }
|
twox-hash = { version = "2.1.0", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||||
unicode-width = "0.2.0"
|
unicode-width = "0.2.0"
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,4 @@ yazi-shared = { path = "../yazi-shared", version = "25.2.5" }
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
shell-words = { workspace = true }
|
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl TryFrom<CmdCow> for PluginOpt {
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = if let Some(s) = c.str("args") {
|
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 {
|
} else {
|
||||||
Default::default()
|
Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ impl TryFrom<CmdCow> for SearchOpt {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
via,
|
via,
|
||||||
subject,
|
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(),
|
args_raw: c.take_str("args").unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ parking_lot = { workspace = true }
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
shell-words = { workspace = true }
|
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
[target."cfg(unix)".dependencies]
|
[target."cfg(unix)".dependencies]
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ impl FromStr for Cmd {
|
||||||
|
|
||||||
#[allow(clippy::explicit_counter_loop)]
|
#[allow(clippy::explicit_counter_loop)]
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
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() {
|
if args.is_empty() || args[0].is_empty() {
|
||||||
bail!("command name cannot be empty");
|
bail!("command name cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
//! Escape characters that may have special meaning in a shell, including
|
//! Escape characters that may have special meaning in a shell, including
|
||||||
//! spaces. This is a modified version of the [`shell-escape`] crate and [`this
|
//! spaces. This is a modified version of the [`shell-escape`], [`shell-words`]
|
||||||
//! PR`].
|
//! and [`this PR`].
|
||||||
//!
|
//!
|
||||||
//! [`shell-escape`]: https://crates.io/crates/shell-escape
|
//! [`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
|
//! [`this PR`]: https://github.com/sfackler/shell-escape/pull/9
|
||||||
|
|
||||||
use std::{borrow::Cow, ffi::OsStr};
|
use std::{borrow::Cow, ffi::OsStr};
|
||||||
|
|
@ -41,7 +42,9 @@ pub fn escape_os_str(s: &OsStr) -> Cow<OsStr> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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)]
|
#[cfg(windows)]
|
||||||
pub fn split_windows(s: &str) -> anyhow::Result<Vec<String>> { Ok(windows::split(s)?) }
|
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> {
|
pub fn escape_str(s: &str) -> Cow<str> {
|
||||||
match escape_slice(s.as_bytes()) {
|
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'+')
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue