diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 285841ca5e5b1c..503ca1da329eaa 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -282,7 +282,7 @@ def do(self) -> None: x, y = r.pos2xy() new_y = y + 1 - if new_y > r.max_row(): + if r.eol() == len(b): if r.historyi < len(r.history): r.select_item(r.historyi + 1) r.pos = r.eol(0) @@ -309,7 +309,7 @@ def do(self) -> None: class left(MotionCommand): def do(self) -> None: r = self.reader - for i in range(r.get_arg()): + for _ in range(r.get_arg()): p = r.pos - 1 if p >= 0: r.pos = p @@ -321,7 +321,7 @@ class right(MotionCommand): def do(self) -> None: r = self.reader b = r.buffer - for i in range(r.get_arg()): + for _ in range(r.get_arg()): p = r.pos + 1 if p <= len(b): r.pos = p diff --git a/Lib/_pyrepl/completing_reader.py b/Lib/_pyrepl/completing_reader.py index 05770aaf5060cc..9a005281dab1e6 100644 --- a/Lib/_pyrepl/completing_reader.py +++ b/Lib/_pyrepl/completing_reader.py @@ -260,10 +260,15 @@ def after_command(self, cmd: Command) -> None: def calc_screen(self) -> list[str]: screen = super().calc_screen() if self.cmpltn_menu_visible: - ly = self.lxy[1] + # We display the completions menu below the current prompt + ly = self.lxy[1] + 1 screen[ly:ly] = self.cmpltn_menu - self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu) - self.cxy = self.cxy[0], self.cxy[1] + len(self.cmpltn_menu) + # If we're not in the middle of multiline edit, don't append to screeninfo + # since that screws up the position calculation in pos2xy function. + # This is a hack to prevent the cursor jumping + # into the completions menu when pressing left or down arrow. + if self.pos != len(self.buffer): + self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu) return screen def finish(self) -> None: diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 4ddf7e25465eeb..3de8a10ba9b801 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -851,7 +851,7 @@ def test_global_namespace_completion(self): output = multiline_input(reader, namespace) self.assertEqual(output, "python") - def test_updown_arrow_with_completion_menu(self): + def test_up_down_arrow_with_completion_menu(self): """Up arrow in the middle of unfinished tab completion when the menu is displayed should work and trigger going back in history. Down arrow should subsequently get us back to the incomplete command.""" @@ -861,6 +861,7 @@ def test_updown_arrow_with_completion_menu(self): events = itertools.chain( code_to_events(code), [ + Event(evt="key", data="down", raw=bytearray(b"\x1bOB")), Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), Event(evt="key", data="down", raw=bytearray(b"\x1bOB")), ], diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index 421545eb1f64b7..8c5efd003de5e8 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -296,8 +296,8 @@ def test_completions_updated_on_key_press(self): actual = reader.screen self.assertEqual(len(actual), 2) - self.assertEqual(actual[0].rstrip(), "itertools.accumulate(") - self.assertEqual(actual[1], f"{code}a") + self.assertEqual(actual[0], f"{code}a") + self.assertEqual(actual[1].rstrip(), "itertools.accumulate(") def test_key_press_on_tab_press_once(self): namespace = {"itertools": itertools} diff --git a/Misc/NEWS.d/next/Library/2024-07-14-23-19-20.gh-issue-119257.9OEzcN.rst b/Misc/NEWS.d/next/Library/2024-07-14-23-19-20.gh-issue-119257.9OEzcN.rst new file mode 100644 index 00000000000000..8f3f863d93e021 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-14-23-19-20.gh-issue-119257.9OEzcN.rst @@ -0,0 +1,2 @@ +Show tab completions menu below the current line, which results in less +janky behaviour, and fixes a cursor movement bug. Patch by Daniel Hollas