diff options
author | Jesse Luehrs <doy@tozt.net> | 2019-07-12 03:35:06 -0400 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2019-07-12 03:48:31 -0400 |
commit | 65ec6eb0f5c587139f730afa2291493a8a828c1f (patch) | |
tree | ed001684324ef8d5b43d32a4c3ef380741b7bc61 | |
parent | ff8d2f2d6f0efc363d3681c3908923ed8a1daf4b (diff) | |
download | nbsh-old-65ec6eb0f5c587139f730afa2291493a8a828c1f.tar.gz nbsh-old-65ec6eb0f5c587139f730afa2291493a8a828c1f.zip |
allow the tui to manage the overall raw screen
-rw-r--r-- | src/builtins.rs | 6 | ||||
-rw-r--r-- | src/eval.rs | 71 | ||||
-rw-r--r-- | src/process.rs | 31 | ||||
-rw-r--r-- | src/readline.rs | 54 | ||||
-rw-r--r-- | src/repl.rs | 12 | ||||
-rw-r--r-- | src/tui.rs | 73 |
6 files changed, 158 insertions, 89 deletions
diff --git a/src/builtins.rs b/src/builtins.rs index 83fa0d1..d1f5b61 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -35,10 +35,6 @@ pub enum Error { pub type Result<T> = std::result::Result<T, Error>; -pub fn exec(cmd: &str, args: &[String]) -> Result<Builtin> { - Builtin::new(cmd, args) -} - pub struct Builtin { cmd: String, args: Vec<String>, @@ -47,7 +43,7 @@ pub struct Builtin { } impl Builtin { - fn new(cmd: &str, args: &[String]) -> Result<Self> { + pub fn new(cmd: &str, args: &[String]) -> Result<Self> { match cmd { "cd" => Ok(Self { cmd: cmd.to_string(), diff --git a/src/eval.rs b/src/eval.rs index af3de9e..a7e09c4 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -28,9 +28,10 @@ pub enum Error { }, } +#[allow(dead_code)] pub type Result<T> = std::result::Result<T, Error>; -pub fn eval(line: &str) -> Result<Eval> { +pub fn eval(line: &str) -> Eval { Eval::new(line) } @@ -41,32 +42,28 @@ pub enum CommandEvent { } pub struct Eval { - stream: Box< - dyn futures::stream::Stream<Item = CommandEvent, Error = Error> - + Send, + line: String, + stream: Option< + Box< + dyn futures::stream::Stream<Item = CommandEvent, Error = Error> + + Send, + >, >, + manage_screen: bool, } impl Eval { - fn new(line: &str) -> Result<Self> { - let (cmd, args) = - crate::parser::parse(line).context(Parser { line })?; - let builtin_stream = crate::builtins::exec(&cmd, &args); - let stream: Box< - dyn futures::stream::Stream<Item = CommandEvent, Error = Error> - + Send, - > = if let Ok(s) = builtin_stream { - Box::new(s.context(BuiltinExecution { cmd })) - } else { - let process_stream = crate::process::spawn(&cmd, &args); - match process_stream { - Ok(s) => Box::new(s.context(ProcessExecution { cmd })), - Err(e) => { - return Err(e).context(Command { cmd }); - } - } - }; - Ok(Self { stream }) + pub fn new(line: &str) -> Self { + Self { + line: line.to_string(), + stream: None, + manage_screen: true, + } + } + + pub fn set_raw(mut self, raw: bool) -> Self { + self.manage_screen = raw; + self } } @@ -76,6 +73,32 @@ impl futures::stream::Stream for Eval { type Error = Error; fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> { - self.stream.poll() + if self.stream.is_none() { + let line = self.line.as_ref(); + let (cmd, args) = + crate::parser::parse(line).context(Parser { line })?; + let builtin_stream = crate::builtins::Builtin::new(&cmd, &args); + let stream: Box< + dyn futures::stream::Stream< + Item = CommandEvent, + Error = Error, + > + Send, + > = if let Ok(s) = builtin_stream { + Box::new(s.context(BuiltinExecution { cmd })) + } else { + let process_stream = + crate::process::Process::new(&cmd, &args) + .context(Command { cmd: cmd.clone() })? + .set_raw(self.manage_screen); + Box::new(process_stream.context(ProcessExecution { cmd })) + }; + self.stream = Some(stream); + } + + if let Some(ref mut stream) = &mut self.stream { + stream.poll() + } else { + unreachable!() + } } } diff --git a/src/process.rs b/src/process.rs index 4845bef..9993be7 100644 --- a/src/process.rs +++ b/src/process.rs @@ -39,11 +39,7 @@ pub enum Error { pub type Result<T> = std::result::Result<T, Error>; -pub fn spawn(cmd: &str, args: &[String]) -> Result<RunningProcess> { - RunningProcess::new(cmd, args) -} - -pub struct RunningProcess { +pub struct Process { pty: tokio_pty_process::AsyncPtyMaster, process: tokio_pty_process::Child, // TODO: tokio::io::Stdin is broken @@ -55,7 +51,8 @@ pub struct RunningProcess { started: bool, output_done: bool, exit_done: bool, - _screen: crossterm::RawScreen, + manage_screen: bool, + raw_screen: Option<crossterm::RawScreen>, } struct Resizer<'a, T> { @@ -75,8 +72,8 @@ impl<'a, T: tokio_pty_process::PtyMaster> futures::future::Future } } -impl RunningProcess { - fn new(cmd: &str, args: &[String]) -> Result<Self> { +impl Process { + pub fn new(cmd: &str, args: &[String]) -> Result<Self> { let pty = tokio_pty_process::AsyncPtyMaster::open().context(OpenPty)?; @@ -108,18 +105,30 @@ impl RunningProcess { started: false, output_done: false, exit_done: false, - _screen: crossterm::RawScreen::into_raw_mode() - .context(IntoRawMode)?, + manage_screen: true, + raw_screen: None, }) } + + #[allow(dead_code)] + pub fn set_raw(mut self, raw: bool) -> Self { + self.manage_screen = raw; + self + } } #[must_use = "streams do nothing unless polled"] -impl futures::stream::Stream for RunningProcess { +impl futures::stream::Stream for Process { type Item = crate::eval::CommandEvent; type Error = Error; fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> { + if self.manage_screen && self.raw_screen.is_none() { + self.raw_screen = Some( + crossterm::RawScreen::into_raw_mode().context(IntoRawMode)?, + ); + } + if !self.started { self.started = true; return Ok(futures::Async::Ready(Some( diff --git a/src/readline.rs b/src/readline.rs index f878d50..0efd982 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -24,19 +24,21 @@ pub enum Error { pub type Result<T> = std::result::Result<T, Error>; -pub fn readline() -> Result<Readline> { +pub fn readline() -> Readline { Readline::new() } pub struct Readline { reader: Option<KeyReader>, state: ReadlineState, - _raw_screen: crossterm::RawScreen, + raw_screen: Option<crossterm::RawScreen>, } struct ReadlineState { prompt: String, echo: bool, + output: bool, + manage_screen: bool, buffer: String, cursor: usize, @@ -44,21 +46,20 @@ struct ReadlineState { } impl Readline { - pub fn new() -> Result<Self> { - let screen = - crossterm::RawScreen::into_raw_mode().context(IntoRawMode)?; - - Ok(Self { + pub fn new() -> Self { + Self { reader: None, state: ReadlineState { prompt: String::from("$ "), echo: true, + output: true, + manage_screen: true, buffer: String::new(), cursor: 0, wrote_prompt: false, }, - _raw_screen: screen, - }) + raw_screen: None, + } } #[allow(dead_code)] @@ -73,6 +74,22 @@ impl Readline { self } + #[allow(dead_code)] + pub fn disable_output(mut self, disable: bool) -> Self { + self.state.output = !disable; + self + } + + pub fn set_raw(mut self, raw: bool) -> Self { + self.state.manage_screen = raw; + self + } + + #[allow(dead_code)] + pub fn cursor_pos(&self) -> usize { + self.state.cursor + } + fn with_reader<F, T>(&mut self, f: F) -> Result<T> where F: FnOnce(&KeyReader, &mut ReadlineState) -> Result<T>, @@ -212,6 +229,10 @@ impl ReadlineState { } fn write(&self, buf: &[u8]) -> std::io::Result<()> { + if !self.output { + return Ok(()); + } + let stdout = std::io::stdout(); let mut stdout = stdout.lock(); stdout.write_all(buf)?; @@ -246,6 +267,15 @@ impl ReadlineState { } } +impl std::fmt::Display for Readline { + fn fmt( + &self, + f: &mut std::fmt::Formatter, + ) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}{}", self.state.prompt, self.state.buffer) + } +} + #[must_use = "futures do nothing unless polled"] impl futures::future::Future for Readline { type Item = String; @@ -257,6 +287,12 @@ impl futures::future::Future for Readline { self.state.wrote_prompt = true; } + if self.state.manage_screen && self.raw_screen.is_none() { + self.raw_screen = Some( + crossterm::RawScreen::into_raw_mode().context(IntoRawMode)?, + ); + } + self.with_reader(|reader, state| { loop { match reader.events.try_recv() { diff --git a/src/repl.rs b/src/repl.rs index 7f4c10d..4188fbe 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,4 +1,4 @@ -use futures::future::{Future as _, IntoFuture as _}; +use futures::future::Future as _; use futures::stream::Stream as _; use snafu::futures01::{FutureExt as _, StreamExt as _}; use snafu::ResultExt as _; @@ -51,20 +51,14 @@ pub fn repl() { } fn read() -> impl futures::future::Future<Item = String, Error = Error> { - crate::readline::readline() - .into_future() - .flatten() - .context(Read) + crate::readline::readline().context(Read) } fn eval( line: &str, ) -> impl futures::stream::Stream<Item = crate::eval::CommandEvent, Error = Error> { - crate::eval::eval(line) - .into_future() - .flatten_stream() - .context(Eval) + crate::eval::eval(line).context(Eval) } fn print(event: &crate::eval::CommandEvent) -> Result<()> { @@ -8,6 +8,12 @@ enum Error { #[snafu(display("invalid command index: {}", idx))] InvalidCommandIndex { idx: usize }, + #[snafu(display( + "failed to put the terminal into raw mode: {}", + source + ))] + IntoRawMode { source: std::io::Error }, + #[snafu(display("error during read: {}", source))] Read { source: crate::readline::Error }, @@ -32,6 +38,7 @@ pub struct Tui { idx: usize, readline: Option<crate::readline::Readline>, commands: std::collections::HashMap<usize, Command>, + raw_screen: Option<crossterm::RawScreen>, } impl Tui { @@ -39,8 +46,8 @@ impl Tui { Self::default() } - fn read() -> Result<crate::readline::Readline> { - crate::readline::readline().context(Read) + fn read() -> crate::readline::Readline { + crate::readline::Readline::new().set_raw(false) } fn eval( @@ -51,13 +58,8 @@ impl Tui { if self.commands.contains_key(&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)); - } - Err(e) => return Err(e), - } + let eval = crate::eval::Eval::new(line).set_raw(false); + self.commands.insert(idx, Command::new(eval)); Ok(()) } @@ -124,25 +126,17 @@ impl Tui { Ok(()) } - fn poll_read(&mut self) -> Result<bool> { + fn poll_read(&mut self) { if self.readline.is_none() && self.commands.is_empty() { self.idx += 1; - self.readline = Some(Self::read()?) + self.readline = Some(Self::read()) } - Ok(false) } fn poll_eval(&mut self) -> Result<bool> { 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 { @@ -173,25 +167,36 @@ impl Tui { let mut did_work = false; for idx in self.commands.keys().cloned().collect::<Vec<usize>>() { - match self - .commands - .get_mut(&idx) - .unwrap() - .future - .poll() - .context(Eval)? - { - futures::Async::Ready(Some(event)) => { + match self.commands.get_mut(&idx).unwrap().future.poll() { + Ok(futures::Async::Ready(Some(event))) => { self.print(idx, event)?; did_work = true; } - futures::Async::Ready(None) => { + Ok(futures::Async::Ready(None)) => { self.commands .remove(&idx) .context(InvalidCommandIndex { idx })?; did_work = true; } - futures::Async::NotReady => {} + Ok(futures::Async::NotReady) => {} + + // Parser and Command errors are always fatal, but execution + // errors might not be + Err(e @ crate::eval::Error::Parser { .. }) => { + self.commands + .remove(&idx) + .context(InvalidCommandIndex { idx })?; + return Err(e).context(Eval); + } + Err(e @ crate::eval::Error::Command { .. }) => { + self.commands + .remove(&idx) + .context(InvalidCommandIndex { idx })?; + return Err(e).context(Eval); + } + Err(e) => { + return Err(e).context(Eval); + } } } @@ -199,10 +204,16 @@ impl Tui { } fn poll_with_errors(&mut self) -> futures::Poll<(), Error> { + if self.raw_screen.is_none() { + self.raw_screen = Some( + crossterm::RawScreen::into_raw_mode().context(IntoRawMode)?, + ); + } + loop { let mut did_work = false; - did_work |= self.poll_read()?; + self.poll_read(); did_work |= self.poll_eval()?; did_work |= self.poll_print()?; |