Skip to content

Commit 8c70c45

Browse files
committed
Slurp the entire input buffer before refreshing display during bracketed paste
1 parent 2bdcd06 commit 8c70c45

6 files changed

Lines changed: 23 additions & 47 deletions

File tree

Lib/_pyrepl/commands.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
# finishing
3232
# [completion]
3333

34+
from .trace import trace
3435

3536
# types
3637
if False:
@@ -471,19 +472,23 @@ def do(self) -> None:
471472

472473

473474
class paste_mode(Command):
474-
475475
def do(self) -> None:
476476
self.reader.paste_mode = not self.reader.paste_mode
477477
self.reader.dirty = True
478478

479479

480-
class enable_bracketed_paste(Command):
481-
def do(self) -> None:
482-
self.reader.paste_mode = True
483-
self.reader.in_bracketed_paste = True
484-
485-
class disable_bracketed_paste(Command):
486-
def do(self) -> None:
487-
self.reader.paste_mode = False
488-
self.reader.in_bracketed_paste = False
489-
self.reader.dirty = True
480+
class perform_bracketed_paste(Command):
481+
def do(self) -> None:
482+
done = "\x1b[201~"
483+
data = ""
484+
import time
485+
start = time.time()
486+
trace("bracketed pasting starts")
487+
while done not in data:
488+
self.reader.console.wait(100)
489+
ev = self.reader.console.getpending()
490+
data += ev.data
491+
trace("len(data) = {d}", d=len(data))
492+
trace("bracketed pasting done in {s:.2f}s", s=time.time() - start)
493+
self.reader.insert(data.replace(done, ""))
494+
self.reader.last_refresh_cache.invalidated = True

Lib/_pyrepl/reader.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ def make_default_commands() -> dict[CommandName, type[Command]]:
103103
(r"\M-9", "digit-arg"),
104104
(r"\M-\n", "accept"),
105105
("\\\\", "self-insert"),
106-
(r"\x1b[200~", "enable_bracketed_paste"),
107-
(r"\x1b[201~", "disable_bracketed_paste"),
106+
(r"\x1b[200~", "perform-bracketed-paste"),
108107
(r"\x03", "ctrl-c"),
109108
]
110109
+ [(c, "self-insert") for c in map(chr, range(32, 127)) if c != "\\"]
@@ -202,7 +201,6 @@ class Reader:
202201
dirty: bool = False
203202
finished: bool = False
204203
paste_mode: bool = False
205-
in_bracketed_paste: bool = False
206204
commands: dict[str, type[Command]] = field(default_factory=make_default_commands)
207205
last_command: type[Command] | None = None
208206
syntax_table: dict[str, int] = field(default_factory=make_default_syntax_table)
@@ -220,7 +218,6 @@ class Reader:
220218
## cached metadata to speed up screen refreshes
221219
@dataclass
222220
class RefreshCache:
223-
in_bracketed_paste: bool = False
224221
screen: list[str] = field(default_factory=list)
225222
screeninfo: list[tuple[int, list[int]]] = field(init=False)
226223
line_end_offsets: list[int] = field(default_factory=list)
@@ -234,7 +231,6 @@ def update_cache(self,
234231
screen: list[str],
235232
screeninfo: list[tuple[int, list[int]]],
236233
) -> None:
237-
self.in_bracketed_paste = reader.in_bracketed_paste
238234
self.screen = screen.copy()
239235
self.screeninfo = screeninfo.copy()
240236
self.pos = reader.pos
@@ -247,8 +243,7 @@ def valid(self, reader: Reader) -> bool:
247243
return False
248244
dimensions = reader.console.width, reader.console.height
249245
dimensions_changed = dimensions != self.dimensions
250-
paste_changed = reader.in_bracketed_paste != self.in_bracketed_paste
251-
return not (dimensions_changed or paste_changed)
246+
return not dimensions_changed
252247

