Skip to content

Commit

Permalink
pythongh-121499: Fix multi-line history rendering in the REPL
Browse files Browse the repository at this point in the history
Signed-off-by: Pablo Galindo <[email protected]>
  • Loading branch information
pablogsal committed Jul 13, 2024
1 parent dc03ce7 commit 9a1a74b
Showing 4 changed files with 51 additions and 0 deletions.
1 change: 1 addition & 0 deletions Lib/_pyrepl/historical_reader.py
Original file line number Diff line number Diff line change
@@ -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):
10 changes: 10 additions & 0 deletions Lib/test/test_pyrepl/support.py
Original file line number Diff line number Diff line change
@@ -38,6 +38,16 @@ 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.
"""
return '\n'.join(screen).replace(
'\x1b[1;35m>>>\x1b[0m', '').replace('\x1b[1;35m...\x1b[0m', '').strip()


def prepare_reader(console: Console, **kwargs):
config = ReadlineConfig(readline_completer=kwargs.pop("readline_completer", None))
reader = ReadlineAlikeReader(console=console, config=config)
38 changes: 38 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
more_lines,
multiline_input,
code_to_events,
clean_screen
)
from _pyrepl.console import Event
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
@@ -490,6 +491,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 +521,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 +543,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 +594,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 +611,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):
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 9a1a74b

Please sign in to comment.