Skip to content

Commit e438388

Browse files
committed
gh-111201: Allow bracketed paste to work
1 parent afbe5bf commit e438388

File tree

4 files changed

+70
-1
lines changed

4 files changed

+70
-1
lines changed

Lib/_pyrepl/commands.py

+10
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,13 @@ class paste_mode(Command):
462462
def do(self) -> None:
463463
self.reader.paste_mode = not self.reader.paste_mode
464464
self.reader.dirty = True
465+
466+
467+
class enable_bracketed_paste(Command):
468+
def do(self) -> None:
469+
self.reader.paste_mode = True
470+
471+
class disable_bracketed_paste(Command):
472+
def do(self) -> None:
473+
self.reader.paste_mode = False
474+
self.reader.insert("\n")

Lib/_pyrepl/reader.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import re
2727
import unicodedata
2828
from _colorize import can_colorize, ANSIColors # type: ignore[import-not-found]
29+
import time
2930

3031

3132
from . import commands, console, input
@@ -128,6 +129,8 @@ def make_default_commands() -> dict[CommandName, type[Command]]:
128129
(r"\M-9", "digit-arg"),
129130
# (r'\M-\n', 'insert-nl'),
130131
("\\\\", "self-insert"),
132+
(r"\x1b[200~", "enable_bracketed_paste"),
133+
(r"\x1b[201~", "disable_bracketed_paste"),
131134
]
132135
+ [(c, "self-insert") for c in map(chr, range(32, 127)) if c != "\\"]
133136
+ [(c, "self-insert") for c in map(chr, range(128, 256)) if c.isalpha()]
@@ -230,6 +233,7 @@ class Reader:
230233
screeninfo: list[tuple[int, list[int]]] = field(init=False)
231234
cxy: tuple[int, int] = field(init=False)
232235
lxy: tuple[int, int] = field(init=False)
236+
buffer_start_time: float = 0.0
233237

234238
def __post_init__(self) -> None:
235239
# Enable the use of `insert` without a `prepare` call - necessary to
@@ -603,7 +607,9 @@ def handle1(self, block: bool = True) -> bool:
603607
event = self.console.get_event(block)
604608
if not event: # can only happen if we're not blocking
605609
return False
606-
610+
if not self.buffer_start_time:
611+
self.buffer_start_time = time.monotonic()
612+
607613
translate = True
608614

609615
if event.evt == "key":

Lib/_pyrepl/simple_interact.py

+13
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ def showtraceback(self):
7979
super().showtraceback(colorize=self.can_colorize)
8080

8181

82+
def _enable_bracketed_paste() -> None:
83+
sys.stdout.write("\x1b[?2004h")
84+
sys.stdout.flush()
85+
86+
87+
def _disable_bracketed_paste() -> None:
88+
sys.stdout.write("\x1b[?2004l")
89+
sys.stdout.flush()
90+
91+
8292
def run_multiline_interactive_console(
8393
mainmodule: ModuleType | None= None, future_flags: int = 0
8494
) -> None:
@@ -137,9 +147,12 @@ def more_lines(unicodetext: str) -> bool:
137147
ps1 = getattr(sys, "ps1", ">>> ")
138148
ps2 = getattr(sys, "ps2", "... ")
139149
try:
150+
_enable_bracketed_paste()
140151
statement = multiline_input(more_lines, ps1, ps2)
141152
except EOFError:
142153
break
154+
finally:
155+
_disable_bracketed_paste()
143156

144157
if maybe_run_command(statement):
145158
continue

Lib/test/test_pyrepl.py

+40
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,46 @@ def test_paste_not_in_paste_mode(self):
811811
reader = self.prepare_reader(events)
812812
output = multiline_input(reader)
813813
self.assertEqual(output, output_code)
814+
815+
def test_bracketed_paste(self):
816+
"""Test that bracketed paste using \x1b[200~ and \x1b[201~ works."""
817+
# fmt: off
818+
input_code = (
819+
'def a():\n'
820+
' for x in range(10):\n'
821+
'\n'
822+
' if x%2:\n'
823+
' print(x)\n'
824+
'\n'
825+
' else:\n'
826+
' pass\n'
827+
)
828+
# fmt: on
829+
830+
output_code = (
831+
'def a():\n'
832+
' for x in range(10):\n'
833+
'\n'
834+
' if x%2:\n'
835+
' print(x)\n'
836+
'\n'
837+
' else:\n'
838+
' pass\n'
839+
'\n'
840+
)
841+
842+
paste_start = "\x1b[200~"
843+
paste_end = "\x1b[201~"
844+
845+
events = itertools.chain(
846+
code_to_events(paste_start),
847+
code_to_events(input_code),
848+
code_to_events(paste_end),
849+
code_to_events("\n"),
850+
)
851+
reader = self.prepare_reader(events)
852+
output = multiline_input(reader)
853+
self.assertEqual(output, output_code)
814854

815855

816856
class TestReader(TestCase):

0 commit comments

Comments
 (0)