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