clearscreen/lib.rs
1//! Cross-platform terminal screen clearing.
2//!
3//! This library provides a set of ways to clear a screen, plus a “best effort” convenience function
4//! to do the right thing most of the time.
5//!
6//! Unlike many cross-platform libraries, this one exposes every available choice all the time, and
7//! only the convenience function varies based on compilation target or environmental factors.
8//!
9//! 90% of the time, you’ll want to use the convenience short-hand:
10//!
11//! ```no_run
12//! clearscreen::clear().expect("failed to clear screen");
13//! ```
14//!
15//! For anything else, refer to the [`ClearScreen`] enum.
16//!
17//! If you are supporting Windows in any capacity, the [`is_windows_10()`] documentation is
18//! **required reading**.
19
20#![doc(html_favicon_url = "https://watchexec.github.io/logo:clearscreen.svg")]
21#![doc(html_logo_url = "https://watchexec.github.io/logo:clearscreen.svg")]
22#![warn(missing_docs)]
23
24use std::{
25 borrow::Cow,
26 env,
27 io::{self, Write},
28 process::{Command, ExitStatus},
29};
30
31use terminfo::{
32 capability::{self},
33 expand::{Context, Expand, Parameter},
34 Capability, Database, Value,
35};
36use thiserror::Error;
37use which::which;
38
39/// Ways to clear the screen.
40///
41/// There isn’t a single way to clear the (terminal/console) screen. Not only are there several
42/// techniques to achieve the outcome, there are differences in the way terminal emulators intepret
43/// some of these techniques, as well as platform particularities.
44///
45/// In addition, there are other conditions a screen can be in that might be beneficial to reset,
46/// such as when a TUI application crashes and leaves the terminal in a less than useful state.
47///
48/// Finally, a terminal may have scrollback, and this can be kept as-is or cleared as well.
49///
50/// Your application may need one particular clearing method, or it might offer several options to
51/// the user, such as “hard” and “soft” clearing. This library makes no assumption and no judgement
52/// on what is considered hard, soft, or something else: that is your responsibility to determine in
53/// your context.
54///
55/// For most cases, you should use [`ClearScreen::default()`] to select the most appropriate method.
56///
57/// In any event, once a way is selected, call [`clear()`][ClearScreen::clear()] to apply it.
58///
59/// # Example
60///
61/// ```no_run
62/// # use clearscreen::ClearScreen;
63/// ClearScreen::default().clear().expect("failed to clear the screen");
64/// ```
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66#[non_exhaustive]
67pub enum ClearScreen {
68 /// Does both [`TerminfoScreen`][ClearScreen::TerminfoScreen] and
69 /// [`TerminfoScrollback`][ClearScreen::TerminfoScrollback], in this order, but skips the
70 /// scrollback reset if the capability isn’t available.
71 ///
72 /// This is essentially what the [`clear`] command on unix does.
73 /// [`clear`]: https://invisible-island.net/ncurses/man/clear.1.html
74 Terminfo,
75
76 /// Looks up the `clear` capability in the terminfo (from the TERM env var), and applies it.
77 ///
78 /// A non-hashed terminfo database is required (this is a [terminfo crate] limitation), such as
79 /// the one provided with ncurses.
80 ///
81 /// [terminfo crate]: https://lib.rs/crates/terminfo
82 TerminfoScreen,
83
84 /// Looks up the `E3` (Erase Scrollback) capability in the terminfo (from the TERM env var), and applies it.
85 ///
86 /// The same terminfo limitation applies as for [`TerminfoScreen`][ClearScreen::TerminfoScreen].
87 TerminfoScrollback,
88
89 /// Performs a terminfo-driven terminal reset sequence.
90 ///
91 /// This prints whichever are available of the **rs1**, **rs2**, **rs3**, and **rf** sequences.
92 /// If none of these are available, it prints whichever are available of the **is1**, **is2**,
93 /// **is3**, and **if** sequences. If none are available, an error is returned.
94 ///
95 /// This generally issues at least an `ESC c` sequence, which resets all terminal state to
96 /// default values, and then may issue more sequences to reset other things or enforce a
97 /// particular kind of state. See [`XtermReset`][ClearScreen::XtermReset] for a description of
98 /// what XTerm does, as an example.
99 ///
100 /// Note that this is _not_ analogous to what `tput reset` does: to emulate that, issuing first
101 /// one of VtCooked/VtWellDone/WindowsCooked followed by this variant will come close.
102 ///
103 /// The same terminfo limitation applies as for [`TerminfoScreen`][ClearScreen::TerminfoScreen].
104 TerminfoReset,
105
106 /// Prints clear screen and scrollback sequence as if TERM=xterm.
107 ///
108 /// This does not look up the correct sequence in the terminfo database, but rather prints:
109 ///
110 /// - `CSI H` (Cursor Position 0,0), which sets the cursor position to 0,0.
111 /// - `CSI 2J` (Erase Screen), which erases the whole screen.
112 /// - `CSI 3J` (Erase Scrollback), which erases the scrollback (xterm extension).
113 XtermClear,
114
115 /// Prints the terminal reset sequence as if TERM=xterm.
116 ///
117 /// This does not look up the correct sequence in the terminfo database, but rather prints:
118 ///
119 /// - `ESC c` (Reset to Initial State), which nominally resets all terminal state to initial
120 /// values, but see the documentation for [`VtRis`][ClearScreen::VtRis].
121 /// - `CSI !p` (Soft Terminal Reset), which nominally does the same thing as RIS, but without
122 /// disconnecting the terminal data lines… which matters when you’re living in 1970.
123 /// - `CSI ?3l` (Reset to 80 Columns), which resets the terminal width to 80 columns, or more
124 /// accurately, resets the option that selects 132 column mode, to its default value of no.
125 /// I don’t know, man.
126 /// - `CSI ?4l` (Reset to Jump Scrolling), which sets the scrolling mode to jump. This is naught
127 /// to do with what we think of as “scrolling,” but rather it’s about the speed at which the
128 /// terminal will add lines to the screen. Jump mode means “give it to me as fast as it comes”
129 /// and Smooth mode means to do some buffering and output lines “at a moderate, smooth rate.”
130 /// - `CSI 4l` (Reset to Replace Mode), which sets the cursor writing mode to Replace, i.e.
131 /// overwriting characters at cursor position, instead of Insert, which pushes characters
132 /// under the cursor to the right.
133 /// - `ESC >` (Set Key Pad to Normal), which sets the keyboard’s numeric keypad to send “what’s
134 /// printed on the keys” i.e. numbers and the arithmetic symbols.
135 /// - `CSI ?69l` (Reset Left and Right Margins to the page), which sets the horizontal margins
136 /// to coincide with the page’s margins: nowadays, no margins.
137 XtermReset,
138
139 /// Calls the command `tput clear`.
140 ///
141 /// That command most likely does what [`Terminfo`][ClearScreen::Terminfo] does internally, but
142 /// may work better in some cases, such as when the terminfo database on the system is hashed or
143 /// in a non-standard location that the terminfo crate does not find.
144 ///
145 /// However, it relies on the `tput` command being available, and on being able to run commands.
146 TputClear,
147
148 /// Calls the command `tput reset`.
149 ///
150 /// See the documentation above on [`TputClear`][ClearScreen::TputClear] for more details, save
151 /// that the equivalent is [`TerminfoReset`][ClearScreen::TerminfoReset].
152 TputReset,
153
154 /// Calls the command `cls`.
155 ///
156 /// This is the Windows command to clear the screen. It has the same caveats as
157 /// [`TputClear`][ClearScreen::TputClear] does, but its internal mechanism is not known. Prefer
158 /// [`WindowsClear`][ClearScreen::WindowsClear] instead to avoid relying on an external command.
159 ///
160 /// This will always attempt to run the command, regardless of compile target, which may have
161 /// unintended effects if the `cls` executable does something different on the platform.
162 Cls,
163
164 /// Sets the Windows Console to support VT escapes.
165 ///
166 /// This sets the `ENABLE_VIRTUAL_TERMINAL_PROCESSING` bit in the console mode, which enables
167 /// support for the terminal escape sequences every other terminal uses. This is supported since
168 /// Windows 10, from the Threshold 2 Update in November 2015.
169 ///
170 /// Does nothing on non-Windows targets.
171 WindowsVt,
172
173 /// Sets the Windows Console to support VT escapes and prints the clear sequence.
174 ///
175 /// This runs [`WindowsVt`][ClearScreen::WindowsVt] and [`XtermClear`][ClearScreen::XtermClear],
176 /// in this order. This is described here:
177 /// https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-1 as the
178 /// recommended clearing method for all new development, although we also reset the cursor
179 /// position.
180 ///
181 /// While `WindowsVt` will do nothing on non-Windows targets, `XtermClear` will still run.
182 WindowsVtClear,
183
184 /// Uses Windows Console function to scroll the screen buffer and fill it with white space.
185 ///
186 /// - Scrolls up one screenful
187 /// - Fills the buffer with whitespace and attributes set to default.
188 /// - Flushes the input buffer
189 /// - Sets the cursor position to 0,0
190 ///
191 /// This is described here: https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-2
192 /// as the equivalent to CMD.EXE's `cls` command.
193 ///
194 /// Does nothing on non-Windows targets.
195 #[cfg(feature = "windows-console")]
196 WindowsConsoleClear,
197
198 /// Uses Windows Console function to blank the screen state.
199 ///
200 /// - Fills the screen buffer with ` ` (space) characters
201 /// - Resets cell attributes over the entire buffer
202 /// - Flushes the input buffer
203 /// - Sets the cursor position to 0,0
204 ///
205 /// This is described here: https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-3
206 ///
207 /// Does nothing on non-Windows targets.
208 #[cfg(feature = "windows-console")]
209 WindowsConsoleBlank,
210
211 /// Uses Windows Console function to disable raw mode.
212 ///
213 /// Does nothing on non-Windows targets.
214 WindowsCooked,
215
216 /// Prints the RIS VT100 escape code: Reset to Initial State.
217 ///
218 /// This is the `ESC c` or `1b 63` escape, which by spec is defined to reset the terminal state
219 /// to all initial values, which may be a range of things, for example as described in the VT510
220 /// manual: https://vt100.net/docs/vt510-rm/RIS
221 ///
222 /// However, the exact behaviour is highly dependent on the terminal emulator, and some modern
223 /// terminal emulators do not always clear scrollback, for example Tmux and GNOME VTE.
224 VtRis,
225
226 /// Prints the CSI sequence to leave the Alternate Screen mode.
227 ///
228 /// If the screen is in alternate screen mode, like how vim or a pager or another such rich TUI
229 /// application would do, this sequence will clear the alternate screen buffer, then revert the
230 /// terminal to normal mode, and restore the position of the cursor to what it was before
231 /// Alternate Screen mode was entered, assuming the proper sequence was used.
232 ///
233 /// It will not clear the normal mode buffer.
234 ///
235 /// This is useful when recovering from a TUI application which crashed without resetting state.
236 VtLeaveAlt,
237
238 /// Sets the terminal to cooked mode.
239 ///
240 /// This attempts to switch the terminal to “cooked” mode, which can be thought of as the
241 /// opposite of “raw” mode, where the terminal does not respond to line discipline (which makes
242 /// carriage return, line feed, and general typing display out to screen, and translates Ctrl-C
243 /// to sending the SIGINT signal, etc) but instead passes all input to the controlling program
244 /// and only displays what it outputs explicitly.
245 ///
246 /// There’s also an intermediate “cbreak” or “rare” mode which behaves like “cooked” but sends
247 /// each character one at a time immediately rather buffering and sending lines.
248 ///
249 /// TUI applications such as editors and pagers often set raw mode to gain precise control of
250 /// the terminal state. If such a program crashes, it may not reset the terminal mode back to
251 /// the mode it found it in, which can leave the terminal behaving oddly or rendering it
252 /// completely unusable.
253 ///
254 /// In truth, these terminal modes are a set of configuration bits that are given to the
255 /// `termios(3)` libc API, and control a variety of terminal modes. “Cooked” mode sets:
256 ///
257 /// - Input BRKINT set: on BREAK, flush i/o queues and send a SIGINT to any running process.
258 /// - Input ICRNL set: translate Carriage Returns to New Lines on input.
259 /// - Input IGNPAR set: ignore framing and parity errors.
260 /// - Input ISTRIP set: strip off eigth bit.
261 /// - Input IXON set: enable XON/XOFF flow control on output.
262 /// - Output OPOST set: enable output processing.
263 /// - Local ICANON set: enable canonical mode (see below).
264 /// - Local ISIG set: when Ctrl-C, Ctrl-Q, etc are received, send the appropriate signal.
265 ///
266 /// Canonical mode is really the core of “cooked” mode and enables:
267 ///
268 /// - line buffering, so input is only sent to the underlying program when a line delimiter
269 /// character is entered (usually a newline);
270 /// - line editing, so ERASE (backspace) and KILL (remove entire line) control characters edit
271 /// the line before it is sent to the program;
272 /// - a maximum line length of 4096 characters (bytes).
273 ///
274 /// When canonical mode is unset (when the bit is cleared), all input processing is disabled.
275 ///
276 /// Due to how the underlying [`tcsetattr`] function is defined in POSIX, this may complete
277 /// without error if _any part_ of the configuration is applied, not just when all of it is set.
278 ///
279 /// Note that you generally want [`VtWellDone`][ClearScreen::VtWellDone] instead.
280 ///
281 /// Does nothing on non-Unix targets.
282 ///
283 /// [`tcsetattr`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html
284 VtCooked,
285
286 /// Sets the terminal to “well done” mode.
287 ///
288 /// This is similar to [`VtCooked`][ClearScreen::VtCooked], but with a different, broader, mode
289 /// configuration which approximates a terminal’s initial state, such as is expected by a shell,
290 /// and clears many bits that should probably never be set (like the translation/mapping modes).
291 ///
292 /// “Well done” mode is an invention of this library, inspired by several other sources such as
293 /// Golang’s goterm, the termios(3) and tput(1) manual pages, but not identical to any.
294 ///
295 /// Notably most implementations read the terminal configuration bits and only modify that set,
296 /// whereas this library authoritatively writes the entire configuration from scratch.
297 ///
298 /// It is a strict superset of [`VtCooked`][ClearScreen::VtCooked].
299 ///
300 /// - Input BRKINT set: on BREAK, flush i/o queues and send a SIGINT to any running process.
301 /// - Input ICRNL set: translate Carriage Return to New Line on input.
302 /// - Input IUTF8 set: input is UTF-8 (Linux only, since 2.6.4).
303 /// - Input IGNPAR set: ignore framing and parity errors.
304 /// - Input IMAXBEL set: ring terminal bell when input queue is full (not implemented in Linux).
305 /// - Input ISTRIP set: strip off eigth bit.
306 /// - Input IXON set: enable XON/XOFF flow control on output.
307 /// - Output ONLCR set: do not translate Carriage Return to CR NL.
308 /// - Output OPOST set: enable output processing.
309 /// - Control CREAD set: enable receiver.
310 /// - Local ICANON set: enable canonical mode (see [`VtCooked`][ClearScreen::VtCooked]).
311 /// - Local ISIG set: when Ctrl-C, Ctrl-Q, etc are received, send the appropriate signal.
312 ///
313 /// Does nothing on non-Unix targets.
314 VtWellDone,
315}
316
317impl Default for ClearScreen {
318 /// Detects the environment and makes its best guess as how to clear the screen.
319 ///
320 /// This function’s behaviour (but not its type signature) may change without notice, as better
321 /// techniques appear. However, it will always strive to provide the best method. It will also
322 /// never have side-effects, and finding any such behaviour should be reported as a bug.
323 ///
324 /// If you wish to make your own, the [`is_microsoft_terminal()`] and [`is_windows_10()`]
325 /// functions may be useful.
326 ///
327 /// The [`ClearScreen`] variant selected is always in the “clear” behaviour side of things. If
328 /// you wish to only clear the screen and not the scrollback, or to perform a terminal reset, or
329 /// apply the other available clearing strategies, you’ll need to select what’s best yourself.
330 ///
331 /// See the [TERMINALS.md file in the repo][TERMINALS.md] for research on many terminals as well
332 /// as the current result of this function for each terminal.
333 ///
334 /// [TERMINALS.md]: https://github.com/watchexec/clearscreen/blob/main/TERMINALS.md
335 fn default() -> Self {
336 use env::var;
337 use std::ffi::OsStr;
338
339 fn varfull(key: impl AsRef<OsStr>) -> bool {
340 var(key).map_or(false, |s| !s.is_empty())
341 }
342
343 let term = var("TERM").ok();
344 let term = term.as_ref();
345
346 if cfg!(windows) {
347 return if is_microsoft_terminal() {
348 Self::XtermClear
349 } else if is_windows_10() {
350 Self::WindowsVtClear
351 } else if term.is_some() && varfull("TERMINFO") {
352 Self::Terminfo
353 } else if term.is_some() && which("tput").is_ok() {
354 Self::TputClear
355 } else {
356 Self::Cls
357 };
358 }
359
360 if let Some(term) = term {
361 // These VTE-based terminals support CSI 3J but their own terminfos don’t have E3
362 if (term.starts_with("gnome")
363 && varfull("GNOME_TERMINAL_SCREEN")
364 && varfull("GNOME_TERMINAL_SERVICE"))
365 || term == "xfce"
366 || term.contains("termite")
367 {
368 return Self::XtermClear;
369 }
370
371 // - SyncTERM does support the XtermClear sequence but does not clear the scrollback,
372 // and does not have a terminfo, so VtRis is the only option.
373 // - rxvt, when using its own terminfos, erases the screen instead of clearing and
374 // doesn’t clear scrollback. It supports and behave properly for the entire XtermClear
375 // sequence, but it also does the right thing with VtRis, and that seems more reliable.
376 // - Other variants of (u)rxvt do the same.
377 // - Kitty does as rxvt does here.
378 // - Tess does support the XtermClear sequence but has a weird scrollbar behaviour,
379 // which does not happen with VtRis.
380 // - Zutty does not support E3, and erases the buffer on clear like rxvt, but does work
381 // properly with VtRis.
382 // - Same behaviour with the multiplexer Zellij.
383 if term == "syncterm"
384 || term.contains("rxvt")
385 || term.contains("kitty")
386 || var("CHROME_DESKTOP").map_or(false, |cd| cd == "tess.desktop")
387 || varfull("ZUTTY_VERSION")
388 || varfull("ZELLIJ")
389 {
390 return Self::VtRis;
391 }
392
393 // - screen supports CSI 3J only within the XtermClear sequence, without E3 capability.
394 // - Konsole handles CSI 3J correctly only within the XtermClear sequence.
395 // - Wezterm handles CSI 3J correctly only within the XtermClear sequence.
396 // - assume tmux TERMs are only used within tmux, and avoid the requirement for a functioning terminfo then
397 if term.starts_with("screen")
398 || term.starts_with("konsole")
399 || term == "wezterm"
400 || term.starts_with("tmux")
401 {
402 return Self::XtermClear;
403 }
404
405 // Default xterm* terminfo on macOS does not include E3, but many terminals support it.
406 if cfg!(target_os = "macos")
407 && term.starts_with("xterm")
408 && Database::from_env()
409 .map(|info| info.get::<ResetScrollback>().is_none())
410 .unwrap_or(true)
411 {
412 return Self::XtermClear;
413 }
414
415 if !term.is_empty() && Database::from_env().is_ok() {
416 return Self::Terminfo;
417 }
418 }
419
420 Self::XtermClear
421 }
422}
423
424const ESC: &[u8] = b"\x1b";
425const CSI: &[u8] = b"\x1b[";
426const RIS: &[u8] = b"c";
427
428impl ClearScreen {
429 /// Performs the clearing action, printing to stdout.
430 pub fn clear(self) -> Result<(), Error> {
431 let mut stdout = io::stdout();
432 self.clear_to(&mut stdout)
433 }
434
435 /// Performs the clearing action, printing to a given writer.
436 ///
437 /// This allows to capture any escape sequences that might be printed, for example, but note
438 /// that it will not prevent actions taken via system APIs, such as the Windows, VtCooked, and
439 /// VtWellDone variants do.
440 ///
441 /// For normal use, prefer [`clear()`].
442 pub fn clear_to(self, mut w: &mut impl Write) -> Result<(), Error> {
443 match self {
444 Self::Terminfo => {
445 let info = Database::from_env()?;
446 let mut ctx = Context::default();
447
448 if let Some(seq) = info.get::<capability::ClearScreen>() {
449 seq.expand().with(&mut ctx).to(&mut w)?;
450 w.flush()?;
451 } else {
452 return Err(Error::TerminfoCap("clear"));
453 }
454
455 if let Some(seq) = info.get::<ResetScrollback>() {
456 seq.expand_to(&mut w, Some(&mut ctx))?;
457 w.flush()?;
458 }
459 }
460 Self::TerminfoScreen => {
461 let info = Database::from_env()?;
462 if let Some(seq) = info.get::<capability::ClearScreen>() {
463 seq.expand().to(&mut w)?;
464 w.flush()?;
465 } else {
466 return Err(Error::TerminfoCap("clear"));
467 }
468 }
469 Self::TerminfoScrollback => {
470 let info = Database::from_env()?;
471 if let Some(seq) = info.get::<ResetScrollback>() {
472 seq.expand_to(&mut w, None)?;
473 w.flush()?;
474 } else {
475 return Err(Error::TerminfoCap("E3"));
476 }
477 }
478 Self::TerminfoReset => {
479 let info = Database::from_env()?;
480 let mut ctx = Context::default();
481 let mut reset = false;
482
483 if let Some(seq) = info.get::<capability::Reset1String>() {
484 reset = true;
485 seq.expand().with(&mut ctx).to(&mut w)?;
486 }
487 if let Some(seq) = info.get::<capability::Reset2String>() {
488 reset = true;
489 seq.expand().with(&mut ctx).to(&mut w)?;
490 }
491 if let Some(seq) = info.get::<capability::Reset3String>() {
492 reset = true;
493 seq.expand().with(&mut ctx).to(&mut w)?;
494 }
495 if let Some(seq) = info.get::<capability::ResetFile>() {
496 reset = true;
497 seq.expand().with(&mut ctx).to(&mut w)?;
498 }
499
500 w.flush()?;
501
502 if reset {
503 return Ok(());
504 }
505
506 if let Some(seq) = info.get::<capability::Init1String>() {
507 reset = true;
508 seq.expand().with(&mut ctx).to(&mut w)?;
509 }
510 if let Some(seq) = info.get::<capability::Init2String>() {
511 reset = true;
512 seq.expand().with(&mut ctx).to(&mut w)?;
513 }
514 if let Some(seq) = info.get::<capability::Init3String>() {
515 reset = true;
516 seq.expand().with(&mut ctx).to(&mut w)?;
517 }
518 if let Some(seq) = info.get::<capability::InitFile>() {
519 reset = true;
520 seq.expand().with(&mut ctx).to(&mut w)?;
521 }
522
523 w.flush()?;
524
525 if !reset {
526 return Err(Error::TerminfoCap("reset"));
527 }
528 }
529 Self::XtermClear => {
530 const CURSOR_HOME: &[u8] = b"H";
531 const ERASE_SCREEN: &[u8] = b"2J";
532 const ERASE_SCROLLBACK: &[u8] = b"3J";
533
534 w.write_all(CSI)?;
535 w.write_all(CURSOR_HOME)?;
536
537 w.write_all(CSI)?;
538 w.write_all(ERASE_SCREEN)?;
539
540 w.write_all(CSI)?;
541 w.write_all(ERASE_SCROLLBACK)?;
542
543 w.flush()?;
544 }
545 Self::XtermReset => {
546 const STR: &[u8] = b"!p";
547 const RESET_WIDTH_AND_SCROLL: &[u8] = b"?3;4l";
548 const RESET_REPLACE: &[u8] = b"4l";
549 const RESET_KEYPAD: &[u8] = b">";
550 const RESET_MARGINS: &[u8] = b"?69l";
551
552 w.write_all(ESC)?;
553 w.write_all(RIS)?;
554
555 w.write_all(CSI)?;
556 w.write_all(STR)?;
557
558 w.write_all(CSI)?;
559 w.write_all(RESET_WIDTH_AND_SCROLL)?;
560
561 w.write_all(CSI)?;
562 w.write_all(RESET_REPLACE)?;
563
564 w.write_all(ESC)?;
565 w.write_all(RESET_KEYPAD)?;
566
567 w.write_all(CSI)?;
568 w.write_all(RESET_MARGINS)?;
569
570 w.flush()?;
571 }
572 Self::TputClear => {
573 let status = Command::new("tput").arg("clear").status()?;
574 if !status.success() {
575 return Err(Error::Command("tput clear", status));
576 }
577 }
578 Self::TputReset => {
579 let status = Command::new("tput").arg("reset").status()?;
580 if !status.success() {
581 return Err(Error::Command("tput reset", status));
582 }
583 }
584 Self::Cls => {
585 let status = Command::new("cmd.exe").arg("/C").arg("cls").status()?;
586 if !status.success() {
587 return Err(Error::Command("cls", status));
588 }
589 }
590 Self::WindowsVt => win::vt()?,
591 Self::WindowsVtClear => {
592 let vtres = win::vt();
593 Self::XtermClear.clear_to(w)?;
594 vtres?;
595 }
596 #[cfg(feature = "windows-console")]
597 Self::WindowsConsoleClear => win::clear()?,
598 #[cfg(feature = "windows-console")]
599 Self::WindowsConsoleBlank => win::blank()?,
600 Self::WindowsCooked => win::cooked()?,
601 Self::VtRis => {
602 w.write_all(ESC)?;
603 w.write_all(RIS)?;
604 w.flush()?;
605 }
606 Self::VtLeaveAlt => {
607 const LEAVE_ALT: &[u8] = b"?1049l";
608 w.write_all(CSI)?;
609 w.write_all(LEAVE_ALT)?;
610 w.flush()?;
611 }
612 Self::VtCooked => unix::vt_cooked()?,
613 Self::VtWellDone => unix::vt_well_done()?,
614 }
615
616 Ok(())
617 }
618}
619
620/// Shorthand for `ClearScreen::default().clear()`.
621pub fn clear() -> Result<(), Error> {
622 ClearScreen::default().clear()
623}
624
625/// Detects Microsoft Terminal.
626///
627/// Note that this is only provided to write your own clearscreen logic and _should not_ be relied
628/// on for other purposes, as it makes no guarantees of reliable detection, and its internal
629/// behaviour may change without notice.
630pub fn is_microsoft_terminal() -> bool {
631 env::var("WT_SESSION").is_ok()
632}
633
634/// Detects Windows ≥10.
635///
636/// As mentioned in the [`WindowsVt`][ClearScreen::WindowsVt] documentation, Windows 10 from the
637/// Threshold 2 Update in November 2015 supports the `ENABLE_VIRTUAL_TERMINAL_PROCESSING` console
638/// mode bit, which enables VT100/ECMA-48 escape sequence processing in the console. This in turn
639/// makes clearing the console vastly easier and is the recommended mode of operation by Microsoft.
640///
641/// However, detecting Windows ≥10 is not trivial. To mitigate broken programs that incorrectly
642/// perform version shimming, Microsoft has deprecated most ways to obtain the version of Windows by
643/// making the relevant APIs _lie_ unless the calling executable [embeds a manifest that explicitely
644/// opts-in to support Windows 10][manifesting].
645///
646/// To be clear, **this is the proper way to go**, and while this function tries, it may return
647/// false under some Win10s if you don't manifest. If you are writing an application which uses this
648/// library, or indeed any application targeting Windows at all, you should embed such a manifest
649/// (and take that opportunity to opt-in to long path support, see e.g. [watchexec#163]). If you are
650/// writing a library on top of this one, it is your responsibility to communicate this requirement
651/// to your users.
652///
653/// It is important to remark that it is not possible to manifest twice. In plainer words,
654/// **libraries _must not_ embed a manifest** as that will make it impossible for applications which
655/// depend on them to embed their own manifest.
656///
657/// This function tries its best to detect Windows ≥10, and specifically, whether the mentioned mode
658/// bit can be used. Critically, it leaves trying to set the bit as feature detection as a last
659/// resort, such that _an error setting the bit_ is not confunded with _the bit not being supported_.
660///
661/// Note that this is only provided to write your own clearscreen logic and _should not_ be relied
662/// on for other purposes, as it makes no guarantees of reliable detection, and its internal
663/// behaviour may change without notice. Additionally, this will always return false if the library
664/// was compiled for a non-Windows target, even if e.g. it’s running under WSL in a Windows 10 host.
665///
666/// TL;DR:
667///
668/// - Runs on Windows ≥10 without manifest and returns `true`: good, expected behaviour.
669/// - Runs on Windows ≥10 without manifest and returns `false`: **not a bug**, please manifest.
670/// - Runs on Windows ≥10 with manifest and returns `true`: good, expected behaviour.
671/// - Runs on Windows ≥10 with manifest and returns `false`: **is a bug**, please report it.
672/// - Runs on Windows <10 and returns `true`: **is a bug**, please report it. [ex #5]
673/// - Runs on Windows <10 and returns `false`: good, expected behaviour.
674///
675/// [ex #5]: https://github.com/watchexec/clearscreen/issues/5
676/// [manifesting]: https://docs.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
677/// [watchexec#163]: https://github.com/watchexec/watchexec/issues/163
678pub fn is_windows_10() -> bool {
679 win::is_windows_10()
680}
681
682/// Error type.
683#[derive(Debug, Error)]
684pub enum Error {
685 /// Any I/O error.
686 #[error("io: {0}")]
687 Io(#[from] io::Error),
688
689 /// A non-success exit status from a command.
690 #[error("command: {0}: {1}")]
691 Command(&'static str, ExitStatus),
692
693 /// Any nix (libc) error.
694 #[cfg(unix)]
695 #[error("unix: {0}")]
696 Nix(#[from] NixError),
697
698 /// Any terminfo error.
699 #[error("terminfo: {0}")]
700 Terminfo(#[from] TerminfoError),
701
702 /// A missing terminfo capability.
703 #[error("terminfo: capability not available: {0}")]
704 TerminfoCap(&'static str),
705
706 /// A null-pointer error.
707 #[error("ffi: encountered a null pointer while reading {0}")]
708 NullPtr(&'static str),
709}
710
711/// Nix error type.
712///
713/// This wraps a [`nix::Error`] to avoid directly exposing the type in the public API, which
714/// required a breaking change every time `clearscreen` updated its `nix` version.
715///
716/// To obtain a nix error, convert this error to an `i32` then use [`nix::Error::from_raw`]:
717///
718/// Creating a [`NixError`] is explicitly not possible from the public API.
719///
720/// ```no_compile
721/// let nix_error = nix::Error::from_raw(error.into());
722/// assert_eq!(nix_error, nix::Error::EINVAL);
723/// ```
724#[cfg(unix)]
725#[derive(Debug, Error)]
726#[error(transparent)]
727pub struct NixError(nix::Error);
728
729#[cfg(unix)]
730impl From<NixError> for i32 {
731 fn from(err: NixError) -> Self {
732 err.0 as _
733 }
734}
735
736/// Terminfo error type.
737///
738/// This wraps a [`terminfo::Error`] to avoid directly exposing the type in the public API, which
739/// required a breaking change every time `clearscreen` updated its `terminfo` version.
740#[derive(Debug, Error)]
741#[error("{description}")]
742pub struct TerminfoError {
743 inner: terminfo::Error,
744 description: String,
745}
746
747impl From<terminfo::Error> for TerminfoError {
748 fn from(inner: terminfo::Error) -> Self {
749 Self {
750 description: inner.to_string(),
751 inner,
752 }
753 }
754}
755
756impl From<terminfo::Error> for Error {
757 fn from(err: terminfo::Error) -> Self {
758 Self::Terminfo(TerminfoError::from(err))
759 }
760}
761
762#[cfg(unix)]
763mod unix {
764 use super::{Error, NixError};
765
766 use nix::{
767 sys::termios::{
768 tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags,
769 SetArg::TCSANOW, Termios,
770 },
771 unistd::isatty,
772 };
773
774 use std::{fs::OpenOptions, io::stdin, os::fd::AsFd};
775
776 pub(crate) fn vt_cooked() -> Result<(), Error> {
777 write_termios(|t| {
778 t.input_flags.insert(
779 InputFlags::BRKINT
780 | InputFlags::ICRNL
781 | InputFlags::IGNPAR
782 | InputFlags::ISTRIP
783 | InputFlags::IXON,
784 );
785 t.output_flags.insert(OutputFlags::OPOST);
786 t.local_flags.insert(LocalFlags::ICANON | LocalFlags::ISIG);
787 })
788 }
789
790 pub(crate) fn vt_well_done() -> Result<(), Error> {
791 write_termios(|t| {
792 let mut inserts = InputFlags::BRKINT
793 | InputFlags::ICRNL
794 | InputFlags::IGNPAR
795 | InputFlags::IMAXBEL
796 | InputFlags::ISTRIP
797 | InputFlags::IXON;
798
799 #[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
800 {
801 inserts |= InputFlags::IUTF8;
802 }
803
804 t.input_flags.insert(inserts);
805 t.output_flags
806 .insert(OutputFlags::ONLCR | OutputFlags::OPOST);
807 t.control_flags.insert(ControlFlags::CREAD);
808 t.local_flags.insert(LocalFlags::ICANON | LocalFlags::ISIG);
809 })
810 }
811
812 fn reset_termios(t: &mut Termios) {
813 t.input_flags.remove(InputFlags::all());
814 t.output_flags.remove(OutputFlags::all());
815 t.control_flags.remove(ControlFlags::all());
816 t.local_flags.remove(LocalFlags::all());
817 }
818
819 fn write_termios(f: impl Fn(&mut Termios)) -> Result<(), Error> {
820 if isatty(stdin()).map_err(NixError)? {
821 let mut t = tcgetattr(stdin().as_fd()).map_err(NixError)?;
822 reset_termios(&mut t);
823 f(&mut t);
824 tcsetattr(stdin().as_fd(), TCSANOW, &t).map_err(NixError)?;
825 } else {
826 let tty = OpenOptions::new().read(true).write(true).open("/dev/tty")?;
827 let fd = tty.as_fd();
828
829 let mut t = tcgetattr(fd).map_err(NixError)?;
830 reset_termios(&mut t);
831 f(&mut t);
832 tcsetattr(fd, TCSANOW, &t).map_err(NixError)?;
833 }
834
835 Ok(())
836 }
837}
838
839#[cfg(windows)]
840mod win {
841 use super::Error;
842
843 use std::{io, mem::size_of, ptr};
844
845 use windows_sys::Win32::Foundation::{FALSE, HANDLE, INVALID_HANDLE_VALUE};
846 use windows_sys::Win32::NetworkManagement::NetManagement::{
847 NetApiBufferAllocate, NetApiBufferFree, NetServerGetInfo, NetWkstaGetInfo,
848 MAJOR_VERSION_MASK, SERVER_INFO_101, SV_PLATFORM_ID_NT, WKSTA_INFO_100,
849 };
850 use windows_sys::Win32::System::Console::{
851 GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_ECHO_INPUT,
852 ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
853 STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
854 };
855 use windows_sys::Win32::System::SystemInformation::{
856 VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION,
857 VER_MINORVERSION, VER_SERVICEPACKMAJOR,
858 };
859 use windows_sys::Win32::System::SystemServices::VER_GREATER_EQUAL;
860
861 #[cfg(feature = "windows-console")]
862 use windows_sys::Win32::System::Console::{
863 FillConsoleOutputAttribute, FillConsoleOutputCharacterW, GetConsoleScreenBufferInfo,
864 ScrollConsoleScreenBufferW, SetConsoleCursorPosition, CHAR_INFO, CHAR_INFO_0,
865 CONSOLE_SCREEN_BUFFER_INFO, COORD, SMALL_RECT,
866 };
867
868 fn console_handle() -> Result<HANDLE, Error> {
869 // SAFETY: FFI call with a valid constant handle ID. Failure is indicated by
870 // INVALID_HANDLE_VALUE which is checked immediately.
871 match unsafe { GetStdHandle(STD_OUTPUT_HANDLE) } {
872 INVALID_HANDLE_VALUE => Err(io::Error::last_os_error().into()),
873 handle => Ok(handle),
874 }
875 }
876
877 fn console_input_handle() -> Result<HANDLE, Error> {
878 // SAFETY: FFI call with a valid constant handle ID. Failure is indicated by
879 // INVALID_HANDLE_VALUE which is checked immediately.
880 match unsafe { GetStdHandle(STD_INPUT_HANDLE) } {
881 INVALID_HANDLE_VALUE => Err(io::Error::last_os_error().into()),
882 handle => Ok(handle),
883 }
884 }
885
886 #[cfg(feature = "windows-console")]
887 fn buffer_info(console: HANDLE) -> Result<CONSOLE_SCREEN_BUFFER_INFO, Error> {
888 // SAFETY: `console` is a valid handle obtained from GetStdHandle. `csbi` is
889 // a stack-allocated zeroed struct and we pass a valid mutable pointer to it.
890 // The return value is checked for failure.
891 let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { std::mem::zeroed() };
892 if unsafe { GetConsoleScreenBufferInfo(console, &mut csbi) } == FALSE {
893 return Err(io::Error::last_os_error().into());
894 }
895
896 Ok(csbi)
897 }
898
899 pub(crate) fn vt() -> Result<(), Error> {
900 let stdout = console_handle()?;
901
902 let mut mode = 0;
903 // SAFETY: `stdout` is a valid console handle, `mode` is a valid mutable reference.
904 if unsafe { GetConsoleMode(stdout, &mut mode) } == FALSE {
905 return Err(io::Error::last_os_error().into());
906 }
907
908 mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
909 // SAFETY: `stdout` is a valid console handle, `mode` is a valid mode value.
910 if unsafe { SetConsoleMode(stdout, mode) } == FALSE {
911 return Err(io::Error::last_os_error().into());
912 }
913
914 Ok(())
915 }
916
917 // Ref https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-2
918 #[cfg(feature = "windows-console")]
919 pub(crate) fn clear() -> Result<(), Error> {
920 let console = console_handle()?;
921 let csbi = buffer_info(console)?;
922
923 // Scroll the rectangle of the entire buffer.
924 let rect = SMALL_RECT {
925 Left: 0,
926 Top: 0,
927 Right: csbi.dwSize.X,
928 Bottom: csbi.dwSize.Y,
929 };
930
931 // Scroll it upwards off the top of the buffer with a magnitude of the entire height.
932 let target = COORD {
933 X: 0,
934 Y: (0 - csbi.dwSize.Y) as i16,
935 };
936
937 // Fill with empty spaces with the buffer’s default text attribute.
938 let space = CHAR_INFO_0 {
939 AsciiChar: b' ' as i8,
940 };
941
942 let fill = CHAR_INFO {
943 Char: space,
944 Attributes: csbi.wAttributes,
945 };
946
947 // Do the scroll.
948 // SAFETY: `console` is a valid handle, all struct arguments are properly initialised,
949 // and the null pointer is the expected way to indicate "no clipping rectangle".
950 if unsafe { ScrollConsoleScreenBufferW(console, &rect, ptr::null(), target, &fill) }
951 == FALSE
952 {
953 return Err(io::Error::last_os_error().into());
954 }
955
956 // Move the cursor to the top left corner too.
957 let mut cursor = csbi.dwCursorPosition;
958 cursor.X = 0;
959 cursor.Y = 0;
960
961 // SAFETY: `console` is a valid handle, `cursor` is a valid COORD.
962 if unsafe { SetConsoleCursorPosition(console, cursor) } == FALSE {
963 return Err(io::Error::last_os_error().into());
964 }
965
966 Ok(())
967 }
968
969 // Ref https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-3
970 #[cfg(feature = "windows-console")]
971 pub(crate) fn blank() -> Result<(), Error> {
972 let console = console_handle()?;
973
974 // Fill the entire screen with blanks.
975 let csbi = buffer_info(console)?;
976
977 let buffer_size = csbi.dwSize.X * csbi.dwSize.Y;
978 let home_coord = COORD { X: 0, Y: 0 };
979
980 // SAFETY: `console` is a valid handle, arguments are properly initialised values.
981 if FALSE
982 == unsafe {
983 FillConsoleOutputCharacterW(
984 console,
985 b' ' as u16,
986 u32::try_from(buffer_size).unwrap_or(0),
987 home_coord,
988 ptr::null_mut(),
989 )
990 } {
991 return Err(io::Error::last_os_error().into());
992 }
993
994 // Set the buffer's attributes accordingly.
995 let csbi = buffer_info(console)?;
996 // SAFETY: `console` is a valid handle, arguments are properly initialised values.
997 if FALSE
998 == unsafe {
999 FillConsoleOutputAttribute(
1000 console,
1001 csbi.wAttributes,
1002 u32::try_from(buffer_size).unwrap_or(0),
1003 home_coord,
1004 ptr::null_mut(),
1005 )
1006 } {
1007 return Err(io::Error::last_os_error().into());
1008 }
1009
1010 // Put the cursor at its home coordinates.
1011 // SAFETY: `console` is a valid handle, `home_coord` is a valid COORD.
1012 if unsafe { SetConsoleCursorPosition(console, home_coord) } == FALSE {
1013 return Err(io::Error::last_os_error().into());
1014 }
1015
1016 Ok(())
1017 }
1018
1019 const ENABLE_COOKED_MODE: CONSOLE_MODE =
1020 ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
1021
1022 pub(crate) fn cooked() -> Result<(), Error> {
1023 let stdin = console_input_handle()?;
1024
1025 let mut mode = 0;
1026 // SAFETY: `stdin` is a valid console handle, `mode` is a valid mutable reference.
1027 if unsafe { GetConsoleMode(stdin, &mut mode) } == FALSE {
1028 return Err(io::Error::last_os_error().into());
1029 }
1030
1031 mode |= ENABLE_COOKED_MODE;
1032 // SAFETY: `stdin` is a valid console handle, `mode` is a valid mode value.
1033 if unsafe { SetConsoleMode(stdin, mode) } == FALSE {
1034 return Err(io::Error::last_os_error().into());
1035 }
1036
1037 Ok(())
1038 }
1039
1040 // I hope someone searches for this one day and gets mad at me for making their life harder.
1041 const ABRACADABRA_THRESHOLD: (u8, u8) = (0x0A, 0x00);
1042
1043 // proper way, requires manifesting
1044 #[inline]
1045 fn um_verify_version() -> bool {
1046 // SAFETY: VerSetConditionMask is a pure function with no pointer arguments.
1047 let condition_mask: u64 = unsafe {
1048 VerSetConditionMask(
1049 VerSetConditionMask(
1050 VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL as u8),
1051 VER_MINORVERSION,
1052 VER_GREATER_EQUAL as u8,
1053 ),
1054 VER_SERVICEPACKMAJOR,
1055 VER_GREATER_EQUAL as u8,
1056 )
1057 };
1058
1059 let mut osvi = OSVERSIONINFOEXW {
1060 dwMinorVersion: ABRACADABRA_THRESHOLD.1 as _,
1061 dwMajorVersion: ABRACADABRA_THRESHOLD.0 as _,
1062 wServicePackMajor: 0,
1063 dwOSVersionInfoSize: size_of::<OSVERSIONINFOEXW>() as u32,
1064 dwBuildNumber: 0,
1065 dwPlatformId: 0,
1066 szCSDVersion: [0; 128],
1067 wServicePackMinor: 0,
1068 wSuiteMask: 0,
1069 wProductType: 0,
1070 wReserved: 0,
1071 };
1072
1073 // SAFETY: `osvi` is a properly initialised OSVERSIONINFOEXW with correct
1074 // dwOSVersionInfoSize. The condition mask and type mask are valid constants.
1075 let ret = unsafe {
1076 VerifyVersionInfoW(
1077 &mut osvi,
1078 VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
1079 condition_mask,
1080 )
1081 };
1082
1083 ret != FALSE
1084 }
1085
1086 // querying the local netserver management api?
1087 #[inline]
1088 fn um_netserver() -> Result<bool, Error> {
1089 // SAFETY: NetApiBufferAllocate/Free and NetServerGetInfo are Windows API
1090 // calls. `buf` is allocated via NetApiBufferAllocate before use, read via
1091 // ptr::read after a successful NetServerGetInfo, and always freed with
1092 // NetApiBufferFree. All return codes are checked.
1093 unsafe {
1094 let mut buf = ptr::null_mut();
1095 match NetApiBufferAllocate(
1096 u32::try_from(size_of::<SERVER_INFO_101>()).unwrap(),
1097 &mut buf,
1098 ) {
1099 0 => {}
1100 err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1101 }
1102
1103 let ret = match NetServerGetInfo(ptr::null_mut(), 101, buf as _) {
1104 0 => {
1105 let info: SERVER_INFO_101 = ptr::read(buf as _);
1106 let version = info.sv101_version_major | MAJOR_VERSION_MASK;
1107
1108 // IS it using the same magic version number? who the fuck knows. let's hope so.
1109 Ok(info.sv101_platform_id == SV_PLATFORM_ID_NT
1110 && version > ABRACADABRA_THRESHOLD.0 as _)
1111 }
1112 err => Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1113 };
1114
1115 // always free, even if the netservergetinfo call fails
1116 match NetApiBufferFree(buf) {
1117 0 => {}
1118 err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1119 }
1120
1121 ret
1122 }
1123 }
1124
1125 // querying the local workstation management api?
1126 #[inline]
1127 fn um_workstation() -> Result<bool, Error> {
1128 // SAFETY: NetApiBufferAllocate/Free and NetWkstaGetInfo are Windows API
1129 // calls. `buf` is allocated via NetApiBufferAllocate before use, read via
1130 // ptr::read after a successful NetWkstaGetInfo, and always freed with
1131 // NetApiBufferFree. All return codes are checked.
1132 unsafe {
1133 let mut buf = ptr::null_mut();
1134 match NetApiBufferAllocate(
1135 u32::try_from(size_of::<WKSTA_INFO_100>()).unwrap(),
1136 &mut buf,
1137 ) {
1138 0 => {}
1139 err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1140 }
1141
1142 let ret = match NetWkstaGetInfo(ptr::null_mut(), 100, buf as _) {
1143 0 => {
1144 let info: WKSTA_INFO_100 = ptr::read(buf as _);
1145
1146 // IS it using the same magic version number? who the fuck knows. let's hope so.
1147 Ok(info.wki100_platform_id == SV_PLATFORM_ID_NT
1148 && info.wki100_ver_major > ABRACADABRA_THRESHOLD.0 as _)
1149 }
1150 err => Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1151 };
1152
1153 // always free, even if the netservergetinfo call fails
1154 match NetApiBufferFree(buf) {
1155 0 => {}
1156 err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1157 }
1158
1159 ret
1160 }
1161 }
1162
1163 // attempt to set the bit, then undo it
1164 fn vt_attempt() -> Result<bool, Error> {
1165 let stdout = console_handle()?;
1166
1167 let mut mode = 0;
1168 // SAFETY: `stdout` is a valid console handle, `mode` is a valid mutable reference.
1169 if unsafe { GetConsoleMode(stdout, &mut mode) } == FALSE {
1170 return Err(io::Error::last_os_error().into());
1171 }
1172
1173 let mut support = false;
1174
1175 let mut newmode = mode;
1176 newmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
1177 // SAFETY: `stdout` is a valid console handle, `newmode` is a valid mode value.
1178 if unsafe { SetConsoleMode(stdout, newmode) } != FALSE {
1179 support = true;
1180 }
1181
1182 // reset it to original value, whatever we do
1183 // SAFETY: `stdout` is a valid console handle, restoring the original `mode` value.
1184 unsafe { SetConsoleMode(stdout, mode) };
1185
1186 Ok(support)
1187 }
1188
1189 #[inline]
1190 pub(crate) fn is_windows_10() -> bool {
1191 if um_verify_version() {
1192 return true;
1193 }
1194
1195 if um_netserver().unwrap_or(false) {
1196 return true;
1197 }
1198
1199 if um_workstation().unwrap_or(false) {
1200 return true;
1201 }
1202
1203 vt_attempt().unwrap_or(false)
1204 }
1205}
1206
1207#[cfg(not(unix))]
1208#[allow(clippy::unnecessary_wraps)]
1209mod unix {
1210 use super::Error;
1211
1212 pub(crate) fn vt_cooked() -> Result<(), Error> {
1213 Ok(())
1214 }
1215
1216 pub(crate) fn vt_well_done() -> Result<(), Error> {
1217 Ok(())
1218 }
1219}
1220
1221#[cfg(not(windows))]
1222#[allow(clippy::unnecessary_wraps)]
1223mod win {
1224 use super::Error;
1225
1226 pub(crate) fn vt() -> Result<(), Error> {
1227 Ok(())
1228 }
1229
1230 #[cfg(feature = "windows-console")]
1231 pub(crate) fn clear() -> Result<(), Error> {
1232 Ok(())
1233 }
1234
1235 #[cfg(feature = "windows-console")]
1236 pub(crate) fn blank() -> Result<(), Error> {
1237 Ok(())
1238 }
1239
1240 pub(crate) fn cooked() -> Result<(), Error> {
1241 Ok(())
1242 }
1243
1244 #[inline]
1245 pub(crate) fn is_windows_10() -> bool {
1246 false
1247 }
1248}
1249
1250#[derive(Eq, PartialEq, Clone, Debug)]
1251struct ResetScrollback<'a>(Cow<'a, [u8]>);
1252
1253impl<'a> Capability<'a> for ResetScrollback<'a> {
1254 #[inline]
1255 fn name() -> &'static str {
1256 "E3"
1257 }
1258
1259 #[inline]
1260 fn from(value: Option<&'a Value>) -> Option<Self> {
1261 if let Some(Value::String(value)) = value {
1262 Some(Self(Cow::Borrowed(value)))
1263 } else {
1264 None
1265 }
1266 }
1267
1268 #[inline]
1269 fn into(self) -> Option<Value> {
1270 Some(Value::String(match self.0 {
1271 Cow::Borrowed(value) => value.into(),
1272
1273 Cow::Owned(value) => value,
1274 }))
1275 }
1276}
1277
1278impl<'a, T: AsRef<&'a [u8]>> From<T> for ResetScrollback<'a> {
1279 #[inline]
1280 fn from(value: T) -> Self {
1281 Self(Cow::Borrowed(value.as_ref()))
1282 }
1283}
1284
1285impl AsRef<[u8]> for ResetScrollback<'_> {
1286 #[inline]
1287 fn as_ref(&self) -> &[u8] {
1288 &self.0
1289 }
1290}
1291
1292impl ResetScrollback<'_> {
1293 #[inline]
1294 fn expand_to(
1295 &self,
1296 output: &mut impl Write,
1297 context: Option<&mut Context>,
1298 ) -> Result<(), terminfo::Error> {
1299 self.as_ref().expand(
1300 output,
1301 &<[Parameter; 9]>::default(),
1302 context.unwrap_or(&mut Default::default()),
1303 )
1304 }
1305}