Skip to content

gh-121610: pyrepl - handle extending blocks when multi-statement blocks are pasted #121757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions Lib/_pyrepl/simple_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import _sitebuiltins
import linecache
import functools
import sys
import code

Expand Down Expand Up @@ -78,6 +79,25 @@ def _clear_screen():
}


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(
console: code.InteractiveConsole,
*,
Expand All @@ -88,6 +108,7 @@ def run_multiline_interactive_console(
if future_flags:
console.compile.compiler.flags |= future_flags

more_lines = functools.partial(_more_lines, console)
input_n = 0

def maybe_run_command(statement: str) -> bool:
Expand All @@ -113,16 +134,6 @@ def maybe_run_command(statement: str) -> bool:

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:
try:
try:
Expand Down
103 changes: 102 additions & 1 deletion Lib/test/test_pyrepl/test_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from test.support import force_not_colorized

from _pyrepl.console import InteractiveColoredConsole

from _pyrepl.simple_interact import _more_lines

class TestSimpleInteract(unittest.TestCase):
def test_multiple_statements(self):
Expand Down Expand Up @@ -111,3 +111,104 @@ def test_no_active_future(self):
result = console.runsource(source)
self.assertFalse(result)
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