Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-131507: Add support for syntax highlighting in PyREPL#133247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e921a80fb95911b4285132bdcd068c70c454d7ae369585bd6b1f255701e112920eff498d3648a656fea3dac89617891fa7362a21bffebbbe388e4949b60382f835dba9003d05ff1f92bbd84cd8080f300File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -21,6 +21,7 @@ | ||
| from __future__ import annotations | ||
| import os | ||
| import time | ||
| # Categories of actions: | ||
| # killing | ||
| @@ -31,6 +32,7 @@ | ||
| # finishing | ||
| # [completion] | ||
| from .trace import trace | ||
| # types | ||
| if False: | ||
| @@ -471,19 +473,24 @@ def do(self) -> None: | ||
| class paste_mode(Command): | ||
| def do(self) -> None: | ||
| self.reader.paste_mode = not self.reader.paste_mode | ||
| self.reader.dirty = True | ||
| class enable_bracketed_paste(Command): | ||
| def do(self) -> None: | ||
| self.reader.paste_mode = 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 | ||
| class perform_bracketed_paste(Command): | ||
| def do(self) -> None: | ||
| done = "\x1b[201~" | ||
ambv marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| data = "" | ||
| start = time.time() | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leftover from testing? ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The trace below shows time. I can move the import up. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then use | ||
| while done not in data: | ||
| self.reader.console.wait(100) | ||
| ev = self.reader.console.getpending() | ||
| data += ev.data | ||
| trace( | ||
| "bracketed pasting of {l} chars done in {s:.2f}s", | ||
| l=len(data), | ||
| s=time.time() - start, | ||
| ) | ||
| self.reader.insert(data.replace(done, "")) | ||
| self.reader.last_refresh_cache.invalidated = True | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -22,14 +22,13 @@ | ||
| from __future__ import annotations | ||
| import sys | ||
| import _colorize | ||
| from contextlib import contextmanager | ||
| from dataclasses import dataclass, field, fields | ||
| from _colorize import can_colorize, ANSIColors | ||
| from . import commands, console, input | ||
| from .utils import wlen, unbracket, disp_str | ||
| from .utils import wlen, unbracket, disp_str, gen_colors | ||
| from .trace import trace | ||
| @@ -38,8 +37,7 @@ | ||
| from .types import Callback, SimpleContextManager, KeySpec, CommandName | ||
| # syntax classes: | ||
| # syntax classes | ||
| SYNTAX_WHITESPACE, SYNTAX_WORD, SYNTAX_SYMBOL = range(3) | ||
| @@ -105,8 +103,7 @@ def make_default_commands() -> dict[CommandName, type[Command]]: | ||
| (r"\M-9", "digit-arg"), | ||
| (r"\M-\n", "accept"), | ||
| ("\\\\", "self-insert"), | ||
| (r"\x1b[200~", "enable_bracketed_paste"), | ||
| (r"\x1b[201~", "disable_bracketed_paste"), | ||
| (r"\x1b[200~", "perform-bracketed-paste"), | ||
| (r"\x03", "ctrl-c"), | ||
| ] | ||
| + [(c, "self-insert") for c in map(chr, range(32, 127)) if c != "\\"] | ||
| @@ -144,16 +141,17 @@ class Reader: | ||
| Instance variables of note include: | ||
| * buffer: | ||
| A *list* (*not* a string at the moment :-) containing all the | ||
| characters that have been entered. | ||
| A per-character list containing all the characters that have been | ||
| entered. Does not include color information. | ||
| * console: | ||
| Hopefully encapsulates the OS dependent stuff. | ||
| * pos: | ||
| A 0-based index into 'buffer' for where the insertion point | ||
| is. | ||
| * screeninfo: | ||
| Ahem. This list contains some info needed to move the | ||
| insertion point around reasonably efficiently. | ||
| A list of screen position tuples. Each list element is a tuple | ||
| representing information on visible line length for a given line. | ||
| Allows for efficient skipping of color escape sequences. | ||
| * cxy, lxy: | ||
| the position of the insertion point in screen ... | ||
| * syntax_table: | ||
| @@ -203,7 +201,6 @@ class Reader: | ||
| dirty: bool = False | ||
| finished: bool = False | ||
| paste_mode: 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) | ||
| @@ -221,7 +218,6 @@ class Reader: | ||
| ## cached metadata to speed up screen refreshes | ||
| @dataclass | ||
| class RefreshCache: | ||
| in_bracketed_paste: bool = False | ||
| screen: list[str] = field(default_factory=list) | ||
| screeninfo: list[tuple[int, list[int]]] = field(init=False) | ||
| line_end_offsets: list[int] = field(default_factory=list) | ||
| @@ -235,7 +231,6 @@ def update_cache(self, | ||
| screen: list[str], | ||
| screeninfo: list[tuple[int, list[int]]], | ||
| ) -> None: | ||
| self.in_bracketed_paste = reader.in_bracketed_paste | ||
| self.screen = screen.copy() | ||
| self.screeninfo = screeninfo.copy() | ||
| self.pos = reader.pos | ||
| @@ -248,8 +243,7 @@ def valid(self, reader: Reader) -> bool: | ||
| return False | ||
| dimensions = reader.console.width, reader.console.height | ||
| dimensions_changed = dimensions != self.dimensions | ||
| paste_changed = reader.in_bracketed_paste != self.in_bracketed_paste | ||
| return not (dimensions_changed or paste_changed) | ||
| return not dimensions_changed | ||
| def get_cached_location(self, reader: Reader) -> tuple[int, int]: | ||
| if self.invalidated: | ||
| @@ -279,7 +273,7 @@ def __post_init__(self) -> None: | ||
| self.screeninfo = [(0, [])] | ||
| self.cxy = self.pos2xy() | ||
| self.lxy = (self.pos, 0) | ||
| self.can_colorize = can_colorize() | ||
| self.can_colorize = _colorize.can_colorize() | ||
| self.last_refresh_cache.screeninfo = self.screeninfo | ||
| self.last_refresh_cache.pos = self.pos | ||
| @@ -316,6 +310,12 @@ def calc_screen(self) -> list[str]: | ||
| pos -= offset | ||
| prompt_from_cache = (offset and self.buffer[offset - 1] != "\n") | ||
| if self.can_colorize: | ||
| colors = list(gen_colors(self.get_unicode())) | ||
ambv marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| else: | ||
| colors = None | ||
| trace("colors = {colors}", colors=colors) | ||
| lines = "".join(self.buffer[offset:]).split("\n") | ||
| cursor_found = False | ||
| lines_beyond_cursor = 0 | ||
| @@ -343,9 +343,8 @@ def calc_screen(self) -> list[str]: | ||
| screeninfo.append((0, [])) | ||
| pos -= line_len + 1 | ||
| prompt, prompt_len = self.process_prompt(prompt) | ||
| chars, char_widths = disp_str(line) | ||
| chars, char_widths = disp_str(line, colors, offset) | ||
| wrapcount = (sum(char_widths) + prompt_len) // self.console.width | ||
| trace("wrapcount = {wrapcount}", wrapcount=wrapcount) | ||
| if wrapcount == 0 or not char_widths: | ||
| offset += line_len + 1 # Takes all of the line plus the newline | ||
| last_refresh_line_end_offsets.append(offset) | ||
| @@ -479,7 +478,7 @@ def get_prompt(self, lineno: int, cursor_on_line: bool) -> str: | ||
| 'lineno'.""" | ||
| if self.arg is not None and cursor_on_line: | ||
| prompt = f"(arg: {self.arg}) " | ||
| elif self.paste_mode and not self.in_bracketed_paste: | ||
| elif self.paste_mode: | ||
| prompt = "(paste) " | ||
| elif "\n" in self.buffer: | ||
| if lineno == 0: | ||
| @@ -492,7 +491,11 @@ def get_prompt(self, lineno: int, cursor_on_line: bool) -> str: | ||
| prompt = self.ps1 | ||
| if self.can_colorize: | ||
| prompt = f"{ANSIColors.BOLD_MAGENTA}{prompt}{ANSIColors.RESET}" | ||
| prompt = ( | ||
| f"{_colorize.theme["PROMPT"]}" | ||
| f"{prompt}" | ||
| f"{_colorize.theme["RESET"]}" | ||
| ) | ||
| return prompt | ||
| def push_input_trans(self, itrans: input.KeymapTranslator) -> None: | ||
| @@ -567,6 +570,7 @@ def insert(self, text: str | list[str]) -> None: | ||
| def update_cursor(self) -> None: | ||
| """Move the cursor to reflect changes in self.pos""" | ||
| self.cxy = self.pos2xy() | ||
| trace("update_cursor({pos}) = {cxy}", pos=self.pos, cxy=self.cxy) | ||
| self.console.move_cursor(*self.cxy) | ||
| def after_command(self, cmd: Command) -> None: | ||
| @@ -633,9 +637,6 @@ def update_screen(self) -> None: | ||
| def refresh(self) -> None: | ||
| """Recalculate and refresh the screen.""" | ||
| if self.in_bracketed_paste and self.buffer and not self.buffer[-1] == "\n": | ||
| return | ||
| # this call sets up self.cxy, so call it first. | ||
| self.screen = self.calc_screen() | ||
| self.console.refresh(self.screen, self.cxy) | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.