108 lines
3.2 KiB
Rust
108 lines
3.2 KiB
Rust
use crate::event::EventMessage;
|
|
use crate::CommandOptions;
|
|
use anyhow::bail;
|
|
use anyhow::Context;
|
|
use anyhow::Result;
|
|
use crossbeam::channel::Receiver;
|
|
use crossbeam::channel::Sender;
|
|
use parking_lot::RwLock;
|
|
use ropey::Rope;
|
|
use std::sync::Arc;
|
|
use std::{
|
|
io::Write,
|
|
process::{Command, Stdio},
|
|
};
|
|
|
|
pub type CommandRequest = (Arc<RwLock<CommandOptions>>, Arc<String>);
|
|
|
|
#[derive(Debug)]
|
|
pub enum CommandCompleted {
|
|
Success(CommandResult),
|
|
Failure(Rope),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct CommandResult {
|
|
pub status_success: bool,
|
|
pub stdout: Rope,
|
|
pub stderr: Rope,
|
|
}
|
|
|
|
pub fn command_event_loop(
|
|
command_request_receiver: Receiver<CommandRequest>,
|
|
event_sender: Sender<EventMessage>,
|
|
) -> Result<()> {
|
|
loop {
|
|
match command_request_receiver.recv() {
|
|
// TODO: Drain the command request channel so we only run the command once with the latest value
|
|
// Could use https://docs.rs/single_value_channel/latest/single_value_channel/
|
|
Ok(command_request) => {
|
|
event_sender.send(EventMessage::CommandCompleted(
|
|
match run_inner(&command_request) {
|
|
Ok(c) => {
|
|
// If there was no stdout and the command failed, don't touch stdout
|
|
if !c.status_success && c.stdout.len_bytes() == 0 {
|
|
CommandCompleted::Failure(c.stderr)
|
|
} else {
|
|
CommandCompleted::Success(c)
|
|
}
|
|
}
|
|
Err(e) => CommandCompleted::Failure(Rope::from_str(&e.to_string())),
|
|
},
|
|
))?;
|
|
}
|
|
Err(e) => {
|
|
event_sender.send(EventMessage::CommandLoopStopped)?;
|
|
bail!("Crossterm read error: {}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_inner(command_request: &CommandRequest) -> Result<CommandResult> {
|
|
// Spawn the child
|
|
let mut child = {
|
|
let request = command_request.0.read();
|
|
|
|
let mut command = Command::new(&request.command);
|
|
command
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.args(&request.hidden_options);
|
|
|
|
if request.wordsplit {
|
|
match shellwords::split(&request.cmdline) {
|
|
Ok(a) => {
|
|
command.args(a);
|
|
}
|
|
Err(e) => {
|
|
bail!("Argument error: {e:?}");
|
|
}
|
|
}
|
|
} else {
|
|
// TODO: Avoid cloning here
|
|
command.arg(&request.cmdline);
|
|
}
|
|
|
|
std::mem::drop(request);
|
|
|
|
command.spawn()
|
|
}?;
|
|
|
|
let mut stdin = child.stdin.take().context("Could not take stdin")?;
|
|
let text_orig_clone = command_request.1.clone();
|
|
std::thread::spawn(move || {
|
|
let _result = stdin.write_all(text_orig_clone.as_bytes());
|
|
});
|
|
|
|
// Collect the output
|
|
let output = child.wait_with_output()?;
|
|
|
|
Ok(CommandResult {
|
|
status_success: output.status.success(),
|
|
stdout: Rope::from_reader(&output.stdout[..])?,
|
|
stderr: Rope::from_reader(&output.stderr[..])?,
|
|
})
|
|
}
|