From 14df28382d8854be4f02e1efe9f4811e729b9651 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Wed, 8 Jan 2025 10:22:00 +0100 Subject: [PATCH] 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. --- CHANGELOG.rst | 5 +++-- src/input_common.rs | 10 +++++++++- src/reader.rs | 31 ++++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6dcafd4b8..2949e2418 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 ^^^^^^^^^^^ diff --git a/src/input_common.rs b/src/input_common.rs index e2b3c589d..f1abc1dd9 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -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] { diff --git a/src/reader.rs b/src/reader.rs index e6d7ca625..caa2ee00c 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -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 {