From b96861eb9b3e43f8958dfbf395a167d8839b8e00 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 9 Jun 2019 12:49:53 -0400 Subject: start using snafu --- Cargo.lock | 92 ++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/parser.rs | 12 ++--- src/process.rs | 97 +++++++++++++++++++++++------------- src/readline.rs | 149 +++++++++++++++++++++++++++++++++++--------------------- src/repl.rs | 40 ++++++++++----- 6 files changed, 285 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 095c97d..f14a3f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,27 @@ name = "autocfg" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "backtrace" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "1.1.0" @@ -37,6 +58,11 @@ dependencies = [ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cc" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cfg-if" version = "0.1.9" @@ -297,6 +323,7 @@ dependencies = [ "crossterm 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "snafu 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-pty-process 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -353,6 +380,22 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.6.5" @@ -457,6 +500,11 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-demangle" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc_version" version = "0.2.3" @@ -511,11 +559,40 @@ name = "smallvec" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "snafu" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)", + "snafu-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "snafu-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "syn" +version = "0.15.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio" version = "0.1.21" @@ -724,6 +801,11 @@ dependencies = [ "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -766,9 +848,12 @@ dependencies = [ "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" +"checksum backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)" = "ada4c783bb7e7443c14e0480f429ae2cc99da95065aeab7ee1b81ada0419404f" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" @@ -804,6 +889,8 @@ dependencies = [ "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" @@ -815,6 +902,7 @@ dependencies = [ "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" @@ -823,7 +911,10 @@ dependencies = [ "checksum signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cded4ffa32146722ec54ab1f16320568465aa922aa9ab4708129599740da85d7" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" +"checksum snafu 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5712353226e5ccb06c8f9b1cab819654b25514479523755ffa94703ceeb00e4f" +"checksum snafu-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "78178fb0042da2d70bba6e3fb26b6412c4824a22b7fe434f5e082d2ca0b894ee" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" "checksum tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2ffcf4bcfc641413fa0f1427bf8f91dfc78f56a6559cbf50e04837ae442a87" "checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" "checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" @@ -840,6 +931,7 @@ dependencies = [ "checksum tokio-trace-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9c8a256d6956f7cb5e2bdfe8b1e8022f1a09206c6c2b1ba00f3b746b260c613" "checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" "checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index 2a6a61b..1dc7a14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,6 @@ edition = "2018" crossterm = "0.9" futures = "0.1" mio = "0.6" +snafu = "0.4" tokio = "0.1" tokio-pty-process = "0.4" diff --git a/src/parser.rs b/src/parser.rs index cc86020..3a03d43 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,14 +1,14 @@ -#[derive(Debug)] +use snafu::{OptionExt, Snafu}; + +#[derive(Debug, Snafu)] pub enum Error { + #[snafu(display("No command given"))] CommandRequired, } pub fn parse(line: &str) -> Result<(String, Vec), Error> { // TODO let mut tokens = line.split_whitespace().map(|s| s.to_string()); - if let Some(cmd) = tokens.next() { - Ok((cmd, tokens.collect())) - } else { - Err(Error::CommandRequired) - } + let cmd = tokens.next().context(CommandRequired)?; + Ok((cmd, tokens.collect())) } diff --git a/src/process.rs b/src/process.rs index 5a82e5c..302c9ca 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,12 +1,52 @@ use futures::future::Future; +use snafu::{ResultExt, Snafu}; use std::io::{Read, Write}; use tokio::io::AsyncRead; use tokio_pty_process::CommandExt; -#[derive(Debug)] +#[derive(Debug, Snafu)] pub enum Error { - IOError(std::io::Error), - ParserError(crate::parser::Error), + #[snafu(display("failed to open a pty: {}", source))] + OpenPty { source: std::io::Error }, + + #[snafu(display( + "failed to spawn process for {} {}: {}", + cmd, + args.join(" "), + source + ))] + SpawnProcess { + cmd: String, + args: Vec, + source: std::io::Error, + }, + + #[snafu(display("failed to parse command line '{}': {}", line, source))] + ParserError { + line: String, + source: crate::parser::Error, + }, + + #[snafu(display("failed to write to pty: {}", source))] + WriteToPty { source: std::io::Error }, + + #[snafu(display("failed to read from terminal: {}", source))] + ReadFromTerminal { source: std::io::Error }, + + #[snafu(display( + "failed to clear ready state on pty for reading: {}", + source + ))] + PtyClearReadReady { source: std::io::Error }, + + #[snafu(display("failed to poll for process exit: {}", source))] + ProcessExitPoll { source: std::io::Error }, + + #[snafu(display( + "failed to put the terminal into raw mode: {}", + source + ))] + IntoRawMode { source: std::io::Error }, } pub fn spawn(line: &str) -> Result { @@ -32,15 +72,15 @@ pub struct RunningProcess { impl RunningProcess { fn new(line: &str) -> Result { - let pty = tokio_pty_process::AsyncPtyMaster::open() - .map_err(|e| Error::IOError(e))?; + let pty = + tokio_pty_process::AsyncPtyMaster::open().context(OpenPty)?; let (cmd, args) = - crate::parser::parse(line).map_err(|e| Error::ParserError(e))?; - let process = std::process::Command::new(cmd) + crate::parser::parse(line).context(ParserError { line })?; + let process = std::process::Command::new(cmd.clone()) .args(&args) .spawn_pty_async(&pty) - .map_err(|e| Error::IOError(e))?; + .context(SpawnProcess { cmd, args })?; // TODO: tokio::io::stdin is broken (it's blocking) // let input = tokio::io::stdin(); @@ -53,7 +93,8 @@ impl RunningProcess { buf: Vec::with_capacity(4096), output_done: false, exit_done: false, - _screen: crossterm::RawScreen::into_raw_mode().unwrap(), + _screen: crossterm::RawScreen::into_raw_mode() + .context(IntoRawMode)?, }) } } @@ -72,21 +113,12 @@ impl futures::stream::Stream for RunningProcess { let mut stdin = stdin.lock(); let mut buf = vec![0; 4096]; // TODO: async - match stdin.read(&mut buf) { - Ok(n) => { - if n > 0 { - let bytes = buf[..n].to_vec(); - - // TODO: async - let res = self.pty.write_all(&bytes); - if let Err(e) = res { - return Err(Error::IOError(e)); - } - } - } - Err(e) => { - return Err(Error::IOError(e)); - } + let n = stdin.read(&mut buf).context(ReadFromTerminal)?; + if n > 0 { + let bytes = buf[..n].to_vec(); + + // TODO: async + self.pty.write_all(&bytes).context(WriteToPty)?; } } _ => {} @@ -95,16 +127,13 @@ impl futures::stream::Stream for RunningProcess { // the buffer but we don't read it all in the previous read call, // since i think we won't get another notification until new bytes // actually arrive even if there are bytes in the buffer - if let Err(e) = self.input.clear_read_ready(ready) { - return Err(Error::IOError(e)); - } + self.input + .clear_read_ready(ready) + .context(PtyClearReadReady)?; if !self.output_done { self.buf.clear(); - let output_poll = self - .pty - .read_buf(&mut self.buf) - .map_err(|e| Error::IOError(e)); + let output_poll = self.pty.read_buf(&mut self.buf); match output_poll { Ok(futures::Async::Ready(n)) => { let bytes = self.buf[..n].to_vec(); @@ -128,14 +157,16 @@ impl futures::stream::Stream for RunningProcess { return Ok(futures::Async::NotReady); } Err(_) => { + // explicitly ignoring errors (for now?) because we + // always read off the end of the pty after the process + // is done self.output_done = true; } } } if !self.exit_done { - let exit_poll = - self.process.poll().map_err(|e| Error::IOError(e)); + let exit_poll = self.process.poll().context(ProcessExitPoll); match exit_poll { Ok(futures::Async::Ready(status)) => { self.exit_done = true; diff --git a/src/readline.rs b/src/readline.rs index af2b4fa..eaf29ec 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -1,9 +1,29 @@ +use snafu::{ensure, ResultExt, Snafu}; use std::io::Write; -#[derive(Debug)] +#[derive(Debug, Snafu)] pub enum Error { + #[snafu(display("failed to write to the terminal: {}", source))] + WriteToTerminal { source: std::io::Error }, + + #[snafu(display("end of input"))] EOF, - IOError(std::io::Error), + + #[snafu(display( + "failed to put the terminal into raw mode: {}", + source + ))] + IntoRawMode { source: std::io::Error }, + + #[snafu(display( + "failed to spawn a background thread to read terminal input: {}", + source + ))] + TerminalInputReadingThread { source: std::io::Error }, +} + +pub fn readline(prompt: &str, echo: bool) -> Result { + Readline::new(prompt, echo) } pub struct Readline { @@ -22,10 +42,11 @@ struct ReadlineState { } impl Readline { - fn new(prompt: &str, echo: bool) -> Self { - let screen = crossterm::RawScreen::into_raw_mode().unwrap(); + fn new(prompt: &str, echo: bool) -> Result { + let screen = + crossterm::RawScreen::into_raw_mode().context(IntoRawMode)?; - Readline { + Ok(Readline { reader: None, state: ReadlineState { prompt: prompt.to_string(), @@ -35,7 +56,20 @@ impl Readline { wrote_prompt: false, }, _raw_screen: screen, + }) + } + + fn with_reader(&mut self, f: F) -> Result + where + F: FnOnce(&KeyReader, &mut ReadlineState) -> Result, + { + let mut reader_opt = self.reader.take(); + if reader_opt.is_none() { + reader_opt = Some(KeyReader::new(futures::task::current())?); } + let ret = f(reader_opt.as_ref().unwrap(), &mut self.state); + self.reader = reader_opt; + ret } } @@ -60,9 +94,9 @@ impl ReadlineState { match event { crossterm::KeyEvent::Char(c) => { if self.cursor != self.buffer.len() && c != '\n' { - self.echo(b"\x1b[@").map_err(|e| Error::IOError(e))?; + self.echo(b"\x1b[@").context(WriteToTerminal)?; } - self.echo_char(c).map_err(|e| Error::IOError(e))?; + self.echo_char(c).context(WriteToTerminal)?; if c == '\n' { return Ok(futures::Async::Ready(self.buffer.clone())); @@ -73,16 +107,15 @@ impl ReadlineState { crossterm::KeyEvent::Ctrl(c) => { if c == 'd' { if self.buffer.is_empty() { - self.echo_char('\n') - .map_err(|e| Error::IOError(e))?; - return Err(Error::EOF); + self.echo_char('\n').context(WriteToTerminal)?; + ensure!(false, EOF); } } if c == 'c' { self.buffer = String::new(); self.cursor = 0; - self.echo_char('\n').map_err(|e| Error::IOError(e))?; - self.prompt().map_err(|e| Error::IOError(e))?; + self.echo_char('\n').context(WriteToTerminal)?; + self.prompt().context(WriteToTerminal)?; } } crossterm::KeyEvent::Backspace => { @@ -90,25 +123,23 @@ impl ReadlineState { self.cursor -= 1; self.buffer.remove(self.cursor); if self.cursor == self.buffer.len() { - self.echo(b"\x08 \x08") - .map_err(|e| Error::IOError(e))?; + self.echo(b"\x08 \x08").context(WriteToTerminal)?; } else { - self.echo(b"\x08\x1b[P") - .map_err(|e| Error::IOError(e))?; + self.echo(b"\x08\x1b[P").context(WriteToTerminal)?; } } } crossterm::KeyEvent::Left => { let cursor = 0.max(self.cursor - 1); if cursor != self.cursor { - self.write(b"\x1b[D").map_err(|e| Error::IOError(e))?; + self.write(b"\x1b[D").context(WriteToTerminal)?; self.cursor = cursor; } } crossterm::KeyEvent::Right => { let cursor = self.buffer.len().min(self.cursor + 1); if cursor != self.cursor { - self.write(b"\x1b[C").map_err(|e| Error::IOError(e))?; + self.write(b"\x1b[C").context(WriteToTerminal)?; self.cursor = cursor; } } @@ -159,67 +190,75 @@ impl futures::future::Future for Readline { fn poll(&mut self) -> futures::Poll { if !self.state.wrote_prompt { - self.state.prompt().map_err(|e| Error::IOError(e))?; + self.state.prompt().context(WriteToTerminal)?; self.state.wrote_prompt = true; } - let reader = self - .reader - .get_or_insert_with(|| KeyReader::new(futures::task::current())); - while let Some(event) = reader.poll() { - let a = self.state.process_event(event)?; - if a.is_ready() { - return Ok(a); + self.with_reader(|reader, state| { + loop { + match reader.try_recv() { + Ok(event) => { + let a = state.process_event(event)?; + if a.is_ready() { + return Ok(a); + } + } + Err(std::sync::mpsc::TryRecvError::Empty) => { + return Ok(futures::Async::NotReady) + } + Err(std::sync::mpsc::TryRecvError::Disconnected) => { + // is EOF correct here? + ensure!(false, EOF) + } + } } - } - Ok(futures::Async::NotReady) + }) } } -pub fn readline(prompt: &str, echo: bool) -> Readline { - Readline::new(prompt, echo) -} - struct KeyReader { events: std::sync::mpsc::Receiver, quit: std::sync::mpsc::Sender<()>, } impl KeyReader { - fn new(task: futures::task::Task) -> Self { + fn new(task: futures::task::Task) -> Result { let reader = crossterm::input().read_sync(); let (events_tx, events_rx) = std::sync::mpsc::channel(); let (quit_tx, quit_rx) = std::sync::mpsc::channel(); // TODO: this is pretty janky - it'd be better to build in more useful // support to crossterm directly - std::thread::spawn(move || { - for event in reader { - let newline = event - == crossterm::InputEvent::Keyboard( - crossterm::KeyEvent::Char('\n'), - ); - events_tx.send(event).unwrap(); - task.notify(); - if newline { - break; - } - if let Ok(_) = quit_rx.try_recv() { - break; + std::thread::Builder::new() + .spawn(move || { + for event in reader { + let newline = event + == crossterm::InputEvent::Keyboard( + crossterm::KeyEvent::Char('\n'), + ); + // unwrap is unpleasant, but so is figuring out how to + // propagate the error back to the main thread + events_tx.send(event).unwrap(); + task.notify(); + if newline { + break; + } + if let Ok(_) = quit_rx.try_recv() { + break; + } } - } - }); + }) + .context(TerminalInputReadingThread)?; - KeyReader { + Ok(KeyReader { events: events_rx, quit: quit_tx, - } + }) } - fn poll(&self) -> Option { - if let Ok(event) = self.events.try_recv() { - return Some(event); - } - None + fn try_recv( + &self, + ) -> Result { + self.events.try_recv() } } diff --git a/src/repl.rs b/src/repl.rs index e54eb22..749e0d6 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,12 +1,18 @@ use futures::future::{Future, IntoFuture}; use futures::stream::Stream; +use snafu::{ResultExt, Snafu}; use std::io::Write; -#[derive(Debug)] +#[derive(Debug, Snafu)] enum Error { - ReadError(crate::readline::Error), - EvalError(crate::process::Error), - PrintError(std::io::Error), + #[snafu(display("error during read: {}", source))] + ReadError { source: crate::readline::Error }, + + #[snafu(display("error during eval: {}", source))] + EvalError { source: crate::process::Error }, + + #[snafu(display("error during print: {}", source))] + PrintError { source: std::io::Error }, } pub fn repl() { @@ -39,17 +45,24 @@ pub fn repl() { eprint!("process exited weirdly?\r\n"); return Ok((done, false)); } - Err(Error::ReadError(crate::readline::Error::EOF)) => { + Err(Error::ReadError { + source: crate::readline::Error::EOF, + }) => { return Ok((done, true)); } - Err(Error::EvalError(crate::process::Error::ParserError( - crate::parser::Error::CommandRequired, - ))) => { + Err(Error::EvalError { + source: + crate::process::Error::ParserError { + source: crate::parser::Error::CommandRequired, + line: _, + }, + }) => { return Ok((done, false)); } Err(e) => { let stderr = std::io::stderr(); let mut stderr = stderr.lock(); + // panics seem fine for errors during error handling write!(stderr, "error: {:?}\r\n", e).unwrap(); stderr.flush().unwrap(); return Ok((done, false)); @@ -60,7 +73,10 @@ pub fn repl() { } fn read() -> impl futures::future::Future { - crate::readline::readline("$ ", true).map_err(|e| Error::ReadError(e)) + crate::readline::readline("$ ", true) + .into_future() + .flatten() + .map_err(|e| Error::ReadError { source: e }) } fn eval( @@ -70,12 +86,12 @@ fn eval( crate::process::spawn(line) .into_future() .flatten_stream() - .map_err(|e| Error::EvalError(e)) + .map_err(|e| Error::EvalError { source: e }) } fn print(out: &[u8]) -> Result<(), Error> { let stdout = std::io::stdout(); let mut stdout = stdout.lock(); - stdout.write(out).map_err(|e| Error::PrintError(e))?; - stdout.flush().map_err(|e| Error::PrintError(e)) + stdout.write(out).context(PrintError)?; + stdout.flush().context(PrintError) } -- cgit v1.2.3-54-g00ecf