Skip to content

Commit 9a1a74b

Browse files
committed
gh-121499: Fix multi-line history rendering in the REPL
Signed-off-by: Pablo Galindo <[email protected]>
1 parent dc03ce7 commit 9a1a74b

File tree

4 files changed

+51
-0
lines changed

4 files changed

+51
-0
lines changed

Lib/_pyrepl/historical_reader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ def select_item(self, i: int) -> None:
264264
self.historyi = i
265265
self.pos = len(self.buffer)
266266
self.dirty = True
267+
self.last_refresh_cache.invalidated = True
267268

268269
def get_item(self, i: int) -> str:
269270
if i != len(self.history):

Lib/test/test_pyrepl/support.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ def code_to_events(code: str):
3838
yield Event(evt="key", data=c, raw=bytearray(c.encode("utf-8")))
3939

4040

41+
def clean_screen(screen: Iterable[str]):
42+
"""Cleans color and console characters out of a screen output.
43+
44+
This is useful for screen testing, it increases the test readability since
45+
it strips out all the unreadable side of the screen.
46+
"""
47+
return '\n'.join(screen).replace(
48+
'\x1b[1;35m>>>\x1b[0m', '').replace('\x1b[1;35m...\x1b[0m', '').strip()
49+
50+
4151
def prepare_reader(console: Console, **kwargs):
4252
config = ReadlineConfig(readline_completer=kwargs.pop("readline_completer", None))
4353
reader = ReadlineAlikeReader(console=console, config=config)

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
more_lines,
2222
multiline_input,
2323
code_to_events,
24+
clean_screen
2425
)
2526
from _pyrepl.console import Event
2627
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
@@ -490,6 +491,7 @@ def test_basic(self):
490491

491492
output = multiline_input(reader)
492493
self.assertEqual(output, "1+1")
494+
self.assertEqual(clean_screen(reader.screen), "1+1")
493495

494496
def test_multiline_edit(self):
495497
events = itertools.chain(
@@ -519,8 +521,10 @@ def test_multiline_edit(self):
519521

520522
output = multiline_input(reader)
521523
self.assertEqual(output, "def f():\n ...\n ")
524+
self.assertEqual(clean_screen(reader.screen), "def f():\n ...")
522525
output = multiline_input(reader)
523526
self.assertEqual(output, "def g():\n pass\n ")
527+
self.assertEqual(clean_screen(reader.screen), "def g():\n pass")
524528

525529
def test_history_navigation_with_up_arrow(self):
526530
events = itertools.chain(
@@ -539,12 +543,40 @@ def test_history_navigation_with_up_arrow(self):
539543

540544
output = multiline_input(reader)
541545
self.assertEqual(output, "1+1")
546+
self.assertEqual(clean_screen(reader.screen), "1+1")
542547
output = multiline_input(reader)
543548
self.assertEqual(output, "2+2")
549+
self.assertEqual(clean_screen(reader.screen), "2+2")
544550
output = multiline_input(reader)
545551
self.assertEqual(output, "2+2")
552+
self.assertEqual(clean_screen(reader.screen), "2+2")
546553
output = multiline_input(reader)
547554
self.assertEqual(output, "1+1")
555+
self.assertEqual(clean_screen(reader.screen), "1+1")
556+
557+
def test_history_with_multiline_entries(self):
558+
code = "def foo():\nx = 1\ny = 2\nz = 3\n\ndef bar():\nreturn 42\n\n"
559+
events = list(itertools.chain(
560+
code_to_events(code),
561+
[
562+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
563+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
564+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
565+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
566+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
567+
]
568+
))
569+
570+
reader = self.prepare_reader(events)
571+
output = multiline_input(reader)
572+
output = multiline_input(reader)
573+
output = multiline_input(reader)
574+
self.assertEqual(
575+
clean_screen(reader.screen),
576+
'def foo():\n x = 1\n y = 2\n z = 3'
577+
)
578+
self.assertEqual(output, "def foo():\n x = 1\n y = 2\n z = 3\n ")
579+
548580

549581
def test_history_navigation_with_down_arrow(self):
550582
events = itertools.chain(
@@ -562,6 +594,7 @@ def test_history_navigation_with_down_arrow(self):
562594

563595
output = multiline_input(reader)
564596
self.assertEqual(output, "1+1")
597+
self.assertEqual(clean_screen(reader.screen), "1+1")
565598

566599
def test_history_search(self):
567600
events = itertools.chain(
@@ -578,18 +611,23 @@ def test_history_search(self):
578611

579612
output = multiline_input(reader)
580613
self.assertEqual(output, "1+1")
614+
self.assertEqual(clean_screen(reader.screen), "1+1")
581615
output = multiline_input(reader)
582616
self.assertEqual(output, "2+2")
617+
self.assertEqual(clean_screen(reader.screen), "2+2")
583618
output = multiline_input(reader)
584619
self.assertEqual(output, "3+3")
620+
self.assertEqual(clean_screen(reader.screen), "3+3")
585621
output = multiline_input(reader)
586622
self.assertEqual(output, "1+1")
623+
self.assertEqual(clean_screen(reader.screen), "1+1")
587624

588625
def test_control_character(self):
589626
events = code_to_events("c\x1d\n")
590627
reader = self.prepare_reader(events)
591628
output = multiline_input(reader)
592629
self.assertEqual(output, "c\x1d")
630+
self.assertEqual(clean_screen(reader.screen), "c")
593631

594632

595633
class TestPyReplCompleter(TestCase):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a bug affecting how multi-line history was being rendered in the new
2+
REPL after interacting with the new screen cache. Patch by Pablo Galindo

0 commit comments

Comments
 (0)