diff --git a/Cargo.lock b/Cargo.lock index 5e0eb3bb..fede7822 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -353,6 +353,7 @@ dependencies = [ "regex", "serde", "shared", + "shell-words", "toml", "xdg", ] @@ -1530,6 +1531,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "signal-hook" version = "0.3.17" diff --git a/app/src/app.rs b/app/src/app.rs index b7722650..633c7b37 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -168,7 +168,7 @@ impl App { Event::Open(targets, opener) => { if let Some(opener) = opener { - tasks.file_open_with(&opener, &targets.iter().map(|(f, _)| f).collect::>()); + tasks.file_open_with(&opener, &targets.into_iter().map(|(f, _)| f).collect::>()); } else { tasks.file_open(&targets); } diff --git a/config/Cargo.toml b/config/Cargo.toml index 74622901..b3b1c65b 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -7,14 +7,15 @@ edition = "2021" shared = { path = "../shared" } # External dependencies -anyhow = "^1" -clap = { version = "^4", features = [ "derive" ] } -crossterm = "^0" -futures = "^0" -glob = "^0" -once_cell = "^1" -ratatui = "^0" -regex = "^1" -serde = { version = "^1", features = [ "derive" ] } -toml = { version = "^0", features = [ "preserve_order" ] } -xdg = "^2" +anyhow = "^1" +clap = { version = "^4", features = [ "derive" ] } +crossterm = "^0" +futures = "^0" +glob = "^0" +once_cell = "^1" +ratatui = "^0" +regex = "^1" +serde = { version = "^1", features = [ "derive" ] } +shell-words = "^1" +toml = { version = "^0", features = [ "preserve_order" ] } +xdg = "^2" diff --git a/config/docs/yazi.md b/config/docs/yazi.md index 0fe3ab0e..a7b414b9 100644 --- a/config/docs/yazi.md +++ b/config/docs/yazi.md @@ -32,7 +32,7 @@ Configure available openers, for example: ```toml [opener] archive = [ - { exec = "unar $0" }, + { exec = "unar $1" }, ] text = [ { exec = "nvim $*", block = true }, diff --git a/config/preset/yazi.toml b/config/preset/yazi.toml index 169244ab..e1e55f84 100644 --- a/config/preset/yazi.toml +++ b/config/preset/yazi.toml @@ -14,22 +14,22 @@ folder = [ { exec = "vim $*" }, ] archive = [ - { exec = "unar $0", display_name = "Extract here" }, + { exec = "unar $1", display_name = "Extract here" }, ] text = [ { exec = "vim $*", block = true }, ] image = [ { exec = "open $*", display_name = "Open" }, - { exec = "sh -c 'exiftool $0; echo \"\n\nPress enter to exit\"; read'", block = true, display_name = "Show EXIF" }, + { exec = "exiftool $1; echo '\n\nPress enter to exit'; read", block = true, display_name = "Show EXIF" }, ] video = [ { exec = "mpv $*" }, - { exec = "sh -c 'mediainfo $0; echo \"\n\nPress enter to exit\"; read'", block = true, display_name = "Show media info" }, + { exec = "mediainfo $1; echo '\n\nPress enter to exit'; read", block = true, display_name = "Show media info" }, ] audio = [ { exec = "mpv $*" }, - { exec = "sh -c 'mediainfo $0; echo \"\n\nPress enter to exit\"; read'", block = true, display_name = "Show media info" }, + { exec = "mediainfo $1; echo '\n\nPress enter to exit'; read", block = true, display_name = "Show media info" }, ] fallback = [ { exec = "open $*", display_name = "Open" }, diff --git a/config/src/exec/exec.rs b/config/src/exec/exec.rs deleted file mode 100644 index e30db82e..00000000 --- a/config/src/exec/exec.rs +++ /dev/null @@ -1,189 +0,0 @@ -use std::{collections::BTreeSet, fmt::{self, Debug}}; - -use regex::Regex; -use serde::{de::{self, Visitor}, Deserializer}; - -use super::ExecItem; - -#[derive(Clone, Debug)] -pub struct Exec { - items: Vec, -} - -impl From<&str> for Exec { - fn from(s: &str) -> Self { Self { items: Self::parse(s) } } -} - -impl ToString for Exec { - fn to_string(&self) -> String { - self.items.iter().map(|i| i.to_string()).collect::>().join("") - } -} - -impl Exec { - pub fn parse(s: &str) -> Vec { - let mut item = ExecItem::Word(Default::default()); - let mut last = b'\0'; - let mut esc = 0; - - let mut items = vec![]; - #[inline] - fn add(items: &mut Vec, item: ExecItem) { - if !item.is_empty() { - items.push(item); - } - } - - for c in s.trim().chars() { - if last == b'\\' && !matches!(c, '\\' | '"' | '\'') { - item.push('\\'); - esc = 0; - last = b'\0'; - } - - match c { - ' ' => match item { - ExecItem::Str(ref mut s, ..) => s.push(c), - ExecItem::Word(ref mut s) if last == b'\\' => { - s.push('\\'); - s.push(c); - } - _ => { - item.push(c); - add(&mut items, item); - item = ExecItem::Word(Default::default()); - } - }, - '-' => match item { - ExecItem::Word(ref mut w) => { - if w.is_empty() { - item = ExecItem::Arg(Default::default(), false); - } else { - w.push(c); - } - } - ExecItem::Arg(_, ref mut b) => *b = true, - ExecItem::Str(ref mut s, ..) => s.push(c), - }, - '\\' => { - if last == b'\\' { - esc += 1; - last = b'\0'; - } else { - last = b'\\'; - } - } - '"' | '\'' => { - if last == b'\\' { - esc += 1; - last = b'\0'; - } - if matches!(item, ExecItem::Str(_, e) if e == esc) { - item.push(c); - add(&mut items, item); - item = ExecItem::Str(Default::default(), esc); - } else { - add(&mut items, item); - item = ExecItem::Str(c.to_string(), esc); - } - esc = 0; - } - c => { - item.push(c); - } - } - } - - add(&mut items, item); - items - } - - pub fn build(&self, args: Vec) -> String { - let re = Regex::new(r"\$(\d+|\*)").unwrap(); - let mut occurs = BTreeSet::new(); - let mut replace = |s: &mut String| { - *s = re - .replace_all(s, |caps: ®ex::Captures| { - let idx = caps.get(1).unwrap().as_str(); - if idx == "*" { - return args - .iter() - .enumerate() - .filter(|(i, _)| !occurs.contains(i)) - .map(|(_, s)| s.as_str()) - .collect::>() - .join(" "); - } - - if let Ok(idx) = idx.parse::() { - if idx < args.len() { - occurs.insert(idx); - return args[idx].to_owned(); - } - } - - Default::default() - }) - .into_owned(); - }; - - let items = self - .items - .iter() - .cloned() - .map(|mut i| { - match i { - ExecItem::Word(ref mut s) => replace(s), - ExecItem::Arg(..) => (), - ExecItem::Str(ref mut s, _) => replace(s), - } - i - }) - .collect::>(); - - Self { items }.to_string() - } - - pub fn has() {} - - pub fn arg() {} - - pub fn named() {} -} - -impl Exec { - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - struct ExecVisitor; - - impl<'de> Visitor<'de> for ExecVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a command string, e.g. tab_switch 0") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: de::SeqAccess<'de>, - { - let mut execs = Vec::new(); - while let Some(value) = &seq.next_element::()? { - execs.push(Exec::from(value.as_str())); - } - Ok(execs) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(vec![Exec::from(value)]) - } - } - - deserializer.deserialize_any(ExecVisitor) - } -} diff --git a/config/src/exec/item.rs b/config/src/exec/item.rs deleted file mode 100644 index 85e663e6..00000000 --- a/config/src/exec/item.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::fmt::{Debug, Formatter}; - -#[derive(Clone)] -pub enum ExecItem { - Word(String), - Arg(String, bool), - Str(String, usize), -} - -impl ExecItem { - #[inline] - pub(super) fn push(&mut self, c: char) { - match self { - ExecItem::Word(s) => s.push(c), - ExecItem::Arg(s, _) => s.push(c), - ExecItem::Str(s, _) => s.push(c), - } - } - - #[inline] - pub(super) fn is_empty(&self) -> bool { - match self { - ExecItem::Word(s) => s.is_empty(), - ExecItem::Arg(..) => false, - ExecItem::Str(s, _) => s.is_empty(), - } - } - - #[inline] - pub(super) fn slash(&self) -> Option<&str> { - match self { - ExecItem::Str(_, n) => { - if *n == 0 { - return None; - } - - let s = "\\\\".repeat(*n); - Some(&s[..s.len() - 1]) - } - _ => None, - } - } -} - -impl Debug for ExecItem { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ExecItem::Word(s) => write!(f, "Word({s})"), - ExecItem::Arg(s, _) => write!(f, "Arg({s})"), - ExecItem::Str(s, n) => write!(f, "Str{n}({s})"), - } - } -} - -impl ToString for ExecItem { - fn to_string(&self) -> String { - match self { - ExecItem::Word(s) => s.clone(), - ExecItem::Arg(s, b) => format!("-{}{s}", if *b { "-" } else { "" }), - ExecItem::Str(s, n) => { - let slash = if let Some(s) = self.slash() { - s - } else { - return s.to_owned(); - }; - - if s == "'" || s == "\"" { - return format!("{slash}{s}"); - } - - let mut s = s.clone(); - if let Some(sub) = s.strip_prefix('"') { - s = format!("{slash}\"{sub}"); - } - if let Some(sub) = s.strip_suffix('"') { - s = format!("{sub}{slash}\""); - } - if let Some(sub) = s.strip_prefix('\'') { - s = format!("{slash}'{sub}"); - } - if let Some(sub) = s.strip_suffix('\'') { - s = format!("{sub}{slash}'"); - } - s - } - } - } -} diff --git a/config/src/exec/mod.rs b/config/src/exec/mod.rs deleted file mode 100644 index 69330caf..00000000 --- a/config/src/exec/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod exec; -mod item; -mod tests; - -pub use exec::*; -pub use item::*; diff --git a/config/src/exec/tests.rs b/config/src/exec/tests.rs deleted file mode 100644 index 9177423a..00000000 --- a/config/src/exec/tests.rs +++ /dev/null @@ -1,49 +0,0 @@ -// cargo test --package config --lib -- exec::tests::build --exact --nocapture - -#[test] -fn parse() { - use crate::exec::Exec; - - fn assert(a: &str) { - let exec = Exec::parse(a); - println!("{:?}", exec); - - let a = a.trim(); - let b = Exec::from(a).to_string(); - - if a != b { - println!("A: {}", a); - println!("B: {}", b); - } - } - - assert(r#" echo 123 "foo" 'bar' "#); - assert(r#" sh -c "sh -c \"\";" "#); - assert(r#" aaa - "bbb --opt \"ccc \\\"Meow\\\"\"" "#); - assert(r#" python4 --code 'bash -c "echo \'\\\'\'"'; "#); - assert(r#" sh -c "sh -c \"exiftool $0; echo \\\"\nPress enter to exit: \\\"; read\"" "#); - - assert(r#"sh -c 'exiftool $0; echo \"\n\nPress enter to exit\"; read'"#) -} - -#[test] -fn build() { - use crate::exec::Exec; - - fn assert(s: &str, args: Vec, expected: &str) { - let exec = Exec::parse(s); - println!("{:?}", exec); - - let got = Exec::from(s).build(args); - if got != expected { - println!("A: {}", expected); - println!("B: {}", got); - } - } - - assert( - r#"sh -c 'exiftool $0 "$1" \'$2\'; echo "\n\nPress enter to exit"; read'"#, - vec!["fo o".into(), "b'a\"r".into(), "b\"a'z".into()], - r#"sh -c 'exiftool foo; echo "\n\nPress enter to exit"; read'"#, - ); -} diff --git a/config/src/keymap/exec.rs b/config/src/keymap/exec.rs new file mode 100644 index 00000000..cb1ebf72 --- /dev/null +++ b/config/src/keymap/exec.rs @@ -0,0 +1,85 @@ +use std::{collections::BTreeMap, fmt::{self, Debug}}; + +use anyhow::bail; +use serde::{de::{self, Visitor}, Deserializer}; + +#[derive(Clone, Debug)] +pub struct Exec { + pub cmd: String, + pub args: Vec, + pub named: BTreeMap, +} + +impl TryFrom<&str> for Exec { + type Error = anyhow::Error; + + fn try_from(s: &str) -> Result { + let s = shell_words::split(s)?; + if s.is_empty() { + bail!("`exec` cannot be empty"); + } + + let mut exec = Self { cmd: s[0].clone(), args: Vec::new(), named: BTreeMap::new() }; + for arg in s.into_iter().skip(1) { + if arg.starts_with("--") { + let mut arg = arg.splitn(2, '='); + let key = arg.next().unwrap().trim_start_matches('-'); + let val = arg.next().unwrap_or("").to_string(); + exec.named.insert(key.to_string(), val); + } else { + exec.args.push(arg); + } + } + Ok(exec) + } +} + +impl ToString for Exec { + fn to_string(&self) -> String { + let mut s = Vec::with_capacity(self.args.len() + self.named.len() + 1); + s.push(self.cmd.clone()); + s.extend(self.args.iter().cloned()); + for (key, val) in self.named.iter() { + s.push(format!("--{}={}", key, val)); + } + + shell_words::join(s) + } +} + +impl Exec { + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct ExecVisitor; + + impl<'de> Visitor<'de> for ExecVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a exec string, e.g. tab_switch 0") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut execs = Vec::new(); + while let Some(value) = &seq.next_element::()? { + execs.push(Exec::try_from(value.as_str()).map_err(de::Error::custom)?); + } + Ok(execs) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(vec![Exec::try_from(value).map_err(de::Error::custom)?]) + } + } + + deserializer.deserialize_any(ExecVisitor) + } +} diff --git a/config/src/keymap/keymap.rs b/config/src/keymap/keymap.rs index de5c0f04..3c864368 100644 --- a/config/src/keymap/keymap.rs +++ b/config/src/keymap/keymap.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Deserializer}; -use super::Key; -use crate::{exec::Exec, MERGED_KEYMAP}; +use super::{Exec, Key}; +use crate::MERGED_KEYMAP; #[derive(Clone, Debug, Deserialize)] pub struct Control { diff --git a/config/src/keymap/mod.rs b/config/src/keymap/mod.rs index e0d6b94e..12bd2809 100644 --- a/config/src/keymap/mod.rs +++ b/config/src/keymap/mod.rs @@ -1,5 +1,7 @@ +mod exec; mod key; mod keymap; +pub use exec::*; pub use key::*; pub use keymap::*; diff --git a/config/src/lib.rs b/config/src/lib.rs index f8fd48db..af5a5fa7 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -3,7 +3,6 @@ use once_cell::sync::Lazy; mod boot; -mod exec; pub mod keymap; mod log; pub mod manager; diff --git a/config/src/open/opener.rs b/config/src/open/opener.rs index 29e7ad5e..9208e5ad 100644 --- a/config/src/open/opener.rs +++ b/config/src/open/opener.rs @@ -2,8 +2,7 @@ use serde::{Deserialize, Deserializer}; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Opener { - pub cmd: String, - pub args: Vec, + pub exec: String, pub block: bool, pub display_name: String, pub spread: bool, @@ -16,20 +15,59 @@ impl<'de> Deserialize<'de> for Opener { { #[derive(Deserialize)] pub struct Shadow { - pub cmd: String, - pub args: Vec, + // TODO: Deprecate this field in v0.1.5 + pub cmd: Option, + // TODO: Deprecate this field in v0.1.5 + pub args: Option>, + + pub exec: Option, #[serde(default)] pub block: bool, pub display_name: Option, - #[serde(skip)] - pub spread: bool, } - let shadow = Shadow::deserialize(deserializer)?; + let mut shadow = Shadow::deserialize(deserializer)?; - let display_name = if let Some(s) = shadow.display_name { s } else { shadow.cmd.clone() }; - let spread = shadow.args.contains(&"$*".to_string()); + // -- TODO: Deprecate this in v0.1.5 + if shadow.exec.is_none() { + if shadow.cmd.is_none() { + return Err(serde::de::Error::missing_field("exec")); + } + if shadow.args.is_none() { + return Err(serde::de::Error::missing_field("args")); + } + // Replace the $0 to $1, $1 to $2, and so on + shadow.args = Some( + shadow + .args + .unwrap() + .into_iter() + .map(|s| { + if !s.starts_with('$') { + return shell_words::quote(&s).into(); + } + if let Ok(idx) = s[1..].parse::() { + return format!("${}", idx + 1); + } + s + }) + .collect(), + ); + shadow.exec = Some(format!("{} {}", shadow.cmd.unwrap(), shadow.args.unwrap().join(" "))); + } + let exec = shadow.exec.unwrap(); + // TODO: Deprecate this in v0.1.5 -- - Ok(Self { cmd: shadow.cmd, args: shadow.args, block: shadow.block, display_name, spread }) + if exec.is_empty() { + return Err(serde::de::Error::custom("`exec` cannot be empty")); + } + let display_name = if let Some(s) = shadow.display_name { + s + } else { + exec.split_whitespace().next().unwrap().to_string() + }; + + let spread = exec.contains("$*") || exec.contains("$@"); + Ok(Self { exec, block: shadow.block, display_name, spread }) } } diff --git a/core/src/external/mod.rs b/core/src/external/mod.rs index 5312e70c..d354f438 100644 --- a/core/src/external/mod.rs +++ b/core/src/external/mod.rs @@ -7,6 +7,7 @@ mod jq; mod lsar; mod pdftoppm; mod rg; +mod shell; mod unar; mod zoxide; @@ -19,5 +20,6 @@ pub use jq::*; pub use lsar::*; pub use pdftoppm::*; pub use rg::*; +pub use shell::*; pub use unar::*; pub use zoxide::*; diff --git a/core/src/external/shell.rs b/core/src/external/shell.rs new file mode 100644 index 00000000..63573f4a --- /dev/null +++ b/core/src/external/shell.rs @@ -0,0 +1,41 @@ +use std::{ffi::OsString, process::Stdio}; + +use anyhow::Result; +use tokio::process::{Child, Command}; + +pub struct ShellOpt { + pub cmd: OsString, + pub args: Vec, + pub piped: bool, +} + +pub fn shell(opt: ShellOpt) -> Result { + #[cfg(not(target_os = "windows"))] + { + Ok( + Command::new("sh") + .arg("-c") + .arg(opt.cmd) + .arg("") // $0 is the command name + .args(opt.args) + .stdout(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) + .stderr(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) + .kill_on_drop(true) + .spawn()?, + ) + } + + #[cfg(target_os = "windows")] + { + Ok( + Command::new("cmd") + .arg("/C") + .arg(opt.cmd) + .args(opt.args) + .stdout(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) + .stderr(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) + .kill_on_drop(true) + .spawn()?, + ) + } +} diff --git a/core/src/manager/manager.rs b/core/src/manager/manager.rs index be38d48a..6660f0d4 100644 --- a/core/src/manager/manager.rs +++ b/core/src/manager/manager.rs @@ -229,16 +229,10 @@ impl Manager { tokio::spawn(async move { let result = emit!(Input(InputOpt::top("Shell:").with_highlight())); - if let Ok(cmd) = result.await { + if let Ok(exec) = result.await { emit!(Open( - vec![(cmd.into(), "".to_string())], - Some(Opener { - cmd: "sh".to_string(), - args: vec!["-c".to_string(), "$0".to_string()], - block, - display_name: Default::default(), - spread: false, - }) + Default::default(), + Some(Opener { exec, block, display_name: Default::default(), spread: true }) )); } }); diff --git a/core/src/tasks/scheduler.rs b/core/src/tasks/scheduler.rs index d8bde035..ae12a259 100644 --- a/core/src/tasks/scheduler.rs +++ b/core/src/tasks/scheduler.rs @@ -1,4 +1,4 @@ -use std::{ffi::{OsStr, OsString}, path::PathBuf, sync::Arc, time::Duration}; +use std::{ffi::OsStr, path::PathBuf, sync::Arc, time::Duration}; use async_channel::{Receiver, Sender}; use config::open::Opener; @@ -280,23 +280,12 @@ impl Scheduler { } pub(super) fn process_open(&self, opener: &Opener, args: &[impl AsRef]) { - let args: Vec = opener - .args - .iter() - .map_while(|a| { - if !a.starts_with('$') { - return Some(vec![a.into()]); - } - if a == "$*" { - return Some(args.iter().map(Into::into).collect()); - } - a[1..].parse().ok().and_then(|n: usize| args.get(n)).map(|a| vec![a.into()]) - }) - .flatten() - .collect(); - let mut running = self.running.write(); - let name = format!("Exec `{} {}`", opener.cmd, args.join(" ".as_ref()).to_string_lossy()); + let name = format!( + "Exec `{}` with `{}`", + opener.exec, + args.iter().map(|a| a.as_ref()).collect::>().join(" ".as_ref()).to_string_lossy() + ); let id = running.add(name); let (cancel_tx, mut cancel_rx) = oneshot::channel(); @@ -313,12 +302,19 @@ impl Scheduler { }) }); + let args = args.into_iter().map(|a| a.as_ref().to_os_string()).collect::>(); tokio::spawn({ let process = self.process.clone(); let opener = opener.clone(); async move { process - .open(ProcessOpOpen { id, cmd: opener.cmd, args, block: opener.block, cancel: cancel_tx }) + .open(ProcessOpOpen { + id, + cmd: opener.exec.into(), + args, + block: opener.block, + cancel: cancel_tx, + }) .await .ok(); } diff --git a/core/src/tasks/tasks.rs b/core/src/tasks/tasks.rs index 8d0f952f..d526c222 100644 --- a/core/src/tasks/tasks.rs +++ b/core/src/tasks/tasks.rs @@ -133,7 +133,7 @@ impl Tasks { let mut openers = BTreeMap::new(); for (path, mime) in targets { if let Some(opener) = OPEN.openers(path, mime).and_then(|o| o.first().cloned()) { - openers.entry(opener).or_insert_with(Vec::new).push(path.as_ref()); + openers.entry(opener).or_insert_with(Vec::new).push(path.as_ref().as_os_str()); } } for (opener, args) in openers { diff --git a/core/src/tasks/workers/process.rs b/core/src/tasks/workers/process.rs index 19d4b1e7..3fade197 100644 --- a/core/src/tasks/workers/process.rs +++ b/core/src/tasks/workers/process.rs @@ -1,10 +1,10 @@ -use std::{ffi::OsString, process::Stdio}; +use std::ffi::OsString; use anyhow::Result; -use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, select, sync::{mpsc, oneshot}}; +use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::{mpsc, oneshot}}; use tracing::trace; -use crate::{emit, tasks::TaskOp, BLOCKER}; +use crate::{emit, external::{self, ShellOpt}, tasks::TaskOp, BLOCKER}; pub(crate) struct Process { sch: mpsc::UnboundedSender, @@ -13,7 +13,7 @@ pub(crate) struct Process { #[derive(Debug)] pub(crate) struct ProcessOpOpen { pub id: usize, - pub cmd: String, + pub cmd: OsString, pub args: Vec, pub block: bool, pub cancel: oneshot::Sender<()>, @@ -33,12 +33,12 @@ impl Process { let _guard = BLOCKER.acquire().await.unwrap(); emit!(Stop(true)).await; - match Command::new(&task.cmd).args(&task.args).kill_on_drop(true).spawn() { + match external::shell(ShellOpt { cmd: task.cmd, args: task.args, piped: false }) { Ok(mut child) => { child.wait().await.ok(); } Err(e) => { - trace!("Failed to spawn {}: {e}", task.cmd); + trace!("Failed to spawn process: {e}"); } } emit!(Stop(false)).await; @@ -48,12 +48,7 @@ impl Process { } self.sch.send(TaskOp::New(task.id, 0))?; - let mut child = Command::new(&task.cmd) - .args(&task.args) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .kill_on_drop(true) - .spawn()?; + let mut child = external::shell(ShellOpt { cmd: task.cmd, args: task.args, piped: true })?; let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines(); let mut stderr = BufReader::new(child.stderr.take().unwrap()).lines();