From a01ce717fde4b6c9e1ca244cb4c5c8d7d194d964 Mon Sep 17 00:00:00 2001 From: Etty Date: Wed, 17 Jul 2024 12:57:12 +0100 Subject: [PATCH] Add REPL history navigation with partial text This changes add supports to navigate the history with arrow up based on partial text in the buffer --- Lib/_pyrepl/historical_reader.py | 13 +++++++++- Lib/test/test_pyrepl/test_pyrepl.py | 40 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/Lib/_pyrepl/historical_reader.py b/Lib/_pyrepl/historical_reader.py index f6e14bdffc33521..0ba3504c9529515 100644 --- a/Lib/_pyrepl/historical_reader.py +++ b/Lib/_pyrepl/historical_reader.py @@ -272,10 +272,21 @@ def collect_keymap(self) -> tuple[tuple[KeySpec, CommandName], ...]: def select_item(self, i: int) -> None: self.transient_history[self.historyi] = self.get_unicode() buf = self.transient_history.get(i) + self.historyi = i + + if self.buffer: + filtered_history = [ + (index, item) + for index, item in enumerate(self.history) + if item.startswith(self.get_unicode()) + and item.strip() not in self.transient_history.values() + ] + if filtered_history: + self.historyi, buf = filtered_history[min(i, len(filtered_history) - 1)] + if buf is None: buf = self.history[i].rstrip() self.buffer = list(buf) - self.historyi = i self.pos = len(self.buffer) self.dirty = True self.last_refresh_cache.invalidated = True diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index e816de3720670f3..8fe04f23600644e 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -601,6 +601,46 @@ def test_history_navigation_with_up_arrow(self): self.assertEqual(output, "1+1") self.assertEqual(clean_screen(reader.screen), "1+1") + def test_history_navigation_with_up_arrow_and_partial_text(self): + events = itertools.chain( + code_to_events("spam = 1\nham = 2\neggs = 3\nsp"), + [ + Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), + Event(evt="key", data="\n", raw=bytearray(b"\n")), + ], + ) + + reader = self.prepare_reader(events) + + output = multiline_input(reader) + self.assertEqual(output, "spam = 1") + + def test_history_navigation_with_up_arrow_and_partial_text_with_similar_entries(self): + events = itertools.chain( + code_to_events("a=111\na=11\na=1\na="), + [ + Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), + Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), + Event(evt="key", data="up", raw=bytearray(b"\x1bOA")), + Event(evt="key", data="\n", raw=bytearray(b"\n")), + ], + ) + + reader = self.prepare_reader(events) + print() + output = multiline_input(reader) + self.assertEqual(output, "a=111") + self.assertEqual(clean_screen(reader.screen), "a=111") + output = multiline_input(reader) + self.assertEqual(output, "a=11") + self.assertEqual(clean_screen(reader.screen), "a=11") + output = multiline_input(reader) + self.assertEqual(output, "a=1") + self.assertEqual(clean_screen(reader.screen), "a=1") + output = multiline_input(reader) + self.assertEqual(output, "a=111") + self.assertEqual(clean_screen(reader.screen), "a=111") + def test_history_with_multiline_entries(self): code = "def foo():\nx = 1\ny = 2\nz = 3\n\ndef bar():\nreturn 42\n\n" events = list(itertools.chain(