1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
use std::io::Write as _;
use crate::private::Output as _;
/// Switches the terminal on `stdout` to alternate screen mode, and restores
/// it when this object goes out of scope.
pub struct ScreenGuard {
cleaned_up: bool,
}
impl ScreenGuard {
/// Switches the terminal on `stdout` to alternate screen mode and returns
/// a guard object. This is typically called as part of
/// [`Output::new`](Output::new).
///
/// # Errors
/// * `Error::WriteStdout`: failed to write initialization to stdout
pub fn new() -> crate::error::Result<Self> {
write_stdout(crate::INIT)?;
Ok(Self { cleaned_up: false })
}
/// Switch back from alternate screen mode early.
///
/// # Errors
/// * `Error::WriteStdout`: failed to write deinitialization to stdout
pub fn cleanup(&mut self) -> crate::error::Result<()> {
if self.cleaned_up {
return Ok(());
}
self.cleaned_up = true;
write_stdout(crate::DEINIT)
}
}
impl Drop for ScreenGuard {
/// Calls `cleanup`.
fn drop(&mut self) {
let _ = self.cleanup();
}
}
/// Manages drawing to the terminal on `stdout`.
///
/// Most functionality is provided by the [`Textmode`](crate::Textmode) trait.
/// You should call those trait methods to draw to the in-memory screen, and
/// then call [`refresh`](Output::refresh) when you want to update the
/// terminal on `stdout`.
pub struct Output {
screen: Option<ScreenGuard>,
cur: vt100::Parser,
next: vt100::Parser,
}
impl crate::private::Output for Output {
fn cur(&self) -> &vt100::Parser {
&self.cur
}
fn cur_mut(&mut self) -> &mut vt100::Parser {
&mut self.cur
}
fn next(&self) -> &vt100::Parser {
&self.next
}
fn next_mut(&mut self) -> &mut vt100::Parser {
&mut self.next
}
}
impl crate::Textmode for Output {}
impl Output {
/// Creates a new `Output` instance containing a
/// [`ScreenGuard`](ScreenGuard) instance.
///
/// # Errors
/// * `Error::WriteStdout`: failed to write initialization to stdout
pub fn new() -> crate::error::Result<Self> {
let mut self_ = Self::new_without_screen();
self_.screen = Some(ScreenGuard::new()?);
Ok(self_)
}
/// Creates a new `Output` instance without creating a
/// [`ScreenGuard`](ScreenGuard) instance.
#[must_use]
pub fn new_without_screen() -> Self {
let (rows, cols) = match terminal_size::terminal_size() {
Some((terminal_size::Width(w), terminal_size::Height(h))) => {
(h, w)
}
_ => (24, 80),
};
let cur = vt100::Parser::new(rows, cols, 0);
let next = vt100::Parser::new(rows, cols, 0);
Self {
screen: None,
cur,
next,
}
}
/// Removes the [`ScreenGuard`](ScreenGuard) instance stored in this
/// `Output` instance and returns it. This can be useful if you need to
/// manage the lifetime of the [`ScreenGuard`](ScreenGuard) instance
/// separately.
pub fn take_screen_guard(&mut self) -> Option<ScreenGuard> {
self.screen.take()
}
/// Draws the in-memory screen to the terminal on `stdout`. This is done
/// using a diff mechanism to only update the parts of the terminal which
/// are different from the in-memory screen.
///
/// # Errors
/// * `Error::WriteStdout`: failed to write screen state to stdout
pub fn refresh(&mut self) -> crate::error::Result<()> {
let diff = self.next().screen().state_diff(self.cur().screen());
write_stdout(&diff)?;
self.cur_mut().process(&diff);
Ok(())
}
/// Draws the in-memory screen to the terminal on `stdout`. This clears
/// the screen and redraws it from scratch, rather than using a diff
/// mechanism like `refresh`. This can be useful when the current state of
/// the terminal screen is unknown, such as after the terminal has been
/// resized.
///
/// # Errors
/// * `Error::WriteStdout`: failed to write screen state to stdout
pub fn hard_refresh(&mut self) -> crate::error::Result<()> {
let contents = self.next().screen().state_formatted();
write_stdout(&contents)?;
self.cur_mut().process(&contents);
Ok(())
}
}
fn write_stdout(buf: &[u8]) -> crate::error::Result<()> {
let mut stdout = std::io::stdout();
stdout
.write_all(buf)
.map_err(crate::error::Error::WriteStdout)?;
stdout.flush().map_err(crate::error::Error::WriteStdout)?;
Ok(())
}