From 9fbf9bf14f5a25890b95290b707db9ef2d3ec0f6 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Tue, 4 Jun 2024 18:09:31 +0200 Subject: [PATCH] gh-120041: Do not use append_to_screen when completions are visible (GH-120042) (cherry picked from commit 8fc7653766b106bdbc4ff6154e0020aea4ab15e6) Co-authored-by: Lysandros Nikolaou --- Lib/_pyrepl/commands.py | 7 +++++- Lib/_pyrepl/completing_reader.py | 20 +++++++++------- Lib/test/test_pyrepl/support.py | 2 +- Lib/test/test_pyrepl/test_reader.py | 37 ++++++++++++++++++++++++++++- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 2ef5dada9d9e58..b967f5206614f8 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -365,7 +365,12 @@ def do(self) -> None: r = self.reader text = self.event * r.get_arg() r.insert(text) - if len(text) == 1 and r.pos == len(r.buffer): + if ( + len(text) == 1 and + r.pos == len(r.buffer) and + not r.cmpltn_menu_visible and # type: ignore[attr-defined] + not r.cmpltn_message_visible # type: ignore[attr-defined] + ): r.calc_screen = r.append_to_screen diff --git a/Lib/_pyrepl/completing_reader.py b/Lib/_pyrepl/completing_reader.py index c11d2dabdd2792..215ad8753c9f8b 100644 --- a/Lib/_pyrepl/completing_reader.py +++ b/Lib/_pyrepl/completing_reader.py @@ -187,18 +187,20 @@ def do(self) -> None: if p: r.insert(p) if last_is_completer: - if not r.cmpltn_menu_visible: - r.cmpltn_menu_visible = True + r.cmpltn_menu_visible = True + r.cmpltn_message_visible = False r.cmpltn_menu, r.cmpltn_menu_end = build_menu( r.console, completions, r.cmpltn_menu_end, r.use_brackets, r.sort_in_column) r.dirty = True - elif stem + p in completions: - r.msg = "[ complete but not unique ]" - r.dirty = True - else: - r.msg = "[ not unique ]" - r.dirty = True + elif not r.cmpltn_menu_visible: + r.cmpltn_message_visible = True + if stem + p in completions: + r.msg = "[ complete but not unique ]" + r.dirty = True + else: + r.msg = "[ not unique ]" + r.dirty = True class self_insert(commands.self_insert): @@ -236,6 +238,7 @@ class CompletingReader(Reader): ### Instance variables cmpltn_menu: list[str] = field(init=False) cmpltn_menu_visible: bool = field(init=False) + cmpltn_message_visible: bool = field(init=False) cmpltn_menu_end: int = field(init=False) cmpltn_menu_choices: list[str] = field(init=False) @@ -271,6 +274,7 @@ def finish(self) -> None: def cmpltn_reset(self) -> None: self.cmpltn_menu = [] self.cmpltn_menu_visible = False + self.cmpltn_message_visible = False self.cmpltn_menu_end = 0 self.cmpltn_menu_choices = [] diff --git a/Lib/test/test_pyrepl/support.py b/Lib/test/test_pyrepl/support.py index d2f5429aea7a11..e807b5f3404550 100644 --- a/Lib/test/test_pyrepl/support.py +++ b/Lib/test/test_pyrepl/support.py @@ -39,7 +39,7 @@ def code_to_events(code: str): def prepare_reader(console: Console, **kwargs): - config = ReadlineConfig(readline_completer=None) + config = ReadlineConfig(readline_completer=kwargs.pop("readline_completer", None)) reader = ReadlineAlikeReader(console=console, config=config) reader.more_lines = partial(more_lines, namespace=None) reader.paste_mode = True # Avoid extra indents diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index 9fb956b655594f..d02815bfa11d74 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -1,5 +1,6 @@ import itertools import functools +import rlcompleter from unittest import TestCase from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader @@ -9,7 +10,7 @@ class TestReader(TestCase): def assert_screen_equals(self, reader, expected): - actual = reader.calc_screen() + actual = reader.screen expected = expected.split("\n") self.assertListEqual(actual, expected) @@ -208,3 +209,37 @@ def test_prompt_length(self): prompt, l = Reader.process_prompt(ps1) self.assertEqual(prompt, "\033[0;32m樂>\033[0m> ") self.assertEqual(l, 5) + + def test_completions_updated_on_key_press(self): + namespace = {"itertools": itertools} + code = "itertools." + events = itertools.chain(code_to_events(code), [ + Event(evt='key', data='\t', raw=bytearray(b'\t')), # Two tabs for completion + Event(evt='key', data='\t', raw=bytearray(b'\t')), + ], code_to_events("a")) + + completing_reader = functools.partial( + prepare_reader, + readline_completer=rlcompleter.Completer(namespace).complete + ) + reader, _ = handle_all_events(events, prepare_reader=completing_reader) + + actual = reader.screen + self.assertEqual(len(actual), 2) + self.assertEqual(actual[0].rstrip(), "itertools.accumulate(") + self.assertEqual(actual[1], f"{code}a") + + def test_key_press_on_tab_press_once(self): + namespace = {"itertools": itertools} + code = "itertools." + events = itertools.chain(code_to_events(code), [ + Event(evt='key', data='\t', raw=bytearray(b'\t')), + ], code_to_events("a")) + + completing_reader = functools.partial( + prepare_reader, + readline_completer=rlcompleter.Completer(namespace).complete + ) + reader, _ = handle_all_events(events, prepare_reader=completing_reader) + + self.assert_screen_equals(reader, f"{code}a")