Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions Lib/_pyrepl/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,14 +411,17 @@ class delete(EditCommand):
def do(self) -> None:
r = self.reader
b = r.buffer
if (
r.pos == 0
and len(b) == 0 # this is something of a hack
and self.event[-1] == "\004"
):
r.update_screen()
r.console.finish()
raise EOFError
if self.event[-1] == "\004":
if b and b[-1].endswith("\n"):
self.finish = True
elif (
r.pos == 0
and len(b) == 0 # this is something of a hack
):
r.update_screen()
r.console.finish()
raise EOFError

for i in range(r.get_arg()):
if r.pos != len(b):
del b[r.pos]
Expand Down
84 changes: 84 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1404,3 +1404,87 @@ def test_showrefcount(self):
output, _ = self.run_repl("1\n1+2\nexit()\n", cmdline_args=['-Xshowrefcount'], env=env)
matches = re.findall(r'\[-?\d+ refs, \d+ blocks\]', output)
self.assertEqual(len(matches), 3)


class TestPyReplCtrlD(TestCase):
"""Test Ctrl+D behavior in _pyrepl to match old pre-3.13 REPL behavior.

Ctrl+D should:
- Exit on empty buffer (raises EOFError)
- Delete character when cursor is in middle of line
- Perform no operation when cursor is at end of line without newline
- Exit multiline mode when cursor is at end with trailing newline
- Run code up to that point when pressed on blank line with preceding lines
"""
def prepare_reader(self, events):
console = FakeConsole(events)
config = ReadlineConfig(readline_completer=None)
reader = ReadlineAlikeReader(console=console, config=config)
return reader

def test_ctrl_d_empty_line(self):
"""Test that pressing Ctrl+D on empty line exits the program"""
events = [
Event(evt="key", data="\x04", raw=bytearray(b"\x04")), # Ctrl+D
]
reader = self.prepare_reader(events)
with self.assertRaises(EOFError):
multiline_input(reader)

def test_ctrl_d_multiline_with_new_line(self):
"""Test that pressing Ctrl+D in multiline mode with trailing newline exits multiline mode"""
events = itertools.chain(
code_to_events("def f():\n pass\n"), # Enter multiline mode with trailing newline
[
Event(evt="key", data="\x04", raw=bytearray(b"\x04")), # Ctrl+D
],
)
reader, _ = handle_all_events(events)
self.assertTrue(reader.finished)
self.assertEqual("def f():\n pass\n", "".join(reader.buffer))

def test_ctrl_d_multiline_middle_of_line(self):
"""Test that pressing Ctrl+D in multiline mode with cursor in middle deletes character"""
events = itertools.chain(
code_to_events("def f():\n hello world"), # Enter multiline mode
[
Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))
] * 5, # move cursor to 'w' in "world"
[
Event(evt="key", data="\x04", raw=bytearray(b"\x04"))
], # Ctrl+D should delete 'w'
)
reader, _ = handle_all_events(events)
self.assertFalse(reader.finished)
self.assertEqual("def f():\n hello orld", "".join(reader.buffer))

def test_ctrl_d_multiline_end_of_line_no_newline(self):
"""Test that pressing Ctrl+D at end of line without newline performs no operation"""
events = itertools.chain(
code_to_events("def f():\n hello"), # Enter multiline mode, no trailing newline
[
Event(evt="key", data="\x04", raw=bytearray(b"\x04"))
], # Ctrl+D should be no-op
)
reader, _ = handle_all_events(events)
self.assertFalse(reader.finished)
self.assertEqual("def f():\n hello", "".join(reader.buffer))

def test_ctrl_d_single_line_middle_of_line(self):
"""Test that pressing Ctrl+D in single line mode deletes current character"""
events = itertools.chain(
code_to_events("hello"),
[Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], # move left
[Event(evt="key", data="\x04", raw=bytearray(b"\x04"))], # Ctrl+D
)
reader, _ = handle_all_events(events)
self.assertEqual("hell", "".join(reader.buffer))

def test_ctrl_d_single_line_end_no_newline(self):
"""Test that pressing Ctrl+D at end of single line without newline does nothing"""
events = itertools.chain(
code_to_events("hello"), # cursor at end of line
[Event(evt="key", data="\x04", raw=bytearray(b"\x04"))], # Ctrl+D
)
reader, _ = handle_all_events(events)
self.assertEqual("hello", "".join(reader.buffer))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed Ctrl+D (^D) behavior in _pyrepl module to match old pre-3.13 REPL behavior.
Loading