@@ -1825,3 +1825,86 @@ def test_detect_pip_usage_in_repl(self):
18251825 " outside of the Python REPL"
18261826 )
18271827 self .assertIn (hint , output )
1828+
1829+ class TestPyReplCtrlD (TestCase ):
1830+ """Test Ctrl+D behavior in _pyrepl to match old pre-3.13 REPL behavior.
1831+
1832+ Ctrl+D should:
1833+ - Exit on empty buffer (raises EOFError)
1834+ - Delete character when cursor is in middle of line
1835+ - Perform no operation when cursor is at end of line without newline
1836+ - Exit multiline mode when cursor is at end with trailing newline
1837+ - Run code up to that point when pressed on blank line with preceding lines
1838+ """
1839+ def prepare_reader (self , events ):
1840+ console = FakeConsole (events )
1841+ config = ReadlineConfig (readline_completer = None )
1842+ reader = ReadlineAlikeReader (console = console , config = config )
1843+ return reader
1844+
1845+ def test_ctrl_d_empty_line (self ):
1846+ """Test that pressing Ctrl+D on empty line exits the program"""
1847+ events = [
1848+ Event (evt = "key" , data = "\x04 " , raw = bytearray (b"\x04 " )), # Ctrl+D
1849+ ]
1850+ reader = self .prepare_reader (events )
1851+ with self .assertRaises (EOFError ):
1852+ multiline_input (reader )
1853+
1854+ def test_ctrl_d_multiline_with_new_line (self ):
1855+ """Test that pressing Ctrl+D in multiline mode with trailing newline exits multiline mode"""
1856+ events = itertools .chain (
1857+ code_to_events ("def f():\n pass\n " ), # Enter multiline mode with trailing newline
1858+ [
1859+ Event (evt = "key" , data = "\x04 " , raw = bytearray (b"\x04 " )), # Ctrl+D
1860+ ],
1861+ )
1862+ reader , _ = handle_all_events (events )
1863+ self .assertTrue (reader .finished )
1864+ self .assertEqual ("def f():\n pass\n " , "" .join (reader .buffer ))
1865+
1866+ def test_ctrl_d_multiline_middle_of_line (self ):
1867+ """Test that pressing Ctrl+D in multiline mode with cursor in middle deletes character"""
1868+ events = itertools .chain (
1869+ code_to_events ("def f():\n hello world" ), # Enter multiline mode
1870+ [
1871+ Event (evt = "key" , data = "left" , raw = bytearray (b"\x1b OD" ))
1872+ ] * 5 , # move cursor to 'w' in "world"
1873+ [
1874+ Event (evt = "key" , data = "\x04 " , raw = bytearray (b"\x04 " ))
1875+ ], # Ctrl+D should delete 'w'
1876+ )
1877+ reader , _ = handle_all_events (events )
1878+ self .assertFalse (reader .finished )
1879+ self .assertEqual ("def f():\n hello orld" , "" .join (reader .buffer ))
1880+
1881+ def test_ctrl_d_multiline_end_of_line_no_newline (self ):
1882+ """Test that pressing Ctrl+D at end of line without newline performs no operation"""
1883+ events = itertools .chain (
1884+ code_to_events ("def f():\n hello" ), # Enter multiline mode, no trailing newline
1885+ [
1886+ Event (evt = "key" , data = "\x04 " , raw = bytearray (b"\x04 " ))
1887+ ], # Ctrl+D should be no-op
1888+ )
1889+ reader , _ = handle_all_events (events )
1890+ self .assertFalse (reader .finished )
1891+ self .assertEqual ("def f():\n hello" , "" .join (reader .buffer ))
1892+
1893+ def test_ctrl_d_single_line_middle_of_line (self ):
1894+ """Test that pressing Ctrl+D in single line mode deletes current character"""
1895+ events = itertools .chain (
1896+ code_to_events ("hello" ),
1897+ [Event (evt = "key" , data = "left" , raw = bytearray (b"\x1b OD" ))], # move left
1898+ [Event (evt = "key" , data = "\x04 " , raw = bytearray (b"\x04 " ))], # Ctrl+D
1899+ )
1900+ reader , _ = handle_all_events (events )
1901+ self .assertEqual ("hell" , "" .join (reader .buffer ))
1902+
1903+ def test_ctrl_d_single_line_end_no_newline (self ):
1904+ """Test that pressing Ctrl+D at end of single line without newline does nothing"""
1905+ events = itertools .chain (
1906+ code_to_events ("hello" ), # cursor at end of line
1907+ [Event (evt = "key" , data = "\x04 " , raw = bytearray (b"\x04 " ))], # Ctrl+D
1908+ )
1909+ reader , _ = handle_all_events (events )
1910+ self .assertEqual ("hello" , "" .join (reader .buffer ))
0 commit comments