1from __future__ import annotations 
    2 
    3from typing import TYPE_CHECKING, Callable, Iterable, List, Tuple, Union, cast 
    4 
    5from prompt_toolkit.mouse_events import MouseEvent 
    6 
    7if TYPE_CHECKING: 
    8    from typing_extensions import Protocol 
    9 
    10    from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone 
    11 
    12__all__ = [ 
    13    "OneStyleAndTextTuple", 
    14    "StyleAndTextTuples", 
    15    "MagicFormattedText", 
    16    "AnyFormattedText", 
    17    "to_formatted_text", 
    18    "is_formatted_text", 
    19    "Template", 
    20    "merge_formatted_text", 
    21    "FormattedText", 
    22] 
    23 
    24OneStyleAndTextTuple = Union[ 
    25    Tuple[str, str], Tuple[str, str, Callable[[MouseEvent], "NotImplementedOrNone"]] 
    26] 
    27 
    28# List of (style, text) tuples. 
    29StyleAndTextTuples = List[OneStyleAndTextTuple] 
    30 
    31 
    32if TYPE_CHECKING: 
    33    from typing_extensions import TypeGuard 
    34 
    35    class MagicFormattedText(Protocol): 
    36        """ 
    37        Any object that implements ``__pt_formatted_text__`` represents formatted 
    38        text. 
    39        """ 
    40 
    41        def __pt_formatted_text__(self) -> StyleAndTextTuples: ... 
    42 
    43 
    44AnyFormattedText = Union[ 
    45    str, 
    46    "MagicFormattedText", 
    47    StyleAndTextTuples, 
    48    Callable[[], "AnyFormattedText"], 
    49    None, 
    50] 
    51 
    52 
    53def to_formatted_text( 
    54    value: AnyFormattedText, style: str = "", auto_convert: bool = False 
    55) -> FormattedText: 
    56    """ 
    57    Convert the given value (which can be formatted text) into a list of text 
    58    fragments. (Which is the canonical form of formatted text.) The outcome is 
    59    always a `FormattedText` instance, which is a list of (style, text) tuples. 
    60 
    61    It can take a plain text string, an `HTML` or `ANSI` object, anything that 
    62    implements `__pt_formatted_text__` or a callable that takes no arguments and 
    63    returns one of those. 
    64 
    65    :param style: An additional style string which is applied to all text 
    66        fragments. 
    67    :param auto_convert: If `True`, also accept other types, and convert them 
    68        to a string first. 
    69    """ 
    70    result: FormattedText | StyleAndTextTuples 
    71 
    72    if value is None: 
    73        result = [] 
    74    elif isinstance(value, str): 
    75        result = [("", value)] 
    76    elif isinstance(value, list): 
    77        result = value  # StyleAndTextTuples 
    78    elif hasattr(value, "__pt_formatted_text__"): 
    79        result = cast("MagicFormattedText", value).__pt_formatted_text__() 
    80    elif callable(value): 
    81        return to_formatted_text(value(), style=style) 
    82    elif auto_convert: 
    83        result = [("", f"{value}")] 
    84    else: 
    85        raise ValueError( 
    86            "No formatted text. Expecting a unicode object, " 
    87            f"HTML, ANSI or a FormattedText instance. Got {value!r}" 
    88        ) 
    89 
    90    # Apply extra style. 
    91    if style: 
    92        result = cast( 
    93            StyleAndTextTuples, 
    94            [(style + " " + item_style, *rest) for item_style, *rest in result], 
    95        ) 
    96 
    97    # Make sure the result is wrapped in a `FormattedText`. Among other 
    98    # reasons, this is important for `print_formatted_text` to work correctly 
    99    # and distinguish between lists and formatted text. 
    100    if isinstance(result, FormattedText): 
    101        return result 
    102    else: 
    103        return FormattedText(result) 
    104 
    105 
    106def is_formatted_text(value: object) -> TypeGuard[AnyFormattedText]: 
    107    """ 
    108    Check whether the input is valid formatted text (for use in assert 
    109    statements). 
    110    In case of a callable, it doesn't check the return type. 
    111    """ 
    112    if callable(value): 
    113        return True 
    114    if isinstance(value, (str, list)): 
    115        return True 
    116    if hasattr(value, "__pt_formatted_text__"): 
    117        return True 
    118    return False 
    119 
    120 
    121class FormattedText(StyleAndTextTuples): 
    122    """ 
    123    A list of ``(style, text)`` tuples. 
    124 
    125    (In some situations, this can also be ``(style, text, mouse_handler)`` 
    126    tuples.) 
    127    """ 
    128 
    129    def __pt_formatted_text__(self) -> StyleAndTextTuples: 
    130        return self 
    131 
    132    def __repr__(self) -> str: 
    133        return f"FormattedText({super().__repr__()})" 
    134 
    135 
    136class Template: 
    137    """ 
    138    Template for string interpolation with formatted text. 
    139 
    140    Example:: 
    141 
    142        Template(' ... {} ... ').format(HTML(...)) 
    143 
    144    :param text: Plain text. 
    145    """ 
    146 
    147    def __init__(self, text: str) -> None: 
    148        assert "{0}" not in text 
    149        self.text = text 
    150 
    151    def format(self, *values: AnyFormattedText) -> AnyFormattedText: 
    152        def get_result() -> AnyFormattedText: 
    153            # Split the template in parts. 
    154            parts = self.text.split("{}") 
    155            assert len(parts) - 1 == len(values) 
    156 
    157            result = FormattedText() 
    158            for part, val in zip(parts, values): 
    159                result.append(("", part)) 
    160                result.extend(to_formatted_text(val)) 
    161            result.append(("", parts[-1])) 
    162            return result 
    163 
    164        return get_result 
    165 
    166 
    167def merge_formatted_text(items: Iterable[AnyFormattedText]) -> AnyFormattedText: 
    168    """ 
    169    Merge (Concatenate) several pieces of formatted text together. 
    170    """ 
    171 
    172    def _merge_formatted_text() -> AnyFormattedText: 
    173        result = FormattedText() 
    174        for i in items: 
    175            result.extend(to_formatted_text(i)) 
    176        return result 
    177 
    178    return _merge_formatted_text