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, Expansion},
33	expand::{Context, 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().with(&mut ctx).to(&mut w)?;
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)?;
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, os::unix::prelude::AsRawFd};
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().as_raw_fd()).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_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		match unsafe { GetStdHandle(STD_OUTPUT_HANDLE) } {
870			INVALID_HANDLE_VALUE => Err(io::Error::last_os_error().into()),
871			handle => Ok(handle),
872		}
873	}
874
875	#[cfg(feature = "windows-console")]
876	fn buffer_info(console: HANDLE) -> Result<CONSOLE_SCREEN_BUFFER_INFO, Error> {
877		let csbi: *mut CONSOLE_SCREEN_BUFFER_INFO = ptr::null_mut();
878		if unsafe { GetConsoleScreenBufferInfo(console, csbi) } == FALSE {
879			return Err(io::Error::last_os_error().into());
880		}
881
882		if csbi.is_null() {
883			Err(Error::NullPtr("GetConsoleScreenBufferInfo"))
884		} else {
885			Ok(unsafe { ptr::read(csbi) })
886		}
887	}
888
889	pub(crate) fn vt() -> Result<(), Error> {
890		let stdout = console_handle()?;
891
892		let mut mode = 0;
893		if unsafe { GetConsoleMode(stdout, &mut mode) } == FALSE {
894			return Err(io::Error::last_os_error().into());
895		}
896
897		mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
898		if unsafe { SetConsoleMode(stdout, mode) } == FALSE {
899			return Err(io::Error::last_os_error().into());
900		}
901
902		Ok(())
903	}
904
905	// Ref https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-2
906	#[cfg(feature = "windows-console")]
907	pub(crate) fn clear() -> Result<(), Error> {
908		let console = console_handle()?;
909		let csbi = buffer_info(console)?;
910
911		// Scroll the rectangle of the entire buffer.
912		let rect = SMALL_RECT {
913			Left: 0,
914			Top: 0,
915			Right: csbi.dwSize.X,
916			Bottom: csbi.dwSize.Y,
917		};
918
919		// Scroll it upwards off the top of the buffer with a magnitude of the entire height.
920		let target = COORD {
921			X: 0,
922			Y: (0 - csbi.dwSize.Y) as i16,
923		};
924
925		// Fill with empty spaces with the buffer’s default text attribute.
926		let space = CHAR_INFO_0 {
927			AsciiChar: b' ' as i8,
928		};
929
930		let fill = CHAR_INFO {
931			Char: space,
932			Attributes: csbi.wAttributes,
933		};
934
935		// Do the scroll.
936		if unsafe { ScrollConsoleScreenBufferW(console, &rect, ptr::null(), target, &fill) }
937			== FALSE
938		{
939			return Err(io::Error::last_os_error().into());
940		}
941
942		// Move the cursor to the top left corner too.
943		let mut cursor = csbi.dwCursorPosition;
944		cursor.X = 0;
945		cursor.Y = 0;
946
947		if unsafe { SetConsoleCursorPosition(console, cursor) } == FALSE {
948			return Err(io::Error::last_os_error().into());
949		}
950
951		Ok(())
952	}
953
954	// Ref https://docs.microsoft.com/en-us/windows/console/clearing-the-screen#example-3
955	#[cfg(feature = "windows-console")]
956	pub(crate) fn blank() -> Result<(), Error> {
957		let console = console_handle()?;
958
959		// Fill the entire screen with blanks.
960		let csbi = buffer_info(console)?;
961
962		let buffer_size = csbi.dwSize.X * csbi.dwSize.Y;
963		let home_coord = COORD { X: 0, Y: 0 };
964
965		if FALSE
966			== unsafe {
967				FillConsoleOutputCharacterW(
968					console,
969					b' ' as u16,
970					u32::try_from(buffer_size).unwrap_or(0),
971					home_coord,
972					ptr::null_mut(),
973				)
974			} {
975			return Err(io::Error::last_os_error().into());
976		}
977
978		// Set the buffer's attributes accordingly.
979		let csbi = buffer_info(console)?;
980		if FALSE
981			== unsafe {
982				FillConsoleOutputAttribute(
983					console,
984					csbi.wAttributes,
985					u32::try_from(buffer_size).unwrap_or(0),
986					home_coord,
987					ptr::null_mut(),
988				)
989			} {
990			return Err(io::Error::last_os_error().into());
991		}
992
993		// Put the cursor at its home coordinates.
994		if unsafe { SetConsoleCursorPosition(console, home_coord) } == FALSE {
995			return Err(io::Error::last_os_error().into());
996		}
997
998		Ok(())
999	}
1000
1001	const ENABLE_COOKED_MODE: CONSOLE_MODE =
1002		ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
1003
1004	pub(crate) fn cooked() -> Result<(), Error> {
1005		let stdout = console_handle()?;
1006
1007		let mut mode = 0;
1008		if unsafe { GetConsoleMode(stdout, &mut mode) } == FALSE {
1009			return Err(io::Error::last_os_error().into());
1010		}
1011
1012		mode |= ENABLE_COOKED_MODE;
1013		if unsafe { SetConsoleMode(stdout, mode) } == FALSE {
1014			return Err(io::Error::last_os_error().into());
1015		}
1016
1017		Ok(())
1018	}
1019
1020	// I hope someone searches for this one day and gets mad at me for making their life harder.
1021	const ABRACADABRA_THRESHOLD: (u8, u8) = (0x0A, 0x00);
1022
1023	// proper way, requires manifesting
1024	#[inline]
1025	fn um_verify_version() -> bool {
1026		let condition_mask: u64 = unsafe {
1027			VerSetConditionMask(
1028				VerSetConditionMask(
1029					VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL as u8),
1030					VER_MINORVERSION,
1031					VER_GREATER_EQUAL as u8,
1032				),
1033				VER_SERVICEPACKMAJOR,
1034				VER_GREATER_EQUAL as u8,
1035			)
1036		};
1037
1038		let mut osvi = OSVERSIONINFOEXW {
1039			dwMinorVersion: ABRACADABRA_THRESHOLD.1 as _,
1040			dwMajorVersion: ABRACADABRA_THRESHOLD.0 as _,
1041			wServicePackMajor: 0,
1042			dwOSVersionInfoSize: size_of::<OSVERSIONINFOEXW>() as u32,
1043			dwBuildNumber: 0,
1044			dwPlatformId: 0,
1045			szCSDVersion: [0; 128],
1046			wServicePackMinor: 0,
1047			wSuiteMask: 0,
1048			wProductType: 0,
1049			wReserved: 0,
1050		};
1051
1052		let ret = unsafe {
1053			VerifyVersionInfoW(
1054				&mut osvi,
1055				VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
1056				condition_mask,
1057			)
1058		};
1059
1060		ret != FALSE
1061	}
1062
1063	// querying the local netserver management api?
1064	#[inline]
1065	fn um_netserver() -> Result<bool, Error> {
1066		unsafe {
1067			let mut buf = ptr::null_mut();
1068			match NetApiBufferAllocate(
1069				u32::try_from(size_of::<SERVER_INFO_101>()).unwrap(),
1070				&mut buf,
1071			) {
1072				0 => {}
1073				err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1074			}
1075
1076			let ret = match NetServerGetInfo(ptr::null_mut(), 101, buf as _) {
1077				0 => {
1078					let info: SERVER_INFO_101 = ptr::read(buf as _);
1079					let version = info.sv101_version_major | MAJOR_VERSION_MASK;
1080
1081					// IS it using the same magic version number? who the fuck knows. let's hope so.
1082					Ok(info.sv101_platform_id == SV_PLATFORM_ID_NT
1083						&& version > ABRACADABRA_THRESHOLD.0 as _)
1084				}
1085				err => Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1086			};
1087
1088			// always free, even if the netservergetinfo call fails
1089			match NetApiBufferFree(buf) {
1090				0 => {}
1091				err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1092			}
1093
1094			ret
1095		}
1096	}
1097
1098	// querying the local workstation management api?
1099	#[inline]
1100	fn um_workstation() -> Result<bool, Error> {
1101		unsafe {
1102			let mut buf = ptr::null_mut();
1103			match NetApiBufferAllocate(
1104				u32::try_from(size_of::<WKSTA_INFO_100>()).unwrap(),
1105				&mut buf,
1106			) {
1107				0 => {}
1108				err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1109			}
1110
1111			let ret = match NetWkstaGetInfo(ptr::null_mut(), 100, buf as _) {
1112				0 => {
1113					let info: WKSTA_INFO_100 = ptr::read(buf as _);
1114
1115					// IS it using the same magic version number? who the fuck knows. let's hope so.
1116					Ok(info.wki100_platform_id == SV_PLATFORM_ID_NT
1117						&& info.wki100_ver_major > ABRACADABRA_THRESHOLD.0 as _)
1118				}
1119				err => Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1120			};
1121
1122			// always free, even if the netservergetinfo call fails
1123			match NetApiBufferFree(buf) {
1124				0 => {}
1125				err => return Err(io::Error::from_raw_os_error(i32::try_from(err).unwrap()).into()),
1126			}
1127
1128			ret
1129		}
1130	}
1131
1132	// attempt to set the bit, then undo it
1133	fn vt_attempt() -> Result<bool, Error> {
1134		let stdout = console_handle()?;
1135
1136		let mut mode = 0;
1137		if unsafe { GetConsoleMode(stdout, &mut mode) } == FALSE {
1138			return Err(io::Error::last_os_error().into());
1139		}
1140
1141		let mut support = false;
1142
1143		let mut newmode = mode;
1144		newmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
1145		if unsafe { SetConsoleMode(stdout, newmode) } != FALSE {
1146			support = true;
1147		}
1148
1149		// reset it to original value, whatever we do
1150		unsafe { SetConsoleMode(stdout, mode) };
1151
1152		Ok(support)
1153	}
1154
1155	#[inline]
1156	pub(crate) fn is_windows_10() -> bool {
1157		if um_verify_version() {
1158			return true;
1159		}
1160
1161		if um_netserver().unwrap_or(false) {
1162			return true;
1163		}
1164
1165		if um_workstation().unwrap_or(false) {
1166			return true;
1167		}
1168
1169		vt_attempt().unwrap_or(false)
1170	}
1171}
1172
1173#[cfg(not(unix))]
1174#[allow(clippy::unnecessary_wraps)]
1175mod unix {
1176	use super::Error;
1177
1178	pub(crate) fn vt_cooked() -> Result<(), Error> {
1179		Ok(())
1180	}
1181
1182	pub(crate) fn vt_well_done() -> Result<(), Error> {
1183		Ok(())
1184	}
1185}
1186
1187#[cfg(not(windows))]
1188#[allow(clippy::unnecessary_wraps)]
1189mod win {
1190	use super::Error;
1191
1192	pub(crate) fn vt() -> Result<(), Error> {
1193		Ok(())
1194	}
1195
1196	#[cfg(feature = "windows-console")]
1197	pub(crate) fn clear() -> Result<(), Error> {
1198		Ok(())
1199	}
1200
1201	#[cfg(feature = "windows-console")]
1202	pub(crate) fn blank() -> Result<(), Error> {
1203		Ok(())
1204	}
1205
1206	pub(crate) fn cooked() -> Result<(), Error> {
1207		Ok(())
1208	}
1209
1210	#[inline]
1211	pub(crate) fn is_windows_10() -> bool {
1212		false
1213	}
1214}
1215
1216#[derive(Eq, PartialEq, Clone, Debug)]
1217struct ResetScrollback<'a>(Cow<'a, [u8]>);
1218
1219impl<'a> Capability<'a> for ResetScrollback<'a> {
1220	#[inline]
1221	fn name() -> &'static str {
1222		"E3"
1223	}
1224
1225	#[inline]
1226	fn from(value: Option<&'a Value>) -> Option<Self> {
1227		if let Some(Value::String(value)) = value {
1228			Some(Self(Cow::Borrowed(value)))
1229		} else {
1230			None
1231		}
1232	}
1233
1234	#[inline]
1235	fn into(self) -> Option<Value> {
1236		Some(Value::String(match self.0 {
1237			Cow::Borrowed(value) => value.into(),
1238
1239			Cow::Owned(value) => value,
1240		}))
1241	}
1242}
1243
1244impl<'a, T: AsRef<&'a [u8]>> From<T> for ResetScrollback<'a> {
1245	#[inline]
1246	fn from(value: T) -> Self {
1247		Self(Cow::Borrowed(value.as_ref()))
1248	}
1249}
1250
1251impl AsRef<[u8]> for ResetScrollback<'_> {
1252	#[inline]
1253	fn as_ref(&self) -> &[u8] {
1254		&self.0
1255	}
1256}
1257
1258impl ResetScrollback<'_> {
1259	#[inline]
1260	fn expand(&self) -> Expansion<Self> {
1261		#[allow(dead_code)]
1262		struct ExpansionHere<'a, T: 'a + AsRef<[u8]>> {
1263			string: &'a T,
1264			params: [Parameter; 9],
1265			context: Option<&'a mut Context>,
1266		}
1267
1268		let here = ExpansionHere {
1269			string: self,
1270			params: Default::default(),
1271			context: None,
1272		};
1273
1274		// UNSAFE >:( this is iffy af but also the only way to create an Expansion
1275		// such that we can add the E3 capability.
1276		unsafe { std::mem::transmute(here) }
1277	}
1278}