Skip to content

Commit 6befa18

Browse files
committed
gh-156186: Fix PyREPL treating input after a bracketed paste as pasted text
1 parent e2311cf commit 6befa18

4 files changed

Lines changed: 29 additions & 6 deletions

File tree

Lib/_pyrepl/commands.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -504,15 +504,19 @@ def do(self) -> None:
504504

505505
class perform_bracketed_paste(Command):
506506
def do(self) -> None:
507-
done = "\x1b[201~"
508-
data = ""
507+
done = b"\x1b[201~"
508+
data = b""
509509
start = time.time()
510510
while done not in data:
511511
ev = self.reader.console.getpending()
512-
data += ev.data
512+
data += ev.raw
513513
trace(
514-
"bracketed pasting of {l} chars done in {s:.2f}s",
514+
"bracketed pasting of {l} bytes done in {s:.2f}s",
515515
l=len(data),
516516
s=time.time() - start,
517517
)
518-
self.reader.insert(data.replace(done, ""))
518+
pasted, _, rest = data.partition(done)
519+
self.reader.insert(pasted.decode(self.reader.console.encoding, "replace"))
520+
521+
for byte in rest:
522+
self.reader.console.push_char(byte)

Lib/_pyrepl/windows_console.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,8 @@ def push_char(self, char: int | bytes) -> None:
627627
"""
628628
Push a character to the console event queue.
629629
"""
630-
raise NotImplementedError("push_char not supported on Windows")
630+
trace("push char {char!r}", char=char)
631+
self.event_queue.push(char)
631632

632633
def beep(self) -> None:
633634
self.__write("\x07")
@@ -671,6 +672,7 @@ def getpending(self) -> Event:
671672
e2 = self.event_queue.get()
672673
if e2:
673674
e.data += e2.data
675+
e.raw += e2.raw
674676

675677
recs, rec_count = self._read_input_bulk(1024)
676678
for i in range(rec_count):
@@ -689,6 +691,7 @@ def getpending(self) -> Event:
689691
if ch == "\r":
690692
ch = "\n"
691693
e.data += ch
694+
e.raw += ch.encode(self.event_queue.encoding, "replace")
692695
return e
693696

694697
def wait_for_event(self, timeout: float | None) -> bool:

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,20 @@ def test_python_basic_repl(self):
22022202
self.assertNotIn("Exception", output)
22032203
self.assertNotIn("Traceback", output)
22042204

2205+
def test_bracketed_paste_newline_after_end_marker(self):
2206+
# A newline arriving in the same read as the closing marker must
2207+
# execute the pasted block instead of being inserted as text.
2208+
# See #156186.
2209+
env = os.environ.copy()
2210+
commands = ("\x1b[200~x = 1\nprint(f'^{x=}')\n\x1b[201~"
2211+
"\n"
2212+
"exit()\n")
2213+
output, exit_code = self.run_repl(commands, env=env, skip=True)
2214+
self.assertEqual(exit_code, 0)
2215+
self.assertIn("^x=1", output)
2216+
self.assertNotIn("Exception", output)
2217+
self.assertNotIn("Traceback", output)
2218+
22052219
@force_not_colorized
22062220
def test_no_pyrepl_source_in_exc(self):
22072221
# Avoid using _pyrepl/__main__.py in traceback reports
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix PyREPL so that a newline sent right after a pasted block runs the block,
2+
instead of being added to the pasted text.

0 commit comments

Comments
 (0)