From 8fc62f921ebe7cfed93c7a53403cc6b93a022b51 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Wed, 10 Jul 2019 00:26:38 -0400 Subject: move the rest of the tui logic into the state --- src/state.rs | 125 ++++++++++++++++++++++++++++++++--------------------------- src/tui.rs | 81 +++----------------------------------- 2 files changed, 73 insertions(+), 133 deletions(-) diff --git a/src/state.rs b/src/state.rs index d291e15..197ff0e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,3 +1,4 @@ +use futures::future::Future as _; use futures::stream::Stream as _; use snafu::{OptionExt as _, ResultExt as _}; use std::io::Write as _; @@ -7,59 +8,59 @@ pub enum Error { #[snafu(display("invalid command index: {}", idx))] InvalidCommandIndex { idx: usize }, - #[snafu(display("error sending message"))] - Sending, - - #[snafu(display("error printing output: {}", source))] - PrintOutput { source: std::io::Error }, + #[snafu(display("error during read: {}", source))] + Read { source: crate::readline::Error }, #[snafu(display("error during eval: {}", source))] Eval { source: crate::eval::Error }, -} -pub type Result = std::result::Result; + #[snafu(display("error during print: {}", source))] + Print { source: std::io::Error }, -#[derive(Debug)] -pub enum StateEvent { - Line(usize, String, futures::sync::oneshot::Sender>), + #[snafu(display("eof"))] + EOF, } +pub type Result = std::result::Result; + pub struct State { - r: futures::sync::mpsc::Receiver, + idx: usize, + readline: Option, commands: std::collections::HashMap, } impl State { - pub fn new(r: futures::sync::mpsc::Receiver) -> Self { - Self { - r, + pub fn new() -> Result { + Ok(Self { + idx: 0, + readline: Some(Self::read()?), commands: std::collections::HashMap::new(), - } + }) + } + + fn read() -> Result { + crate::readline::readline("$ ", true).context(Read) } - pub fn eval( + fn eval( &mut self, idx: usize, line: &str, - res: futures::sync::oneshot::Sender>, - ) -> std::result::Result< - (), - (futures::sync::oneshot::Sender>, Error), - > { + ) -> std::result::Result<(), Error> { if self.commands.contains_key(&idx) { - return Err((res, Error::InvalidCommandIndex { idx })); + return Err(Error::InvalidCommandIndex { idx }); } let eval = crate::eval::eval(line).context(Eval); match eval { Ok(eval) => { - self.commands.insert(idx, Command::new(eval, res)); + self.commands.insert(idx, Command::new(eval)); } - Err(e) => return Err((res, e)), + Err(e) => return Err(e), } Ok(()) } - pub fn command_start( + fn command_start( &mut self, idx: usize, cmd: &str, @@ -77,11 +78,7 @@ impl State { Ok(()) } - pub fn command_output( - &mut self, - idx: usize, - output: &[u8], - ) -> Result<()> { + fn command_output(&mut self, idx: usize, output: &[u8]) -> Result<()> { let command = self .commands .get_mut(&idx) @@ -90,13 +87,13 @@ impl State { let stdout = std::io::stdout(); let mut stdout = stdout.lock(); - stdout.write(output).context(PrintOutput)?; - stdout.flush().context(PrintOutput)?; + stdout.write(output).context(Print)?; + stdout.flush().context(Print)?; Ok(()) } - pub fn command_exit( + fn command_exit( &mut self, idx: usize, status: std::process::ExitStatus, @@ -114,24 +111,43 @@ impl State { loop { let mut did_work = false; - match self.r.poll().map_err(|()| unreachable!())? { - futures::Async::Ready(Some(StateEvent::Line( - idx, - line, - res, - ))) => { - match self.eval(idx, &line, res) { - Ok(()) => {} - Err((res, e)) => { - res.send(Err(e)).map_err(|_| Error::Sending)?; + if self.readline.is_none() && self.commands.is_empty() { + self.idx += 1; + self.readline = Some(Self::read()?) + } + + if let Some(mut r) = self.readline.take() { + match r.poll() { + Ok(futures::Async::Ready(line)) => { + // overlapping RawScreen lifespans don't work properly + // - if readline creates a RawScreen, then eval + // creates a separate one, then readline drops it, the + // screen will go back to cooked even though a + // RawScreen instance is still live. + drop(r); + + match self.eval(self.idx, &line) { + Ok(()) + | Err(Error::Eval { + source: + crate::eval::Error::Parser { + source: + crate::parser::Error::CommandRequired, + .. + }, + }) => {} + Err(e) => return Err(e), } + did_work = true; } - did_work = true; - } - futures::Async::Ready(None) => { - return Ok(futures::Async::Ready(())); + Ok(futures::Async::NotReady) => { + self.readline.replace(r); + } + Err(crate::readline::Error::EOF) => { + return Err(Error::EOF) + } + Err(e) => return Err(e).context(Read), } - futures::Async::NotReady => {} } for idx in self.commands.keys().cloned().collect::>() { @@ -163,10 +179,7 @@ impl State { futures::Async::Ready(None) => { self.commands .remove(&idx) - .context(InvalidCommandIndex { idx })? - .res - .send(Ok(())) - .map_err(|_| Error::Sending)?; + .context(InvalidCommandIndex { idx })?; did_work = true; } futures::Async::NotReady => {} @@ -188,6 +201,7 @@ impl futures::future::Future for State { loop { match self.poll_with_errors() { Ok(a) => return Ok(a), + Err(Error::EOF) => return Ok(futures::Async::Ready(())), Err(e) => { eprint!("error polling state: {}\r\n", e); } @@ -198,7 +212,6 @@ impl futures::future::Future for State { struct Command { future: crate::eval::Eval, - res: futures::sync::oneshot::Sender>, cmd: Option, args: Option>, output: Vec, @@ -206,13 +219,9 @@ struct Command { } impl Command { - fn new( - future: crate::eval::Eval, - res: futures::sync::oneshot::Sender>, - ) -> Self { + fn new(future: crate::eval::Eval) -> Self { Self { future, - res, cmd: None, args: None, output: vec![], diff --git a/src/tui.rs b/src/tui.rs index 221604d..296f1bf 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,84 +1,15 @@ -use futures::future::{Future as _, IntoFuture as _}; -use futures::sink::Sink as _; -use snafu::futures01::FutureExt as _; -use std::io::Write as _; +use snafu::ResultExt as _; #[derive(Debug, snafu::Snafu)] pub enum Error { - #[snafu(display("error during read: {}", source))] - Read { source: crate::readline::Error }, - #[snafu(display("error from state: {}", source))] State { source: crate::state::Error }, - - #[snafu(display("error during sending: {}", source))] - Sending { - source: futures::sync::mpsc::SendError, - }, - - #[snafu(display("error during receiving: {}", source))] - Receiving { - source: futures::sync::oneshot::Canceled, - }, } pub fn tui() { - tokio::run(futures::lazy(|| { - let (w, r) = futures::sync::mpsc::channel(0); - - tokio::spawn( - crate::state::State::new(r).map_err(|()| unreachable!()), - ); - - futures::future::loop_fn(0, move |idx| { - let w = w.clone(); - read() - .and_then(move |line| { - let (res, req) = futures::sync::oneshot::channel(); - w.send(crate::state::StateEvent::Line(idx, line, res)) - .context(Sending) - .and_then(|_| req.context(Receiving)) - }) - .then(move |res| match res { - // successful run or empty input means prompt again - Ok(Ok(())) - | Ok(Err(crate::state::Error::Eval { - source: - crate::eval::Error::Parser { - source: crate::parser::Error::CommandRequired, - .. - }, - })) => Ok(futures::future::Loop::Continue(idx + 1)), - // eof means we're done - Err(Error::Read { - source: crate::readline::Error::EOF, - }) => Ok(futures::future::Loop::Break(())), - // any other errors should be displayed, then we - // prompt again - Err(e) => { - error(&e); - Ok(futures::future::Loop::Continue(idx + 1)) - } - Ok(Err(e)) => { - error(&Error::State { source: e }); - Ok(futures::future::Loop::Continue(idx + 1)) - } - }) - }) - })); -} - -fn read() -> impl futures::future::Future { - crate::readline::readline("$ ", true) - .into_future() - .flatten() - .context(Read) -} - -fn error(e: &Error) { - let stderr = std::io::stderr(); - let mut stderr = stderr.lock(); - // panics seem fine for errors during error handling - write!(stderr, "{}\r\n", e).unwrap(); - stderr.flush().unwrap(); + let state = crate::state::State::new().context(State); + match state { + Ok(state) => tokio::run(state), + Err(e) => eprintln!("failed to create state: {}", e), + } } -- cgit v1.2.3-54-g00ecf