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