From 1fcc3d7e1b5e884c3992babcd764581df5f10610 Mon Sep 17 00:00:00 2001 From: Thom Date: Sun, 21 Jan 2018 09:45:40 -0500 Subject: [PATCH] Add support for Ctrl-C aborting the current command on the CLI (#528) r=rnewman --- tools/cli/src/mentat_cli/input.rs | 44 ++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/tools/cli/src/mentat_cli/input.rs b/tools/cli/src/mentat_cli/input.rs index 97d28342..5cf07e49 100644 --- a/tools/cli/src/mentat_cli/input.rs +++ b/tools/cli/src/mentat_cli/input.rs @@ -14,6 +14,7 @@ use linefeed::{ DefaultTerminal, Reader, ReadResult, + Signal, }; use self::InputResult::*; @@ -51,11 +52,26 @@ pub struct InputReader { in_process_cmd: Option, } +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 { /// Constructs a new `InputReader` reading from `stdin`. pub fn new() -> InputReader { let r = match Reader::new("mentat") { Ok(mut r) => { + // Handle SIGINT (Ctrl-C) + r.set_report_signal(Signal::Interrupt, true); r.set_word_break_chars(" \t\n!\"#$%&'(){}*+,-./:;<=>?@[\\]^`"); Some(r) }, @@ -80,8 +96,16 @@ impl InputReader { pub fn read_input(&mut self) -> Result { let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT }; let line = match self.read_line(prompt) { - Some(s) => s, - None => return Ok(Eof), + UserAction::TextInput(s) => s, + 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() { @@ -142,14 +166,16 @@ impl InputReader { } } - fn read_line(&mut self, prompt: &str) -> Option { + fn read_line(&mut self, prompt: &str) -> UserAction { match self.reader { Some(ref mut r) => { r.set_prompt(prompt); - r.read_line().ok().and_then(|line| + r.read_line().ok().map_or(UserAction::Quit, |line| match line { - ReadResult::Input(s) => Some(s), - _ => None + ReadResult::Input(s) => UserAction::TextInput(s), + ReadResult::Signal(Signal::Interrupt) => + UserAction::Interrupt, + _ => UserAction::Quit, }) }, @@ -157,12 +183,12 @@ impl InputReader { } } - fn read_stdin(&self) -> Option { + fn read_stdin(&self) -> UserAction { let mut s = String::new(); match stdin().read_line(&mut s) { - Ok(0) | Err(_) => None, - Ok(_) => Some(s) + Ok(0) | Err(_) => UserAction::Quit, + Ok(_) => UserAction::TextInput(s) } }