Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
syntax.py959 linesDownload Raw Back to rich
1import os.path2import platform3import re4import sys5import textwrap6from abc import ABC, abstractmethod7from pathlib import Path8from typing import (9    Any,10    Dict,11    Iterable,12    List,13    NamedTuple,14    Optional,15    Sequence,16    Set,17    Tuple,18    Type,19    Union,20)21 22from pygments.lexer import Lexer23from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename24from pygments.style import Style as PygmentsStyle25from pygments.styles import get_style_by_name26from pygments.token import (27    Comment,28    Error,29    Generic,30    Keyword,31    Name,32    Number,33    Operator,34    String,35    Token,36    Whitespace,37)38from pygments.util import ClassNotFound39 40from rich.containers import Lines41from rich.padding import Padding, PaddingDimensions42 43from ._loop import loop_first44from .cells import cell_len45from .color import Color, blend_rgb46from .console import Console, ConsoleOptions, JustifyMethod, RenderResult47from .jupyter import JupyterMixin48from .measure import Measurement49from .segment import Segment, Segments50from .style import Style, StyleType51from .text import Text52 53TokenType = Tuple[str, ...]54 55WINDOWS = platform.system() == "Windows"56DEFAULT_THEME = "monokai"57 58# The following styles are based on https://github.com/pygments/pygments/blob/master/pygments/formatters/terminal.py59# A few modifications were made60 61ANSI_LIGHT: Dict[TokenType, Style] = {62    Token: Style(),63    Whitespace: Style(color="white"),64    Comment: Style(dim=True),65    Comment.Preproc: Style(color="cyan"),66    Keyword: Style(color="blue"),67    Keyword.Type: Style(color="cyan"),68    Operator.Word: Style(color="magenta"),69    Name.Builtin: Style(color="cyan"),70    Name.Function: Style(color="green"),71    Name.Namespace: Style(color="cyan", underline=True),72    Name.Class: Style(color="green", underline=True),73    Name.Exception: Style(color="cyan"),74    Name.Decorator: Style(color="magenta", bold=True),75    Name.Variable: Style(color="red"),76    Name.Constant: Style(color="red"),77    Name.Attribute: Style(color="cyan"),78    Name.Tag: Style(color="bright_blue"),79    String: Style(color="yellow"),80    Number: Style(color="blue"),81    Generic.Deleted: Style(color="bright_red"),82    Generic.Inserted: Style(color="green"),83    Generic.Heading: Style(bold=True),84    Generic.Subheading: Style(color="magenta", bold=True),85    Generic.Prompt: Style(bold=True),86    Generic.Error: Style(color="bright_red"),87    Error: Style(color="red", underline=True),88}89 90ANSI_DARK: Dict[TokenType, Style] = {91    Token: Style(),92    Whitespace: Style(color="bright_black"),93    Comment: Style(dim=True),94    Comment.Preproc: Style(color="bright_cyan"),95    Keyword: Style(color="bright_blue"),96    Keyword.Type: Style(color="bright_cyan"),97    Operator.Word: Style(color="bright_magenta"),98    Name.Builtin: Style(color="bright_cyan"),99    Name.Function: Style(color="bright_green"),100    Name.Namespace: Style(color="bright_cyan", underline=True),101    Name.Class: Style(color="bright_green", underline=True),102    Name.Exception: Style(color="bright_cyan"),103    Name.Decorator: Style(color="bright_magenta", bold=True),104    Name.Variable: Style(color="bright_red"),105    Name.Constant: Style(color="bright_red"),106    Name.Attribute: Style(color="bright_cyan"),107    Name.Tag: Style(color="bright_blue"),108    String: Style(color="yellow"),109    Number: Style(color="bright_blue"),110    Generic.Deleted: Style(color="bright_red"),111    Generic.Inserted: Style(color="bright_green"),112    Generic.Heading: Style(bold=True),113    Generic.Subheading: Style(color="bright_magenta", bold=True),114    Generic.Prompt: Style(bold=True),115    Generic.Error: Style(color="bright_red"),116    Error: Style(color="red", underline=True),117}118 119RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK}120NUMBERS_COLUMN_DEFAULT_PADDING = 2121 122 123class SyntaxTheme(ABC):124    """Base class for a syntax theme."""125 126    @abstractmethod127    def get_style_for_token(self, token_type: TokenType) -> Style:128        """Get a style for a given Pygments token."""129        raise NotImplementedError  # pragma: no cover130 131    @abstractmethod132    def get_background_style(self) -> Style:133        """Get the background color."""134        raise NotImplementedError  # pragma: no cover135 136 137class PygmentsSyntaxTheme(SyntaxTheme):138    """Syntax theme that delegates to Pygments theme."""139 140    def __init__(self, theme: Union[str, Type[PygmentsStyle]]) -> None:141        self._style_cache: Dict[TokenType, Style] = {}142        if isinstance(theme, str):143            try:144                self._pygments_style_class = get_style_by_name(theme)145            except ClassNotFound:146                self._pygments_style_class = get_style_by_name("default")147        else:148            self._pygments_style_class = theme149 150        self._background_color = self._pygments_style_class.background_color151        self._background_style = Style(bgcolor=self._background_color)152 153    def get_style_for_token(self, token_type: TokenType) -> Style:154        """Get a style from a Pygments class."""155        try:156            return self._style_cache[token_type]157        except KeyError:158            try:159                pygments_style = self._pygments_style_class.style_for_token(token_type)160            except KeyError:161                style = Style.null()162            else:163                color = pygments_style["color"]164                bgcolor = pygments_style["bgcolor"]165                style = Style(166                    color="#" + color if color else "#000000",167                    bgcolor="#" + bgcolor if bgcolor else self._background_color,168                    bold=pygments_style["bold"],169                    italic=pygments_style["italic"],170                    underline=pygments_style["underline"],171                )172            self._style_cache[token_type] = style173        return style174 175    def get_background_style(self) -> Style:176        return self._background_style177 178 179class ANSISyntaxTheme(SyntaxTheme):180    """Syntax theme to use standard colors."""181 182    def __init__(self, style_map: Dict[TokenType, Style]) -> None:183        self.style_map = style_map184        self._missing_style = Style.null()185        self._background_style = Style.null()186        self._style_cache: Dict[TokenType, Style] = {}187 188    def get_style_for_token(self, token_type: TokenType) -> Style:189        """Look up style in the style map."""190        try:191            return self._style_cache[token_type]192        except KeyError:193            # Styles form a hierarchy194            # We need to go from most to least specific195            # e.g. ("foo", "bar", "baz") to ("foo", "bar")  to ("foo",)196            get_style = self.style_map.get197            token = tuple(token_type)198            style = self._missing_style199            while token:200                _style = get_style(token)201                if _style is not None:202                    style = _style203                    break204                token = token[:-1]205            self._style_cache[token_type] = style206            return style207 208    def get_background_style(self) -> Style:209        return self._background_style210 211 212SyntaxPosition = Tuple[int, int]213 214 215class _SyntaxHighlightRange(NamedTuple):216    """217    A range to highlight in a Syntax object.218    `start` and `end` are 2-integers tuples, where the first integer is the line number219    (starting from 1) and the second integer is the column index (starting from 0).220    """221 222    style: StyleType223    start: SyntaxPosition224    end: SyntaxPosition225 226 227class Syntax(JupyterMixin):228    """Construct a Syntax object to render syntax highlighted code.229 230    Args:231        code (str): Code to highlight.232        lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/)233        theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai".234        dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False.235        line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.236        start_line (int, optional): Starting number for line numbers. Defaults to 1.237        line_range (Tuple[int | None, int | None], optional): If given should be a tuple of the start and end line to render.238            A value of None in the tuple indicates the range is open in that direction.239        highlight_lines (Set[int]): A set of line numbers to highlight.240        code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.241        tab_size (int, optional): Size of tabs. Defaults to 4.242        word_wrap (bool, optional): Enable word wrapping.243        background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.244        indent_guides (bool, optional): Show indent guides. Defaults to False.245        padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).246    """247 248    _pygments_style_class: Type[PygmentsStyle]249    _theme: SyntaxTheme250 251    @classmethod252    def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme:253        """Get a syntax theme instance."""254        if isinstance(name, SyntaxTheme):255            return name256        theme: SyntaxTheme257        if name in RICH_SYNTAX_THEMES:258            theme = ANSISyntaxTheme(RICH_SYNTAX_THEMES[name])259        else:260            theme = PygmentsSyntaxTheme(name)261        return theme262 263    def __init__(264        self,265        code: str,266        lexer: Union[Lexer, str],267        *,268        theme: Union[str, SyntaxTheme] = DEFAULT_THEME,269        dedent: bool = False,270        line_numbers: bool = False,271        start_line: int = 1,272        line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,273        highlight_lines: Optional[Set[int]] = None,274        code_width: Optional[int] = None,275        tab_size: int = 4,276        word_wrap: bool = False,277        background_color: Optional[str] = None,278        indent_guides: bool = False,279        padding: PaddingDimensions = 0,280    ) -> None:281        self.code = code282        self._lexer = lexer283        self.dedent = dedent284        self.line_numbers = line_numbers285        self.start_line = start_line286        self.line_range = line_range287        self.highlight_lines = highlight_lines or set()288        self.code_width = code_width289        self.tab_size = tab_size290        self.word_wrap = word_wrap291        self.background_color = background_color292        self.background_style = (293            Style(bgcolor=background_color) if background_color else Style()294        )295        self.indent_guides = indent_guides296        self.padding = padding297 298        self._theme = self.get_theme(theme)299        self._stylized_ranges: List[_SyntaxHighlightRange] = []300 301    @classmethod302    def from_path(303        cls,304        path: str,305        encoding: str = "utf-8",306        lexer: Optional[Union[Lexer, str]] = None,307        theme: Union[str, SyntaxTheme] = DEFAULT_THEME,308        dedent: bool = False,309        line_numbers: bool = False,310        line_range: Optional[Tuple[int, int]] = None,311        start_line: int = 1,312        highlight_lines: Optional[Set[int]] = None,313        code_width: Optional[int] = None,314        tab_size: int = 4,315        word_wrap: bool = False,316        background_color: Optional[str] = None,317        indent_guides: bool = False,318        padding: PaddingDimensions = 0,319    ) -> "Syntax":320        """Construct a Syntax object from a file.321 322        Args:323            path (str): Path to file to highlight.324            encoding (str): Encoding of file.325            lexer (str | Lexer, optional): Lexer to use. If None, lexer will be auto-detected from path/file content.326            theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs".327            dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True.328            line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.329            start_line (int, optional): Starting number for line numbers. Defaults to 1.330            line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render.331            highlight_lines (Set[int]): A set of line numbers to highlight.332            code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.333            tab_size (int, optional): Size of tabs. Defaults to 4.334            word_wrap (bool, optional): Enable word wrapping of code.335            background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.336            indent_guides (bool, optional): Show indent guides. Defaults to False.337            padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).338 339        Returns:340            [Syntax]: A Syntax object that may be printed to the console341        """342        code = Path(path).read_text(encoding=encoding)343 344        if not lexer:345            lexer = cls.guess_lexer(path, code=code)346 347        return cls(348            code,349            lexer,350            theme=theme,351            dedent=dedent,352            line_numbers=line_numbers,353            line_range=line_range,354            start_line=start_line,355            highlight_lines=highlight_lines,356            code_width=code_width,357            tab_size=tab_size,358            word_wrap=word_wrap,359            background_color=background_color,360            indent_guides=indent_guides,361            padding=padding,362        )363 364    @classmethod365    def guess_lexer(cls, path: str, code: Optional[str] = None) -> str:366        """Guess the alias of the Pygments lexer to use based on a path and an optional string of code.367        If code is supplied, it will use a combination of the code and the filename to determine the368        best lexer to use. For example, if the file is ``index.html`` and the file contains Django369        templating syntax, then "html+django" will be returned. If the file is ``index.html``, and no370        templating language is used, the "html" lexer will be used. If no string of code371        is supplied, the lexer will be chosen based on the file extension..372 373        Args:374             path (AnyStr): The path to the file containing the code you wish to know the lexer for.375             code (str, optional): Optional string of code that will be used as a fallback if no lexer376                is found for the supplied path.377 378        Returns:379            str: The name of the Pygments lexer that best matches the supplied path/code.380        """381        lexer: Optional[Lexer] = None382        lexer_name = "default"383        if code:384            try:385                lexer = guess_lexer_for_filename(path, code)386            except ClassNotFound:387                pass388 389        if not lexer:390            try:391                _, ext = os.path.splitext(path)392                if ext:393                    extension = ext.lstrip(".").lower()394                    lexer = get_lexer_by_name(extension)395            except ClassNotFound:396                pass397 398        if lexer:399            if lexer.aliases:400                lexer_name = lexer.aliases[0]401            else:402                lexer_name = lexer.name403 404        return lexer_name405 406    def _get_base_style(self) -> Style:407        """Get the base style."""408        default_style = self._theme.get_background_style() + self.background_style409        return default_style410 411    def _get_token_color(self, token_type: TokenType) -> Optional[Color]:412        """Get a color (if any) for the given token.413 414        Args:415            token_type (TokenType): A token type tuple from Pygments.416 417        Returns:418            Optional[Color]: Color from theme, or None for no color.419        """420        style = self._theme.get_style_for_token(token_type)421        return style.color422 423    @property424    def lexer(self) -> Optional[Lexer]:425        """The lexer for this syntax, or None if no lexer was found.426 427        Tries to find the lexer by name if a string was passed to the constructor.428        """429 430        if isinstance(self._lexer, Lexer):431            return self._lexer432        try:433            return get_lexer_by_name(434                self._lexer,435                stripnl=False,436                ensurenl=True,437                tabsize=self.tab_size,438            )439        except ClassNotFound:440            return None441 442    @property443    def default_lexer(self) -> Lexer:444        """A Pygments Lexer to use if one is not specified or invalid."""445        return get_lexer_by_name(446            "text",447            stripnl=False,448            ensurenl=True,449            tabsize=self.tab_size,450        )451 452    def highlight(453        self,454        code: str,455        line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,456    ) -> Text:457        """Highlight code and return a Text instance.458 459        Args:460            code (str): Code to highlight.461            line_range(Tuple[int, int], optional): Optional line range to highlight.462 463        Returns:464            Text: A text instance containing highlighted syntax.465        """466 467        base_style = self._get_base_style()468        justify: JustifyMethod = (469            "default" if base_style.transparent_background else "left"470        )471 472        text = Text(473            justify=justify,474            style=base_style,475            tab_size=self.tab_size,476            no_wrap=not self.word_wrap,477        )478        _get_theme_style = self._theme.get_style_for_token479 480        lexer = self.lexer or self.default_lexer481 482        if lexer is None:483            text.append(code)484        else:485            if line_range:486                # More complicated path to only stylize a portion of the code487                # This speeds up further operations as there are less spans to process488                line_start, line_end = line_range489 490                def line_tokenize() -> Iterable[Tuple[Any, str]]:491                    """Split tokens to one per line."""492                    assert lexer  # required to make MyPy happy - we know lexer is not None at this point493 494                    for token_type, token in lexer.get_tokens(code):495                        while token:496                            line_token, new_line, token = token.partition("\n")497                            yield token_type, line_token + new_line498 499                def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]:500                    """Convert tokens to spans."""501                    tokens = iter(line_tokenize())502                    line_no = 0503                    _line_start = line_start - 1 if line_start else 0504 505                    # Skip over tokens until line start506                    while line_no < _line_start:507                        try:508                            _token_type, token = next(tokens)509                        except StopIteration:510                            break511                        yield (token, None)512                        if token.endswith("\n"):513                            line_no += 1514                    # Generate spans until line end515                    for token_type, token in tokens:516                        yield (token, _get_theme_style(token_type))517                        if token.endswith("\n"):518                            line_no += 1519                            if line_end and line_no >= line_end:520                                break521 522                text.append_tokens(tokens_to_spans())523 524            else:525                text.append_tokens(526                    (token, _get_theme_style(token_type))527                    for token_type, token in lexer.get_tokens(code)528                )529            if self.background_color is not None:530                text.stylize(f"on {self.background_color}")531 532        if self._stylized_ranges:533            self._apply_stylized_ranges(text)534 535        return text536 537    def stylize_range(538        self, style: StyleType, start: SyntaxPosition, end: SyntaxPosition539    ) -> None:540        """541        Adds a custom style on a part of the code, that will be applied to the syntax display when it's rendered.542        Line numbers are 1-based, while column indexes are 0-based.543 544        Args:545            style (StyleType): The style to apply.546            start (Tuple[int, int]): The start of the range, in the form `[line number, column index]`.547            end (Tuple[int, int]): The end of the range, in the form `[line number, column index]`.548        """549        self._stylized_ranges.append(_SyntaxHighlightRange(style, start, end))550 551    def _get_line_numbers_color(self, blend: float = 0.3) -> Color:552        background_style = self._theme.get_background_style() + self.background_style553        background_color = background_style.bgcolor554        if background_color is None or background_color.is_system_defined:555            return Color.default()556        foreground_color = self._get_token_color(Token.Text)557        if foreground_color is None or foreground_color.is_system_defined:558            return foreground_color or Color.default()559        new_color = blend_rgb(560            background_color.get_truecolor(),561            foreground_color.get_truecolor(),562            cross_fade=blend,563        )564        return Color.from_triplet(new_color)565 566    @property567    def _numbers_column_width(self) -> int:568        """Get the number of characters used to render the numbers column."""569        column_width = 0570        if self.line_numbers:571            column_width = (572                len(str(self.start_line + self.code.count("\n")))573                + NUMBERS_COLUMN_DEFAULT_PADDING574            )575        return column_width576 577    def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]:578        """Get background, number, and highlight styles for line numbers."""579        background_style = self._get_base_style()580        if background_style.transparent_background:581            return Style.null(), Style(dim=True), Style.null()582        if console.color_system in ("256", "truecolor"):583            number_style = Style.chain(584                background_style,585                self._theme.get_style_for_token(Token.Text),586                Style(color=self._get_line_numbers_color()),587                self.background_style,588            )589            highlight_number_style = Style.chain(590                background_style,591                self._theme.get_style_for_token(Token.Text),592                Style(bold=True, color=self._get_line_numbers_color(0.9)),593                self.background_style,594            )595        else:596            number_style = background_style + Style(dim=True)597            highlight_number_style = background_style + Style(dim=False)598        return background_style, number_style, highlight_number_style599 600    def __rich_measure__(601        self, console: "Console", options: "ConsoleOptions"602    ) -> "Measurement":603        _, right, _, left = Padding.unpack(self.padding)604        padding = left + right605        if self.code_width is not None:606            width = self.code_width + self._numbers_column_width + padding + 1607            return Measurement(self._numbers_column_width, width)608        lines = self.code.splitlines()609        width = (610            self._numbers_column_width611            + padding612            + (max(cell_len(line) for line in lines) if lines else 0)613        )614        if self.line_numbers:615            width += 1616        return Measurement(self._numbers_column_width, width)617 618    def __rich_console__(619        self, console: Console, options: ConsoleOptions620    ) -> RenderResult:621        segments = Segments(self._get_syntax(console, options))622        if self.padding:623            yield Padding(624                segments, style=self._theme.get_background_style(), pad=self.padding625            )626        else:627            yield segments628 629    def _get_syntax(630        self,631        console: Console,632        options: ConsoleOptions,633    ) -> Iterable[Segment]:634        """635        Get the Segments for the Syntax object, excluding any vertical/horizontal padding636        """637        transparent_background = self._get_base_style().transparent_background638        code_width = (639            (640                (options.max_width - self._numbers_column_width - 1)641                if self.line_numbers642                else options.max_width643            )644            if self.code_width is None645            else self.code_width646        )647 648        ends_on_nl, processed_code = self._process_code(self.code)649        text = self.highlight(processed_code, self.line_range)650 651        if not self.line_numbers and not self.word_wrap and not self.line_range:652            if not ends_on_nl:653                text.remove_suffix("\n")654            # Simple case of just rendering text655            style = (656                self._get_base_style()657                + self._theme.get_style_for_token(Comment)658                + Style(dim=True)659                + self.background_style660            )661            if self.indent_guides and not options.ascii_only:662                text = text.with_indent_guides(self.tab_size, style=style)663                text.overflow = "crop"664            if style.transparent_background:665                yield from console.render(666                    text, options=options.update(width=code_width)667                )668            else:669                syntax_lines = console.render_lines(670                    text,671                    options.update(width=code_width, height=None, justify="left"),672                    style=self.background_style,673                    pad=True,674                    new_lines=True,675                )676                for syntax_line in syntax_lines:677                    yield from syntax_line678            return679 680        start_line, end_line = self.line_range or (None, None)681        line_offset = 0682        if start_line:683            line_offset = max(0, start_line - 1)684        lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl)685        if self.line_range:686            if line_offset > len(lines):687                return688            lines = lines[line_offset:end_line]689 690        if self.indent_guides and not options.ascii_only:691            style = (692                self._get_base_style()693                + self._theme.get_style_for_token(Comment)694                + Style(dim=True)695                + self.background_style696            )697            lines = (698                Text("\n")699                .join(lines)700                .with_indent_guides(self.tab_size, style=style + Style(italic=False))701                .split("\n", allow_blank=True)702            )703 704        numbers_column_width = self._numbers_column_width705        render_options = options.update(width=code_width)706 707        highlight_line = self.highlight_lines.__contains__708        _Segment = Segment709        new_line = _Segment("\n")710 711        line_pointer = "> " if options.legacy_windows else "❱ "712 713        (714            background_style,715            number_style,716            highlight_number_style,717        ) = self._get_number_styles(console)718 719        for line_no, line in enumerate(lines, self.start_line + line_offset):720            if self.word_wrap:721                wrapped_lines = console.render_lines(722                    line,723                    render_options.update(height=None, justify="left"),724                    style=background_style,725                    pad=not transparent_background,726                )727            else:728                segments = list(line.render(console, end=""))729                if options.no_wrap:730                    wrapped_lines = [segments]731                else:732                    wrapped_lines = [733                        _Segment.adjust_line_length(734                            segments,735                            render_options.max_width,736                            style=background_style,737                            pad=not transparent_background,738                        )739                    ]740 741            if self.line_numbers:742                wrapped_line_left_pad = _Segment(743                    " " * numbers_column_width + " ", background_style744                )745                for first, wrapped_line in loop_first(wrapped_lines):746                    if first:747                        line_column = str(line_no).rjust(numbers_column_width - 2) + " "748                        if highlight_line(line_no):749                            yield _Segment(line_pointer, Style(color="red"))750                            yield _Segment(line_column, highlight_number_style)751                        else:752                            yield _Segment("  ", highlight_number_style)753                            yield _Segment(line_column, number_style)754                    else:755                        yield wrapped_line_left_pad756                    yield from wrapped_line757                    yield new_line758            else:759                for wrapped_line in wrapped_lines:760                    yield from wrapped_line761                    yield new_line762 763    def _apply_stylized_ranges(self, text: Text) -> None:764        """765        Apply stylized ranges to a text instance,766        using the given code to determine the right portion to apply the style to.767 768        Args:769            text (Text): Text instance to apply the style to.770        """771        code = text.plain772        newlines_offsets = [773            # Let's add outer boundaries at each side of the list:774            0,775            # N.B. using "\n" here is much faster than using metacharacters such as "^" or "\Z":776            *[777                match.start() + 1778                for match in re.finditer("\n", code, flags=re.MULTILINE)779            ],780            len(code) + 1,781        ]782 783        for stylized_range in self._stylized_ranges:784            start = _get_code_index_for_syntax_position(785                newlines_offsets, stylized_range.start786            )787            end = _get_code_index_for_syntax_position(788                newlines_offsets, stylized_range.end789            )790            if start is not None and end is not None:791                text.stylize(stylized_range.style, start, end)792 793    def _process_code(self, code: str) -> Tuple[bool, str]:794        """795        Applies various processing to a raw code string796        (normalises it so it always ends with a line return, dedents it if necessary, etc.)797 798        Args:799            code (str): The raw code string to process800 801        Returns:802            Tuple[bool, str]: the boolean indicates whether the raw code ends with a line return,803                while the string is the processed code.804        """805        ends_on_nl = code.endswith("\n")806        processed_code = code if ends_on_nl else code + "\n"807        processed_code = (808            textwrap.dedent(processed_code) if self.dedent else processed_code809        )810        processed_code = processed_code.expandtabs(self.tab_size)811        return ends_on_nl, processed_code812 813 814def _get_code_index_for_syntax_position(815    newlines_offsets: Sequence[int], position: SyntaxPosition816) -> Optional[int]:817    """818    Returns the index of the code string for the given positions.819 820    Args:821        newlines_offsets (Sequence[int]): The offset of each newline character found in the code snippet.822        position (SyntaxPosition): The position to search for.823 824    Returns:825        Optional[int]: The index of the code string for this position, or `None`826            if the given position's line number is out of range (if it's the column that is out of range827            we silently clamp its value so that it reaches the end of the line)828    """829    lines_count = len(newlines_offsets)830 831    line_number, column_index = position832    if line_number > lines_count or len(newlines_offsets) < (line_number + 1):833        return None  # `line_number` is out of range834    line_index = line_number - 1835    line_length = newlines_offsets[line_index + 1] - newlines_offsets[line_index] - 1836    # If `column_index` is out of range: let's silently clamp it:837    column_index = min(line_length, column_index)838    return newlines_offsets[line_index] + column_index839 840 841if __name__ == "__main__":  # pragma: no cover842    import argparse843    import sys844 845    parser = argparse.ArgumentParser(846        description="Render syntax to the console with Rich"847    )848    parser.add_argument(849        "path",850        metavar="PATH",851        help="path to file, or - for stdin",852    )853    parser.add_argument(854        "-c",855        "--force-color",856        dest="force_color",857        action="store_true",858        default=None,859        help="force color for non-terminals",860    )861    parser.add_argument(862        "-i",863        "--indent-guides",864        dest="indent_guides",865        action="store_true",866        default=False,867        help="display indent guides",868    )869    parser.add_argument(870        "-l",871        "--line-numbers",872        dest="line_numbers",873        action="store_true",874        help="render line numbers",875    )876    parser.add_argument(877        "-w",878        "--width",879        type=int,880        dest="width",881        default=None,882        help="width of output (default will auto-detect)",883    )884    parser.add_argument(885        "-r",886        "--wrap",887        dest="word_wrap",888        action="store_true",889        default=False,890        help="word wrap long lines",891    )892    parser.add_argument(893        "-s",894        "--soft-wrap",895        action="store_true",896        dest="soft_wrap",897        default=False,898        help="enable soft wrapping mode",899    )900    parser.add_argument(901        "-t", "--theme", dest="theme", default="monokai", help="pygments theme"902    )903    parser.add_argument(904        "-b",905        "--background-color",906        dest="background_color",907        default=None,908        help="Override background color",909    )910    parser.add_argument(911        "-x",912        "--lexer",913        default=None,914        dest="lexer_name",915        help="Lexer name",916    )917    parser.add_argument(918        "-p", "--padding", type=int, default=0, dest="padding", help="Padding"919    )920    parser.add_argument(921        "--highlight-line",922        type=int,923        default=None,924        dest="highlight_line",925        help="The line number (not index!) to highlight",926    )927    args = parser.parse_args()928 929    from rich.console import Console930 931    console = Console(force_terminal=args.force_color, width=args.width)932 933    if args.path == "-":934        code = sys.stdin.read()935        syntax = Syntax(936            code=code,937            lexer=args.lexer_name,938            line_numbers=args.line_numbers,939            word_wrap=args.word_wrap,940            theme=args.theme,941            background_color=args.background_color,942            indent_guides=args.indent_guides,943            padding=args.padding,944            highlight_lines={args.highlight_line},945        )946    else:947        syntax = Syntax.from_path(948            args.path,949            lexer=args.lexer_name,950            line_numbers=args.line_numbers,951            word_wrap=args.word_wrap,952            theme=args.theme,953            background_color=args.background_color,954            indent_guides=args.indent_guides,955            padding=args.padding,956            highlight_lines={args.highlight_line},957        )958    console.print(syntax, soft_wrap=args.soft_wrap)959 
codekingpro/portable-devtools · Team Ai