Skip to content
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

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

Merged
merged 8 commits into from
May 7, 2024
Merged
5 changes: 4 additions & 1 deletion Lib/_pyrepl/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,15 +460,18 @@ 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


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
1 change: 1 addition & 0 deletions Lib/_pyrepl/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion Lib/_pyrepl/readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions Lib/_pyrepl/simple_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
22 changes: 19 additions & 3 deletions Lib/test/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -830,7 +831,6 @@ def test_bracketed_paste(self):
' else:\n'
' pass\n'
)
# fmt: on

output_code = (
'def a():\n'
Expand All @@ -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~"
Expand All @@ -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):
Expand Down Expand Up @@ -986,5 +1002,5 @@ def test_up_arrow_after_ctrl_r(self):
self.assert_screen_equals(reader, "")


if __name__ == "__main__":
if __name__ == '__main__':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WHAT IS THIS

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resistance won't be defeated

unittest.main()
Loading