Work around terminals that echo DCS queries

Some terminals such as conhost and putty cannot parse DCS commands,
and will echo them back.

Work around this by making sure that this echoed text will not
be visible.

Do so by temporarily enabling the alternative screen buffer when
sending DCS queries (in this case only XTGETTCAP).  The alternative
screen buffer feature seems widely supported, and easier to get right
than trying to clear individual lines etc.

The alternative screen may still be visible for a
short time.  Luckily we can use [Synchronized Output](
https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036)
to make sure the screen change is never visible to the user.

Querying support for that is deemed safe since it only requires a
CSI command.

Note that it seems that every terminal that supports Synchronized
Output also parses DCS commands successfully.  This means that we
could get away without the alternative screen buffer in practice.
Not sure yet.

The implementation is slightly more complex than necessary in that it
defines a redundant ImplicitEvent. This is for two reasons: 1. I have
a pending change that wants to use it, so this removes diff noise and
2. we historically have sc/input_common.rs not depend on src/output.rs.
I dont' think any are strong reasons though.
This commit is contained in:
Johannes Altmanninger 2025-01-08 10:22:00 +01:00
parent e6d57f2fb2
commit 14df28382d
3 changed files with 38 additions and 8 deletions

View file

@ -23,8 +23,9 @@ New or improved bindings
- :kbd:`ctrl-z` (undo) after executing a command will restore the previous cursor position instead of placing the cursor at the end of the command line.
- The OSC 133 prompt marking feature has learned about kitty's ``click_events=1`` flag, which allows moving fish's cursor by clicking.
- :kbd:`ctrl-l` no longer clears the screen but only pushes to the terminal's scrollback all text above the prompt (via a new special input function ``scrollback-push``).
This feature depends on the terminal advertising via XTGETTCAP support for the ``indn`` and ``cuu`` terminfo capabilities.
If not presesnt, the binding falls back to ``clear-screen``.
This feature depends on the terminal advertising via XTGETTCAP support for the ``indn`` and ``cuu`` terminfo capabilities,
and on the terminal supporting Synchronized Output (which is used by fish to detect features).
If any is missing, the binding falls back to ``clear-screen``.
Completions
^^^^^^^^^^^

View file

@ -194,6 +194,8 @@ pub enum ImplicitEvent {
MouseLeftClickContinuation(ViewportPosition, ViewportPosition),
/// Push prompt to top.
ScrollbackPushContinuation(usize),
/// The Synchronized Output feature is supported by the terminal.
SynchronizedOutputSupported,
}
#[derive(Debug, Clone)]
@ -957,8 +959,14 @@ pub trait InputEventQueuer {
let key = match c {
b'$' => {
// DECRPM
if private_mode == Some(b'?') && next_char(self) == b'y' {
// DECRPM
if params[0][0] == 2026 && matches!(params[1][0], 1 | 2) {
self.push_front(CharEvent::Implicit(
ImplicitEvent::SynchronizedOutputSupported,
));
}
return None;
}
match params[0][0] {

View file

@ -2098,11 +2098,8 @@ impl<'a> Reader<'a> {
let _ = out.write(KITTY_PROGRESSIVE_ENHANCEMENTS_QUERY);
// Query for cursor position reporting support.
zelf.request_cursor_position(&mut out, CursorPositionWait::InitialFeatureProbe);
let mut xtgettcap = |cap| {
let _ = write!(&mut out, "\x1bP+q{}\x1b\\", DisplayAsHex(cap));
};
xtgettcap("indn");
xtgettcap("cuu");
// Query for synchronized output support.
let _ = out.write(b"\x1b[?2026$p");
out.end_buffering();
}
@ -2422,12 +2419,36 @@ impl<'a> Reader<'a> {
self.screen.push_to_scrollback(cursor_y);
self.stop_waiting_for_cursor_position();
}
ImplicitEvent::SynchronizedOutputSupported => {
synchronized_supported();
}
},
}
ControlFlow::Continue(())
}
}
fn xtgettcap(out: &mut impl Write, cap: &str) {
let _ = write!(out, "\x1bP+q{}\x1b\\", DisplayAsHex(cap));
}
fn synchronized_supported() {
static QUERIED: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
if QUERIED.load() {
return;
}
QUERIED.store(true);
let mut out = Outputter::stdoutput().borrow_mut();
out.begin_buffering();
let _ = out.write(b"\x1b[?2026h"); // begin synchronized update
let _ = out.write(b"\x1b[?1049h"); // enable alternative screen buffer
xtgettcap(out.by_ref(), "indn");
xtgettcap(out.by_ref(), "cuu");
let _ = out.write(b"\x1b[?1049l"); // disable alternative screen buffer
let _ = out.write(b"\x1b[?2026l"); // end synchronized update
out.end_buffering();
}
impl<'a> Reader<'a> {
// Convenience cover to return the length of the command line.
fn command_line_len(&self) -> usize {