1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-21 19:58:15 +03:00

Add BufferedTerminal

This commit is contained in:
Wez Furlong 2018-07-22 08:03:11 -07:00
parent c91219d65b
commit 2559257869
6 changed files with 187 additions and 25 deletions

View File

@ -0,0 +1,36 @@
//! This example shows how to use `BufferedTerminal` to queue
//! up changes and then flush them. `BufferedTerminal` enables
//! optimizing the output sequence to update the screen, which is
//! important on links with poor connectivity.
extern crate failure;
extern crate termwiz;
use failure::Error;
use termwiz::caps::Capabilities;
use termwiz::cell::AttributeChange;
use termwiz::color::AnsiColor;
use termwiz::surface::Change;
use termwiz::terminal::buffered::BufferedTerminal;
use termwiz::terminal::{new_terminal, Terminal};
fn main() -> Result<(), Error> {
let caps = Capabilities::new_from_env()?;
let mut terminal = new_terminal(caps)?;
terminal.set_raw_mode()?;
let mut buf = BufferedTerminal::new(terminal)?;
buf.add_change(Change::Attribute(AttributeChange::Foreground(
AnsiColor::Maroon.into(),
)));
buf.add_change("Hello world\r\n");
buf.add_change(Change::Attribute(AttributeChange::Foreground(
AnsiColor::Red.into(),
)));
buf.add_change("and in red here\r\n");
buf.flush()?;
Ok(())
}

View File

