Add support for Ctrl-C aborting the current command on the CLI (#528) r=rnewman

This commit is contained in:
Thom 2018-01-21 09:45:40 -05:00 committed by GitHub
parent 9740cafdbd
commit 1fcc3d7e1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -14,6 +14,7 @@ use linefeed::{
DefaultTerminal, DefaultTerminal,
Reader, Reader,
ReadResult, ReadResult,
Signal,
}; };
use self::InputResult::*; use self::InputResult::*;
@ -51,11 +52,26 @@ pub struct InputReader {
in_process_cmd: Option<Command>, in_process_cmd: Option<Command>,
} }
enum UserAction {
// We've received some text that we should interpret as a new command, or
// as part of the current command.
TextInput(String),
// We were interrupted, if we have a current command we should clear it,
// otherwise we should exit. Currently can only be generated by reading from
// a terminal (and not by reading from stdin).
Interrupt,
// We hit the end of the file, there was an error getting user input, or
// something else happened that means we should exit.
Quit,
}
impl InputReader { impl InputReader {
/// Constructs a new `InputReader` reading from `stdin`. /// Constructs a new `InputReader` reading from `stdin`.
pub fn new() -> InputReader { pub fn new() -> InputReader {
let r = match Reader::new("mentat") { let r = match Reader::new("mentat") {
Ok(mut r) => { Ok(mut r) => {
// Handle SIGINT (Ctrl-C)
r.set_report_signal(Signal::Interrupt, true);
r.set_word_break_chars(" \t\n!\"#$%&'(){}*+,-./:;<=>?@[\\]^`"); r.set_word_break_chars(" \t\n!\"#$%&'(){}*+,-./:;<=>?@[\\]^`");
Some(r) Some(r)
}, },
@ -80,8 +96,16 @@ impl InputReader {
pub fn read_input(&mut self) -> Result<InputResult, cli::Error> { pub fn read_input(&mut self) -> Result<InputResult, cli::Error> {
let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT }; let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT };
let line = match self.read_line(prompt) { let line = match self.read_line(prompt) {
Some(s) => s, UserAction::TextInput(s) => s,
None => return Ok(Eof), UserAction::Interrupt if self.in_process_cmd.is_some() => {
self.in_process_cmd = None;
self.buffer.clear();
// Move to the next line, so that our next prompt isn't on top
// of the previous.
println!();
String::new()
},
_ => return Ok(Eof),
}; };
if !self.buffer.is_empty() { if !self.buffer.is_empty() {
@ -142,14 +166,16 @@ impl InputReader {
} }
} }
fn read_line(&mut self, prompt: &str) -> Option<String> { fn read_line(&mut self, prompt: &str) -> UserAction {
match self.reader { match self.reader {
Some(ref mut r) => { Some(ref mut r) => {
r.set_prompt(prompt); r.set_prompt(prompt);
r.read_line().ok().and_then(|line| r.read_line().ok().map_or(UserAction::Quit, |line|
match line { match line {
ReadResult::Input(s) => Some(s), ReadResult::Input(s) => UserAction::TextInput(s),
_ => None ReadResult::Signal(Signal::Interrupt) =>
UserAction::Interrupt,
_ => UserAction::Quit,
}) })
}, },
@ -157,12 +183,12 @@ impl InputReader {
} }
} }
fn read_stdin(&self) -> Option<String> { fn read_stdin(&self) -> UserAction {
let mut s = String::new(); let mut s = String::new();
match stdin().read_line(&mut s) { match stdin().read_line(&mut s) {
Ok(0) | Err(_) => None, Ok(0) | Err(_) => UserAction::Quit,
Ok(_) => Some(s) Ok(_) => UserAction::TextInput(s)
} }
} }