From 58dbb4a4b053800bb802aaf9662e3977ad114a52 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 23 May 2024 06:23:40 +0200 Subject: [PATCH] [3.13] gh-111201: Speed up paste mode in the REPL (#119341) (GH-119432) (#119439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit e6572e8f98d33994d2d0dd3afa92a2a72ee642a9) Also includes: * gh-111201: Use calc_complete_screen after bracketed paste in PyREPL (GH-119432) (cherry picked from commit 14b063cbf1bb11a489d04a31f277edba0fc8893c) Co-authored-by: Pablo Galindo Salgado Co-authored-by: Ɓukasz Langa Co-authored-by: Lysandros Nikolaou --- Lib/_pyrepl/commands.py | 6 +++--- Lib/_pyrepl/reader.py | 8 ++++---- Lib/_pyrepl/readline.py | 11 ++++++----- Lib/_pyrepl/simple_interact.py | 3 ++- Lib/_pyrepl/utils.py | 8 ++++++-- Lib/test/test_pyrepl/test_pyrepl.py | 2 +- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Lib/_pyrepl/commands.py b/Lib/_pyrepl/commands.py index 3d9722d1586c2a..ed977f84baac4e 100644 --- a/Lib/_pyrepl/commands.py +++ b/Lib/_pyrepl/commands.py @@ -461,8 +461,6 @@ def do(self) -> None: class paste_mode(Command): 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.dirty = True @@ -470,9 +468,11 @@ def do(self) -> None: class enable_bracketed_paste(Command): def do(self) -> None: self.reader.paste_mode = True - self.reader.was_paste_mode_activated = True + self.reader.in_bracketed_paste = True class disable_bracketed_paste(Command): def do(self) -> None: self.reader.paste_mode = False + self.reader.in_bracketed_paste = False self.reader.dirty = True + self.reader.calc_screen = self.reader.calc_complete_screen diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 40cbe422fabdbe..0f0ef15f9eb2ea 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -54,7 +54,7 @@ def disp_str(buffer: str) -> tuple[str, list[int]]: b: list[int] = [] s: list[str] = [] 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) s.append(c) b.append(wlen(c)) @@ -225,7 +225,7 @@ class Reader: dirty: bool = False finished: 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) last_command: type[Command] | None = None syntax_table: dict[str, int] = field(default_factory=make_default_syntax_table) @@ -454,7 +454,7 @@ def get_prompt(self, lineno: int, cursor_on_line: bool) -> str: elif "\n" in self.buffer: if lineno == 0: prompt = self.ps2 - elif lineno == self.buffer.count("\n"): + elif self.ps4 and lineno == self.buffer.count("\n"): prompt = self.ps4 else: prompt = self.ps3 @@ -617,7 +617,7 @@ def do_cmd(self, cmd: tuple[str, list[str]]) -> None: self.after_command(command) - if self.dirty: + if self.dirty and not self.in_bracketed_paste: self.refresh() else: self.update_cursor() diff --git a/Lib/_pyrepl/readline.py b/Lib/_pyrepl/readline.py index 57e00a66295b7b..ffa14a9ce31a8f 100644 --- a/Lib/_pyrepl/readline.py +++ b/Lib/_pyrepl/readline.py @@ -341,7 +341,7 @@ def input(self, prompt: object = "") -> str: reader.ps1 = str(prompt) 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 lines as long as 'more_lines(unicodetext)' returns an object whose boolean value is true. @@ -350,14 +350,15 @@ def multiline_input(self, more_lines: MoreLinesCallable, ps1: str, ps2: str) -> saved = reader.more_lines try: reader.more_lines = more_lines - reader.ps1 = reader.ps2 = ps1 - reader.ps3 = reader.ps4 = ps2 + reader.ps1 = ps1 + reader.ps2 = ps1 + reader.ps3 = ps2 + reader.ps4 = "" with warnings.catch_warnings(action="ignore"): - return reader.readline(), reader.was_paste_mode_activated + return reader.readline() finally: reader.more_lines = saved reader.paste_mode = False - reader.was_paste_mode_activated = False def parse_and_bind(self, string: str) -> None: pass # XXX we don't support parsing GNU-readline-style init files diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py index d65b6d0d62790a..8ab4dab757685e 100644 --- a/Lib/_pyrepl/simple_interact.py +++ b/Lib/_pyrepl/simple_interact.py @@ -62,6 +62,7 @@ def _strip_final_indent(text: str) -> str: "quit": _sitebuiltins.Quitter('quit' ,''), "copyright": _sitebuiltins._Printer('copyright', sys.copyright), "help": "help", + "clear": "clear_screen", } class InteractiveColoredConsole(code.InteractiveConsole): @@ -163,7 +164,7 @@ def more_lines(unicodetext: str) -> bool: ps1 = getattr(sys, "ps1", ">>> ") ps2 = getattr(sys, "ps2", "... ") try: - statement, contains_pasted_code = multiline_input(more_lines, ps1, ps2) + statement = multiline_input(more_lines, ps1, ps2) except EOFError: break diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py index cd1df7c49a216d..96e917e487d91a 100644 --- a/Lib/_pyrepl/utils.py +++ b/Lib/_pyrepl/utils.py @@ -1,10 +1,14 @@ import re import unicodedata +import functools ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]") +@functools.cache def str_width(c: str) -> int: + if ord(c) < 128: + return 1 w = unicodedata.east_asian_width(c) if w in ('N', 'Na', 'H', 'A'): return 1 @@ -13,6 +17,6 @@ def str_width(c: str) -> int: def wlen(s: str) -> int: length = sum(str_width(i) for i in s) - # 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) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 7dff2888da3a58..bdcabf9be05b9e 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -587,7 +587,7 @@ def test_func(self): reader = self.prepare_reader(events, namespace) mock_get_reader.return_value = reader output = readline_multiline_input(more_lines, ">>>", "...") - self.assertEqual(output[0], "dummy.test_func.__") + self.assertEqual(output, "dummy.test_func.__") self.assertEqual(mock_stderr.getvalue(), "")