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 --- src/error.rs | 5 ++++ src/lib.rs | 4 +++ src/process.rs | 4 +++ src/resize.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 src/resize.rs (limited to 'src') 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-54-g00ecf