]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-118911: Trailing whitespace in a block shouldn't prevent the user from terminating...
authorAya Elsayed <ayah.ehab11@gmail.com>
Wed, 22 May 2024 05:56:35 +0000 (06:56 +0100)
committerGitHub <noreply@github.com>
Wed, 22 May 2024 05:56:35 +0000 (07:56 +0200)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
Lib/_pyrepl/historical_reader.py
Lib/_pyrepl/readline.py
Lib/test/test_pyrepl/test_pyrepl.py
Lib/test/test_pyrepl/test_reader.py
Misc/NEWS.d/next/Library/2024-05-21-20-13-23.gh-issue-118911.iG8nMq.rst [new file with mode: 0644]

index eef7d901b083ef090a379466208e8d5c0bc60a0a..121de33da5052fec69f57c69deda6f29edba45f7 100644 (file)
@@ -259,7 +259,7 @@ class HistoricalReader(Reader):
         self.transient_history[self.historyi] = self.get_unicode()
         buf = self.transient_history.get(i)
         if buf is None:
-            buf = self.history[i]
+            buf = self.history[i].rstrip()
         self.buffer = list(buf)
         self.historyi = i
         self.pos = len(self.buffer)
index 796f1ef86360deb2dde268bd56a0498c54a21820..ffa14a9ce31a8f3510822db57def47d473edf921 100644 (file)
@@ -244,14 +244,27 @@ class maybe_accept(commands.Command):
         r: ReadlineAlikeReader
         r = self.reader  # type: ignore[assignment]
         r.dirty = True  # this is needed to hide the completion menu, if visible
-        #
+
         # if there are already several lines and the cursor
         # is not on the last one, always insert a new \n.
         text = r.get_unicode()
+
         if "\n" in r.buffer[r.pos :] or (
             r.more_lines is not None and r.more_lines(text)
         ):
-            #
+            def _newline_before_pos():
+                before_idx = r.pos - 1
+                while before_idx > 0 and text[before_idx].isspace():
+                    before_idx -= 1
+                return text[before_idx : r.pos].count("\n") > 0
+
+            # if there's already a new line before the cursor then
+            # even if the cursor is followed by whitespace, we assume
+            # the user is trying to terminate the block
+            if _newline_before_pos() and text[r.pos:].isspace():
+                self.finish = True
+                return
+
             # auto-indent the next line like the previous line
             prevlinestart, indent = _get_previous_line_indent(r.buffer, r.pos)
             r.insert("\n")
index 7b5217e4b01fd02cd250341fc1af508357805743..bdcabf9be05b9eda4839ff888f0aa0e8e250e74d 100644 (file)
@@ -405,12 +405,21 @@ class TestPyReplOutput(TestCase):
             [
                 Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
                 Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
-                Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
-                Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
-                Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
+                Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
+                Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
+                Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
+                Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
                 Event(evt="key", data="g", raw=bytearray(b"g")),
                 Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
-                Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
+                Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
+                Event(evt="key", data="delete", raw=bytearray(b"\x7F")),
+                Event(evt="key", data="right", raw=bytearray(b"g")),
+                Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
+                Event(evt="key", data="p", raw=bytearray(b"p")),
+                Event(evt="key", data="a", raw=bytearray(b"a")),
+                Event(evt="key", data="s", raw=bytearray(b"s")),
+                Event(evt="key", data="s", raw=bytearray(b"s")),
+                Event(evt="key", data="\n", raw=bytearray(b"\n")),
                 Event(evt="key", data="\n", raw=bytearray(b"\n")),
             ],
         )
@@ -419,7 +428,7 @@ class TestPyReplOutput(TestCase):
         output = multiline_input(reader)
         self.assertEqual(output, "def f():\n    ...\n    ")
         output = multiline_input(reader)
-        self.assertEqual(output, "def g():\n    ...\n    ")
+        self.assertEqual(output, "def g():\n    pass\n    ")
 
     def test_history_navigation_with_up_arrow(self):
         events = itertools.chain(
index dc7d8a5ba97cda95924f74083a831ed32114e0c4..7bf7a36d8d7bb9ccdfa3fd5d3cd95407b0c130ab 100644 (file)
@@ -1,7 +1,8 @@
 import itertools
+import functools
 from unittest import TestCase
 
-from .support import handle_all_events, handle_events_narrow_console, code_to_events
+from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
 from _pyrepl.console import Event
 
 
@@ -133,3 +134,45 @@ class TestReader(TestCase):
 
         reader, _ = handle_all_events(events)
         self.assert_screen_equals(reader, "")
+
+    def test_newline_within_block_trailing_whitespace(self):
+        # fmt: off
+        code = (
+            "def foo():\n"
+                 "a = 1\n"
+        )
+        # fmt: on
+
+        events = itertools.chain(
+            code_to_events(code),
+            [
+                # go to the end of the first line
+                Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
+                Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
+                Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
+                # new lines in-block shouldn't terminate the block
+                Event(evt="key", data="\n", raw=bytearray(b"\n")),
+                Event(evt="key", data="\n", raw=bytearray(b"\n")),
+                # end of line 2
+                Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
+                Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
+                # a double new line in-block should terminate the block
+                # even if its followed by whitespace
+                Event(evt="key", data="\n", raw=bytearray(b"\n")),
+                Event(evt="key", data="\n", raw=bytearray(b"\n")),
+            ],
+        )
+
+        no_paste_reader = functools.partial(prepare_reader, paste_mode=False)
+        reader, _ = handle_all_events(events, prepare_reader=no_paste_reader)
+
+        expected = (
+            "def foo():\n"
+            "\n"
+            "\n"
+            "    a = 1\n"
+            "    \n"
+            "    "    # HistoricalReader will trim trailing whitespace
+        )
+        self.assert_screen_equals(reader, expected)
+        self.assertTrue(reader.finished)
diff --git a/Misc/NEWS.d/next/Library/2024-05-21-20-13-23.gh-issue-118911.iG8nMq.rst b/Misc/NEWS.d/next/Library/2024-05-21-20-13-23.gh-issue-118911.iG8nMq.rst
new file mode 100644 (file)
index 0000000..4f15c1b
--- /dev/null
@@ -0,0 +1,5 @@
+In PyREPL, updated ``maybe-accept``'s logic so that if the user hits
+:kbd:`Enter` twice, they are able to terminate the block even if there's
+trailing whitespace. Also, now when the user hits arrow up, the cursor
+is on the last functional line. This matches IPython's behavior.
+Patch by Aya Elsayed.