1""" 
    2Utilities for manipulating formatted text. 
    3 
    4When ``to_formatted_text`` has been called, we get a list of ``(style, text)`` 
    5tuples. This file contains functions for manipulating such a list. 
    6""" 
    7 
    8from __future__ import annotations 
    9 
    10from typing import Iterable, cast 
    11 
    12from prompt_toolkit.utils import get_cwidth 
    13 
    14from .base import ( 
    15    AnyFormattedText, 
    16    OneStyleAndTextTuple, 
    17    StyleAndTextTuples, 
    18    to_formatted_text, 
    19) 
    20 
    21__all__ = [ 
    22    "to_plain_text", 
    23    "fragment_list_len", 
    24    "fragment_list_width", 
    25    "fragment_list_to_text", 
    26    "split_lines", 
    27] 
    28 
    29 
    30def to_plain_text(value: AnyFormattedText) -> str: 
    31    """ 
    32    Turn any kind of formatted text back into plain text. 
    33    """ 
    34    return fragment_list_to_text(to_formatted_text(value)) 
    35 
    36 
    37def fragment_list_len(fragments: StyleAndTextTuples) -> int: 
    38    """ 
    39    Return the amount of characters in this text fragment list. 
    40 
    41    :param fragments: List of ``(style_str, text)`` or 
    42        ``(style_str, text, mouse_handler)`` tuples. 
    43    """ 
    44    ZeroWidthEscape = "[ZeroWidthEscape]" 
    45    return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0]) 
    46 
    47 
    48def fragment_list_width(fragments: StyleAndTextTuples) -> int: 
    49    """ 
    50    Return the character width of this text fragment list. 
    51    (Take double width characters into account.) 
    52 
    53    :param fragments: List of ``(style_str, text)`` or 
    54        ``(style_str, text, mouse_handler)`` tuples. 
    55    """ 
    56    ZeroWidthEscape = "[ZeroWidthEscape]" 
    57    return sum( 
    58        get_cwidth(c) 
    59        for item in fragments 
    60        for c in item[1] 
    61        if ZeroWidthEscape not in item[0] 
    62    ) 
    63 
    64 
    65def fragment_list_to_text(fragments: StyleAndTextTuples) -> str: 
    66    """ 
    67    Concatenate all the text parts again. 
    68 
    69    :param fragments: List of ``(style_str, text)`` or 
    70        ``(style_str, text, mouse_handler)`` tuples. 
    71    """ 
    72    ZeroWidthEscape = "[ZeroWidthEscape]" 
    73    return "".join(item[1] for item in fragments if ZeroWidthEscape not in item[0]) 
    74 
    75 
    76def split_lines( 
    77    fragments: Iterable[OneStyleAndTextTuple], 
    78) -> Iterable[StyleAndTextTuples]: 
    79    """ 
    80    Take a single list of (style_str, text) tuples and yield one such list for each 
    81    line. Just like str.split, this will yield at least one item. 
    82 
    83    :param fragments: Iterable of ``(style_str, text)`` or 
    84        ``(style_str, text, mouse_handler)`` tuples. 
    85    """ 
    86    line: StyleAndTextTuples = [] 
    87 
    88    for style, string, *mouse_handler in fragments: 
    89        parts = string.split("\n") 
    90 
    91        for part in parts[:-1]: 
    92            line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler))) 
    93            yield line 
    94            line = [] 
    95 
    96        line.append(cast(OneStyleAndTextTuple, (style, parts[-1], *mouse_handler))) 
    97 
    98    # Always yield the last line, even when this is an empty line. This ensures 
    99    # that when `fragments` ends with a newline character, an additional empty 
    100    # line is yielded. (Otherwise, there's no way to differentiate between the 
    101    # cases where `fragments` does and doesn't end with a newline.) 
    102    yield line