253248
def get_cached_location(self, reader: Reader) -> tuple[int, int]:
254249
if self.invalidated:
@@ -350,7 +345,7 @@ def calc_screen(self) -> list[str]:
350345
prompt, prompt_len = self.process_prompt(prompt)
351346
chars, char_widths = disp_str(line, colors, offset)
352347
wrapcount = (sum(char_widths) + prompt_len) // self.console.width
353-
trace("wrapcount = {wrapcount}", wrapcount=wrapcount)
348+
# trace("wrapcount = {wrapcount}", wrapcount=wrapcount)
354349
if wrapcount == 0 or not char_widths:
355350
offset += line_len + 1 # Takes all of the line plus the newline
356351
last_refresh_line_end_offsets.append(offset)
@@ -484,7 +479,7 @@ def get_prompt(self, lineno: int, cursor_on_line: bool) -> str:
484479
'lineno'."""
485480
if self.arg is not None and cursor_on_line:
486481
prompt = f"(arg: {self.arg}) "
487-
elif self.paste_mode and not self.in_bracketed_paste:
482+
elif self.paste_mode:
488483
prompt = "(paste) "
489484
elif "\n" in self.buffer:
490485
if lineno == 0:
@@ -639,9 +634,6 @@ def update_screen(self) -> None:
639634

640635
def refresh(self) -> None:
641636
"""Recalculate and refresh the screen."""
642-
if self.in_bracketed_paste and self.buffer and not self.buffer[-1] == "\n":
643-
return
644-
645637
# this call sets up self.cxy, so call it first.
646638
self.screen = self.calc_screen()
647639
self.console.refresh(self.screen, self.cxy)

Lib/_pyrepl/readline.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,6 @@ def do(self) -> None:
276276
r = self.reader # type: ignore[assignment]
277277
r.dirty = True # this is needed to hide the completion menu, if visible
278278

279-
if self.reader.in_bracketed_paste:
280-
r.insert("\n")
281-
return
282-
283279
# if there are already several lines and the cursor
284280
# is not on the last one, always insert a new \n.
285281
text = r.get_unicode()

Lib/_pyrepl/simple_interact.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ def maybe_run_command(statement: str) -> bool:
157157
r.pos = len(r.get_unicode())
158158
r.dirty = True
159159
r.refresh()
160-
r.in_bracketed_paste = False
161160
console.write("\nKeyboardInterrupt\n")
162161
console.resetbuffer()
163162
except MemoryError:

Lib/_pyrepl/unix_console.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,6 @@ def __init__(
150150

151151
self.pollob = poll()
152152
self.pollob.register(self.input_fd, select.POLLIN)
153-
self.input_buffer = b""
154-
self.input_buffer_pos = 0
155153
curses.setupterm(term or None, self.output_fd)
156154
self.term = term
157155

@@ -199,22 +197,8 @@ def _my_getstr(cap: str, optional: bool = False) -> bytes | None:
199197
self.event_queue = EventQueue(self.input_fd, self.encoding)
200198
self.cursor_visible = 1
201199

202-
def more_in_buffer(self) -> bool:
203-
return bool(
204-
self.input_buffer
205-
and self.input_buffer_pos < len(self.input_buffer)
206-
)
207-
208200
def __read(self, n: int) -> bytes:
209-
if not self.more_in_buffer():
210-
self.input_buffer = os.read(self.input_fd, 10000)
211-
212-
ret = self.input_buffer[self.input_buffer_pos : self.input_buffer_pos + n]
213-
self.input_buffer_pos += len(ret)
214-
if self.input_buffer_pos >= len(self.input_buffer):
215-
self.input_buffer = b""
216-
self.input_buffer_pos = 0
217-
return ret
201+
return os.read(self.input_fd, n)
218202

219203

220204
def change_encoding(self, encoding: str) -> None:
@@ -422,7 +406,6 @@ def wait(self, timeout: float | None = None) -> bool:
422406
"""
423407
return (
424408
not self.event_queue.empty()
425-
or self.more_in_buffer()
426409
or bool(self.pollob.poll(timeout))
427410
)
428411

@@ -525,6 +508,7 @@ def getpending(self):
525508
e.raw += e.raw
526509

527510
amount = struct.unpack("i", ioctl(self.input_fd, FIONREAD, b"\0\0\0\0"))[0]
511+
trace("getpending({a})", a=amount)
528512
raw = self.__read(amount)
529513
data = str(raw, self.encoding, "replace")
530514
e.data += data

Lib/_pyrepl/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,5 +304,5 @@ def disp_str(
304304
# the next call to `disp_str()` will revive it.
305305
chars[-1] += TAG_TO_ANSI["SYNC"]
306306

307-
trace("disp_str({buffer}) = {s}, {b}", buffer=repr(buffer), s=chars, b=char_widths)
307+
# trace("disp_str({buffer}) = {s}, {b}", buffer=repr(buffer), s=chars, b=char_widths)
308308
return chars, char_widths

0 commit comments

Comments
 (0)