Skip to content

Commit 29caec6

Browse files
gh-118878: Pyrepl: show completions menu below the current line (#118939)
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
1 parent 5a9afe2 commit 29caec6

File tree

5 files changed

+17
-9
lines changed

5 files changed

+17
-9
lines changed

Lib/_pyrepl/commands.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ def do(self) -> None:
282282
x, y = r.pos2xy()
283283
new_y = y + 1
284284

285-
if new_y > r.max_row():
285+
if r.eol() == len(b):
286286
if r.historyi < len(r.history):
287287
r.select_item(r.historyi + 1)
288288
r.pos = r.eol(0)
@@ -309,7 +309,7 @@ def do(self) -> None:
309309
class left(MotionCommand):
310310
def do(self) -> None:
311311
r = self.reader
312-
for i in range(r.get_arg()):
312+
for _ in range(r.get_arg()):
313313
p = r.pos - 1
314314
if p >= 0:
315315
r.pos = p
@@ -321,7 +321,7 @@ class right(MotionCommand):
321321
def do(self) -> None:
322322
r = self.reader
323323
b = r.buffer
324-
for i in range(r.get_arg()):
324+
for _ in range(r.get_arg()):
325325
p = r.pos + 1
326326
if p <= len(b):
327327
r.pos = p

Lib/_pyrepl/completing_reader.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,15 @@ def after_command(self, cmd: Command) -> None:
260260
def calc_screen(self) -> list[str]:
261261
screen = super().calc_screen()
262262
if self.cmpltn_menu_visible:
263-
ly = self.lxy[1]
263+
# We display the completions menu below the current prompt
264+
ly = self.lxy[1] + 1
264265
screen[ly:ly] = self.cmpltn_menu
265-
self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu)
266-
self.cxy = self.cxy[0], self.cxy[1] + len(self.cmpltn_menu)
266+
# If we're not in the middle of multiline edit, don't append to screeninfo
267+
# since that screws up the position calculation in pos2xy function.
268+
# This is a hack to prevent the cursor jumping
269+
# into the completions menu when pressing left or down arrow.
270+
if self.pos != len(self.buffer):
271+
self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu)
267272
return screen
268273

269274
def finish(self) -> None:

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ def test_global_namespace_completion(self):
850850
output = multiline_input(reader, namespace)
851851
self.assertEqual(output, "python")
852852

853-
def test_updown_arrow_with_completion_menu(self):
853+
def test_up_down_arrow_with_completion_menu(self):
854854
"""Up arrow in the middle of unfinished tab completion when the menu is displayed
855855
should work and trigger going back in history. Down arrow should subsequently
856856
get us back to the incomplete command."""
@@ -860,6 +860,7 @@ def test_updown_arrow_with_completion_menu(self):
860860
events = itertools.chain(
861861
code_to_events(code),
862862
[
863+
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
863864
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
864865
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
865866
],

Lib/test/test_pyrepl/test_reader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,8 @@ def test_completions_updated_on_key_press(self):
295295

296296
actual = reader.screen
297297
self.assertEqual(len(actual), 2)
298-
self.assertEqual(actual[0].rstrip(), "itertools.accumulate(")
299-
self.assertEqual(actual[1], f"{code}a")
298+
self.assertEqual(actual[0], f"{code}a")
299+
self.assertEqual(actual[1].rstrip(), "itertools.accumulate(")
300300

301301
def test_key_press_on_tab_press_once(self):
302302
namespace = {"itertools": itertools}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Show tab completions menu below the current line, which results in less
2+
janky behaviour, and fixes a cursor movement bug. Patch by Daniel Hollas

0 commit comments

Comments
 (0)