From f88153f62b024d1c2ef82c5ac7ae9002e4fe2967 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 27 Oct 2019 13:33:47 -0400 Subject: add terminal resizing functionality to keep the process's pty size in sync with the size of the user's terminal --- Cargo.toml | 1 + examples/interhack.rs | 12 ++++---- examples/shell.rs | 2 ++ src/error.rs | 5 ++++ src/lib.rs | 4 +++ src/process.rs | 4 +++ src/resize.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/resize.rs diff --git a/Cargo.toml b/Cargo.toml index 8bc303c..dfd7067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ log = "0.4" snafu = "0.5" tokio = "0.1.22" tokio-pty-process = "0.4" +tokio-terminal-resize = "0.1" [dev-dependencies] crossterm = "0.11" diff --git a/examples/interhack.rs b/examples/interhack.rs index 2e753b5..88a3349 100644 --- a/examples/interhack.rs +++ b/examples/interhack.rs @@ -7,7 +7,7 @@ use tokio::io::AsyncRead as _; mod input; struct Interhack { - process: tokio_pty_process_stream::Process, + process: tokio_pty_process_stream::ResizingProcess, stdin: input::evented_stdin::Stdin, read_buf: [u8; 4096], } @@ -15,10 +15,12 @@ struct Interhack { impl Interhack { fn new() -> Self { Self { - process: tokio_pty_process_stream::Process::new( - "nethack", - &[], - input::buf::Stdin::new(), + process: tokio_pty_process_stream::ResizingProcess::new( + tokio_pty_process_stream::Process::new( + "nethack", + &[], + input::buf::Stdin::new(), + ), ), stdin: input::evented_stdin::Stdin::new(), read_buf: [0; 4096], diff --git a/examples/shell.rs b/examples/shell.rs index 682c008..2cd7ba2 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -15,6 +15,7 @@ fn main() { &args, input::evented_stdin::Stdin::new(), ); + let process = tokio_pty_process_stream::ResizingProcess::new(process); let _raw = crossterm::RawScreen::into_raw_mode().unwrap(); tokio::run( @@ -33,6 +34,7 @@ fn main() { tokio_pty_process_stream::Event::CommandExit { .. } => {} + tokio_pty_process_stream::Event::Resize { .. } => {} } futures::future::ok(()) }) diff --git a/src/error.rs b/src/error.rs index c8fc902..2259015 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,6 +22,11 @@ pub enum Error { #[snafu(display("failed to resize pty: {}", source))] ResizePty { source: std::io::Error }, + #[snafu(display("failed to poll for terminal resizing: {}", source))] + Resize { + source: tokio_terminal_resize::Error, + }, + /// failed to spawn process #[snafu(display("failed to spawn process for `{}`: {}", cmd, source))] SpawnProcess { cmd: String, source: std::io::Error }, diff --git a/src/lib.rs b/src/lib.rs index 4ad3960..9a19ed8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ //! //! let process = //! tokio_pty_process_stream::Process::new(&cmd, &args, Stdin::new()); +//! let process = tokio_pty_process_stream::ResizingProcess::new(process); //! //! let _raw = crossterm::RawScreen::into_raw_mode().unwrap(); //! tokio::run( @@ -49,6 +50,7 @@ //! tokio_pty_process_stream::Event::CommandExit { //! .. //! } => {} +//! tokio_pty_process_stream::Event::Resize { .. } => {} //! } //! futures::future::ok(()) //! }) @@ -151,3 +153,5 @@ pub use error::Error; mod process; pub use process::Event; pub use process::Process; +mod resize; +pub use resize::ResizingProcess; diff --git a/src/process.rs b/src/process.rs index ed3344c..f5c76ed 100644 --- a/src/process.rs +++ b/src/process.rs @@ -20,6 +20,9 @@ pub enum Event { /// Emitted when the command has exited. CommandExit { status: std::process::ExitStatus }, + + /// Emitted by a `ResizingProcess` when a resize event happens + Resize { size: (u16, u16) }, } struct State { @@ -354,6 +357,7 @@ mod test { assert!(status.success()); exited = true; } + Event::Resize { .. } => {} } } assert!(exited); diff --git a/src/resize.rs b/src/resize.rs new file mode 100644 index 0000000..5b59f9e --- /dev/null +++ b/src/resize.rs @@ -0,0 +1,78 @@ +use futures::future::Future as _; +use futures::stream::Stream as _; +use snafu::futures01::StreamExt as _; + +pub struct ResizingProcess { + process: crate::process::Process, + resizer: Box< + dyn futures::stream::Stream< + Item = (u16, u16), + Error = crate::error::Error, + > + Send, + >, +} + +impl ResizingProcess { + pub fn new(process: crate::process::Process) -> Self { + Self { + process, + resizer: Box::new( + tokio_terminal_resize::resizes() + .flatten_stream() + .context(crate::error::Resize), + ), + } + } + + pub fn input(&mut self) -> &mut R { + self.process.input() + } +} + +impl ResizingProcess { + const POLL_FNS: + &'static [&'static dyn for<'a> Fn( + &'a mut Self, + ) + -> component_future::Poll< + Option, + crate::error::Error, + >] = &[&Self::poll_resize, &Self::poll_process]; + + fn poll_resize( + &mut self, + ) -> component_future::Poll< + Option, + crate::error::Error, + > { + let (rows, cols) = + component_future::try_ready!(self.resizer.poll()).unwrap(); + self.process.resize(rows, cols); + Ok(component_future::Async::Ready(Some( + crate::process::Event::Resize { size: (rows, cols) }, + ))) + } + + fn poll_process( + &mut self, + ) -> component_future::Poll< + Option, + crate::error::Error, + > { + Ok(component_future::Async::Ready( + component_future::try_ready!(self.process.poll()), + )) + } +} + +#[must_use = "streams do nothing unless polled"] +impl futures::stream::Stream + for ResizingProcess +{ + type Item = crate::process::Event; + type Error = crate::error::Error; + + fn poll(&mut self) -> futures::Poll, Self::Error> { + component_future::poll_stream(self, Self::POLL_FNS) + } +} -- cgit v1.2.3