Skip to content

Commit 8fc7653

Browse files
authored
gh-120041: Do not use append_to_screen when completions are visible (GH-120042)
1 parent 4dcd91c commit 8fc7653

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

Lib/_pyrepl/commands.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,12 @@ def do(self) -> None:
365365
r = self.reader
366366
text = self.event * r.get_arg()
367367
r.insert(text)
368-
if len(text) == 1 and r.pos == len(r.buffer):
368+
if (
369+
len(text) == 1 and
370+
r.pos == len(r.buffer) and
371+
not r.cmpltn_menu_visible and # type: ignore[attr-defined]
372+
not r.cmpltn_message_visible # type: ignore[attr-defined]
373+
):
369374
r.calc_screen = r.append_to_screen
370375

371376

Lib/_pyrepl/completing_reader.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,20 @@ def do(self) -> None:
187187
if p:
188188
r.insert(p)
189189
if last_is_completer:
190-
if not r.cmpltn_menu_visible:
191-
r.cmpltn_menu_visible = True
190+
r.cmpltn_menu_visible = True
191+
r.cmpltn_message_visible = False
192192
r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
193193
r.console, completions, r.cmpltn_menu_end,
194194
r.use_brackets, r.sort_in_column)
195195
r.dirty = True
196-
elif stem + p in completions:
197-
r.msg = "[ complete but not unique ]"
198-
r.dirty = True
199-
else:
200-
r.msg = "[ not unique ]"
201-
r.dirty = True
196+
elif not r.cmpltn_menu_visible:
197+
r.cmpltn_message_visible = True
198+
if stem + p in completions:
199+
r.msg = "[ complete but not unique ]"
200+
r.dirty = True
201+
else:
202+
r.msg = "[ not unique ]"
203+
r.dirty = True
202204

203205

204206
class self_insert(commands.self_insert):
@@ -236,6 +238,7 @@ class CompletingReader(Reader):
236238
### Instance variables
237239
cmpltn_menu: list[str] = field(init=False)
238240
cmpltn_menu_visible: bool = field(init=False)
241+
cmpltn_message_visible: bool = field(init=False)
239242
cmpltn_menu_end: int = field(init=False)
240243
cmpltn_menu_choices: list[str] = field(init=False)
241244

@@ -271,6 +274,7 @@ def finish(self) -> None:
271274
def cmpltn_reset(self) -> None:
272275
self.cmpltn_menu = []
273276
self.cmpltn_menu_visible = False
277+
self.cmpltn_message_visible = False
274278
self.cmpltn_menu_end = 0
275279
self.cmpltn_menu_choices = []
276280

Lib/test/test_pyrepl/support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def code_to_events(code: str):
3939

4040

4141
def prepare_reader(console: Console, **kwargs):
42-
config = ReadlineConfig(readline_completer=None)
42+
config = ReadlineConfig(readline_completer=kwargs.pop("readline_completer", None))
4343
reader = ReadlineAlikeReader(console=console, config=config)
4444
reader.more_lines = partial(more_lines, namespace=None)
4545
reader.paste_mode = True # Avoid extra indents

Lib/test/test_pyrepl/test_reader.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import itertools
22
import functools
3+
import rlcompleter
34
from unittest import TestCase
45

56
from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
@@ -9,7 +10,7 @@
910

1011
class TestReader(TestCase):
1112
def assert_screen_equals(self, reader, expected):
12-
actual = reader.calc_screen()
13+
actual = reader.screen
1314
expected = expected.split("\n")
1415
self.assertListEqual(actual, expected)
1516

@@ -208,3 +209,37 @@ def test_prompt_length(self):
208209
prompt, l = Reader.process_prompt(ps1)
209210
self.assertEqual(prompt, "\033[0;32m樂>\033[0m> ")
210211
self.assertEqual(l, 5)
212+
213+
def test_completions_updated_on_key_press(self):
214+
namespace = {"itertools": itertools}
215+
code = "itertools."
216+
events = itertools.chain(code_to_events(code), [
217+
Event(evt='key', data='\t', raw=bytearray(b'\t')), # Two tabs for completion
218+
Event(evt='key', data='\t', raw=bytearray(b'\t')),
219+
], code_to_events("a"))
220+
221+
completing_reader = functools.partial(
222+
prepare_reader,
223+
readline_completer=rlcompleter.Completer(namespace).complete
224+
)
225+
reader, _ = handle_all_events(events, prepare_reader=completing_reader)
226+
227+
actual = reader.screen
228+
self.assertEqual(len(actual), 2)
229+
self.assertEqual(actual[0].rstrip(), "itertools.accumulate(")
230+
self.assertEqual(actual[1], f"{code}a")
231+
232+
def test_key_press_on_tab_press_once(self):
233+
namespace = {"itertools": itertools}
234+
code = "itertools."
235+
events = itertools.chain(code_to_events(code), [
236+
Event(evt='key', data='\t', raw=bytearray(b'\t')),
237+
], code_to_events("a"))
238+
239+
completing_reader = functools.partial(
240+
prepare_reader,
241+
readline_completer=rlcompleter.Completer(namespace).complete
242+
)
243+
reader, _ = handle_all_events(events, prepare_reader=completing_reader)
244+
245+
self.assert_screen_equals(reader, f"{code}a")

0 commit comments

Comments
 (0)