mirror of
https://github.com/python/cpython.git
synced 2025-09-18 06:30:38 +00:00
[3.13] gh-121610: pyrepl - handle extending blocks when multi-statement blocks are pasted (GH-121757) (GH-121825)
console.compile with the "single" param throws an exception when
there are multiple statements, never allowing to adding newlines
to a pasted code block (gh-121610)
This adds a few extra checks to allow extending when in an indented
block, and tests for a few examples.
(cherry picked from commit 7d111dac16
)
Co-authored-by: saucoide <32314353+saucoide@users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
a1505afd39
commit
73f77e642a
2 changed files with 123 additions and 11 deletions
|
@ -27,6 +27,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import _sitebuiltins
|
import _sitebuiltins
|
||||||
import linecache
|
import linecache
|
||||||
|
import functools
|
||||||
import sys
|
import sys
|
||||||
import code
|
import code
|
||||||
|
|
||||||
|
@ -78,6 +79,25 @@ REPL_COMMANDS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _more_lines(console: code.InteractiveConsole, unicodetext: str) -> bool:
|
||||||
|
# ooh, look at the hack:
|
||||||
|
src = _strip_final_indent(unicodetext)
|
||||||
|
try:
|
||||||
|
code = console.compile(src, "<stdin>", "single")
|
||||||
|
except (OverflowError, SyntaxError, ValueError):
|
||||||
|
lines = src.splitlines(keepends=True)
|
||||||
|
if len(lines) == 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
last_line = lines[-1]
|
||||||
|
was_indented = last_line.startswith((" ", "\t"))
|
||||||
|
not_empty = last_line.strip() != ""
|
||||||
|
incomplete = not last_line.endswith("\n")
|
||||||
|
return (was_indented or not_empty) and incomplete
|
||||||
|
else:
|
||||||
|
return code is None
|
||||||
|
|
||||||
|
|
||||||
def run_multiline_interactive_console(
|
def run_multiline_interactive_console(
|
||||||
console: code.InteractiveConsole,
|
console: code.InteractiveConsole,
|
||||||
*,
|
*,
|
||||||
|
@ -88,6 +108,7 @@ def run_multiline_interactive_console(
|
||||||
if future_flags:
|
if future_flags:
|
||||||
console.compile.compiler.flags |= future_flags
|
console.compile.compiler.flags |= future_flags
|
||||||
|
|
||||||
|
more_lines = functools.partial(_more_lines, console)
|
||||||
input_n = 0
|
input_n = 0
|
||||||
|
|
||||||
def maybe_run_command(statement: str) -> bool:
|
def maybe_run_command(statement: str) -> bool:
|
||||||
|
@ -113,16 +134,6 @@ def run_multiline_interactive_console(
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def more_lines(unicodetext: str) -> bool:
|
|
||||||
# ooh, look at the hack:
|
|
||||||
src = _strip_final_indent(unicodetext)
|
|
||||||
try:
|
|
||||||
code = console.compile(src, "<stdin>", "single")
|
|
||||||
except (OverflowError, SyntaxError, ValueError):
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return code is None
|
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -7,7 +7,7 @@ from textwrap import dedent
|
||||||
from test.support import force_not_colorized
|
from test.support import force_not_colorized
|
||||||
|
|
||||||
from _pyrepl.console import InteractiveColoredConsole
|
from _pyrepl.console import InteractiveColoredConsole
|
||||||
|
from _pyrepl.simple_interact import _more_lines
|
||||||
|
|
||||||
class TestSimpleInteract(unittest.TestCase):
|
class TestSimpleInteract(unittest.TestCase):
|
||||||
def test_multiple_statements(self):
|
def test_multiple_statements(self):
|
||||||
|
@ -111,3 +111,104 @@ class TestSimpleInteract(unittest.TestCase):
|
||||||
result = console.runsource(source)
|
result = console.runsource(source)
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n")
|
self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n")
|
||||||
|
|
||||||
|
|
||||||
|
class TestMoreLines(unittest.TestCase):
|
||||||
|
def test_invalid_syntax_single_line(self):
|
||||||
|
namespace = {}
|
||||||
|
code = "if foo"
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertFalse(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_empty_line(self):
|
||||||
|
namespace = {}
|
||||||
|
code = ""
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertFalse(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_valid_single_statement(self):
|
||||||
|
namespace = {}
|
||||||
|
code = "foo = 1"
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertFalse(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_multiline_single_assignment(self):
|
||||||
|
namespace = {}
|
||||||
|
code = dedent("""\
|
||||||
|
foo = [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
]""")
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertFalse(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_multiline_single_block(self):
|
||||||
|
namespace = {}
|
||||||
|
code = dedent("""\
|
||||||
|
def foo():
|
||||||
|
'''docs'''
|
||||||
|
|
||||||
|
return 1""")
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertTrue(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_multiple_statements_single_line(self):
|
||||||
|
namespace = {}
|
||||||
|
code = "foo = 1;bar = 2"
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertFalse(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_multiple_statements(self):
|
||||||
|
namespace = {}
|
||||||
|
code = dedent("""\
|
||||||
|
import time
|
||||||
|
|
||||||
|
foo = 1""")
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertTrue(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_multiple_blocks(self):
|
||||||
|
namespace = {}
|
||||||
|
code = dedent("""\
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Point:
|
||||||
|
x: float
|
||||||
|
y: float""")
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertTrue(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_multiple_blocks_empty_newline(self):
|
||||||
|
namespace = {}
|
||||||
|
code = dedent("""\
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Point:
|
||||||
|
x: float
|
||||||
|
y: float
|
||||||
|
""")
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertFalse(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_multiple_blocks_indented_newline(self):
|
||||||
|
namespace = {}
|
||||||
|
code = (
|
||||||
|
"from dataclasses import dataclass\n"
|
||||||
|
"\n"
|
||||||
|
"@dataclass\n"
|
||||||
|
"class Point:\n"
|
||||||
|
" x: float\n"
|
||||||
|
" y: float\n"
|
||||||
|
" "
|
||||||
|
)
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertFalse(_more_lines(console, code))
|
||||||
|
|
||||||
|
def test_incomplete_statement(self):
|
||||||
|
namespace = {}
|
||||||
|
code = "if foo:"
|
||||||
|
console = InteractiveColoredConsole(namespace, filename="<stdin>")
|
||||||
|
self.assertTrue(_more_lines(console, code))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue