mirror of
https://github.com/python/cpython.git
synced 2025-09-09 18:32:22 +00:00
gh-111201: Allow pasted code to contain multiple statements in the REPL (#118712)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
26bab423fb
commit
a94ac56628
6 changed files with 33 additions and 9 deletions
|
@ -460,6 +460,8 @@ class show_history(Command):
|
||||||
class paste_mode(Command):
|
class paste_mode(Command):
|
||||||
|
|
||||||
def do(self) -> None:
|
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.paste_mode = not self.reader.paste_mode
|
||||||
self.reader.dirty = True
|
self.reader.dirty = True
|
||||||
|
|
||||||
|
@ -467,8 +469,9 @@ class paste_mode(Command):
|
||||||
class enable_bracketed_paste(Command):
|
class enable_bracketed_paste(Command):
|
||||||
def do(self) -> None:
|
def do(self) -> None:
|
||||||
self.reader.paste_mode = True
|
self.reader.paste_mode = True
|
||||||
|
self.reader.was_paste_mode_activated = True
|
||||||
|
|
||||||
class disable_bracketed_paste(Command):
|
class disable_bracketed_paste(Command):
|
||||||
def do(self) -> None:
|
def do(self) -> None:
|
||||||
self.reader.paste_mode = False
|
self.reader.paste_mode = False
|
||||||
self.reader.insert("\n")
|
self.reader.dirty = True
|
||||||
|
|
|
@ -221,6 +221,7 @@ class Reader:
|
||||||
dirty: bool = False
|
dirty: bool = False
|
||||||
finished: bool = False
|
finished: bool = False
|
||||||
paste_mode: bool = False
|
paste_mode: bool = False
|
||||||
|
was_paste_mode_activated: bool = False
|
||||||
commands: dict[str, type[Command]] = field(default_factory=make_default_commands)
|
commands: dict[str, type[Command]] = field(default_factory=make_default_commands)
|
||||||
last_command: type[Command] | None = None
|
last_command: type[Command] | None = None
|
||||||
syntax_table: dict[str, int] = field(default_factory=make_default_syntax_table)
|
syntax_table: dict[str, int] = field(default_factory=make_default_syntax_table)
|
||||||
|
|
|
@ -298,10 +298,11 @@ class _ReadlineWrapper:
|
||||||
reader.more_lines = more_lines
|
reader.more_lines = more_lines
|
||||||
reader.ps1 = reader.ps2 = ps1
|
reader.ps1 = reader.ps2 = ps1
|
||||||
reader.ps3 = reader.ps4 = ps2
|
reader.ps3 = reader.ps4 = ps2
|
||||||
return reader.readline()
|
return reader.readline(), reader.was_paste_mode_activated
|
||||||
finally:
|
finally:
|
||||||
reader.more_lines = saved
|
reader.more_lines = saved
|
||||||
reader.paste_mode = False
|
reader.paste_mode = False
|
||||||
|
reader.was_paste_mode_activated = False
|
||||||
|
|
||||||
def parse_and_bind(self, string: str) -> None:
|
def parse_and_bind(self, string: str) -> None:
|
||||||
pass # XXX we don't support parsing GNU-readline-style init files
|
pass # XXX we don't support parsing GNU-readline-style init files
|
||||||
|
|
|
@ -135,7 +135,7 @@ def run_multiline_interactive_console(
|
||||||
ps1 = getattr(sys, "ps1", ">>> ")
|
ps1 = getattr(sys, "ps1", ">>> ")
|
||||||
ps2 = getattr(sys, "ps2", "... ")
|
ps2 = getattr(sys, "ps2", "... ")
|
||||||
try:
|
try:
|
||||||
statement = multiline_input(more_lines, ps1, ps2)
|
statement, contains_pasted_code = multiline_input(more_lines, ps1, ps2)
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -144,7 +144,10 @@ def run_multiline_interactive_console(
|
||||||
|
|
||||||
input_name = f"<python-input-{input_n}>"
|
input_name = f"<python-input-{input_n}>"
|
||||||
linecache._register_code(input_name, statement, "<stdin>") # type: ignore[attr-defined]
|
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
|
assert not more
|
||||||
input_n += 1
|
input_n += 1
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
@ -281,7 +281,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||||
elif exitmsg != '':
|
elif exitmsg != '':
|
||||||
self.write('%s\n' % 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.
|
"""Push a line to the interpreter.
|
||||||
|
|
||||||
The line should not have a trailing newline; it may have
|
The line should not have a trailing newline; it may have
|
||||||
|
@ -299,7 +299,7 @@ class InteractiveConsole(InteractiveInterpreter):
|
||||||
source = "\n".join(self.buffer)
|
source = "\n".join(self.buffer)
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = self.filename
|
filename = self.filename
|
||||||
more = self.runsource(source, filename)
|
more = self.runsource(source, filename, symbol=_symbol)
|
||||||
if not more:
|
if not more:
|
||||||
self.resetbuffer()
|
self.resetbuffer()
|
||||||
return more
|
return more
|
||||||
|
|
|
@ -23,6 +23,7 @@ from _pyrepl.console import Console, Event
|
||||||
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
|
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
|
||||||
from _pyrepl.simple_interact import _strip_final_indent
|
from _pyrepl.simple_interact import _strip_final_indent
|
||||||
from _pyrepl.unix_eventqueue import EventQueue
|
from _pyrepl.unix_eventqueue import EventQueue
|
||||||
|
from _pyrepl.simple_interact import InteractiveColoredConsole
|
||||||
|
|
||||||
|
|
||||||
def more_lines(unicodetext, namespace=None):
|
def more_lines(unicodetext, namespace=None):
|
||||||
|
@ -830,7 +831,6 @@ class TestPasteEvent(TestCase):
|
||||||
' else:\n'
|
' else:\n'
|
||||||
' pass\n'
|
' pass\n'
|
||||||
)
|
)
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
output_code = (
|
output_code = (
|
||||||
'def a():\n'
|
'def a():\n'
|
||||||
|
@ -841,8 +841,8 @@ class TestPasteEvent(TestCase):
|
||||||
'\n'
|
'\n'
|
||||||
' else:\n'
|
' else:\n'
|
||||||
' pass\n'
|
' pass\n'
|
||||||
'\n'
|
|
||||||
)
|
)
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
paste_start = "\x1b[200~"
|
paste_start = "\x1b[200~"
|
||||||
paste_end = "\x1b[201~"
|
paste_end = "\x1b[201~"
|
||||||
|
@ -857,6 +857,22 @@ class TestPasteEvent(TestCase):
|
||||||
output = multiline_input(reader)
|
output = multiline_input(reader)
|
||||||
self.assertEqual(output, output_code)
|
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):
|
class TestReader(TestCase):
|
||||||
def assert_screen_equals(self, reader, expected):
|
def assert_screen_equals(self, reader, expected):
|
||||||
|
@ -986,5 +1002,5 @@ class TestReader(TestCase):
|
||||||
self.assert_screen_equals(reader, "")
|
self.assert_screen_equals(reader, "")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue