mirror of
https://github.com/Textualize/rich.git
synced 2026-02-06 10:58:48 +00:00
added pretty functionality
This commit is contained in:
parent
236338a76b
commit
6dfdf1a40c
11
CHANGELOG.md
11
CHANGELOG.md
@ -5,13 +5,22 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [5.0.1] - Unreleased
|
||||
## [5.1.0] - Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added Text.cell_len
|
||||
- Added helpful message regarding unicode decoding errors
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed deprecation warnings re backslash https://github.com/willmcgugan/rich/issues/210
|
||||
- Fixed repr highlighting of scientific notation, e.g. 1e100
|
||||
|
||||
### Changed
|
||||
|
||||
- Implemented pretty printing, and removed pprintpp from dependancies
|
||||
|
||||
## [5.0.0] - 2020-08-02
|
||||
|
||||
### Changed
|
||||
|
||||
23
poetry.lock
generated
23
poetry.lock
generated
@ -1,7 +1,7 @@
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Disable App Nap on OS X 10.9"
|
||||
marker = "python_version >= \"3.3\" and sys_platform == \"darwin\" or platform_system == \"Darwin\""
|
||||
marker = "sys_platform == \"darwin\" or platform_system == \"Darwin\" or python_version >= \"3.3\" and sys_platform == \"darwin\""
|
||||
name = "appnope"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
@ -24,7 +24,6 @@ tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.i
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Specifications for callback functions passed in to an API"
|
||||
marker = "python_version >= \"3.3\""
|
||||
name = "backcall"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
@ -193,7 +192,6 @@ test = ["pytest (>=3.6.0)", "pytest-cov", "mock"]
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "An autocompletion tool for Python that can be used for text editors."
|
||||
marker = "python_version >= \"3.3\""
|
||||
name = "jedi"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
@ -394,7 +392,7 @@ testing = ["docopt", "pytest (>=3.0.7)"]
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Pexpect allows easy control of interactive console applications."
|
||||
marker = "python_version >= \"3.3\" and sys_platform != \"win32\""
|
||||
marker = "python_version >= \"3.3\" and sys_platform != \"win32\" or sys_platform != \"win32\""
|
||||
name = "pexpect"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
@ -406,20 +404,11 @@ ptyprocess = ">=0.5"
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Tiny 'shelve'-like database with concurrency support"
|
||||
marker = "python_version >= \"3.3\""
|
||||
name = "pickleshare"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "0.7.5"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A drop-in replacement for pprint that's actually pretty"
|
||||
name = "pprintpp"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.4.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python client for the Prometheus monitoring system."
|
||||
@ -434,7 +423,6 @@ twisted = ["twisted"]
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
marker = "python_version >= \"3.3\""
|
||||
name = "prompt-toolkit"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
@ -633,7 +621,8 @@ testing = ["jaraco.itertools", "func-timeout"]
|
||||
jupyter = ["ipywidgets"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "e2c24649506040755aa391e68196f00a65ec8e7f7130feb9254a3b6d6352bb72"
|
||||
content-hash = "64c8b886ebf3d4fec7647f65af7ebf6b20e031fbec205d96af3c937ff686c415"
|
||||
lock-version = "1.0"
|
||||
python-versions = "^3.6"
|
||||
|
||||
[metadata.files]
|
||||
@ -787,10 +776,6 @@ pickleshare = [
|
||||
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
|
||||
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
|
||||
]
|
||||
pprintpp = [
|
||||
{file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"},
|
||||
{file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"},
|
||||
]
|
||||
prometheus-client = [
|
||||
{file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"},
|
||||
{file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
name = "rich"
|
||||
homepage = "https://github.com/willmcgugan/rich"
|
||||
documentation = "https://rich.readthedocs.io/en/latest/"
|
||||
version = "5.0.1"
|
||||
version = "5.1.0"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
||||
license = "MIT"
|
||||
@ -23,7 +23,6 @@ include = ["rich/py.typed"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.6"
|
||||
pprintpp = "^0.4.0"
|
||||
typing-extensions = "^3.7.4"
|
||||
dataclasses = {version="^0.7", python = "~3.6"}
|
||||
pygments = "^2.6.0"
|
||||
|
||||
@ -984,14 +984,18 @@ class Console:
|
||||
else:
|
||||
text = self._render_buffer()
|
||||
if text:
|
||||
if WINDOWS: # pragma: no cover
|
||||
# https://bugs.python.org/issue37871
|
||||
write = self.file.write
|
||||
for line in text.splitlines(True):
|
||||
write(line)
|
||||
else:
|
||||
self.file.write(text)
|
||||
self.file.flush()
|
||||
try:
|
||||
if WINDOWS: # pragma: no cover
|
||||
# https://bugs.python.org/issue37871
|
||||
write = self.file.write
|
||||
for line in text.splitlines(True):
|
||||
write(line)
|
||||
else:
|
||||
self.file.write(text)
|
||||
self.file.flush()
|
||||
except UnicodeEncodeError as error:
|
||||
error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
|
||||
raise
|
||||
|
||||
def _render_buffer(self) -> str:
|
||||
"""Render buffered output, and clear buffer."""
|
||||
|
||||
@ -47,8 +47,10 @@ DEFAULT_STYLES: Dict[str, Style] = {
|
||||
"log.time": Style(color="cyan", dim=True),
|
||||
"log.message": Style(),
|
||||
"log.path": Style(dim=True),
|
||||
"repr.error": Style(color="red", bold=True),
|
||||
"repr.str": Style(color="green", italic=False, bold=False),
|
||||
"repr.brace": Style(bold=True),
|
||||
"repr.comma": Style(bold=True),
|
||||
"repr.tag_start": Style(bold=True),
|
||||
"repr.tag_name": Style(color="bright_magenta", bold=True),
|
||||
"repr.tag_contents": Style(color="default"),
|
||||
|
||||
282
rich/pretty.py
282
rich/pretty.py
@ -1,9 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
import sys
|
||||
|
||||
from typing import Any, Iterable, List, Optional, Tuple, TYPE_CHECKING
|
||||
from dataclasses import dataclass, field
|
||||
from rich.highlighter import ReprHighlighter
|
||||
|
||||
from pprintpp import pformat
|
||||
from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING
|
||||
|
||||
from .cells import cell_len
|
||||
from .highlighter import Highlighter, NullHighlighter, ReprHighlighter
|
||||
from ._loop import loop_last
|
||||
from .measure import Measurement
|
||||
from .text import Text
|
||||
@ -12,101 +15,250 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
from .console import Console, ConsoleOptions, HighlighterType, RenderResult
|
||||
|
||||
|
||||
def install(console: "Console" = None) -> None:
|
||||
"""Install automatic pretty printing in the Python REPL."""
|
||||
from rich import get_console
|
||||
|
||||
console = console or get_console()
|
||||
|
||||
def display_hook(value: Any) -> None:
|
||||
if value is not None:
|
||||
console.print(
|
||||
value
|
||||
if hasattr(value, "__rich_console__") or hasattr(value, "__rich__")
|
||||
else pretty_repr(value)
|
||||
)
|
||||
|
||||
sys.displayhook = display_hook
|
||||
|
||||
|
||||
class Pretty:
|
||||
"""A rich renderable that pretty prints an object."""
|
||||
|
||||
def __init__(self, _object: Any, highlighter: "HighlighterType" = None) -> None:
|
||||
self._object = _object
|
||||
self.highlighter = highlighter or Text
|
||||
self.highlighter = highlighter or NullHighlighter()
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
# TODO: pformat tends to render a smaller width than it needs to, investigate why
|
||||
print(options)
|
||||
_min, max_width = Measurement.get(console, self, options.max_width)
|
||||
pretty_str = pformat(self._object, width=max_width)
|
||||
pretty_str = pretty_str.replace("\r", "")
|
||||
pretty_text = self.highlighter(pretty_str)
|
||||
pretty_text = pretty_repr(self._object, max_width=options.max_width)
|
||||
yield pretty_text
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
pretty_str = pformat(self._object, width=max_width)
|
||||
pretty_str = pretty_str.replace("\r", "")
|
||||
text = Text(pretty_str)
|
||||
measurement = Measurement.get(console, text, max_width)
|
||||
print(measurement)
|
||||
return measurement
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Node:
|
||||
indent: int = -1
|
||||
object_repr: Optional[str] = None
|
||||
name: str = ""
|
||||
braces: Optional[Tuple[str, str]] = None
|
||||
values: Optional[List["_Node"]] = None
|
||||
items: Optional[List[Tuple[str, "_Node"]]] = None
|
||||
expanded: bool = False
|
||||
pretty_text = pretty_repr(self._object, max_width=max_width)
|
||||
text_width = max(cell_len(line) for line in pretty_text.plain.splitlines())
|
||||
return Measurement(text_width, text_width)
|
||||
|
||||
|
||||
_BRACES = {
|
||||
list: ("", "[", "]"),
|
||||
tuple: ("", "(", ")"),
|
||||
set: ("", "{", "}"),
|
||||
frozenset: ("frozenset", "({", "})"),
|
||||
dict: (Text("{", "repr.brace"), Text("}", "repr.brace")),
|
||||
frozenset: (
|
||||
Text.assemble("frozenset(", ("{", "repr.brace")),
|
||||
Text.assemble(("}", "repr.brace"), ")"),
|
||||
),
|
||||
list: (Text("[", "repr.brace"), Text("]", "repr.brace")),
|
||||
set: (Text("{", "repr.brace"), Text("}", "repr.brace")),
|
||||
tuple: (Text("(", "repr.brace"), Text(")", "repr.brace")),
|
||||
}
|
||||
_CONTAINERS = tuple(_BRACES.keys())
|
||||
_REPR_STYLES = {
|
||||
type(None): "repr.none",
|
||||
str: "repr.str",
|
||||
float: "repr.number",
|
||||
int: "repr.number",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Value:
|
||||
object_repr: str
|
||||
class _Line:
|
||||
"""A line in a pretty repr."""
|
||||
|
||||
parts: List[Text] = field(default_factory=list)
|
||||
_cell_len: int = 0
|
||||
|
||||
def append(self, text: Text) -> None:
|
||||
"""Add text to line."""
|
||||
# Efficiently keep track of cell length
|
||||
self.parts.append(text)
|
||||
self._cell_len += text.cell_len
|
||||
|
||||
@property
|
||||
def cell_len(self) -> int:
|
||||
return (
|
||||
self._cell_len
|
||||
if self.parts and not self.parts[-1].plain.endswith(" ")
|
||||
else self._cell_len - 1
|
||||
)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""The text as a while."""
|
||||
return Text("").join(self.parts)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Container:
|
||||
braces: Tuple[str]
|
||||
def pretty_repr(
|
||||
_object: Any,
|
||||
*,
|
||||
max_width: Optional[int] = 80,
|
||||
indent_size: int = 4,
|
||||
highlighter: Highlighter = None,
|
||||
) -> Text:
|
||||
"""Return a 'pretty' repr.
|
||||
|
||||
Args:
|
||||
_object (Any): Object to repr.
|
||||
max_width (int, optional): Maximum desired width. Defaults to 80.
|
||||
indent_size (int, optional): Number of spaces in an indent. Defaults to 4.
|
||||
highlighter (Highlighter, optional): A highlighter for repr strings. Defaults to ReprHighlighter.
|
||||
|
||||
def pretty_repr(_object: Any, *, width: int = 80, indent_size: int = 4) -> str:
|
||||
Returns:
|
||||
Text: A Text instance conaining a pretty repr.
|
||||
"""
|
||||
|
||||
class MaxLineReached(Exception):
|
||||
"""Line is greater than maximum"""
|
||||
|
||||
def __init__(self, line_no: int) -> None:
|
||||
self.line_no = line_no
|
||||
super().__init__()
|
||||
|
||||
if highlighter is None:
|
||||
highlighter = ReprHighlighter()
|
||||
|
||||
indent = " " * indent_size
|
||||
|
||||
stack = []
|
||||
push = stack.append
|
||||
expand_level = 0
|
||||
|
||||
node = _object
|
||||
lines: List[_Line] = [_Line()]
|
||||
|
||||
if isinstance(node, (tuple, list, set, dict)):
|
||||
braces = _BRACES[type(node)]
|
||||
push(_Container())
|
||||
visited_set: Set[int] = set()
|
||||
repr_cache: Dict[int, Text] = {}
|
||||
repr_cache_get = repr_cache.get
|
||||
|
||||
def add_node(parent_node: _Node, node_object: Any) -> _Node:
|
||||
if isinstance(node_object, (list, set, frozenset, tuple)):
|
||||
name, open_brace, close_brace = _BRACES[type(node_object)]
|
||||
node = _Node(
|
||||
indent=parent_node.indent + 1, braces=(open_brace, close_brace)
|
||||
)
|
||||
node.values = [add_node(node, child) for child in node_object]
|
||||
elif isinstance(node_object, dict):
|
||||
node = _Node(indent=parent_node.indent + 1, braces=("{", "}"))
|
||||
node.items = [
|
||||
(key, add_node(node, value)) for key, value in node_object.items()
|
||||
]
|
||||
def to_repr_text(node: Any) -> Text:
|
||||
node_id = id(node)
|
||||
cached = repr_cache_get(node_id)
|
||||
if cached is not None:
|
||||
return cached
|
||||
style: Optional[str]
|
||||
if node is True:
|
||||
style = "repr.bool_true"
|
||||
elif node is False:
|
||||
style = "repr.bool_false"
|
||||
else:
|
||||
node = _Node(indent=parent_node.indent + 1, object_repr=repr(node_object))
|
||||
return node
|
||||
style = _REPR_STYLES.get(type(node))
|
||||
try:
|
||||
repr_text = repr(node)
|
||||
except Exception as error:
|
||||
text = Text(f"<error in repr: {error}>", "repr.error")
|
||||
else:
|
||||
if style is None:
|
||||
text = highlighter(Text(repr_text))
|
||||
else:
|
||||
text = Text(repr_text, style)
|
||||
repr_cache[node_id] = text
|
||||
visited_set.add(node_id)
|
||||
return text
|
||||
|
||||
node = add_node(_Node(), _object)
|
||||
print(node)
|
||||
node.expanded = True
|
||||
comma = Text(", ")
|
||||
colon = Text(": ")
|
||||
line_break: Optional[int] = None
|
||||
|
||||
output = []
|
||||
def traverse(node: Any, level: int = 0) -> None:
|
||||
nonlocal line_break
|
||||
|
||||
return ""
|
||||
append_line = lines.append
|
||||
|
||||
def append_text(text: Text) -> None:
|
||||
nonlocal line_break
|
||||
line = lines[-1]
|
||||
line.append(text)
|
||||
if max_width is not None and line.cell_len > max_width:
|
||||
if line_break is not None and len(lines) <= line_break:
|
||||
return
|
||||
line_break = len(lines)
|
||||
raise MaxLineReached(level)
|
||||
|
||||
node_id = id(node)
|
||||
if node_id in visited_set:
|
||||
append_text(Text("...", "repr.error"))
|
||||
return
|
||||
|
||||
visited_set.add(node_id)
|
||||
|
||||
if type(node) in _CONTAINERS:
|
||||
brace_open, brace_close = _BRACES[type(node)]
|
||||
expanded = level < expand_level
|
||||
|
||||
append_text(brace_open)
|
||||
if isinstance(node, dict):
|
||||
for last, (key, value) in loop_last(node.items()):
|
||||
if expanded:
|
||||
append_line(_Line())
|
||||
append_text(Text(indent * (level + 1)))
|
||||
append_text(to_repr_text(key))
|
||||
append_text(colon)
|
||||
traverse(value, level + 1)
|
||||
if not last:
|
||||
append_text(comma)
|
||||
else:
|
||||
for last, value in loop_last(node):
|
||||
if expanded:
|
||||
append_line(_Line())
|
||||
append_text(Text(indent * (level + 1)))
|
||||
traverse(value, level + 1)
|
||||
if not last:
|
||||
append_text(comma)
|
||||
if expanded:
|
||||
lines.append(_Line())
|
||||
append_text(Text.assemble(f"{indent * level}", brace_close))
|
||||
else:
|
||||
append_text(brace_close)
|
||||
else:
|
||||
append_text(to_repr_text(node))
|
||||
|
||||
visited_set.remove(node_id)
|
||||
|
||||
while True:
|
||||
try:
|
||||
traverse(_object)
|
||||
except MaxLineReached as max_line:
|
||||
del lines[:]
|
||||
visited_set.clear()
|
||||
lines.append(_Line())
|
||||
expand_level += 1
|
||||
else:
|
||||
break # pragma: no cover
|
||||
|
||||
return Text("\n").join(line.text for line in lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
data = {"d": [1, "Hello World!", 2, 3, 4, {5, 6, 7, (1, 2, 3, 4), 8}]}
|
||||
from collections import defaultdict
|
||||
|
||||
class BrokenRepr:
|
||||
def __repr__(self):
|
||||
1 / 0
|
||||
|
||||
d = defaultdict(int)
|
||||
d["foo"] = 5
|
||||
data = {
|
||||
"foo": [1, "Hello World!", 2, 3, 4, {5, 6, 7, (1, 2, 3, 4), 8}],
|
||||
"bar": frozenset({1, 2, 3}),
|
||||
False: "This is false",
|
||||
True: "This is true",
|
||||
None: "This is None",
|
||||
"Broken": BrokenRepr(),
|
||||
}
|
||||
data["foo"].append(data)
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
print("-" * console.width)
|
||||
from rich import print
|
||||
|
||||
print(pretty_repr(data))
|
||||
|
||||
p = Pretty(data)
|
||||
print(Measurement.get(console, p))
|
||||
console.print(p)
|
||||
|
||||
@ -13,11 +13,11 @@ class PromptError(Exception):
|
||||
|
||||
|
||||
class InvalidResponse(PromptError):
|
||||
"""Exception to indicate a response was invalid. Raise this within process_respons to indicate an error
|
||||
"""Exception to indicate a response was invalid. Raise this within process_response() to indicate an error
|
||||
and provide an error message.
|
||||
|
||||
Args:
|
||||
message (str): Error message.
|
||||
message (Union[str, Text]): Error message.
|
||||
"""
|
||||
|
||||
def __init__(self, message: TextType) -> None:
|
||||
|
||||
@ -27,7 +27,7 @@ def render_scope(
|
||||
RenderableType: A renderable object.
|
||||
"""
|
||||
highlighter = ReprHighlighter()
|
||||
items_table = Table.grid(padding=(0, 1))
|
||||
items_table = Table.grid(padding=(0, 1), expand=False)
|
||||
items_table.add_column(justify="right")
|
||||
|
||||
def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
|
||||
|
||||
@ -432,6 +432,12 @@ class Table(JupyterMixin):
|
||||
widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)
|
||||
table_width = sum(widths)
|
||||
|
||||
width_ranges = [
|
||||
self._measure_column(console, column, width)
|
||||
for width, column in zip(widths, columns)
|
||||
]
|
||||
widths = [_range.maximum or 1 for _range in width_ranges]
|
||||
|
||||
if table_width < max_width and self.expand:
|
||||
pad_widths = ratio_distribute(max_width - table_width, widths)
|
||||
widths = [_width + pad for _width, pad in zip(widths, pad_widths)]
|
||||
|
||||
89
rich/text.py
89
rich/text.py
@ -180,6 +180,11 @@ class Text(JupyterMixin):
|
||||
return other.plain in self.plain
|
||||
return False
|
||||
|
||||
@property
|
||||
def cell_len(self) -> int:
|
||||
"""Get the number of cells required to render this text."""
|
||||
return cell_len(self.plain)
|
||||
|
||||
@classmethod
|
||||
def from_markup(
|
||||
cls,
|
||||
@ -560,11 +565,16 @@ class Text(JupyterMixin):
|
||||
"""
|
||||
|
||||
new_text = self.blank_copy()
|
||||
append = new_text.append
|
||||
for last, line in loop_last(lines):
|
||||
append(line)
|
||||
if not last:
|
||||
append(self)
|
||||
append = new_text.append_text
|
||||
if self.plain:
|
||||
for last, line in loop_last(lines):
|
||||
append(line)
|
||||
if not last:
|
||||
append(self)
|
||||
else:
|
||||
for line in lines:
|
||||
append(line)
|
||||
|
||||
return new_text
|
||||
|
||||
def tabs_to_spaces(self, tab_size: int = None) -> "Text":
|
||||
@ -716,38 +726,57 @@ class Text(JupyterMixin):
|
||||
style (str, optional): A style name. Defaults to None.
|
||||
|
||||
Returns:
|
||||
text (Text): Returns self for chaining.
|
||||
Text: Returns self for chaining.
|
||||
"""
|
||||
|
||||
if not isinstance(text, (str, Text)):
|
||||
raise TypeError("Only str or Text can be appended to Text")
|
||||
|
||||
if not len(text):
|
||||
return self
|
||||
if isinstance(text, str):
|
||||
text = strip_control_codes(text)
|
||||
self._text.append(text)
|
||||
offset = len(self)
|
||||
text_length = len(text)
|
||||
if style is not None:
|
||||
self._spans.append(Span(offset, offset + text_length, style))
|
||||
self._length += text_length
|
||||
elif isinstance(text, Text):
|
||||
_Span = Span
|
||||
if style is not None:
|
||||
raise ValueError("style must not be set when appending Text instance")
|
||||
if len(text):
|
||||
if isinstance(text, str):
|
||||
text = strip_control_codes(text)
|
||||
self._text.append(text)
|
||||
offset = len(self)
|
||||
text_length = len(text)
|
||||
if style is not None:
|
||||
self._spans.append(Span(offset, offset + text_length, style))
|
||||
self._length += text_length
|
||||
elif isinstance(text, Text):
|
||||
_Span = Span
|
||||
if style is not None:
|
||||
raise ValueError(
|
||||
"style must not be set when appending Text instance"
|
||||
)
|
||||
|
||||
text_length = self._length
|
||||
if text.style is not None:
|
||||
self._spans.append(
|
||||
_Span(text_length, text_length + len(text), text.style)
|
||||
text_length = self._length
|
||||
if text.style is not None:
|
||||
self._spans.append(
|
||||
_Span(text_length, text_length + len(text), text.style)
|
||||
)
|
||||
self._text.append(text.plain)
|
||||
self._spans.extend(
|
||||
_Span(start + text_length, end + text_length, style)
|
||||
for start, end, style in text._spans
|
||||
)
|
||||
self._text.append(text.plain)
|
||||
self._spans.extend(
|
||||
_Span(start + text_length, end + text_length, style)
|
||||
for start, end, style in text._spans
|
||||
)
|
||||
self._length += len(text)
|
||||
self._length += len(text)
|
||||
return self
|
||||
|
||||
def append_text(self, text: "Text") -> "Text":
|
||||
"""Append another Text instance. This method is more performant that Text.append.
|
||||
|
||||
Returns:
|
||||
Text: Returns self for chaining.
|
||||
"""
|
||||
_Span = Span
|
||||
text_length = self._length
|
||||
if text.style is not None:
|
||||
self._spans.append(_Span(text_length, text_length + len(text), text.style))
|
||||
self._text.append(text.plain)
|
||||
self._spans.extend(
|
||||
_Span(start + text_length, end + text_length, style)
|
||||
for start, end, style in text._spans
|
||||
)
|
||||
self._length += len(text)
|
||||
return self
|
||||
|
||||
def copy_styles(self, text: "Text") -> None:
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user