Skip to content

Commit 7252828

Browse files
committed
pythongh-127495: append to history file after every successful statement in new repl
1 parent f5f1ac8 commit 7252828

File tree

4 files changed

+44
-1
lines changed

4 files changed

+44
-1
lines changed

Lib/_pyrepl/readline.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
# "set_pre_input_hook",
9090
"set_startup_hook",
9191
"write_history_file",
92+
"append_history_file",
9293
# ---- multiline extensions ----
9394
"multiline_input",
9495
]
@@ -446,6 +447,7 @@ def read_history_file(self, filename: str = gethistoryfile()) -> None:
446447
del buffer[:]
447448
if line:
448449
history.append(line)
450+
self.set_history_length(self.get_current_history_length())
449451

450452
def write_history_file(self, filename: str = gethistoryfile()) -> None:
451453
maxlength = self.saved_history_length
@@ -457,6 +459,19 @@ def write_history_file(self, filename: str = gethistoryfile()) -> None:
457459
entry = entry.replace("\n", "\r\n") # multiline history support
458460
f.write(entry + "\n")
459461

462+
def append_history_file(self, filename: str = gethistoryfile()) -> None:
463+
reader = self.get_reader()
464+
saved_length = self.get_history_length()
465+
length = self.get_current_history_length() - saved_length
466+
history = reader.get_trimmed_history(length)
467+
f = open(os.path.expanduser(filename), "a",
468+
encoding="utf-8", newline="\n")
469+
with f:
470+
for entry in history:
471+
entry = entry.replace("\n", "\r\n") # multiline history support
472+
f.write(entry + "\n")
473+
self.set_history_length(saved_length + length)
474+
460475
def clear_history(self) -> None:
461476
del self.get_reader().history[:]
462477

@@ -526,6 +541,7 @@ def insert_text(self, text: str) -> None:
526541
get_current_history_length = _wrapper.get_current_history_length
527542
read_history_file = _wrapper.read_history_file
528543
write_history_file = _wrapper.write_history_file
544+
append_history_file = _wrapper.append_history_file
529545
clear_history = _wrapper.clear_history
530546
get_history_item = _wrapper.get_history_item
531547
remove_history_item = _wrapper.remove_history_item

Lib/_pyrepl/simple_interact.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import sys
3232
import code
3333

34-
from .readline import _get_reader, multiline_input
34+
from .readline import _get_reader, multiline_input, append_history_file
3535

3636

3737
_error: tuple[type[Exception], ...] | type[Exception]
@@ -143,6 +143,10 @@ def maybe_run_command(statement: str) -> bool:
143143

144144
input_name = f"<python-input-{input_n}>"
145145
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
146+
try:
147+
append_history_file()
148+
except (FileNotFoundError, PermissionError):
149+
pass
146150
assert not more
147151
input_n += 1
148152
except KeyboardInterrupt:

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,26 @@ def test_readline_history_file(self):
13411341
self.assertEqual(exit_code, 0)
13421342
self.assertNotIn("\\040", pathlib.Path(hfile.name).read_text())
13431343

1344+
def test_history_survive_crash(self):
1345+
env = os.environ.copy()
1346+
commands = "1\nexit()\n"
1347+
output, exit_code = self.run_repl(commands, env=env)
1348+
if "can't use pyrepl" in output:
1349+
self.skipTest("pyrepl not available")
1350+
1351+
with tempfile.NamedTemporaryFile() as hfile:
1352+
env["PYTHON_HISTORY"] = hfile.name
1353+
commands = "spam\nimport time\ntime.sleep(1000)\n"
1354+
try:
1355+
self.run_repl(commands, env=env)
1356+
except AssertionError:
1357+
pass
1358+
1359+
history = pathlib.Path(hfile.name).read_text()
1360+
self.assertIn("spam", history)
1361+
self.assertIn("time", history)
1362+
self.assertNotIn("sleep", history)
1363+
13441364
def test_keyboard_interrupt_after_isearch(self):
13451365
output, exit_code = self.run_repl(["\x12", "\x03", "exit"])
13461366
self.assertEqual(exit_code, 0)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Save (append new entry) the repl history to file every time new statement
2+
was succesful. This should preserve command-line history after interpreter
3+
crash. Patch by Sergey B Kirpichev.

0 commit comments

Comments
 (0)