Skip to main content

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}