From e9ae143babd8e2c7b09be0c14efe52ca7f1165a1 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Fri, 26 Sep 2025 09:18:46 +0200 Subject: [PATCH] Fix cursor position reports being ignored When we receive a cursor position report, we only store the result; we'll act on it only when we receive the primary DA reply. Make sure we don't discard the query state until then. Fixes 06ede39ec93 (Degrade gracefully when failing to receive cursor position report, 2025-09-23) --- src/reader.rs | 2 +- tests/pexpect_helper.py | 24 +++++++++++++++++++++--- tests/pexpects/scrollback.py | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 tests/pexpects/scrollback.py diff --git a/src/reader.rs b/src/reader.rs index 688a88fb3..169e0c8f9 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2617,7 +2617,7 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo Response(CursorPosition(cursor_pos)), ) => { cursor_pos_query.result = Some(cursor_pos); - maybe_query + return ControlFlow::Continue(()); } ( Some(TerminalQuery::CursorPosition(cursor_pos_query)), diff --git a/tests/pexpect_helper.py b/tests/pexpect_helper.py index a0d0ab794..f7d40e492 100644 --- a/tests/pexpect_helper.py +++ b/tests/pexpect_helper.py @@ -150,7 +150,12 @@ class SpawnedProc(object): """ def __init__( - self, name="fish", timeout=TIMEOUT_SECS, env=os.environ.copy(), **kwargs + self, + name="fish", + timeout=TIMEOUT_SECS, + env=os.environ.copy(), + scroll_up_content_supported: bool = False, + **kwargs, ): """Construct from a name, timeout, and environment. @@ -179,8 +184,21 @@ class SpawnedProc(object): ) self.spawn.delaybeforesend = None self.prompt_counter = 0 + if scroll_up_content_supported: + # XTGETTCAP + key = bytes.hex(b"indn") + value = bytes.hex(b"dont-care") + self.spawn.send(f"\x1bP1+r{key}={value}\x1b\\") if env.get("TERM") != "dumb": - self.spawn.send("\x1b[?123c") # Primary Device Attribute + self.send_primary_device_attribute() + + def send_cursor_position_report(self, *, y: int, x: int): + assert x != 0 + assert y != 0 + self.spawn.send(f"\x1b[{y};{x}R") + + def send_primary_device_attribute(self): + self.spawn.send("\x1b[?123c") # Primary Device Attribute def time_since_first_message(self): """Return a delta in seconds since the first message, or 0 if this is the first.""" @@ -341,7 +359,7 @@ class SpawnedProc(object): filename=m.filename, lineno=m.lineno, etext=etext, - **colors + **colors, ) ) print("") diff --git a/tests/pexpects/scrollback.py b/tests/pexpects/scrollback.py new file mode 100644 index 000000000..898494533 --- /dev/null +++ b/tests/pexpects/scrollback.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +from pexpect_helper import SpawnedProc, control +import os + +env = os.environ.copy() +env["TERM"] = "not-dumb" + +sp = SpawnedProc(env=env, scroll_up_content_supported=True) +sendline, expect_prompt = sp.sendline, sp.expect_prompt +expect_prompt() + +sendline("bind ctrl-g scrollback-push") +expect_prompt() +sp.send(control("g")) +sp.send_cursor_position_report(y=10, x=5) +sp.send_primary_device_attribute() + +sp.expect_str("\x1b[9S\x1b[9A")