aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-07-12 03:35:06 -0400
committerJesse Luehrs <doy@tozt.net>2019-07-12 03:48:31 -0400
commit65ec6eb0f5c587139f730afa2291493a8a828c1f (patch)
treeed001684324ef8d5b43d32a4c3ef380741b7bc61
parentff8d2f2d6f0efc363d3681c3908923ed8a1daf4b (diff)
downloadnbsh-old-65ec6eb0f5c587139f730afa2291493a8a828c1f.tar.gz
nbsh-old-65ec6eb0f5c587139f730afa2291493a8a828c1f.zip
allow the tui to manage the overall raw screen
-rw-r--r--src/builtins.rs6
-rw-r--r--src/eval.rs71
-rw-r--r--src/process.rs31
-rw-r--r--src/readline.rs54
-rw-r--r--src/repl.rs12
-rw-r--r--src/tui.rs73
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<()> {
diff --git a/src/tui.rs b/src/tui.rs
index 49f7d6f..17b2eed 100644
--- a/src/tui.rs
+++ b/src/tui.rs
@@ -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()?;