[3.13] gh-111201: Speed up paste mode in the REPL (#119341) (GH-119432) (#119439)

(cherry picked from commit e6572e8f98)

Also includes:

* gh-111201: Use calc_complete_screen after bracketed paste in PyREPL (GH-119432)
(cherry picked from commit 14b063cbf1)

Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
Co-authored-by: Lysandros Nikolaou <lisandrosnik@gmail.com>
This commit is contained in:
Miss Islington (bot) 2024-05-23 06:23:40 +02:00 committed by GitHub
parent 9fa1b4fc46
commit 58dbb4a4b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 22 additions and 16 deletions

View file

@ -461,8 +461,6 @@ class show_history(Command):
class paste_mode(Command): class paste_mode(Command):
def do(self) -> None: def do(self) -> None:
if not self.reader.paste_mode:
self.reader.was_paste_mode_activated = True
self.reader.paste_mode = not self.reader.paste_mode self.reader.paste_mode = not self.reader.paste_mode
self.reader.dirty = True self.reader.dirty = True
@ -470,9 +468,11 @@ class paste_mode(Command):
class enable_bracketed_paste(Command): class enable_bracketed_paste(Command):
def do(self) -> None: def do(self) -> None:
self.reader.paste_mode = True self.reader.paste_mode = True
self.reader.was_paste_mode_activated = True self.reader.in_bracketed_paste = True
class disable_bracketed_paste(Command): class disable_bracketed_paste(Command):
def do(self) -> None: def do(self) -> None:
self.reader.paste_mode = False self.reader.paste_mode = False
self.reader.in_bracketed_paste = False
self.reader.dirty = True self.reader.dirty = True
self.reader.calc_screen = self.reader.calc_complete_screen

View file

@ -54,7 +54,7 @@ def disp_str(buffer: str) -> tuple[str, list[int]]:
b: list[int] = [] b: list[int] = []
s: list[str] = [] s: list[str] = []
for c in buffer: for c in buffer:
if unicodedata.category(c).startswith("C"): if ord(c) > 128 and unicodedata.category(c).startswith("C"):
c = r"\u%04x" % ord(c) c = r"\u%04x" % ord(c)
s.append(c) s.append(c)
b.append(wlen(c)) b.append(wlen(c))
@ -225,7 +225,7 @@ class Reader:
dirty: bool = False dirty: bool = False
finished: bool = False finished: bool = False
paste_mode: bool = False paste_mode: bool = False
was_paste_mode_activated: bool = False in_bracketed_paste: bool = False
commands: dict[str, type[Command]] = field(default_factory=make_default_commands) commands: dict[str, type[Command]] = field(default_factory=make_default_commands)
last_command: type[Command] | None = None last_command: type[Command] | None = None
syntax_table: dict[str, int] = field(default_factory=make_default_syntax_table) syntax_table: dict[str, int] = field(default_factory=make_default_syntax_table)
@ -454,7 +454,7 @@ class Reader:
elif "\n" in self.buffer: elif "\n" in self.buffer:
if lineno == 0: if lineno == 0:
prompt = self.ps2 prompt = self.ps2
elif lineno == self.buffer.count("\n"): elif self.ps4 and lineno == self.buffer.count("\n"):
prompt = self.ps4 prompt = self.ps4
else: else:
prompt = self.ps3 prompt = self.ps3
@ -617,7 +617,7 @@ class Reader:
self.after_command(command) self.after_command(command)
if self.dirty: if self.dirty and not self.in_bracketed_paste:
self.refresh() self.refresh()
else: else:
self.update_cursor() self.update_cursor()

View file

@ -341,7 +341,7 @@ class _ReadlineWrapper:
reader.ps1 = str(prompt) reader.ps1 = str(prompt)
return reader.readline(startup_hook=self.startup_hook) return reader.readline(startup_hook=self.startup_hook)
def multiline_input(self, more_lines: MoreLinesCallable, ps1: str, ps2: str) -> tuple[str, bool]: def multiline_input(self, more_lines: MoreLinesCallable, ps1: str, ps2: str) -> str:
"""Read an input on possibly multiple lines, asking for more """Read an input on possibly multiple lines, asking for more
lines as long as 'more_lines(unicodetext)' returns an object whose lines as long as 'more_lines(unicodetext)' returns an object whose
boolean value is true. boolean value is true.
@ -350,14 +350,15 @@ class _ReadlineWrapper:
saved = reader.more_lines saved = reader.more_lines
try: try:
reader.more_lines = more_lines reader.more_lines = more_lines
reader.ps1 = reader.ps2 = ps1 reader.ps1 = ps1
reader.ps3 = reader.ps4 = ps2 reader.ps2 = ps1
reader.ps3 = ps2
reader.ps4 = ""
with warnings.catch_warnings(action="ignore"): with warnings.catch_warnings(action="ignore"):
return reader.readline(), reader.was_paste_mode_activated return reader.readline()
finally: finally:
reader.more_lines = saved reader.more_lines = saved
reader.paste_mode = False reader.paste_mode = False
reader.was_paste_mode_activated = False
def parse_and_bind(self, string: str) -> None: def parse_and_bind(self, string: str) -> None:
pass # XXX we don't support parsing GNU-readline-style init files pass # XXX we don't support parsing GNU-readline-style init files

View file

@ -62,6 +62,7 @@ REPL_COMMANDS = {
"quit": _sitebuiltins.Quitter('quit' ,''), "quit": _sitebuiltins.Quitter('quit' ,''),
"copyright": _sitebuiltins._Printer('copyright', sys.copyright), "copyright": _sitebuiltins._Printer('copyright', sys.copyright),
"help": "help", "help": "help",
"clear": "clear_screen",
} }
class InteractiveColoredConsole(code.InteractiveConsole): class InteractiveColoredConsole(code.InteractiveConsole):
@ -163,7 +164,7 @@ def run_multiline_interactive_console(
ps1 = getattr(sys, "ps1", ">>> ") ps1 = getattr(sys, "ps1", ">>> ")
ps2 = getattr(sys, "ps2", "... ") ps2 = getattr(sys, "ps2", "... ")
try: try:
statement, contains_pasted_code = multiline_input(more_lines, ps1, ps2) statement = multiline_input(more_lines, ps1, ps2)
except EOFError: except EOFError:
break break

View file

@ -1,10 +1,14 @@
import re import re
import unicodedata import unicodedata
import functools
ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]") ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]")
@functools.cache
def str_width(c: str) -> int: def str_width(c: str) -> int:
if ord(c) < 128:
return 1
w = unicodedata.east_asian_width(c) w = unicodedata.east_asian_width(c)
if w in ('N', 'Na', 'H', 'A'): if w in ('N', 'Na', 'H', 'A'):
return 1 return 1
@ -13,6 +17,6 @@ def str_width(c: str) -> int:
def wlen(s: str) -> int: def wlen(s: str) -> int:
length = sum(str_width(i) for i in s) length = sum(str_width(i) for i in s)
# remove lengths of any escape sequences # remove lengths of any escape sequences
return length - sum(len(i) for i in ANSI_ESCAPE_SEQUENCE.findall(s)) sequence = ANSI_ESCAPE_SEQUENCE.findall(s)
return length - sum(len(i) for i in sequence)

View file

@ -587,7 +587,7 @@ class TestPyReplCompleter(TestCase):
reader = self.prepare_reader(events, namespace) reader = self.prepare_reader(events, namespace)
mock_get_reader.return_value = reader mock_get_reader.return_value = reader
output = readline_multiline_input(more_lines, ">>>", "...") output = readline_multiline_input(more_lines, ">>>", "...")
self.assertEqual(output[0], "dummy.test_func.__") self.assertEqual(output, "dummy.test_func.__")
self.assertEqual(mock_stderr.getvalue(), "") self.assertEqual(mock_stderr.getvalue(), "")