Skip to content

Commit 7d90b8a

Browse files
authored
gh-111201: Allow bracketed paste to work (GH-118700)
1 parent ad3d877 commit 7d90b8a

File tree

4 files changed

+61
-0
lines changed

4 files changed

+61
-0
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

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ def make_default_commands() -> dict[CommandName, type[Command]]:
127127
(r"\M-9", "digit-arg"),
128128
# (r'\M-\n', 'insert-nl'),
129129
("\\\\", "self-insert"),
130+
(r"\x1b[200~", "enable_bracketed_paste"),
131+
(r"\x1b[201~", "disable_bracketed_paste"),
130132
]
131133
+ [(c, "self-insert") for c in map(chr, range(32, 127)) if c != "\\"]
132134
+ [(c, "self-insert") for c in map(chr, range(128, 256)) if c.isalpha()]

Lib/_pyrepl/unix_console.py

+9
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,13 @@ def prepare(self):
336336
except ValueError:
337337
pass
338338

339+
self.__enable_bracketed_paste()
340+
339341
def restore(self):
340342
"""
341343
Restore the console to the default state
342344
"""
345+
self.__disable_bracketed_paste()
343346
self.__maybe_write_code(self._rmkx)
344347
self.flushoutput()
345348
tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate)
@@ -525,6 +528,12 @@ def clear(self):
525528
self.__posxy = 0, 0
526529
self.screen = []
527530

531+
def __enable_bracketed_paste(self) -> None:
532+
os.write(self.output_fd, b"\x1b[?2004h")
533+
534+
def __disable_bracketed_paste(self) -> None:
535+
os.write(self.output_fd, b"\x1b[?2004l")
536+
528537
def __setup_movement(self):
529538
"""
530539
Set up the movement functions based on the terminal capabilities.

Lib/test/test_pyrepl.py

+40
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,46 @@ def test_paste_not_in_paste_mode(self):
817817
output = multiline_input(reader)
818818
self.assertEqual(output, output_code)
819819

820+
def test_bracketed_paste(self):
821+
"""Test that bracketed paste using \x1b[200~ and \x1b[201~ works."""
822+
# fmt: off
823+
input_code = (
824+
'def a():\n'
825+
' for x in range(10):\n'
826+
'\n'
827+
' if x%2:\n'
828+
' print(x)\n'
829+
'\n'
830+
' else:\n'
831+
' pass\n'
832+
)
833+
# fmt: on
834+
835+
output_code = (
836+
'def a():\n'
837+
' for x in range(10):\n'
838+
'\n'
839+
' if x%2:\n'
840+
' print(x)\n'
841+
'\n'
842+
' else:\n'
843+
' pass\n'
844+
'\n'
845+
)
846+
847+
paste_start = "\x1b[200~"
848+
paste_end = "\x1b[201~"
849+
850+
events = itertools.chain(
851+
code_to_events(paste_start),
852+
code_to_events(input_code),
853+
code_to_events(paste_end),
854+
code_to_events("\n"),
855+
)
856+
reader = self.prepare_reader(events)
857+
output = multiline_input(reader)
858+
self.assertEqual(output, output_code)
859+
820860

821861
class TestReader(TestCase):
822862
def assert_screen_equals(self, reader, expected):

0 commit comments

Comments
 (0)