From 539707c1801d33408595e1be3d3eb9b003097777 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Mon, 9 Mar 2026 23:06:26 -0700 Subject: [PATCH] gh-145448: Fix REPL tab completion cursor position in pending wrap state When __write_changed_line writes to the last column of the terminal, the cursor enters "pending wrap" state where it physically stays at width-1. However, posxy was recording width, causing subsequent CUB (Cursor Backward) calculations to overshoot by one cell. Cap posxy x-coordinate at width-1 to match the physical cursor position in pending wrap state. This fixes the cursor moving backward during tab completion in xterm and Ghostty. Co-Authored-By: Claude Opus 4.6 --- Lib/_pyrepl/unix_console.py | 15 ++++++++++++--- ...03-09-00-00-02.gh-issue-145448.repl-cursor.rst | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-03-09-00-00-02.gh-issue-145448.repl-cursor.rst diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py index 937b5df6ff7d4c..5817075ea7bda6 100644 --- a/Lib/_pyrepl/unix_console.py +++ b/Lib/_pyrepl/unix_console.py @@ -675,7 +675,8 @@ def __write_changed_line(self, y, oldline, newline, px_coord): self.__move(x_coord, y) self.__write_code(self.ich1) self.__write(newline[x_pos]) - self.posxy = x_coord + character_width, y + new_x = x_coord + character_width + self.posxy = min(new_x, self.width - 1), y # if it's a single character change in the middle of the line elif ( @@ -686,7 +687,8 @@ def __write_changed_line(self, y, oldline, newline, px_coord): character_width = wlen(newline[x_pos]) self.__move(x_coord, y) self.__write(newline[x_pos]) - self.posxy = x_coord + character_width, y + new_x = x_coord + character_width + self.posxy = min(new_x, self.width - 1), y # if this is the last character to fit in the line and we edit in the middle of the line elif ( @@ -713,7 +715,14 @@ def __write_changed_line(self, y, oldline, newline, px_coord): if wlen(oldline) > wlen(newline): self.__write_code(self._el) self.__write(newline[x_pos:]) - self.posxy = wlen(newline), y + # When writing reaches the last column, the terminal enters + # "pending wrap" state where the cursor physically stays at + # width-1. Record position accordingly so CUB calculations + # use the correct physical cursor position (gh-145448). + newline_width = wlen(newline) + if newline_width >= self.width: + newline_width = self.width - 1 + self.posxy = newline_width, y if "\x1b" in newline: # ANSI escape characters are present, so we can't assume diff --git a/Misc/NEWS.d/next/Library/2026-03-09-00-00-02.gh-issue-145448.repl-cursor.rst b/Misc/NEWS.d/next/Library/2026-03-09-00-00-02.gh-issue-145448.repl-cursor.rst new file mode 100644 index 00000000000000..2387b886de59c9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-09-00-00-02.gh-issue-145448.repl-cursor.rst @@ -0,0 +1,3 @@ +Fixed REPL tab completion cursor moving backward by one cell in terminals +that correctly handle CUB (Cursor Backward) in pending wrap state, such as +xterm and Ghostty.