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