mirror of
https://github.com/python/cpython.git
synced 2025-09-24 09:23:02 +00:00
gh-127495: Append to history file after every statement in PyREPL (GH-132294)
This commit is contained in:
parent
614d79231d
commit
276252565c
4 changed files with 47 additions and 1 deletions
|
@ -90,6 +90,7 @@ __all__ = [
|
||||||
# "set_pre_input_hook",
|
# "set_pre_input_hook",
|
||||||
"set_startup_hook",
|
"set_startup_hook",
|
||||||
"write_history_file",
|
"write_history_file",
|
||||||
|
"append_history_file",
|
||||||
# ---- multiline extensions ----
|
# ---- multiline extensions ----
|
||||||
"multiline_input",
|
"multiline_input",
|
||||||
]
|
]
|
||||||
|
@ -453,6 +454,7 @@ class _ReadlineWrapper:
|
||||||
del buffer[:]
|
del buffer[:]
|
||||||
if line:
|
if line:
|
||||||
history.append(line)
|
history.append(line)
|
||||||
|
self.set_history_length(self.get_current_history_length())
|
||||||
|
|
||||||
def write_history_file(self, filename: str = gethistoryfile()) -> None:
|
def write_history_file(self, filename: str = gethistoryfile()) -> None:
|
||||||
maxlength = self.saved_history_length
|
maxlength = self.saved_history_length
|
||||||
|
@ -464,6 +466,19 @@ class _ReadlineWrapper:
|
||||||
entry = entry.replace("\n", "\r\n") # multiline history support
|
entry = entry.replace("\n", "\r\n") # multiline history support
|
||||||
f.write(entry + "\n")
|
f.write(entry + "\n")
|
||||||
|
|
||||||
|
def append_history_file(self, filename: str = gethistoryfile()) -> None:
|
||||||
|
reader = self.get_reader()
|
||||||
|
saved_length = self.get_history_length()
|
||||||
|
length = self.get_current_history_length() - saved_length
|
||||||
|
history = reader.get_trimmed_history(length)
|
||||||
|
f = open(os.path.expanduser(filename), "a",
|
||||||
|
encoding="utf-8", newline="\n")
|
||||||
|
with f:
|
||||||
|
for entry in history:
|
||||||
|
entry = entry.replace("\n", "\r\n") # multiline history support
|
||||||
|
f.write(entry + "\n")
|
||||||
|
self.set_history_length(saved_length + length)
|
||||||
|
|
||||||
def clear_history(self) -> None:
|
def clear_history(self) -> None:
|
||||||
del self.get_reader().history[:]
|
del self.get_reader().history[:]
|
||||||
|
|
||||||
|
@ -533,6 +548,7 @@ set_history_length = _wrapper.set_history_length
|
||||||
get_current_history_length = _wrapper.get_current_history_length
|
get_current_history_length = _wrapper.get_current_history_length
|
||||||
read_history_file = _wrapper.read_history_file
|
read_history_file = _wrapper.read_history_file
|
||||||
write_history_file = _wrapper.write_history_file
|
write_history_file = _wrapper.write_history_file
|
||||||
|
append_history_file = _wrapper.append_history_file
|
||||||
clear_history = _wrapper.clear_history
|
clear_history = _wrapper.clear_history
|
||||||
get_history_item = _wrapper.get_history_item
|
get_history_item = _wrapper.get_history_item
|
||||||
remove_history_item = _wrapper.remove_history_item
|
remove_history_item = _wrapper.remove_history_item
|
||||||
|
|
|
@ -30,8 +30,9 @@ import functools
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import code
|
import code
|
||||||
|
import warnings
|
||||||
|
|
||||||
from .readline import _get_reader, multiline_input
|
from .readline import _get_reader, multiline_input, append_history_file
|
||||||
|
|
||||||
|
|
||||||
_error: tuple[type[Exception], ...] | type[Exception]
|
_error: tuple[type[Exception], ...] | type[Exception]
|
||||||
|
@ -144,6 +145,10 @@ def run_multiline_interactive_console(
|
||||||
input_name = f"<python-input-{input_n}>"
|
input_name = f"<python-input-{input_n}>"
|
||||||
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
|
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
|
||||||
assert not more
|
assert not more
|
||||||
|
try:
|
||||||
|
append_history_file()
|
||||||
|
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||||
|
warnings.warn(f"failed to open the history file for writing: {e}")
|
||||||
input_n += 1
|
input_n += 1
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
r = _get_reader()
|
r = _get_reader()
|
||||||
|
|
|
@ -112,6 +112,7 @@ class ReplTestCase(TestCase):
|
||||||
else:
|
else:
|
||||||
os.close(master_fd)
|
os.close(master_fd)
|
||||||
process.kill()
|
process.kill()
|
||||||
|
process.wait(timeout=SHORT_TIMEOUT)
|
||||||
self.fail(f"Timeout while waiting for output, got: {''.join(output)}")
|
self.fail(f"Timeout while waiting for output, got: {''.join(output)}")
|
||||||
|
|
||||||
os.close(master_fd)
|
os.close(master_fd)
|
||||||
|
@ -1564,6 +1565,27 @@ class TestMain(ReplTestCase):
|
||||||
self.assertEqual(exit_code, 0)
|
self.assertEqual(exit_code, 0)
|
||||||
self.assertNotIn("\\040", pathlib.Path(hfile.name).read_text())
|
self.assertNotIn("\\040", pathlib.Path(hfile.name).read_text())
|
||||||
|
|
||||||
|
def test_history_survive_crash(self):
|
||||||
|
env = os.environ.copy()
|
||||||
|
commands = "1\nexit()\n"
|
||||||
|
output, exit_code = self.run_repl(commands, env=env)
|
||||||
|
if "can't use pyrepl" in output:
|
||||||
|
self.skipTest("pyrepl not available")
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile() as hfile:
|
||||||
|
env["PYTHON_HISTORY"] = hfile.name
|
||||||
|
commands = "spam\nimport time\ntime.sleep(1000)\npreved\n"
|
||||||
|
try:
|
||||||
|
self.run_repl(commands, env=env)
|
||||||
|
except AssertionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
history = pathlib.Path(hfile.name).read_text()
|
||||||
|
self.assertIn("spam", history)
|
||||||
|
self.assertIn("time", history)
|
||||||
|
self.assertNotIn("sleep", history)
|
||||||
|
self.assertNotIn("preved", history)
|
||||||
|
|
||||||
def test_keyboard_interrupt_after_isearch(self):
|
def test_keyboard_interrupt_after_isearch(self):
|
||||||
output, exit_code = self.run_repl(["\x12", "\x03", "exit"])
|
output, exit_code = self.run_repl(["\x12", "\x03", "exit"])
|
||||||
self.assertEqual(exit_code, 0)
|
self.assertEqual(exit_code, 0)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
In PyREPL, append a new entry to the ``PYTHON_HISTORY`` file *after* every
|
||||||
|
statement. This should preserve command-line history after interpreter is
|
||||||
|
terminated. Patch by Sergey B Kirpichev.
|
Loading…
Add table
Add a link
Reference in a new issue