https://github.com/python/cpython/commit/a94ac566285662b214ca97d74481e07e51ccd7d9
commit: a94ac566285662b214ca97d74481e07e51ccd7d9
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: ambv <[email protected]>
date: 2024-05-07T16:01:49Z
summary:

gh-111201: Allow pasted code to contain multiple statements in the REPL 
(#118712)

Co-authored-by: Ɓukasz Langa <[email protected]>

files:
M Lib/_pyrepl/commands.py
M Lib/_pyrepl/reader.py
M Lib/_pyrepl/readline.py
M Lib/_pyrepl/simple_interact.py
M Lib/code.py
M Lib/test/test_pyrepl.py

diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py
index bb6bebace30ec8..456cba0769c952 100644
--- a/Lib/_pyrepl/commands.py
+++ b/Lib/_pyrepl/commands.py
@@ -460,6 +460,8 @@ def do(self) -> None:
 class paste_mode(Command):
 
     def do(self) -> None:
+        if not self.reader.paste_mode:
+            self.reader.was_paste_mode_activated = True
         self.reader.paste_mode = not self.reader.paste_mode
         self.reader.dirty = True
 
@@ -467,8 +469,9 @@ def do(self) -> None:
 class enable_bracketed_paste(Command):
     def do(self) -> None:
         self.reader.paste_mode = True
+        self.reader.was_paste_mode_activated = True
 
 class disable_bracketed_paste(Command):
     def do(self) -> None:
         self.reader.paste_mode = False
-        self.reader.insert("\n")
+        self.reader.dirty = True
diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py
index d84c164a05308d..d15a150180811d 100644
--- a/Lib/_pyrepl/reader.py
+++ b/Lib/_pyrepl/reader.py
@@ -221,6 +221,7 @@ class Reader:
     dirty: bool = False
     finished: bool = False
     paste_mode: bool = False
+    was_paste_mode_activated: bool = False
     commands: dict[str, type[Command]] = 
field(default_factory=make_default_commands)
     last_command: type[Command] | None = None
     syntax_table: dict[str, int] = 
field(default_factory=make_default_syntax_table)
diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py
index 37ba98d4c8c87a..d28a7f3779f302 100644
--- a/Lib/_pyrepl/readline.py
+++ b/Lib/_pyrepl/readline.py
@@ -298,10 +298,11 @@ def multiline_input(self, more_lines, ps1, ps2):
             reader.more_lines = more_lines
             reader.ps1 = reader.ps2 = ps1
             reader.ps3 = reader.ps4 = ps2
-            return reader.readline()
+            return reader.readline(), reader.was_paste_mode_activated
         finally:
             reader.more_lines = saved
             reader.paste_mode = False
+            reader.was_paste_mode_activated = False
 
     def parse_and_bind(self, string: str) -> None:
         pass  # XXX we don't support parsing GNU-readline-style init files
diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py
index 4bc8368169336a..31b2097a78a226 100644
--- a/Lib/_pyrepl/simple_interact.py
+++ b/Lib/_pyrepl/simple_interact.py
@@ -135,7 +135,7 @@ def more_lines(unicodetext: str) -> bool:
             ps1 = getattr(sys, "ps1", ">>> ")
             ps2 = getattr(sys, "ps2", "... ")
             try:
-                statement = multiline_input(more_lines, ps1, ps2)
+                statement, contains_pasted_code = multiline_input(more_lines, 
ps1, ps2)
             except EOFError:
                 break
 
@@ -144,7 +144,10 @@ def more_lines(unicodetext: str) -> bool:
 
             input_name = f"<python-input-{input_n}>"
             linecache._register_code(input_name, statement, "<stdin>")  # 
type: ignore[attr-defined]
-            more = console.push(_strip_final_indent(statement), 
filename=input_name)  # type: ignore[call-arg]
+            symbol = "single" if not contains_pasted_code else "exec"
+            more = console.push(_strip_final_indent(statement), 
filename=input_name, _symbol=symbol)  # type: ignore[call-arg]
+            if contains_pasted_code and more:
+                more = console.push(_strip_final_indent(statement), 
filename=input_name, _symbol="single")  # type: ignore[call-arg]
             assert not more
             input_n += 1
         except KeyboardInterrupt:
diff --git a/Lib/code.py b/Lib/code.py
index 1ee1ad62ff4506..9d124563f728c2 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -281,7 +281,7 @@ def interact(self, banner=None, exitmsg=None):
             elif exitmsg != '':
                 self.write('%s\n' % exitmsg)
 
-    def push(self, line, filename=None):
+    def push(self, line, filename=None, _symbol="single"):
         """Push a line to the interpreter.
 
         The line should not have a trailing newline; it may have
@@ -299,7 +299,7 @@ def push(self, line, filename=None):
         source = "\n".join(self.buffer)
         if filename is None:
             filename = self.filename
-        more = self.runsource(source, filename)
+        more = self.runsource(source, filename, symbol=_symbol)
         if not more:
             self.resetbuffer()
         return more
diff --git a/Lib/test/test_pyrepl.py b/Lib/test/test_pyrepl.py
index bb9e36bc69ba4d..c8990b699b214c 100644
--- a/Lib/test/test_pyrepl.py
+++ b/Lib/test/test_pyrepl.py
@@ -23,6 +23,7 @@
 from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
 from _pyrepl.simple_interact import _strip_final_indent
 from _pyrepl.unix_eventqueue import EventQueue
+from _pyrepl.simple_interact import InteractiveColoredConsole
 
 
 def more_lines(unicodetext, namespace=None):
@@ -830,7 +831,6 @@ def test_bracketed_paste(self):
             '    else:\n'
             '      pass\n'
         )
-        # fmt: on
 
         output_code = (
             'def a():\n'
@@ -841,8 +841,8 @@ def test_bracketed_paste(self):
             '\n'
             '    else:\n'
             '      pass\n'
-            '\n'
         )
+        # fmt: on
 
         paste_start = "\x1b[200~"
         paste_end = "\x1b[201~"
@@ -857,6 +857,22 @@ def test_bracketed_paste(self):
         output = multiline_input(reader)
         self.assertEqual(output, output_code)
 
+    def test_bracketed_paste_single_line(self):
+        input_code = "oneline"
+
+        paste_start = "\x1b[200~"
+        paste_end = "\x1b[201~"
+
+        events = itertools.chain(
+            code_to_events(paste_start),
+            code_to_events(input_code),
+            code_to_events(paste_end),
+            code_to_events("\n"),
+        )
+        reader = self.prepare_reader(events)
+        output = multiline_input(reader)
+        self.assertEqual(output, input_code)
+
 
 class TestReader(TestCase):
     def assert_screen_equals(self, reader, expected):
@@ -986,5 +1002,5 @@ def test_up_arrow_after_ctrl_r(self):
         self.assert_screen_equals(reader, "")
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     unittest.main()

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to