mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
integrate cmdexpand for windows
This commit is contained in:
parent
c174b01b8f
commit
131e44d7bf
2 changed files with 162 additions and 6 deletions
|
|
@ -30,7 +30,7 @@ yazi-prebuild = "^0"
|
|||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
clipboard-win = "^4"
|
||||
cmdexpand = {git = "https://github.com/ndtoan96/cmdexpand.git", tag = "v0.1.0"}
|
||||
nom = "^7"
|
||||
|
||||
[target.'cfg(not(target_os = "netbsd"))'.dependencies]
|
||||
trash = "^3"
|
||||
|
|
|
|||
166
core/src/external/shell.rs
vendored
166
core/src/external/shell.rs
vendored
|
|
@ -46,14 +46,11 @@ pub fn shell(opt: ShellOpt) -> Result<Child> {
|
|||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let args: Vec<String> = opt.args.iter().map(|s| s.to_string_lossy().to_string()).collect();
|
||||
let cmd = cmdexpand::Expander::new(&opt.cmd.to_string_lossy().to_string())
|
||||
.disable_context(true)
|
||||
.add_args(&args)
|
||||
.expand()?;
|
||||
let expanded_cmd = cmdexpand::expand_cmd(opt.cmd.to_string_lossy().as_ref(), &args)?;
|
||||
Ok(
|
||||
Command::new("cmd")
|
||||
.arg("/C")
|
||||
.arg(cmd)
|
||||
.arg(expanded_cmd)
|
||||
.stdin(if opt.piped { Stdio::piped() } else { Stdio::inherit() })
|
||||
.stdout(if opt.piped { Stdio::piped() } else { Stdio::inherit() })
|
||||
.stderr(if opt.piped { Stdio::piped() } else { Stdio::inherit() })
|
||||
|
|
@ -62,3 +59,162 @@ pub fn shell(opt: ShellOpt) -> Result<Child> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod cmdexpand {
|
||||
use anyhow::{anyhow, Result};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{is_not, tag, take_while1};
|
||||
use nom::character::complete::{anychar, char, digit1, space0, space1};
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::{many0, many1};
|
||||
use nom::sequence::{delimited, pair, preceded, tuple};
|
||||
use nom::IResult;
|
||||
|
||||
enum CommandPart<'a> {
|
||||
Space(&'a str),
|
||||
Text(&'a str),
|
||||
}
|
||||
|
||||
enum TextPart<'a> {
|
||||
NormalText(&'a str),
|
||||
PercentNumber(usize),
|
||||
PercentStar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Quote {
|
||||
DoubleQuote,
|
||||
SingleQuote,
|
||||
NoQuote,
|
||||
}
|
||||
|
||||
pub fn expand_cmd<T>(cmd: &str, args: &[T]) -> Result<String>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let parts = parse_cmd(cmd)?;
|
||||
let mut expanded = String::new();
|
||||
for part in parts {
|
||||
match part {
|
||||
CommandPart::Space(s) => expanded.push_str(s),
|
||||
CommandPart::Text(text) => {
|
||||
expanded.push_str(&expand_text(text, args)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(expanded)
|
||||
}
|
||||
|
||||
fn escaped_char(input: &str) -> IResult<&str, &str> {
|
||||
recognize(pair(char('\\'), anychar))(input)
|
||||
}
|
||||
|
||||
fn parse_cmd(cmd: &str) -> Result<Vec<CommandPart>> {
|
||||
fn double_quote_text(input: &str) -> IResult<&str, &str> {
|
||||
recognize(delimited(char('"'), is_not("\""), char('"')))(input)
|
||||
}
|
||||
|
||||
fn single_quote_text(input: &str) -> IResult<&str, &str> {
|
||||
recognize(delimited(char('\''), is_not("'"), char('\'')))(input)
|
||||
}
|
||||
|
||||
fn no_quote_text(input: &str) -> IResult<&str, &str> {
|
||||
take_while1(|c: char| !c.is_whitespace())(input)
|
||||
}
|
||||
|
||||
let (_, (leading_space, command_name, args, trailing_space)) = tuple((
|
||||
space0,
|
||||
alt((double_quote_text, single_quote_text, no_quote_text)),
|
||||
many0(pair(space1, alt((double_quote_text, single_quote_text, no_quote_text)))),
|
||||
space0,
|
||||
))(cmd)
|
||||
.map_err(|_| anyhow!("Cannot parse command `{cmd}`"))?;
|
||||
let mut parts = Vec::new();
|
||||
if !leading_space.is_empty() {
|
||||
parts.push(CommandPart::Space(leading_space));
|
||||
}
|
||||
parts.push(CommandPart::Text(command_name));
|
||||
for (space, arg) in args {
|
||||
parts.push(CommandPart::Space(space));
|
||||
parts.push(CommandPart::Text(arg));
|
||||
}
|
||||
if !trailing_space.is_empty() {
|
||||
parts.push(CommandPart::Space(trailing_space));
|
||||
}
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
fn expand_text<T>(text: &str, args: &[T]) -> Result<String>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let quote = if text.starts_with("\"") {
|
||||
Quote::DoubleQuote
|
||||
} else if text.starts_with("'") {
|
||||
Quote::SingleQuote
|
||||
} else {
|
||||
Quote::NoQuote
|
||||
};
|
||||
|
||||
let parts = parse_text(text)?;
|
||||
let mut expanded = String::new();
|
||||
for part in parts {
|
||||
match part {
|
||||
TextPart::NormalText(s) => expanded.push_str(s),
|
||||
TextPart::PercentNumber(i) => {
|
||||
if i > 0 {
|
||||
let replace_text = args
|
||||
.get(i - 1)
|
||||
.map(|content| preprocess(content.as_ref(), quote))
|
||||
.unwrap_or_default();
|
||||
expanded.push_str(&replace_text);
|
||||
} else {
|
||||
// Not sure what to do with %0
|
||||
}
|
||||
}
|
||||
TextPart::PercentStar => {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
expanded.push_str(&preprocess(arg.as_ref(), quote));
|
||||
if i + 1 < args.len() {
|
||||
expanded.push_str(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(expanded)
|
||||
}
|
||||
|
||||
fn parse_text(text: &str) -> Result<Vec<TextPart>> {
|
||||
fn normal_text(input: &str) -> IResult<&str, TextPart> {
|
||||
let (input, output) = recognize(many1(alt((escaped_char, is_not("\\%")))))(input)?;
|
||||
Ok((input, TextPart::NormalText(output)))
|
||||
}
|
||||
|
||||
fn percent_star(input: &str) -> IResult<&str, TextPart> {
|
||||
let (input, _) = tag("%*")(input)?;
|
||||
Ok((input, TextPart::PercentStar))
|
||||
}
|
||||
|
||||
fn percent_number(input: &str) -> IResult<&str, TextPart> {
|
||||
let (input, output) = preceded(char('%'), digit1)(input)?;
|
||||
let num: usize = output.parse().unwrap();
|
||||
Ok((input, TextPart::PercentNumber(num)))
|
||||
}
|
||||
|
||||
let (_, parts) = many0(alt((normal_text, percent_star, percent_number)))(text)
|
||||
.map_err(|_| anyhow!("Cannot parse text `{text}`"))?;
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
fn preprocess(content: &str, quote: Quote) -> String {
|
||||
let inner_space = content.chars().any(|c| c.is_whitespace());
|
||||
match (quote, inner_space) {
|
||||
(Quote::NoQuote, true) => format!("\"{}\"", content.replace("\"", "\\\"")),
|
||||
(Quote::NoQuote, false) => content.to_string(),
|
||||
(Quote::SingleQuote, _) => content.replace("'", "\\'").to_string(),
|
||||
(Quote::DoubleQuote, _) => content.replace("\"", "\\\"").to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue