diff --git a/Cargo.lock b/Cargo.lock index aab23d0..a21cffb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ctrlc" +version = "3.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +dependencies = [ + "nix", + "windows-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -179,6 +208,8 @@ checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" name = "pmuw-project" version = "0.1.0" dependencies = [ + "crossbeam-channel", + "ctrlc", "once_cell", "rustyline", "shlex", diff --git a/Cargo.toml b/Cargo.toml index 91d07aa..15abb06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] +crossbeam-channel = "0.5" +ctrlc = "3.2" once_cell = "1.17" rustyline = "10.1.0" shlex = "1.1" diff --git a/src/error.rs b/src/error.rs index dba8036..b73f6ed 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ pub enum ShellError { NotFound(Command), ExecuteFailure(String), MalformedArgs(String), + Interrupt, } impl PartialEq for ShellError { @@ -15,6 +16,7 @@ impl PartialEq for ShellError { &ShellError::NotFound(_) => matches!(other, ShellError::NotFound(_)), &ShellError::ExecuteFailure(_) => matches!(other, ShellError::ExecuteFailure(_)), &ShellError::MalformedArgs(_) => matches!(other, ShellError::MalformedArgs(_)), + &ShellError::Interrupt => matches!(other, ShellError::Interrupt), } } } diff --git a/src/execute.rs b/src/execute.rs index 92f000f..7a2f18d 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,11 +1,13 @@ -use std::io::ErrorKind::NotFound; -use std::process::Command; +use crossbeam_channel::{select, Receiver}; +use std::io::ErrorKind; +use std::process::{Command, ExitStatus}; +use std::{io, thread}; use crate::builtins::{execute_builtin, is_builtin}; use crate::error::ShellError; use crate::parse::parse_line; -pub fn interpret(line: String) -> Result<(), ShellError> { +pub fn interpret(line: String, ctrlc_recv: Receiver<()>) -> Result<(), ShellError> { if line.is_empty() { return Err(ShellError::EmptyLine); } @@ -17,25 +19,33 @@ pub fn interpret(line: String) -> Result<(), ShellError> { let mut command = Command::new(keyword); command.args(args); - execute(command)?; + execute(command, ctrlc_recv)?; } Ok(()) } -fn execute(mut command: Command) -> Result<(), ShellError> { +fn execute(mut command: Command, ctrlc_recv: Receiver<()>) -> Result<(), ShellError> { match command.spawn() { Ok(mut child) => { - if let Err(err) = child.wait() { - return Err(ShellError::ExecuteFailure(err.to_string())); - } - Ok(()) - } - Err(err) => { - if err.kind() == NotFound { - return Err(ShellError::NotFound(command)); + let (cmd_send, cmd_recv) = crossbeam_channel::unbounded::>(); + thread::spawn(move || cmd_send.send(child.wait())); + + select! { + recv(ctrlc_recv) -> _ => Err(ShellError::Interrupt), + recv(cmd_recv) -> result => { + if let Err(err) = result.unwrap() { + Err(ShellError::ExecuteFailure(err.to_string())) + } else { + Ok(()) + } + } } - Err(ShellError::ExecuteFailure(err.to_string())) } + Err(err) => match err.kind() { + ErrorKind::NotFound => Err(ShellError::NotFound(command)), + ErrorKind::Interrupted => Err(ShellError::Interrupt), + _ => Err(ShellError::ExecuteFailure(err.to_string())), + }, } } diff --git a/src/main.rs b/src/main.rs index 76c24d1..f60ae72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,11 @@ use rustyline::{Editor, Result}; use crate::prompt::Prompt; fn main() -> Result<()> { + let (ctrlc_send, ctrlc_recv) = crossbeam_channel::unbounded::<()>(); + let _ = ctrlc::set_handler(move || { + let _ = ctrlc_send.send(()); + }); + let mut rl = Editor::<()>::new()?; let mut prompt = Prompt::new(); @@ -20,7 +25,7 @@ fn main() -> Result<()> { let readline = rl.readline(&prompt.get_prompt()); match readline { - Ok(line) => match interpret(line) { + Ok(line) => match interpret(line, ctrlc_recv.clone()) { Ok(_) => {} Err(ShellError::EmptyLine) => { continue; @@ -34,6 +39,9 @@ fn main() -> Result<()> { Err(ShellError::MalformedArgs(args)) => { eprintln!("Malformed arguments: {}", args) } + Err(ShellError::Interrupt) => { + continue; + } }, Err(ReadlineError::Interrupted) => { continue;