Douglas eedf833b6f
Send both 2J and 3J on clear (#14181)
Fixes #14176

# Description

Since the Linux `/usr/bin/clear` binary doesn't exhibit the issue in
#14176, I checked to see what ANSI escapes it is emitting:

```nu
nu -c '^clear; "111\n222\n333"' | less
# or
bash -c 'clear -x; echo -e "111\n222\n333"' | less
```

Both show the same thing:

```
ESC[HESC[2JESC[3J111
222
333
(END)
```

This is the equivalent of:

```nu
$"(ansi home)(ansi clear_entire_screen)(ansi clear_entire_screen_plus_buffer)111\n222\n333"
```

However, our internal `clear` is sending only the Home and 3J. While
this *should*, in theory, work, it's (a) clear that it doesn't, and (b)
`/usr/bin/clear` seemingly knows this and already has the solution (or
at least workaround). From looking at the `ncurses` source, it appears
it is getting this information from the terminal capabilities. That
said, support for `2J` and `3J` is fairly universal, and it's what we
send in `clear` and `clear --keep-scrollback` anyway, so there's no harm
AFAICT in sending both like `/usr/bin/clear` does.

Also tested and fixes the issue on Windows. Note that PowerShell
`Clear-Host` also did not have the issue.

Side-note: It's interesting that on Tmux, which doesn't support 2J and
3J, that `/usr/bin/clear` knows this and doesn't send those codes,
sending just an escape-[J instead. However, Nushell's `clear`, of
course, isn't checking terminal capabilities, and is continuing to send
the unsupported codes. Fortunately this doesn't appear to cause any
issues on Tmux.

# User-Facing Changes

None, AFAICT - Bugfix only.

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

N/A
2024-10-28 06:42:18 -05:00

78 lines
2.0 KiB
Rust

use crossterm::{
cursor::MoveTo,
terminal::{Clear as ClearCommand, ClearType},
QueueableCommand,
};
use nu_engine::command_prelude::*;
use std::io::Write;
#[derive(Clone)]
pub struct Clear;
impl Command for Clear {
fn name(&self) -> &str {
"clear"
}
fn description(&self) -> &str {
"Clear the terminal."
}
fn extra_description(&self) -> &str {
"By default clears the current screen and the off-screen scrollback buffer."
}
fn signature(&self) -> Signature {
Signature::build("clear")
.category(Category::Platform)
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.switch(
"keep-scrollback",
"Do not clear the scrollback history",
Some('k'),
)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
match call.has_flag(engine_state, stack, "keep-scrollback")? {
true => {
std::io::stdout()
.queue(MoveTo(0, 0))?
.queue(ClearCommand(ClearType::All))?
.flush()?;
}
_ => {
std::io::stdout()
.queue(MoveTo(0, 0))?
.queue(ClearCommand(ClearType::All))?
.queue(ClearCommand(ClearType::Purge))?
.flush()?;
}
};
Ok(PipelineData::Empty)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Clear the terminal",
example: "clear",
result: None,
},
Example {
description: "Clear the terminal but not its scrollback history",
example: "clear --keep-scrollback",
result: None,
},
]
}
}