diff options
author | Pablo Galindo Salgado <Pablogsal@gmail.com> | 2024-07-13 10:54:10 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-13 10:54:10 (GMT) |
commit | 4b9e10d0ea352592049c1f2a00318d7274143fa4 (patch) | |
tree | 1c2c4a8484e656db28fd88dac57702c69bcf60da | |
parent | e745996b2d24b9234f896bdc2f3320e49287dcd0 (diff) | |
download | cpython-4b9e10d0ea352592049c1f2a00318d7274143fa4.zip cpython-4b9e10d0ea352592049c1f2a00318d7274143fa4.tar.gz cpython-4b9e10d0ea352592049c1f2a00318d7274143fa4.tar.bz2 |
gh-121499: Fix multi-line history rendering in the REPL (#121531)
Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
-rw-r--r-- | Lib/_pyrepl/historical_reader.py | 1 | ||||
-rw-r--r-- | Lib/_pyrepl/reader.py | 6 | ||||
-rw-r--r-- | Lib/test/test_pyrepl/support.py | 14 | ||||
-rw-r--r-- | Lib/test/test_pyrepl/test_pyrepl.py | 39 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2024-07-09-13-53-18.gh-issue-121499.rpp7il.rst | 2 |
5 files changed, 62 insertions, 0 deletions
diff --git a/Lib/_pyrepl/historical_reader.py b/Lib/_pyrepl/historical_reader.py index dd90912..7f4d067 100644 --- a/Lib/_pyrepl/historical_reader.py +++ b/Lib/_pyrepl/historical_reader.py @@ -264,6 +264,7 @@ class HistoricalReader(Reader): self.historyi = i self.pos = len(self.buffer) self.dirty = True + self.last_refresh_cache.invalidated = True def get_item(self, i: int) -> str: if i != len(self.history): diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 1622d30..b2da038 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -253,6 +253,7 @@ class Reader: pos: int = field(init=False) cxy: tuple[int, int] = field(init=False) dimensions: tuple[int, int] = field(init=False) + invalidated: bool = False def update_cache(self, reader: Reader, @@ -265,14 +266,19 @@ class Reader: self.pos = reader.pos self.cxy = reader.cxy self.dimensions = reader.console.width, reader.console.height + self.invalidated = False def valid(self, reader: Reader) -> bool: + if self.invalidated: + return False dimensions = reader.console.width, reader.console.height dimensions_changed = dimensions != self.dimensions paste_changed = reader.in_bracketed_paste != self.in_bracketed_paste return not (dimensions_changed or paste_changed) def get_cached_location(self, reader: Reader) -> tuple[int, int]: + if self.invalidated: + raise ValueError("Cache is invalidated") offset = 0 earliest_common_pos = min(reader.pos, self.pos) num_common_lines = len(self.line_end_offsets) diff --git a/Lib/test/test_pyrepl/support.py b/Lib/test/test_pyrepl/support.py index 70e1228..58b1a92 100644 --- a/Lib/test/test_pyrepl/support.py +++ b/Lib/test/test_pyrepl/support.py @@ -38,6 +38,20 @@ def code_to_events(code: str): yield Event(evt="key", data=c, raw=bytearray(c.encode("utf-8"))) +def clean_screen(screen: Iterable[str]): + """Cleans color and console characters out of a screen output. + + This is useful for screen testing, it increases the test readability since + it strips out all the unreadable side of the screen. + """ + output = [] + for line in screen: + if line.startswith(">>>") or line.startswith("..."): + line = line[3:] + output.append(line) + return "\n".join(output).strip() + + def prepare_reader(console: Console, **kwargs): config = ReadlineConfig(readline_completer=kwargs.pop("readline_completer", None)) reader = ReadlineAlikeReader(console=console, config=config) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 015b690..4320610 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -21,6 +21,7 @@ from .support import ( more_lines, multiline_input, code_to_events, + clean_screen ) from _pyrepl.console import Event from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig @@ -483,6 +484,7 @@ class TestPyReplOutput(TestCase): console = FakeConsole(events) config = ReadlineConfig(readline_completer=None) reader = ReadlineAlikeReader(console=console, config=config) + reader.can_colorize = False return reader def test_basic(self): @@ -490,6 +492,7 @@ class TestPyReplOutput(TestCase): output = multiline_input(reader) self.assertEqual(output, "1+1") + self.assertEqual(clean_screen(reader.screen), "1+1") def test_multiline_edit(self): events = itertools.chain( @@ -519,8 +522,10 @@ class TestPyReplOutput(TestCase): output = multiline_input(reader) self.assertEqual(output, "def f():\n ...\n ") + self.assertEqual(clean_screen(reader.screen), "def f():\n ...") output = multiline_input(reader) self.assertEqual(output, "def g():\n pass\n ") + self.assertEqual(clean_screen(reader.screen), "def g():\n pass") def test_history_navigation_with_up_arrow(self): events = itertools.chain( @@ -539,12 +544,40 @@ class TestPyReplOutput(TestCase): output = multiline_input(reader) self.assertEqual(output, "1+1") + self.assertEqual(clean_screen(reader.screen), "1+1") output = multiline_input(reader) self.assertEqual(output, "2+2") + self.assertEqual(clean_screen(reader.screen), "2+2") output = multiline_input(reader) self.assertEqual(output, "2+2") + self.assertEqual(clean_screen(reader.screen), "2+2") output = multiline_input(reader) self.assertEqual(output, "1+1") + self.assertEqual(clean_screen(reader.screen), "1+1") + + def test_history_with_multiline_entries(self): + code = "def foo():\nx = 1\ny = 2\nz = 3\n\ndef bar():\nreturn 42\n\n" + events = list(itertools.chain( + code_to_events(code), + [ + Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), + Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), + Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), + Event(evt="key", data="\n", raw=bytearray(b"\n")), + Event(evt="key", data="\n", raw=bytearray(b"\n")), + ] + )) + + reader = self.prepare_reader(events) + output = multiline_input(reader) + output = multiline_input(reader) + output = multiline_input(reader) + self.assertEqual( + clean_screen(reader.screen), + 'def foo():\n x = 1\n y = 2\n z = 3' + ) + self.assertEqual(output, "def foo():\n x = 1\n y = 2\n z = 3\n ") + def test_history_navigation_with_down_arrow(self): events = itertools.chain( @@ -562,6 +595,7 @@ class TestPyReplOutput(TestCase): output = multiline_input(reader) self.assertEqual(output, "1+1") + self.assertEqual(clean_screen(reader.screen), "1+1") def test_history_search(self): events = itertools.chain( @@ -578,18 +612,23 @@ class TestPyReplOutput(TestCase): output = multiline_input(reader) self.assertEqual(output, "1+1") + self.assertEqual(clean_screen(reader.screen), "1+1") output = multiline_input(reader) self.assertEqual(output, "2+2") + self.assertEqual(clean_screen(reader.screen), "2+2") output = multiline_input(reader) self.assertEqual(output, "3+3") + self.assertEqual(clean_screen(reader.screen), "3+3") output = multiline_input(reader) self.assertEqual(output, "1+1") + self.assertEqual(clean_screen(reader.screen), "1+1") def test_control_character(self): events = code_to_events("c\x1d\n") reader = self.prepare_reader(events) output = multiline_input(reader) self.assertEqual(output, "c\x1d") + self.assertEqual(clean_screen(reader.screen), "c") class TestPyReplCompleter(TestCase): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-07-09-13-53-18.gh-issue-121499.rpp7il.rst b/Misc/NEWS.d/next/Core and Builtins/2024-07-09-13-53-18.gh-issue-121499.rpp7il.rst new file mode 100644 index 0000000..aec8ab9 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-07-09-13-53-18.gh-issue-121499.rpp7il.rst @@ -0,0 +1,2 @@ +Fix a bug affecting how multi-line history was being rendered in the new +REPL after interacting with the new screen cache. Patch by Pablo Galindo |