https://github.com/python/cpython/commit/4b9e10d0ea352592049c1f2a00318d7274143fa4
commit: 4b9e10d0ea352592049c1f2a00318d7274143fa4
branch: main
author: Pablo Galindo Salgado <pablog...@gmail.com>
committer: ambv <luk...@langa.pl>
date: 2024-07-13T12:54:10+02:00
summary:

gh-121499: Fix multi-line history rendering in the REPL (#121531)

Signed-off-by: Pablo Galindo <pablog...@gmail.com>

files:
A Misc/NEWS.d/next/Core and 
Builtins/2024-07-09-13-53-18.gh-issue-121499.rpp7il.rst
M Lib/_pyrepl/historical_reader.py
M Lib/_pyrepl/reader.py
M Lib/test/test_pyrepl/support.py
M Lib/test/test_pyrepl/test_pyrepl.py

diff --git a/Lib/_pyrepl/historical_reader.py b/Lib/_pyrepl/historical_reader.py
index dd90912d1d67f8..7f4d0672d02094 100644
--- a/Lib/_pyrepl/historical_reader.py
+++ b/Lib/_pyrepl/historical_reader.py
@@ -264,6 +264,7 @@ def select_item(self, i: int) -> None:
         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 1622d30dbba990..b2da038906939b 100644
--- a/Lib/_pyrepl/reader.py
+++ b/Lib/_pyrepl/reader.py
@@ -253,6 +253,7 @@ class RefreshCache:
         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 @@ def update_cache(self,
             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 70e12286f7d781..58b1a92d68b184 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 015b690566223d..43206103645bc1 100644
--- a/Lib/test/test_pyrepl/test_pyrepl.py
+++ b/Lib/test/test_pyrepl/test_pyrepl.py
@@ -21,6 +21,7 @@
     more_lines,
     multiline_input,
     code_to_events,
+    clean_screen
 )
 from _pyrepl.console import Event
 from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
@@ -483,6 +484,7 @@ def prepare_reader(self, events):
         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 @@ def test_basic(self):
 
         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 @@ def test_multiline_edit(self):
 
         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 @@ def test_history_navigation_with_up_arrow(self):
 
         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 @@ def test_history_navigation_with_down_arrow(self):
 
         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 @@ def test_history_search(self):
 
         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 00000000000000..aec8ab9d4662b5
--- /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

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to