From 4a51771b2762abfc8ebe765fae7606c62a0a3264 Mon Sep 17 00:00:00 2001 From: deepwzh Date: Sun, 11 May 2025 09:43:02 +0000 Subject: [PATCH 1/4] gh-133400: Fixed Ctrl+D (^D) behavior in :mod:`_pyrepl` module --- Lib/_pyrepl/commands.py | 3 ++ Lib/test/test_pyrepl/test_pyrepl.py | 39 +++++++++++++++++++ ...-05-11-09-40-19.gh-issue-133400.zkWla8.rst | 3 ++ 3 files changed, 45 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 50c824995d85b8..58a2fcb71439aa 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -428,6 +428,9 @@ def do(self) -> None: r.update_screen() r.console.finish() raise EOFError + elif "\n" in b and self.event[-1] == "\004": + self.finish = True + for i in range(r.get_arg()): if r.pos != len(b): del b[r.pos] diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 657a971f8769df..689e0e79fb39a4 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1768,3 +1768,42 @@ def test_detect_pip_usage_in_repl(self): " outside of the Python REPL" ) self.assertIn(hint, output) +class TestPyReplCtrlD(TestCase): + 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_buffer(self): + """Test that pressing Ctrl+D on empty buffer 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_mode(self): + """Test that pressing Ctrl+D in multiline mode exits multiline mode""" + events = itertools.chain( + code_to_events("def f():\n"), # Enter multiline mode + [ + Event(evt="key", data="\x04", raw=bytearray(b"\x04")), # Ctrl+D + ], + ) + reader = self.prepare_reader(events) + output = multiline_input(reader) + self.assertEqual(output, "def f():\n ") # Should return current input + + def test_ctrl_d_single_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 + code_to_events("\n"), + ) + reader = self.prepare_reader(events) + output = multiline_input(reader) + self.assertEqual(output, "hell") # Should delete the last character diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst new file mode 100644 index 00000000000000..449121b00cc301 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst @@ -0,0 +1,3 @@ +Fixed Ctrl+D (^D) behavior in _pyrepl module: Now properly exits multiline +mode when pressed in multiline section. Added test cases to verify the +behavior in both multiline and single line modes. From cfd5a68b83d12e61a1f3d277b4fd7681dddd09a9 Mon Sep 17 00:00:00 2001 From: DeepWzh Date: Sun, 20 Jul 2025 15:17:04 +0800 Subject: [PATCH 2/4] Fix the logic of Ctrl+D (^ D) behavior in the mod: ` _pyrepl ` module to ensure correct termination of input under specific conditions. Co-authored-by: adam j hartz --- Lib/_pyrepl/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 58a2fcb71439aa..3f00495483a951 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -428,7 +428,7 @@ def do(self) -> None: r.update_screen() r.console.finish() raise EOFError - elif "\n" in b and self.event[-1] == "\004": + elif b and b[-1].endswith('\n') and self.event[-1] == "\004": self.finish = True for i in range(r.get_arg()): From d0dc73261b024d9a84bc46173568bde071ee788c Mon Sep 17 00:00:00 2001 From: deepwzh Date: Sun, 20 Jul 2025 09:03:21 +0000 Subject: [PATCH 3/4] Add more tests for Ctrl+D behavior of pyrep in multiline mode --- Lib/test/test_pyrepl/test_pyrepl.py | 48 ++++++++++++++++--- ...-05-11-09-40-19.gh-issue-133400.zkWla8.rst | 4 +- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 689e0e79fb39a4..01c41355ab1896 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1768,7 +1768,16 @@ def test_detect_pip_usage_in_repl(self): " outside of the Python REPL" ) self.assertIn(hint, output) + class TestPyReplCtrlD(TestCase): + """Test Ctrl+D behavior in _pyrepl to match old pre-3.13 REPL behavior. + + Ctrl+D should: + - Exit on empty buffer + - 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 + """ def prepare_reader(self, events): console = FakeConsole(events) config = ReadlineConfig(readline_completer=None) @@ -1784,17 +1793,44 @@ def test_ctrl_d_empty_buffer(self): with self.assertRaises(EOFError): multiline_input(reader) - def test_ctrl_d_multiline_mode(self): - """Test that pressing Ctrl+D in multiline mode exits multiline mode""" + 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"), # Enter multiline mode + 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 = self.prepare_reader(events) - output = multiline_input(reader) - self.assertEqual(output, "def f():\n ") # Should return current input + 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(self): """Test that pressing Ctrl+D in single line mode deletes current character""" diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst index 449121b00cc301..2498d6ebaa543e 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-11-09-40-19.gh-issue-133400.zkWla8.rst @@ -1,3 +1 @@ -Fixed Ctrl+D (^D) behavior in _pyrepl module: Now properly exits multiline -mode when pressed in multiline section. Added test cases to verify the -behavior in both multiline and single line modes. +Fixed Ctrl+D (^D) behavior in _pyrepl module to match old pre-3.13 REPL behavior. From bb00c6c4956fc484e7311b8aff99ab6813a5203d Mon Sep 17 00:00:00 2001 From: deepwzh Date: Sun, 20 Jul 2025 09:50:56 +0000 Subject: [PATCH 4/4] optimize Ctrl+D (^ D) behavior logic and add more test --- Lib/_pyrepl/commands.py | 20 ++++++++++---------- Lib/test/test_pyrepl/test_pyrepl.py | 24 ++++++++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 3f00495483a951..10127e58897a58 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -420,16 +420,16 @@ 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 - elif b and b[-1].endswith('\n') and self.event[-1] == "\004": - self.finish = True + 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): diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 01c41355ab1896..cfc571c7408dfe 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1773,10 +1773,11 @@ class TestPyReplCtrlD(TestCase): """Test Ctrl+D behavior in _pyrepl to match old pre-3.13 REPL behavior. Ctrl+D should: - - Exit on empty buffer + - 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) @@ -1784,8 +1785,8 @@ def prepare_reader(self, events): reader = ReadlineAlikeReader(console=console, config=config) return reader - def test_ctrl_d_empty_buffer(self): - """Test that pressing Ctrl+D on empty buffer exits the program""" + 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 ] @@ -1832,14 +1833,21 @@ def test_ctrl_d_multiline_end_of_line_no_newline(self): self.assertFalse(reader.finished) self.assertEqual("def f():\n hello", "".join(reader.buffer)) - def test_ctrl_d_single_line(self): + 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 - code_to_events("\n"), ) - reader = self.prepare_reader(events) - output = multiline_input(reader) - self.assertEqual(output, "hell") # Should delete the last character + 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)) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy