From 437bf3dc9b838859b8779b6d140ad7c5b0ab7ab1 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 7 Oct 2026 12:23:44 -0700 Subject: [PATCH 1/6] PyREPL: Update changed line planning for WSL --- Lib/_pyrepl/unix_console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py index 3e15a3401356697..d556e6dd7c85f6a 100644 --- a/Lib/_pyrepl/unix_console.py +++ b/Lib/_pyrepl/unix_console.py @@ -835,7 +835,7 @@ def __plan_changed_line( planned_cells = diff.new_cells changed_cell = visible_new_cells[0] return LineUpdate( - kind="insert_char", + kind=("replace_char" if start_x == oldline.width else "insert_char"), y=y, start_cell=start_cell, start_x=start_x, From 073ff5ce8a4429172fa081d7bb8514f9b8ae4481 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 7 Oct 2026 14:53:03 -0700 Subject: [PATCH 2/6] Add test --- Lib/test/test_pyrepl/test_unix_console.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index dbf7f91696b5591..55e639884b76d1c 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -257,6 +257,12 @@ def test_cursor_back_write(self, _os_write): _os_write.assert_any_call(ANY, b"1") _os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1") _os_write.assert_any_call(ANY, b"2") + self.assertEqual( + _os_write.mock_calls.count( + call(ANY, TERM_CAPABILITIES["ich"] + b":1") + ), + 1, + ) con.restore() def test_multiline_function_move_up_short_terminal(self, _os_write): From 42596b862e581c2a0d61c7c1c0cb2bca192f5945 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 21:54:31 +0000 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-10-07-21-54-29.gh-issue-158955.Ey4Iei.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-07-21-54-29.gh-issue-158955.Ey4Iei.rst diff --git a/Misc/NEWS.d/next/Library/2026-10-07-21-54-29.gh-issue-158955.Ey4Iei.rst b/Misc/NEWS.d/next/Library/2026-10-07-21-54-29.gh-issue-158955.Ey4Iei.rst new file mode 100644 index 000000000000000..e7e613ec77e6945 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-07-21-54-29.gh-issue-158955.Ey4Iei.rst @@ -0,0 +1 @@ +Fixed PyREPL rendering on WSL by avoiding the unnecessary INS terminal sequence when appending a character at the end of a line. INS remains used for true mid-line insertions, with regression coverage added. From e60bfe3167ede99d0e982c6e3b5926affc09d6f5 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 7 Oct 2026 15:51:18 -0700 Subject: [PATCH 4/6] Fix test incorrectly registering ich1 --- Lib/test/test_pyrepl/test_unix_console.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index 55e639884b76d1c..8d50f01f8c875f1 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -257,10 +257,9 @@ def test_cursor_back_write(self, _os_write): _os_write.assert_any_call(ANY, b"1") _os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1") _os_write.assert_any_call(ANY, b"2") + self.assertIsNotNone(con.ich1) self.assertEqual( - _os_write.mock_calls.count( - call(ANY, TERM_CAPABILITIES["ich"] + b":1") - ), + _os_write.mock_calls.count(call(ANY, con.ich1)), 1, ) con.restore() From 2435b197370b4f4e5e123f60c9dc11d117729092 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 7 Oct 2026 16:49:36 -0700 Subject: [PATCH 5/6] Fix tests --- Lib/test/test_pyrepl/test_unix_console.py | 45 ++++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index 8d50f01f8c875f1..d0aeb6ebac1c715 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -15,7 +15,7 @@ from unittest import TestCase from unittest.mock import MagicMock, call, patch, ANY, Mock -from .support import handle_all_events, code_to_events, more_lines +from .support import handle_all_events, code_to_events, more_lines, prepare_reader try: from _pyrepl.console import Event @@ -248,20 +248,39 @@ def test_cursor_up_down(self, _os_write): con.restore() def test_cursor_back_write(self, _os_write): - events = itertools.chain( - code_to_events("1"), - [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], - code_to_events("2"), + insert_char = b"" + events = iter( + itertools.chain( + code_to_events("1"), + [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], + code_to_events("2"), + ) ) - _, con = handle_events_unix_console(events) - _os_write.assert_any_call(ANY, b"1") + con = unix_console(events, ich1=insert_char) + reader = prepare_reader(con) + + _os_write.reset_mock() + reader.handle1() + append_writes = [write.args[1] for write in _os_write.call_args_list] + self.assertIn(b"1", append_writes) + self.assertFalse(any(insert_char in output for output in append_writes)) + self.assertEqual(reader.rendered_screen.screen_lines, ("1",)) + + _os_write.reset_mock() + reader.handle1() _os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1") - _os_write.assert_any_call(ANY, b"2") - self.assertIsNotNone(con.ich1) - self.assertEqual( - _os_write.mock_calls.count(call(ANY, con.ich1)), - 1, - ) + + _os_write.reset_mock() + reader.handle1() + insertion_writes = [write.args[1] for write in _os_write.call_args_list] + insert_writes = [ + index + for index, output in enumerate(insertion_writes) + if insert_char in output + ] + self.assertEqual(len(insert_writes), 1) + self.assertEqual(insertion_writes[insert_writes[0] + 1], b"2") + self.assertEqual(reader.rendered_screen.screen_lines, ("21",)) con.restore() def test_multiline_function_move_up_short_terminal(self, _os_write): From 5eb47048a9c8e0cdeda5c110640478023556d4e1 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 7 Oct 2026 16:52:06 -0700 Subject: [PATCH 6/6] Revert existing test and add new test --- Lib/test/test_pyrepl/test_unix_console.py | 59 +++++++++++------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index d0aeb6ebac1c715..05c716f4a75a9cd 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -15,7 +15,7 @@ from unittest import TestCase from unittest.mock import MagicMock, call, patch, ANY, Mock -from .support import handle_all_events, code_to_events, more_lines, prepare_reader +from .support import handle_all_events, code_to_events, more_lines try: from _pyrepl.console import Event @@ -248,39 +248,36 @@ def test_cursor_up_down(self, _os_write): con.restore() def test_cursor_back_write(self, _os_write): - insert_char = b"" - events = iter( - itertools.chain( - code_to_events("1"), - [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], - code_to_events("2"), - ) + events = itertools.chain( + code_to_events("1"), + [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], + code_to_events("2"), ) - con = unix_console(events, ich1=insert_char) - reader = prepare_reader(con) - - _os_write.reset_mock() - reader.handle1() - append_writes = [write.args[1] for write in _os_write.call_args_list] - self.assertIn(b"1", append_writes) - self.assertFalse(any(insert_char in output for output in append_writes)) - self.assertEqual(reader.rendered_screen.screen_lines, ("1",)) - - _os_write.reset_mock() - reader.handle1() + _, con = handle_events_unix_console(events) + _os_write.assert_any_call(ANY, b"1") _os_write.assert_any_call(ANY, TERM_CAPABILITIES["cub"] + b":1") + _os_write.assert_any_call(ANY, b"2") + con.restore() - _os_write.reset_mock() - reader.handle1() - insertion_writes = [write.args[1] for write in _os_write.call_args_list] - insert_writes = [ - index - for index, output in enumerate(insertion_writes) - if insert_char in output - ] - self.assertEqual(len(insert_writes), 1) - self.assertEqual(insertion_writes[insert_writes[0] + 1], b"2") - self.assertEqual(reader.rendered_screen.screen_lines, ("21",)) + def test_insert_char_only_for_midline_insert(self, _os_write): + insert_char = b"" + events = itertools.chain( + code_to_events("1"), + [Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], + code_to_events("2"), + ) + _, con = handle_all_events( + events, + prepare_console=partial(unix_console, ich1=insert_char), + ) + relevant_writes = [] + for write in _os_write.call_args_list: + output = write.args[1] + if insert_char in output: + relevant_writes.append(insert_char) + elif output in {b"1", b"2"}: + relevant_writes.append(output) + self.assertEqual(relevant_writes, [b"1", insert_char, b"2"]) con.restore() def test_multiline_function_move_up_short_terminal(self, _os_write):