Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions Lib/_pyrepl/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,15 +504,19 @@ def do(self) -> None:

class perform_bracketed_paste(Command):
def do(self) -> None:
done = "\x1b[201~"
data = ""
done = b"\x1b[201~"
data = b""
start = time.time()
while done not in data:
ev = self.reader.console.getpending()
data += ev.data
data += ev.raw
trace(
"bracketed pasting of {l} chars done in {s:.2f}s",
"bracketed pasting of {l} bytes done in {s:.2f}s",
l=len(data),
s=time.time() - start,
)
self.reader.insert(data.replace(done, ""))
pasted, _, rest = data.partition(done)
self.reader.insert(pasted.decode(self.reader.console.encoding, "replace"))

for byte in rest:
self.reader.console.push_char(byte)
5 changes: 4 additions & 1 deletion Lib/_pyrepl/windows_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,8 @@ def push_char(self, char: int | bytes) -> None:
"""
Push a character to the console event queue.
"""
raise NotImplementedError("push_char not supported on Windows")
trace("push char {char!r}", char=char)
self.event_queue.push(char)

def beep(self) -> None:
self.__write("\x07")
Expand Down Expand Up @@ -671,6 +672,7 @@ def getpending(self) -> Event:
e2 = self.event_queue.get()
if e2:
e.data += e2.data
e.raw += e2.raw

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

def wait_for_event(self, timeout: float | None) -> bool:
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,20 @@ def test_python_basic_repl(self):
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)

def test_bracketed_paste_newline_after_end_marker(self):
# A newline arriving in the same read as the closing marker must
# execute the pasted block instead of being inserted as text.
# See #156186.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit

Suggested change
# See #156186.
# See gh-156186.

env = os.environ.copy()
commands = ("\x1b[200~x = 1\nprint(f'^{x=}')\n\x1b[201~"
"\n"
"exit()\n")
output, exit_code = self.run_repl(commands, env=env, skip=True)
self.assertEqual(exit_code, 0)
self.assertIn("^x=1", output)
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)

@force_not_colorized
def test_no_pyrepl_source_in_exc(self):
# Avoid using _pyrepl/__main__.py in traceback reports
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix PyREPL so that a newline sent right after a pasted block runs the block,
instead of being added to the pasted text.
Loading