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