@ -5,7 +5,8 @@ use failure::Error;
use termwiz::caps::Capabilities;
use termwiz::cell::AttributeChange;
use termwiz::color::AnsiColor;
use termwiz::surface::{Change, Surface};
use termwiz::surface::Change;
use termwiz::terminal::buffered::BufferedTerminal;
use termwiz::terminal::{new_terminal, Terminal};
fn main() -> Result<(), Error> {
@ -14,22 +15,18 @@ fn main() -> Result<(), Error> {
let mut terminal = new_terminal(caps)?;
terminal.set_raw_mode()?;
let size = terminal.get_screen_size()?;
let mut screen = Surface::new(size.cols as usize, size.rows as usize);
let mut buf = BufferedTerminal::new(terminal)?;
screen.add_change(Change::Attribute(AttributeChange::Foreground(
buf.add_change(Change::Attribute(AttributeChange::Foreground(
AnsiColor::Maroon.into(),
)));
screen.add_change("Hello world\r\n");
screen.add_change(Change::Attribute(AttributeChange::Foreground(
buf.add_change("Hello world\r\n");
buf.add_change(Change::Attribute(AttributeChange::Foreground(
AnsiColor::Red.into(),
)));
screen.add_change("and in red here\r\n");
buf.add_change("and in red here\r\n");
let (_seq, changes) = screen.get_changes(0);
terminal.render(&changes)?;
//println!("changes: {:?}", changes);
println!("size: {:?}", size);
buf.flush()?;
Ok(())
}

View File

@ -1,7 +1,9 @@
//! This example shows how to render `Change`s directly to
//! an instance of `Terminal`. When used in this way, the
//! library performas no optimization on the change stream.
//! Consider using the `Surface` struct to enable optimization.
//! Consider using the `Surface` struct to enable optimization;
//! the `buffered_terminal.rs` example demonstrates a simple
//! way to enable optimizations.
extern crate failure;
extern crate termwiz;

View File

@ -209,7 +209,7 @@ pub struct Surface {
}
impl Surface {
/// Create a new Surface surface with the specified width and height.
/// Create a new Surface with the specified width and height.
pub fn new(width: usize, height: usize) -> Self {
let mut scr = Surface {
width,
@ -220,7 +220,12 @@ impl Surface {
scr
}
/// Resize the Surface surface to the specified width and height.
/// Returns the (width, height) of the surface
pub fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
/// Resize the Surface to the specified width and height.
/// If the width and/or height are smaller than previously, the rows and/or
/// columns are truncated. If the width and/or height are larger than
/// previously then an appropriate number of cells are added to the

121
src/terminal/buffered.rs Normal file
View File

@ -0,0 +1,121 @@
//! A Terminal buffered with a Surface
use failure::Error;
use std::ops::{Deref, DerefMut};
use surface::{SequenceNo, Surface};
use terminal::Terminal;
/// `BufferedTerminal` is a convenience wrapper around both
/// a `Terminal` and a `Surface`. It enables easier use of
/// the output optimization features available to `Surface`
/// and internally keeps track of the sequence number.
/// `BufferedTerminal` derefs to `Surface` and makes available
/// the surface API.
/// The `flush` method is used to compute the optimized set
/// of changes and actually render them to the underlying
/// `Terminal`. No output will be visible until it is flushed!
pub struct BufferedTerminal<T: Terminal> {
terminal: T,
surface: Surface,
seqno: SequenceNo,
}
impl<T: Terminal> BufferedTerminal<T> {
/// Create a new `BufferedTerminal` with a `Surface` of
/// a matching size.
pub fn new(mut terminal: T) -> Result<Self, Error> {
let size = terminal.get_screen_size()?;
let surface = Surface::new(size.cols, size.rows);
Ok(Self {
terminal,
surface,
seqno: 0,
})
}
/// Get a mutable reference to the underlying terminal instance
pub fn terminal(&mut self) -> &mut T {
&mut self.terminal
}
/// Compute the set of changes needed to update the screen to
/// match the current contents of the embedded `Surface` and
/// send them to the `Terminal`.
/// If some other process has output over the terminal screen,
/// or other artifacts are present, this routine has no way to
/// detect the lose of synchronization.
/// Applications typically build in a refresh function (CTRL-L
/// is common for unix applications) to request a repaint.
/// You can use the `repaint` function for that situation.
pub fn flush(&mut self) -> Result<(), Error> {
{
let (seq, changes) = self.surface.get_changes(self.seqno);
// If we encounter an error during rendering, we want to
// reset the sequence number so that a subsequent paint
// renders all.
self.seqno = 0;
self.terminal.render(&changes)?;
self.terminal.flush()?;
self.seqno = seq;
}
self.surface.flush_changes_older_than(self.seqno);
Ok(())
}
/// Clears the screen and re-draws the surface contents onto
/// the Terminal.
pub fn repaint(&mut self) -> Result<(), Error> {
self.seqno = 0;
self.flush()
}
/// Check to see if the Terminal has been resized by its user.
/// If it has, resize the surface to match the new dimensions
/// and return true. If the terminal was resized, the application
/// will typically want to apply changes to match the new size
/// and follow it up with a `flush` call to update the screen.
///
/// Why isn't this automatic? On Unix systems the SIGWINCH signal
/// is used to indicate that a terminal size has changed. This notification
/// is completely out of band from the interactions with the underlying
/// terminal device, and thus requires a function such as this one to
/// be called after receipt of SIGWINCH, or just speculatively from time
/// to time.
///
/// Attaching signal handlers unilaterally from a library is undesirable,
/// as the embedding application may have strong opinions about how
/// best to do such a thing, so we do not automatically configure a
/// signal handler.
///
/// On Windows it is possible to receive notification about window
/// resizing by processing input events. Enabling those requires
/// manipulating the input mode and establishing a handler to
/// consume the input records. Such a thing is possible, but is
/// better suited for a higher level abstraction than this basic
/// `BufferedTerminal` interface.
pub fn check_for_resize(&mut self) -> Result<bool, Error> {
let size = self.terminal.get_screen_size()?;
let (width, height) = self.surface.dimensions();
if width != size.cols || height != size.rows {
self.surface.resize(width, height);
Ok(true)
} else {
Ok(false)
}
}
}
impl<T: Terminal> Deref for BufferedTerminal<T> {
type Target = Surface;
fn deref(&self) -> &Surface {
&self.surface
}
}
impl<T: Terminal> DerefMut for BufferedTerminal<T> {
fn deref_mut(&mut self) -> &mut Surface {
&mut self.surface
}
}

View File

@ -16,6 +16,8 @@ pub mod unix;
#[cfg(windows)]
pub mod windows;
pub mod buffered;
#[cfg(unix)]
pub use self::unix::UnixTerminal;
#[cfg(windows)]
@ -68,6 +70,15 @@ pub trait Terminal: Read + Write {
*/
}
/// `SystemTerminal` is a concrete implementation of `Terminal`.
/// Ideally you wouldn't reference `SystemTerminal` in consuming
/// code. This type is exposed for convenience if you are doing
/// something unusual and want easier access to the constructors.
#[cfg(unix)]
pub type SystemTerminal = UnixTerminal;
#[cfg(windows)]
pub type SystemTerminal = WindowsTerminal;
/// Construct a new instance of Terminal.
/// The terminal will have a renderer that is influenced by the configuration
/// in the provided `Capabilities` instance.
@ -78,17 +89,7 @@ pub trait Terminal: Read + Write {
/// constructors for `UnixTerminal` and `WindowsTerminal` and call whichever
/// one is most suitable for your needs.
pub fn new_terminal(caps: Capabilities) -> Result<impl Terminal, Error> {
new_terminal_sys(caps)
}
#[cfg(unix)]
fn new_terminal_sys(caps: Capabilities) -> Result<impl Terminal, Error> {
UnixTerminal::new(caps)
}
#[cfg(windows)]
fn new_terminal_sys(caps: Capabilities) -> Result<impl Terminal, Error> {
WindowsTerminal::new(caps)
SystemTerminal::new(caps)
}
const BUF_SIZE: usize = 128;