Skip to content

Commit b121d64

Browse files
committed
Separate _pyrepl overlays from base rendering
Move completion and message UI onto overlays layered over the base rendered screen. The completion menu stops pretending it is the base reality.
1 parent c15ed65 commit b121d64

File tree

3 files changed

+66
-23
lines changed

3 files changed

+66
-23
lines changed

Lib/_pyrepl/completing_reader.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
import re
2626
from . import commands, console, reader
27-
from .render import RenderLine, RenderedScreen
27+
from .render import RenderLine, ScreenOverlay
2828
from .reader import Reader
2929

3030

@@ -259,15 +259,15 @@ def after_command(self, cmd: Command) -> None:
259259
if not isinstance(cmd, (complete, self_insert)):
260260
self.cmpltn_reset()
261261

262-
def calc_screen(self) -> RenderedScreen:
263-
rendered_screen = super().calc_screen()
264-
if self.cmpltn_menu_visible:
265-
rendered_screen = rendered_screen.with_overlay(
262+
def get_screen_overlays(self) -> tuple[ScreenOverlay, ...]:
263+
if not self.cmpltn_menu_visible:
264+
return ()
265+
return (
266+
ScreenOverlay(
266267
self.lxy[1] + 1,
267-
(RenderLine.from_rendered_text(line) for line in self.cmpltn_menu),
268-
)
269-
self.rendered_screen = rendered_screen
270-
return rendered_screen
268+
tuple(RenderLine.from_rendered_text(line) for line in self.cmpltn_menu),
269+
),
270+
)
271271

272272
def finish(self) -> None:
273273
super().finish()

Lib/_pyrepl/reader.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
process_prompt as build_prompt_content,
3737
)
3838
from .layout import LayoutMap, LayoutResult, LayoutRow, WrappedRow, layout_content_lines
39-
from .render import RenderCell, RenderLine, RenderedScreen, StyleRef
39+
from .render import RenderCell, RenderLine, RenderedScreen, ScreenOverlay, StyleRef
4040
from .utils import ANSI_ESCAPE_SEQUENCE, wlen, gen_colors
4141
from .trace import trace
4242

@@ -380,7 +380,7 @@ def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]:
380380
return default_keymap
381381

382382
def calc_screen(self) -> RenderedScreen:
383-
"""Translate changes in self.buffer into a structured rendered screen."""
383+
"""Translate the editable buffer into a base rendered screen."""
384384
num_common_lines = 0
385385
offset = 0
386386
if self.last_refresh_cache.valid(self):
@@ -427,12 +427,7 @@ def calc_screen(self) -> RenderedScreen:
427427
layout_rows,
428428
last_refresh_line_end_offsets,
429429
)
430-
431-
render_lines = base_render_lines.copy()
432-
render_lines.extend(self._render_message_lines())
433-
434-
self.rendered_screen = RenderedScreen(tuple(render_lines), self.cxy)
435-
return self.rendered_screen
430+
return RenderedScreen(tuple(base_render_lines), self.cxy)
436431

437432
def _buffer_refresh_from_pos(self) -> int | None:
438433
buffer_from_pos = self.invalidation.buffer_rebuild_from_pos
@@ -536,13 +531,25 @@ def _render_wrapped_rows(
536531
for row in wrapped_rows
537532
]
538533

539-
def _render_message_lines(self) -> list[RenderLine]:
534+
def _render_message_lines(self) -> tuple[RenderLine, ...]:
540535
if not self.msg:
541-
return []
542-
return [
536+
return ()
537+
return tuple(
543538
RenderLine.from_rendered_text(message_line)
544539
for message_line in self.msg.split("\n")
545-
]
540+
)
541+
542+
def get_screen_overlays(self) -> tuple[ScreenOverlay, ...]:
543+
return ()
544+
545+
def compose_rendered_screen(self, base_screen: RenderedScreen) -> RenderedScreen:
546+
overlays = list(self.get_screen_overlays())
547+
message_lines = self._render_message_lines()
548+
if message_lines:
549+
overlays.append(ScreenOverlay(len(base_screen.lines), message_lines))
550+
if not overlays:
551+
return base_screen
552+
return RenderedScreen(base_screen.lines, base_screen.cursor, tuple(overlays))
546553

547554
def _render_line(
548555
self,
@@ -754,7 +761,8 @@ def prepare(self) -> None:
754761
self.rendered_screen = RenderedScreen.empty()
755762
self.invalidate_full()
756763
self.last_command = None
757-
self.calc_screen()
764+
base_screen = self.calc_screen()
765+
self.rendered_screen = self.compose_rendered_screen(base_screen)
758766
except BaseException:
759767
self.restore()
760768
raise
@@ -813,7 +821,9 @@ def update_screen(self) -> None:
813821
def refresh(self) -> None:
814822
"""Recalculate and refresh the screen."""
815823
# this call sets up self.cxy, so call it first.
816-
rendered_screen = self.calc_screen()
824+
base_screen = self.calc_screen()
825+
rendered_screen = self.compose_rendered_screen(base_screen)
826+
self.rendered_screen = rendered_screen
817827
trace(
818828
"reader.refresh cursor={cursor} lines={lines} "
819829
"dims=({width},{height}) invalidation={invalidation}",

Lib/test/test_pyrepl/test_reader.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,41 @@ def test_message_refresh_keeps_base_render_cache(self):
324324
reader.update_screen()
325325

326326
self.assertEqual([line.text for line in reader.last_refresh_cache.render_lines], ["ab"])
327+
self.assertEqual([line.text for line in reader.rendered_screen.lines], ["ab"])
328+
self.assertEqual(len(reader.rendered_screen.overlays), 1)
327329
self.assertEqual(reader.screen, ["ab", "! boom "])
328330

331+
def test_completion_overlay_keeps_base_render_cache(self):
332+
namespace = {"itertools": itertools}
333+
code = "itertools."
334+
events = itertools.chain(
335+
code_to_events(code),
336+
[
337+
Event(evt="key", data="\t", raw=bytearray(b"\t")),
338+
Event(evt="key", data="\t", raw=bytearray(b"\t")),
339+
],
340+
)
341+
342+
completing_reader = functools.partial(
343+
prepare_reader,
344+
readline_completer=rlcompleter.Completer(namespace).complete,
345+
)
346+
reader, _ = handle_all_events(events, prepare_reader=completing_reader)
347+
348+
self.assertEqual([line.text for line in reader.last_refresh_cache.render_lines], [code])
349+
self.assertEqual([line.text for line in reader.rendered_screen.lines], [code])
350+
self.assertEqual(len(reader.rendered_screen.overlays), 1)
351+
self.assertEqual(reader.screen[0], code)
352+
self.assertEqual(reader.screen[1].rstrip(), "itertools.accumulate(")
353+
354+
reader.cmpltn_reset()
355+
reader.update_screen()
356+
357+
self.assertEqual([line.text for line in reader.last_refresh_cache.render_lines], [code])
358+
self.assertEqual([line.text for line in reader.rendered_screen.lines], [code])
359+
self.assertEqual(reader.rendered_screen.overlays, ())
360+
self.assertEqual(reader.screen, [code])
361+
329362
def test_completions_updated_on_key_press(self):
330363
namespace = {"itertools": itertools}
331364
code = "itertools."

0 commit comments

Comments
 (0)