| | from __future__ import annotations |
| |
|
| | from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Tuple, Union, cast |
| |
|
| | from prompt_toolkit.mouse_events import MouseEvent |
| |
|
| | if TYPE_CHECKING: |
| | from typing_extensions import Protocol |
| |
|
| | from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone |
| |
|
| | __all__ = [ |
| | "OneStyleAndTextTuple", |
| | "StyleAndTextTuples", |
| | "MagicFormattedText", |
| | "AnyFormattedText", |
| | "to_formatted_text", |
| | "is_formatted_text", |
| | "Template", |
| | "merge_formatted_text", |
| | "FormattedText", |
| | ] |
| |
|
| | OneStyleAndTextTuple = Union[ |
| | Tuple[str, str], Tuple[str, str, Callable[[MouseEvent], "NotImplementedOrNone"]] |
| | ] |
| |
|
| | |
| | StyleAndTextTuples = List[OneStyleAndTextTuple] |
| |
|
| |
|
| | if TYPE_CHECKING: |
| | from typing_extensions import TypeGuard |
| |
|
| | class MagicFormattedText(Protocol): |
| | """ |
| | Any object that implements ``__pt_formatted_text__`` represents formatted |
| | text. |
| | """ |
| |
|
| | def __pt_formatted_text__(self) -> StyleAndTextTuples: ... |
| |
|
| |
|
| | AnyFormattedText = Union[ |
| | str, |
| | "MagicFormattedText", |
| | StyleAndTextTuples, |
| | |
| | Callable[[], Any], |
| | None, |
| | ] |
| |
|
| |
|
| | def to_formatted_text( |
| | value: AnyFormattedText, style: str = "", auto_convert: bool = False |
| | ) -> FormattedText: |
| | """ |
| | Convert the given value (which can be formatted text) into a list of text |
| | fragments. (Which is the canonical form of formatted text.) The outcome is |
| | always a `FormattedText` instance, which is a list of (style, text) tuples. |
| | |
| | It can take a plain text string, an `HTML` or `ANSI` object, anything that |
| | implements `__pt_formatted_text__` or a callable that takes no arguments and |
| | returns one of those. |
| | |
| | :param style: An additional style string which is applied to all text |
| | fragments. |
| | :param auto_convert: If `True`, also accept other types, and convert them |
| | to a string first. |
| | """ |
| | result: FormattedText | StyleAndTextTuples |
| |
|
| | if value is None: |
| | result = [] |
| | elif isinstance(value, str): |
| | result = [("", value)] |
| | elif isinstance(value, list): |
| | result = value |
| | elif hasattr(value, "__pt_formatted_text__"): |
| | result = cast("MagicFormattedText", value).__pt_formatted_text__() |
| | elif callable(value): |
| | return to_formatted_text(value(), style=style) |
| | elif auto_convert: |
| | result = [("", f"{value}")] |
| | else: |
| | raise ValueError( |
| | "No formatted text. Expecting a unicode object, " |
| | f"HTML, ANSI or a FormattedText instance. Got {value!r}" |
| | ) |
| |
|
| | |
| | if style: |
| | result = cast( |
| | StyleAndTextTuples, |
| | [(style + " " + item_style, *rest) for item_style, *rest in result], |
| | ) |
| |
|
| | |
| | |
| | |
| | if isinstance(result, FormattedText): |
| | return result |
| | else: |
| | return FormattedText(result) |
| |
|
| |
|
| | def is_formatted_text(value: object) -> TypeGuard[AnyFormattedText]: |
| | """ |
| | Check whether the input is valid formatted text (for use in assert |
| | statements). |
| | In case of a callable, it doesn't check the return type. |
| | """ |
| | if callable(value): |
| | return True |
| | if isinstance(value, (str, list)): |
| | return True |
| | if hasattr(value, "__pt_formatted_text__"): |
| | return True |
| | return False |
| |
|
| |
|
| | class FormattedText(StyleAndTextTuples): |
| | """ |
| | A list of ``(style, text)`` tuples. |
| | |
| | (In some situations, this can also be ``(style, text, mouse_handler)`` |
| | tuples.) |
| | """ |
| |
|
| | def __pt_formatted_text__(self) -> StyleAndTextTuples: |
| | return self |
| |
|
| | def __repr__(self) -> str: |
| | return f"FormattedText({super().__repr__()})" |
| |
|
| |
|
| | class Template: |
| | """ |
| | Template for string interpolation with formatted text. |
| | |
| | Example:: |
| | |
| | Template(' ... {} ... ').format(HTML(...)) |
| | |
| | :param text: Plain text. |
| | """ |
| |
|
| | def __init__(self, text: str) -> None: |
| | assert "{0}" not in text |
| | self.text = text |
| |
|
| | def format(self, *values: AnyFormattedText) -> AnyFormattedText: |
| | def get_result() -> AnyFormattedText: |
| | |
| | parts = self.text.split("{}") |
| | assert len(parts) - 1 == len(values) |
| |
|
| | result = FormattedText() |
| | for part, val in zip(parts, values): |
| | result.append(("", part)) |
| | result.extend(to_formatted_text(val)) |
| | result.append(("", parts[-1])) |
| | return result |
| |
|
| | return get_result |
| |
|
| |
|
| | def merge_formatted_text(items: Iterable[AnyFormattedText]) -> AnyFormattedText: |
| | """ |
| | Merge (Concatenate) several pieces of formatted text together. |
| | """ |
| |
|
| | def _merge_formatted_text() -> AnyFormattedText: |
| | result = FormattedText() |
| | for i in items: |
| | result.extend(to_formatted_text(i)) |
| | return result |
| |
|
| | return _merge_formatted_text |
| |
|