Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
|
@ -0,0 +1,52 @@
|
|||
"""
|
||||
Many places in prompt_toolkit can take either plain text, or formatted text.
|
||||
For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either
|
||||
plain text or formatted text for the prompt. The
|
||||
:class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain
|
||||
text or formatted text.
|
||||
|
||||
In any case, there is an input that can either be just plain text (a string),
|
||||
an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of
|
||||
`(style_string, text)` tuples. The :func:`.to_formatted_text` conversion
|
||||
function takes any of these and turns all of them into such a tuple sequence.
|
||||
"""
|
||||
from .ansi import ANSI
|
||||
from .base import (
|
||||
AnyFormattedText,
|
||||
FormattedText,
|
||||
StyleAndTextTuples,
|
||||
Template,
|
||||
is_formatted_text,
|
||||
merge_formatted_text,
|
||||
to_formatted_text,
|
||||
)
|
||||
from .html import HTML
|
||||
from .pygments import PygmentsTokens
|
||||
from .utils import (
|
||||
fragment_list_len,
|
||||
fragment_list_to_text,
|
||||
fragment_list_width,
|
||||
split_lines,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base.
|
||||
"AnyFormattedText",
|
||||
"to_formatted_text",
|
||||
"is_formatted_text",
|
||||
"Template",
|
||||
"merge_formatted_text",
|
||||
"FormattedText",
|
||||
"StyleAndTextTuples",
|
||||
# HTML.
|
||||
"HTML",
|
||||
# ANSI.
|
||||
"ANSI",
|
||||
# Pygments.
|
||||
"PygmentsTokens",
|
||||
# Utils.
|
||||
"fragment_list_len",
|
||||
"fragment_list_width",
|
||||
"fragment_list_to_text",
|
||||
"split_lines",
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
246
venv/Lib/site-packages/prompt_toolkit/formatted_text/ansi.py
Normal file
246
venv/Lib/site-packages/prompt_toolkit/formatted_text/ansi.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
from typing import Generator, List, Optional
|
||||
|
||||
from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS
|
||||
from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table
|
||||
|
||||
from .base import StyleAndTextTuples
|
||||
|
||||
__all__ = [
|
||||
"ANSI",
|
||||
"ansi_escape",
|
||||
]
|
||||
|
||||
|
||||
class ANSI:
|
||||
"""
|
||||
ANSI formatted text.
|
||||
Take something ANSI escaped text, for use as a formatted string. E.g.
|
||||
|
||||
::
|
||||
|
||||
ANSI('\\x1b[31mhello \\x1b[32mworld')
|
||||
|
||||
Characters between ``\\001`` and ``\\002`` are supposed to have a zero width
|
||||
when printed, but these are literally sent to the terminal output. This can
|
||||
be used for instance, for inserting Final Term prompt commands. They will
|
||||
be translated into a prompt_toolkit '[ZeroWidthEscape]' fragment.
|
||||
"""
|
||||
|
||||
def __init__(self, value: str) -> None:
|
||||
self.value = value
|
||||
self._formatted_text: StyleAndTextTuples = []
|
||||
|
||||
# Default style attributes.
|
||||
self._color: Optional[str] = None
|
||||
self._bgcolor: Optional[str] = None
|
||||
self._bold = False
|
||||
self._underline = False
|
||||
self._italic = False
|
||||
self._blink = False
|
||||
self._reverse = False
|
||||
self._hidden = False
|
||||
|
||||
# Process received text.
|
||||
parser = self._parse_corot()
|
||||
parser.send(None) # type: ignore
|
||||
for c in value:
|
||||
parser.send(c)
|
||||
|
||||
def _parse_corot(self) -> Generator[None, str, None]:
|
||||
"""
|
||||
Coroutine that parses the ANSI escape sequences.
|
||||
"""
|
||||
style = ""
|
||||
formatted_text = self._formatted_text
|
||||
|
||||
while True:
|
||||
csi = False
|
||||
c = yield
|
||||
|
||||
# Everything between \001 and \002 should become a ZeroWidthEscape.
|
||||
if c == "\001":
|
||||
escaped_text = ""
|
||||
while c != "\002":
|
||||
c = yield
|
||||
if c == "\002":
|
||||
formatted_text.append(("[ZeroWidthEscape]", escaped_text))
|
||||
c = yield
|
||||
break
|
||||
else:
|
||||
escaped_text += c
|
||||
|
||||
if c == "\x1b":
|
||||
# Start of color escape sequence.
|
||||
square_bracket = yield
|
||||
if square_bracket == "[":
|
||||
csi = True
|
||||
else:
|
||||
continue
|
||||
elif c == "\x9b":
|
||||
csi = True
|
||||
|
||||
if csi:
|
||||
# Got a CSI sequence. Color codes are following.
|
||||
current = ""
|
||||
params = []
|
||||
while True:
|
||||
char = yield
|
||||
if char.isdigit():
|
||||
current += char
|
||||
else:
|
||||
params.append(min(int(current or 0), 9999))
|
||||
if char == ";":
|
||||
current = ""
|
||||
elif char == "m":
|
||||
# Set attributes and token.
|
||||
self._select_graphic_rendition(params)
|
||||
style = self._create_style_string()
|
||||
break
|
||||
else:
|
||||
# Ignore unsupported sequence.
|
||||
break
|
||||
else:
|
||||
# Add current character.
|
||||
# NOTE: At this point, we could merge the current character
|
||||
# into the previous tuple if the style did not change,
|
||||
# however, it's not worth the effort given that it will
|
||||
# be "Exploded" once again when it's rendered to the
|
||||
# output.
|
||||
formatted_text.append((style, c))
|
||||
|
||||
def _select_graphic_rendition(self, attrs: List[int]) -> None:
|
||||
"""
|
||||
Taken a list of graphics attributes and apply changes.
|
||||
"""
|
||||
if not attrs:
|
||||
attrs = [0]
|
||||
else:
|
||||
attrs = list(attrs[::-1])
|
||||
|
||||
while attrs:
|
||||
attr = attrs.pop()
|
||||
|
||||
if attr in _fg_colors:
|
||||
self._color = _fg_colors[attr]
|
||||
elif attr in _bg_colors:
|
||||
self._bgcolor = _bg_colors[attr]
|
||||
elif attr == 1:
|
||||
self._bold = True
|
||||
elif attr == 3:
|
||||
self._italic = True
|
||||
elif attr == 4:
|
||||
self._underline = True
|
||||
elif attr == 5:
|
||||
self._blink = True
|
||||
elif attr == 6:
|
||||
self._blink = True # Fast blink.
|
||||
elif attr == 7:
|
||||
self._reverse = True
|
||||
elif attr == 8:
|
||||
self._hidden = True
|
||||
elif attr == 22:
|
||||
self._bold = False
|
||||
elif attr == 23:
|
||||
self._italic = False
|
||||
elif attr == 24:
|
||||
self._underline = False
|
||||
elif attr == 25:
|
||||
self._blink = False
|
||||
elif attr == 27:
|
||||
self._reverse = False
|
||||
elif not attr:
|
||||
self._color = None
|
||||
self._bgcolor = None
|
||||
self._bold = False
|
||||
self._underline = False
|
||||
self._italic = False
|
||||
self._blink = False
|
||||
self._reverse = False
|
||||
self._hidden = False
|
||||
|
||||
elif attr in (38, 48) and len(attrs) > 1:
|
||||
n = attrs.pop()
|
||||
|
||||
# 256 colors.
|
||||
if n == 5 and len(attrs) >= 1:
|
||||
if attr == 38:
|
||||
m = attrs.pop()
|
||||
self._color = _256_colors.get(m)
|
||||
elif attr == 48:
|
||||
m = attrs.pop()
|
||||
self._bgcolor = _256_colors.get(m)
|
||||
|
||||
# True colors.
|
||||
if n == 2 and len(attrs) >= 3:
|
||||
try:
|
||||
color_str = "#%02x%02x%02x" % (
|
||||
attrs.pop(),
|
||||
attrs.pop(),
|
||||
attrs.pop(),
|
||||
)
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if attr == 38:
|
||||
self._color = color_str
|
||||
elif attr == 48:
|
||||
self._bgcolor = color_str
|
||||
|
||||
def _create_style_string(self) -> str:
|
||||
"""
|
||||
Turn current style flags into a string for usage in a formatted text.
|
||||
"""
|
||||
result = []
|
||||
if self._color:
|
||||
result.append(self._color)
|
||||
if self._bgcolor:
|
||||
result.append("bg:" + self._bgcolor)
|
||||
if self._bold:
|
||||
result.append("bold")
|
||||
if self._underline:
|
||||
result.append("underline")
|
||||
if self._italic:
|
||||
result.append("italic")
|
||||
if self._blink:
|
||||
result.append("blink")
|
||||
if self._reverse:
|
||||
result.append("reverse")
|
||||
if self._hidden:
|
||||
result.append("hidden")
|
||||
|
||||
return " ".join(result)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "ANSI(%r)" % (self.value,)
|
||||
|
||||
def __pt_formatted_text__(self) -> StyleAndTextTuples:
|
||||
return self._formatted_text
|
||||
|
||||
def format(self, *args: str, **kwargs: str) -> "ANSI":
|
||||
"""
|
||||
Like `str.format`, but make sure that the arguments are properly
|
||||
escaped. (No ANSI escapes can be injected.)
|
||||
"""
|
||||
# Escape all the arguments.
|
||||
args = tuple(ansi_escape(a) for a in args)
|
||||
kwargs = {k: ansi_escape(v) for k, v in kwargs.items()}
|
||||
|
||||
return ANSI(self.value.format(*args, **kwargs))
|
||||
|
||||
|
||||
# Mapping of the ANSI color codes to their names.
|
||||
_fg_colors = {v: k for k, v in FG_ANSI_COLORS.items()}
|
||||
_bg_colors = {v: k for k, v in BG_ANSI_COLORS.items()}
|
||||
|
||||
# Mapping of the escape codes for 256colors to their 'ffffff' value.
|
||||
_256_colors = {}
|
||||
|
||||
for i, (r, g, b) in enumerate(_256_colors_table.colors):
|
||||
_256_colors[i] = "#%02x%02x%02x" % (r, g, b)
|
||||
|
||||
|
||||
def ansi_escape(text: str) -> str:
|
||||
"""
|
||||
Replace characters with a special meaning.
|
||||
"""
|
||||
return text.replace("\x1b", "?").replace("\b", "?")
|
175
venv/Lib/site-packages/prompt_toolkit/formatted_text/base.py
Normal file
175
venv/Lib/site-packages/prompt_toolkit/formatted_text/base.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
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
|
||||
|
||||
__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], None]]
|
||||
]
|
||||
|
||||
# List of (style, text) tuples.
|
||||
StyleAndTextTuples = List[OneStyleAndTextTuple]
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
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[[], 'AnyFormattedText'] # Recursive definition not supported by mypy.
|
||||
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: Union[FormattedText, StyleAndTextTuples]
|
||||
|
||||
if value is None:
|
||||
result = []
|
||||
elif isinstance(value, str):
|
||||
result = [("", value)]
|
||||
elif isinstance(value, list):
|
||||
result = cast(StyleAndTextTuples, 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 = [("", "{}".format(value))]
|
||||
else:
|
||||
raise ValueError(
|
||||
"No formatted text. Expecting a unicode object, "
|
||||
"HTML, ANSI or a FormattedText instance. Got %r" % (value,)
|
||||
)
|
||||
|
||||
# Apply extra style.
|
||||
if style:
|
||||
result = cast(
|
||||
StyleAndTextTuples,
|
||||
[(style + " " + item_style, *rest) for item_style, *rest in result],
|
||||
)
|
||||
|
||||
# Make sure the result is wrapped in a `FormattedText`. Among other
|
||||
# reasons, this is important for `print_formatted_text` to work correctly
|
||||
# and distinguish between lists and formatted text.
|
||||
if isinstance(result, FormattedText):
|
||||
return result
|
||||
else:
|
||||
return FormattedText(result)
|
||||
|
||||
|
||||
def is_formatted_text(value: object) -> bool:
|
||||
"""
|
||||
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 "FormattedText(%s)" % 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():
|
||||
# Split the template in parts.
|
||||
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():
|
||||
result = FormattedText()
|
||||
for i in items:
|
||||
result.extend(to_formatted_text(i))
|
||||
return result
|
||||
|
||||
return _merge_formatted_text
|
137
venv/Lib/site-packages/prompt_toolkit/formatted_text/html.py
Normal file
137
venv/Lib/site-packages/prompt_toolkit/formatted_text/html.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
import xml.dom.minidom as minidom
|
||||
from typing import Any, List, Tuple, Union
|
||||
|
||||
from .base import FormattedText, StyleAndTextTuples
|
||||
|
||||
__all__ = ["HTML"]
|
||||
|
||||
|
||||
class HTML:
|
||||
"""
|
||||
HTML formatted text.
|
||||
Take something HTML-like, for use as a formatted string.
|
||||
|
||||
::
|
||||
|
||||
# Turn something into red.
|
||||
HTML('<style fg="ansired" bg="#00ff44">...</style>')
|
||||
|
||||
# Italic, bold and underline.
|
||||
HTML('<i>...</i>')
|
||||
HTML('<b>...</b>')
|
||||
HTML('<u>...</u>')
|
||||
|
||||
All HTML elements become available as a "class" in the style sheet.
|
||||
E.g. ``<username>...</username>`` can be styled, by setting a style for
|
||||
``username``.
|
||||
"""
|
||||
|
||||
def __init__(self, value: str) -> None:
|
||||
self.value = value
|
||||
document = minidom.parseString("<html-root>%s</html-root>" % (value,))
|
||||
|
||||
result: StyleAndTextTuples = []
|
||||
name_stack: List[str] = []
|
||||
fg_stack: List[str] = []
|
||||
bg_stack: List[str] = []
|
||||
|
||||
def get_current_style() -> str:
|
||||
" Build style string for current node. "
|
||||
parts = []
|
||||
if name_stack:
|
||||
parts.append("class:" + ",".join(name_stack))
|
||||
|
||||
if fg_stack:
|
||||
parts.append("fg:" + fg_stack[-1])
|
||||
if bg_stack:
|
||||
parts.append("bg:" + bg_stack[-1])
|
||||
return " ".join(parts)
|
||||
|
||||
def process_node(node: Any) -> None:
|
||||
" Process node recursively. "
|
||||
for child in node.childNodes:
|
||||
if child.nodeType == child.TEXT_NODE:
|
||||
result.append((get_current_style(), child.data))
|
||||
else:
|
||||
add_to_name_stack = child.nodeName not in (
|
||||
"#document",
|
||||
"html-root",
|
||||
"style",
|
||||
)
|
||||
fg = bg = ""
|
||||
|
||||
for k, v in child.attributes.items():
|
||||
if k == "fg":
|
||||
fg = v
|
||||
if k == "bg":
|
||||
bg = v
|
||||
if k == "color":
|
||||
fg = v # Alias for 'fg'.
|
||||
|
||||
# Check for spaces in attributes. This would result in
|
||||
# invalid style strings otherwise.
|
||||
if " " in fg:
|
||||
raise ValueError('"fg" attribute contains a space.')
|
||||
if " " in bg:
|
||||
raise ValueError('"bg" attribute contains a space.')
|
||||
|
||||
if add_to_name_stack:
|
||||
name_stack.append(child.nodeName)
|
||||
if fg:
|
||||
fg_stack.append(fg)
|
||||
if bg:
|
||||
bg_stack.append(bg)
|
||||
|
||||
process_node(child)
|
||||
|
||||
if add_to_name_stack:
|
||||
name_stack.pop()
|
||||
if fg:
|
||||
fg_stack.pop()
|
||||
if bg:
|
||||
bg_stack.pop()
|
||||
|
||||
process_node(document)
|
||||
|
||||
self.formatted_text = FormattedText(result)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "HTML(%r)" % (self.value,)
|
||||
|
||||
def __pt_formatted_text__(self) -> StyleAndTextTuples:
|
||||
return self.formatted_text
|
||||
|
||||
def format(self, *args: object, **kwargs: object) -> "HTML":
|
||||
"""
|
||||
Like `str.format`, but make sure that the arguments are properly
|
||||
escaped.
|
||||
"""
|
||||
# Escape all the arguments.
|
||||
escaped_args = [html_escape(a) for a in args]
|
||||
escaped_kwargs = {k: html_escape(v) for k, v in kwargs.items()}
|
||||
|
||||
return HTML(self.value.format(*escaped_args, **escaped_kwargs))
|
||||
|
||||
def __mod__(self, value: Union[object, Tuple[object, ...]]) -> "HTML":
|
||||
"""
|
||||
HTML('<b>%s</b>') % value
|
||||
"""
|
||||
if not isinstance(value, tuple):
|
||||
value = (value,)
|
||||
|
||||
value = tuple(html_escape(i) for i in value)
|
||||
return HTML(self.value % value)
|
||||
|
||||
|
||||
def html_escape(text: object) -> str:
|
||||
# The string interpolation functions also take integers and other types.
|
||||
# Convert to string first.
|
||||
if not isinstance(text, str):
|
||||
text = "{}".format(text)
|
||||
|
||||
return (
|
||||
text.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """)
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
from typing import TYPE_CHECKING, List, Tuple
|
||||
|
||||
from prompt_toolkit.styles.pygments import pygments_token_to_classname
|
||||
|
||||
from .base import StyleAndTextTuples
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pygments.token import Token
|
||||
|
||||
__all__ = [
|
||||
"PygmentsTokens",
|
||||
]
|
||||
|
||||
|
||||
class PygmentsTokens:
|
||||
"""
|
||||
Turn a pygments token list into a list of prompt_toolkit text fragments
|
||||
(``(style_str, text)`` tuples).
|
||||
"""
|
||||
|
||||
def __init__(self, token_list: List[Tuple["Token", str]]) -> None:
|
||||
self.token_list = token_list
|
||||
|
||||
def __pt_formatted_text__(self) -> StyleAndTextTuples:
|
||||
result: StyleAndTextTuples = []
|
||||
|
||||
for token, text in self.token_list:
|
||||
result.append(("class:" + pygments_token_to_classname(token), text))
|
||||
|
||||
return result
|
|
@ -0,0 +1,85 @@
|
|||
"""
|
||||
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 typing import Iterable, cast
|
||||
|
||||
from prompt_toolkit.utils import get_cwidth
|
||||
|
||||
from .base import OneStyleAndTextTuple, StyleAndTextTuples
|
||||
|
||||
__all__ = [
|
||||
"fragment_list_len",
|
||||
"fragment_list_width",
|
||||
"fragment_list_to_text",
|
||||
"split_lines",
|
||||
]
|
||||
|
||||
|
||||
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
|
Loading…
Add table
Add a link
Reference in a new issue