diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 456cba0769c952..3f51def3b9b44c 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -245,7 +245,7 @@ def do(self) -> None: x, y = r.pos2xy() new_y = y - 1 - if new_y < 0: + if r.bol() == 0: if r.historyi > 0: r.select_item(r.historyi - 1) return diff --git a/Lib/test/test_pyrepl.py b/Lib/test/test_pyrepl.py index c8990b699b214c..ee6ba658f11e39 100644 --- a/Lib/test/test_pyrepl.py +++ b/Lib/test/test_pyrepl.py @@ -607,6 +607,30 @@ def test_global_namespace_completion(self): output = multiline_input(reader, namespace) self.assertEqual(output, "python") + def test_updown_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.""" + code = "import os\nos.\t\t" + namespace = {"os": os} + + events = itertools.chain( + code_to_events(code), + [ + Event(evt='key', data='up', raw=bytearray(b'\x1bOA')), + Event(evt="key", data="down", raw=bytearray(b"\x1bOB")), + ], + code_to_events("\n") + ) + reader = self.prepare_reader(events, namespace=namespace) + output = multiline_input(reader, namespace) + # This is the first line, nothing to see here + self.assertEqual(output, "import os") + # This is the second line. We pressed up and down arrows + # so we should end up where we were when we initiated tab completion. + output = multiline_input(reader, namespace) + self.assertEqual(output, "os.") + @patch("_pyrepl.curses.tigetstr", lambda x: b"") class TestUnivEventQueue(TestCase): @@ -1001,6 +1025,5 @@ def test_up_arrow_after_ctrl_r(self): reader, _ = handle_all_events(events) self.assert_screen_equals(reader, "") - if __name__ == '__main__': unittest.main()