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