summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2024-09-06 11:15:00 (GMT)
committerGitHub <noreply@github.com>2024-09-06 11:15:00 (GMT)
commit8311b11800509c975023e062e2c336f417c5e4c0 (patch)
tree043fe248bcbf084b30276f8b42754cb479e1e3ed
parentd683f49a7b0635a26150cfbb398a3d93b227a74e (diff)
downloadcpython-8311b11800509c975023e062e2c336f417c5e4c0.zip
cpython-8311b11800509c975023e062e2c336f417c5e4c0.tar.gz
cpython-8311b11800509c975023e062e2c336f417c5e4c0.tar.bz2
gh-119034, REPL: Change page up/down keys to search in history (#123607)
Change <page up> and <page down> keys of the Python REPL to history search forward/backward. Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>
-rw-r--r--Lib/_pyrepl/historical_reader.py71
-rw-r--r--Lib/_pyrepl/readline.py2
-rw-r--r--Lib/_pyrepl/simple_interact.py3
-rw-r--r--Lib/test/test_pyrepl/test_pyrepl.py39
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2024-09-02-17-32-15.gh-issue-119034.HYh5Vj.rst2
5 files changed, 113 insertions, 4 deletions
diff --git a/Lib/_pyrepl/historical_reader.py b/Lib/_pyrepl/historical_reader.py
index 7f4d067..f6e14bd 100644
--- a/Lib/_pyrepl/historical_reader.py
+++ b/Lib/_pyrepl/historical_reader.py
@@ -71,6 +71,18 @@ class previous_history(commands.Command):
r.select_item(r.historyi - 1)
+class history_search_backward(commands.Command):
+ def do(self) -> None:
+ r = self.reader
+ r.search_next(forwards=False)
+
+
+class history_search_forward(commands.Command):
+ def do(self) -> None:
+ r = self.reader
+ r.search_next(forwards=True)
+
+
class restore_history(commands.Command):
def do(self) -> None:
r = self.reader
@@ -234,6 +246,8 @@ class HistoricalReader(Reader):
isearch_forwards,
isearch_backwards,
operate_and_get_next,
+ history_search_backward,
+ history_search_forward,
]:
self.commands[c.__name__] = c
self.commands[c.__name__.replace("_", "-")] = c
@@ -251,8 +265,8 @@ class HistoricalReader(Reader):
(r"\C-s", "forward-history-isearch"),
(r"\M-r", "restore-history"),
(r"\M-.", "yank-arg"),
- (r"\<page down>", "last-history"),
- (r"\<page up>", "first-history"),
+ (r"\<page down>", "history-search-forward"),
+ (r"\<page up>", "history-search-backward"),
)
def select_item(self, i: int) -> None:
@@ -305,6 +319,59 @@ class HistoricalReader(Reader):
else:
return super().get_prompt(lineno, cursor_on_line)
+ def search_next(self, *, forwards: bool) -> None:
+ """Search history for the current line contents up to the cursor.
+
+ Selects the first item found. If nothing is under the cursor, any next
+ item in history is selected.
+ """
+ pos = self.pos
+ s = self.get_unicode()
+ history_index = self.historyi
+
+ # In multiline contexts, we're only interested in the current line.
+ nl_index = s.rfind('\n', 0, pos)
+ prefix = s[nl_index + 1:pos]
+ pos = len(prefix)
+
+ match_prefix = len(prefix)
+ len_item = 0
+ if history_index < len(self.history):
+ len_item = len(self.get_item(history_index))
+ if len_item and pos == len_item:
+ match_prefix = False
+ elif not pos:
+ match_prefix = False
+
+ while 1:
+ if forwards:
+ out_of_bounds = history_index >= len(self.history) - 1
+ else:
+ out_of_bounds = history_index == 0
+ if out_of_bounds:
+ if forwards and not match_prefix:
+ self.pos = 0
+ self.buffer = []
+ self.dirty = True
+ else:
+ self.error("not found")
+ return
+
+ history_index += 1 if forwards else -1
+ s = self.get_item(history_index)
+
+ if not match_prefix:
+ self.select_item(history_index)
+ return
+
+ len_acc = 0
+ for i, line in enumerate(s.splitlines(keepends=True)):
+ if line.startswith(prefix):
+ self.select_item(history_index)
+ self.pos = pos + len_acc
+ return
+ len_acc += len(line)
+
def isearch_next(self) -> None:
st = self.isearch_term
p = self.pos
diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py
index a6ef138..4929dd3 100644
--- a/Lib/_pyrepl/readline.py
+++ b/Lib/_pyrepl/readline.py
@@ -438,7 +438,7 @@ class _ReadlineWrapper:
else:
line = self._histline(line)
if buffer:
- line = "".join(buffer).replace("\r", "") + line
+ line = self._histline("".join(buffer).replace("\r", "") + line)
del buffer[:]
if line:
history.append(line)
diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py
index 91aef5e..3c79cf6 100644
--- a/Lib/_pyrepl/simple_interact.py
+++ b/Lib/_pyrepl/simple_interact.py
@@ -163,7 +163,8 @@ def run_multiline_interactive_console(
r.isearch_direction = ''
r.console.forgetinput()
r.pop_input_trans()
- r.dirty = True
+ r.pos = len(r.get_unicode())
+ r.dirty = True
r.refresh()
r.in_bracketed_paste = False
console.write("\nKeyboardInterrupt\n")
diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py
index d9d83c4..84030e0 100644
--- a/Lib/test/test_pyrepl/test_pyrepl.py
+++ b/Lib/test/test_pyrepl/test_pyrepl.py
@@ -676,6 +676,45 @@ class TestPyReplOutput(TestCase):
self.assertEqual(output, "c\x1d")
self.assertEqual(clean_screen(reader.screen), "c")
+ def test_history_search_backward(self):
+ # Test <page up> history search backward with "imp" input
+ events = itertools.chain(
+ code_to_events("import os\n"),
+ code_to_events("imp"),
+ [
+ Event(evt='key', data='page up', raw=bytearray(b'\x1b[5~')),
+ Event(evt="key", data="\n", raw=bytearray(b"\n")),
+ ],
+ )
+
+ # fill the history
+ reader = self.prepare_reader(events)
+ multiline_input(reader)
+
+ # search for "imp" in history
+ output = multiline_input(reader)
+ self.assertEqual(output, "import os")
+ self.assertEqual(clean_screen(reader.screen), "import os")
+
+ def test_history_search_backward_empty(self):
+ # Test <page up> history search backward with an empty input
+ events = itertools.chain(
+ code_to_events("import os\n"),
+ [
+ Event(evt='key', data='page up', raw=bytearray(b'\x1b[5~')),
+ Event(evt="key", data="\n", raw=bytearray(b"\n")),
+ ],
+ )
+
+ # fill the history
+ reader = self.prepare_reader(events)
+ multiline_input(reader)
+
+ # search backward in history
+ output = multiline_input(reader)
+ self.assertEqual(output, "import os")
+ self.assertEqual(clean_screen(reader.screen), "import os")
+
class TestPyReplCompleter(TestCase):
def prepare_reader(self, events, namespace):
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-09-02-17-32-15.gh-issue-119034.HYh5Vj.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-02-17-32-15.gh-issue-119034.HYh5Vj.rst
new file mode 100644
index 0000000..f528691
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-09-02-17-32-15.gh-issue-119034.HYh5Vj.rst
@@ -0,0 +1,2 @@
+Change ``<page up>`` and ``<page down>`` keys of the Python REPL to history
+search forward/backward. Patch by Victor Stinner.