aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-06-09 12:49:53 -0400
committerJesse Luehrs <doy@tozt.net>2019-06-09 13:30:31 -0400
commitb96861eb9b3e43f8958dfbf395a167d8839b8e00 (patch)
treedf8cc6517817584a1dbee8cf2d21be23ff8a2e37
parent035c598646d54ac0dc55fb9161e2f42e1863529e (diff)
downloadnbsh-old-b96861eb9b3e43f8958dfbf395a167d8839b8e00.tar.gz
nbsh-old-b96861eb9b3e43f8958dfbf395a167d8839b8e00.zip
start using snafu
-rw-r--r--Cargo.lock92
-rw-r--r--Cargo.toml1
-rw-r--r--src/parser.rs12
-rw-r--r--src/process.rs97
-rw-r--r--src/readline.rs149
-rw-r--r--src/repl.rs40
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
@@ -19,6 +19,27 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -38,6 +59,11 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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)",
]
@@ -354,6 +381,22 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -458,6 +501,11 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -512,11 +560,40 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -725,6 +802,11 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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<String>), 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<String>,
+ 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<RunningProcess, Error> {
@@ -32,15 +72,15 @@ pub struct RunningProcess {
impl RunningProcess {
fn new(line: &str) -> Result<Self, Error> {
- 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, Error> {
+ 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<Self, Error> {
+ 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<F, T>(&mut self, f: F) -> Result<T, Error>
+ where
+ F: FnOnce(&KeyReader, &mut ReadlineState) -> Result<T, Error>,
+ {
+ 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<Self::Item, Self::Error> {
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<crossterm::InputEvent>,
quit: std::sync::mpsc::Sender<()>,
}
impl KeyReader {
- fn new(task: futures::task::Task) -> Self {
+ fn new(task: futures::task::Task) -> Result<Self, Error> {
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<crossterm::InputEvent> {
- if let Ok(event) = self.events.try_recv() {
- return Some(event);
- }
- None
+ fn try_recv(
+ &self,
+ ) -> Result<crossterm::InputEvent, std::sync::mpsc::TryRecvError> {
+ 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<Item = String, Error = Error> {
- 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)
}