diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py index 3e15a340135669..1551f19daef2fb 100644 --- a/Lib/_pyrepl/unix_console.py +++ b/Lib/_pyrepl/unix_console.py @@ -946,6 +946,14 @@ def __apply_line_update( self.__write(text) self.posxy = update.start_x + update.char_width, update.y + # When a write reaches the last column, the terminal enters the + # "pending wrap" state and the cursor physically stays at width - 1. + # Record that position so later relative moves (CUB) don't overshoot + # by one cell (gh-145448). + x, y = self.posxy + if x >= self.width: + self.posxy = self.width - 1, y + if update.reset_to_margin: # Non-SGR terminal controls can affect the cursor position. self.move_cursor(0, update.y) diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index dbf7f91696b559..6a9a45d5d830e8 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -259,6 +259,20 @@ def test_cursor_back_write(self, _os_write): _os_write.assert_any_call(ANY, b"2") con.restore() + def test_write_to_last_column_records_pending_wrap_position(self, _os_write): + # gh-145448: after writing into the last column the terminal is in + # "pending wrap" state and the cursor stays at width - 1. + from _pyrepl.render import LineUpdate, RenderCell + con = unix_console(iter([]), width=5) + cells = tuple(RenderCell(ch, 1) for ch in "abcde") + update = LineUpdate( + kind="replace_span", y=0, start_cell=0, start_x=0, + cells=cells, char_width=5, + ) + con._UnixConsole__apply_line_update(update) + self.assertEqual(con.posxy, (4, 0)) + con.restore() + def test_multiline_function_move_up_short_terminal(self, _os_write): # fmt: off code = ( 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.