mirror of
https://github.com/Textualize/rich.git
synced 2025-08-04 10:08:40 +00:00
replace is_control with list of control codes
This commit is contained in:
parent
6f972a059a
commit
a998f23084
14 changed files with 188 additions and 156 deletions
|
@ -5,6 +5,13 @@ 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).
|
||||
|
||||
## [9.13.1] - Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- Made pydoc import lazy as at least one use found it slow to import https://github.com/willmcgugan/rich/issues/1104
|
||||
- Modified string highlighting to not match in the middle of a word, so that apostrophes are not considered strings
|
||||
|
||||
## [9.13.0] - 2021-03-06
|
||||
|
||||
### Added
|
||||
|
|
|
@ -890,7 +890,7 @@ class Console:
|
|||
|
||||
def bell(self) -> None:
|
||||
"""Play a 'bell' sound (if supported by the terminal)."""
|
||||
self.control("\x07")
|
||||
self.control(Control.bell())
|
||||
|
||||
def capture(self) -> Capture:
|
||||
"""A context manager to *capture* the result of print() or log() in a string,
|
||||
|
@ -948,7 +948,10 @@ class Console:
|
|||
Args:
|
||||
home (bool, optional): Also move the cursor to 'home' position. Defaults to True.
|
||||
"""
|
||||
self.control("\033[2J\033[H" if home else "\033[2J")
|
||||
if home:
|
||||
self.control(Control.clear(), Control.home())
|
||||
else:
|
||||
self.control(Control.clear())
|
||||
|
||||
def status(
|
||||
self,
|
||||
|
@ -991,7 +994,7 @@ class Console:
|
|||
show (bool, optional): Set visibility of the cursor.
|
||||
"""
|
||||
if self.is_terminal and not self.legacy_windows:
|
||||
self.control("\033[?25h" if show else "\033[?25l")
|
||||
self.control(Control.show_cursor(show))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -1011,7 +1014,7 @@ class Console:
|
|||
"""
|
||||
changed = False
|
||||
if self.is_terminal and not self.legacy_windows:
|
||||
self.control("\033[?1049h\033[H" if enable else "\033[?1049l")
|
||||
self.control(Control.alt_screen(enable))
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
|
@ -1312,14 +1315,15 @@ class Console:
|
|||
rule = Rule(title=title, characters=characters, style=style, align=align)
|
||||
self.print(rule)
|
||||
|
||||
def control(self, control_codes: Union["Control", str]) -> None:
|
||||
def control(self, *control: Control) -> None:
|
||||
"""Insert non-printing control codes.
|
||||
|
||||
Args:
|
||||
control_codes (str): Control codes, such as those that may move the cursor.
|
||||
"""
|
||||
if not self.is_dumb_terminal:
|
||||
self._buffer.append(Segment.control(str(control_codes)))
|
||||
for _control in control:
|
||||
self._buffer.append(_control.segment)
|
||||
self._check_buffer()
|
||||
|
||||
def out(
|
||||
|
@ -1598,7 +1602,7 @@ class Console:
|
|||
not_terminal = not self.is_terminal
|
||||
if self.no_color and color_system:
|
||||
buffer = Segment.remove_color(buffer)
|
||||
for text, style, is_control in buffer:
|
||||
for text, style, control in buffer:
|
||||
if style:
|
||||
append(
|
||||
style.render(
|
||||
|
@ -1607,7 +1611,7 @@ class Console:
|
|||
legacy_windows=legacy_windows,
|
||||
)
|
||||
)
|
||||
elif not (not_terminal and is_control):
|
||||
elif not (not_terminal and control):
|
||||
append(text)
|
||||
|
||||
rendered = "".join(output)
|
||||
|
@ -1679,7 +1683,7 @@ class Console:
|
|||
text = "".join(
|
||||
segment.text
|
||||
for segment in self._record_buffer
|
||||
if not segment.is_control
|
||||
if not segment.control
|
||||
)
|
||||
if clear:
|
||||
del self._record_buffer[:]
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from enum import IntEnum
|
||||
from typing import NamedTuple, TYPE_CHECKING
|
||||
from typing import Callable, Dict, TYPE_CHECKING, Union, Tuple
|
||||
|
||||
|
||||
from .segment import Segment
|
||||
from .segment import ControlType, Segment
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
|
@ -16,44 +14,83 @@ STRIP_CONTROL_CODES = [
|
|||
_CONTROL_TRANSLATE = {_codepoint: None for _codepoint in STRIP_CONTROL_CODES}
|
||||
|
||||
|
||||
class ControlCodeEnum(IntEnum):
|
||||
HOME = 1
|
||||
CURSOR_UP = 2
|
||||
CURSOR_DOWN = 3
|
||||
CURSOR_FORWARD = 4
|
||||
CURSOR_BACKWARD = 5
|
||||
ERASE_IN_LINE = 5
|
||||
|
||||
|
||||
class ControlCode(NamedTuple):
|
||||
code: ControlCodeEnum
|
||||
param: int
|
||||
CONTROL_CODES_FORMAT: Dict[ControlType, Callable[[int], str]] = {
|
||||
ControlType.BELL: lambda _: "\x07",
|
||||
ControlType.CARRIAGE_RETURN: lambda _: "\r",
|
||||
ControlType.HOME: lambda _: "\x1b[H",
|
||||
ControlType.CLEAR: lambda _: "\x1b[2J",
|
||||
ControlType.ENABLE_ALT_SCREEN: lambda _: "\x1b[?1049h",
|
||||
ControlType.DISABLE_ALT_SCREEN: lambda _: "\x1b[?1049l",
|
||||
ControlType.SHOW_CURSOR: lambda _: "\x1b[?25h",
|
||||
ControlType.HIDE_CURSOR: lambda _: "\x1b[?25l",
|
||||
ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A",
|
||||
ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
|
||||
ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C",
|
||||
ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D",
|
||||
ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
|
||||
}
|
||||
|
||||
|
||||
class Control:
|
||||
"""A renderable that inserts a control code (non printable but may move cursor).
|
||||
|
||||
Args:
|
||||
control_codes (str): A string containing control codes.
|
||||
*codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a
|
||||
tuple of ControlType and an integer parameter
|
||||
"""
|
||||
|
||||
__slots__ = ["_control_codes"]
|
||||
__slots__ = ["_segment"]
|
||||
|
||||
def __init__(self, control_codes: str) -> None:
|
||||
self._control_codes = Segment.control(control_codes)
|
||||
def __init__(self, *codes: Union[ControlType, Tuple[ControlType, int]]) -> None:
|
||||
control_codes = [
|
||||
code if isinstance(code, tuple) else (code, 0) for code in codes
|
||||
]
|
||||
_format_map = CONTROL_CODES_FORMAT
|
||||
self._segment = Segment(
|
||||
"".join(_format_map[code](param) for code, param in control_codes),
|
||||
None,
|
||||
control_codes,
|
||||
)
|
||||
|
||||
@property
|
||||
def segment(self) -> "Segment":
|
||||
return self._segment
|
||||
|
||||
@classmethod
|
||||
def bell(cls) -> "Control":
|
||||
"""Ring the 'bell'."""
|
||||
return cls(ControlType.BELL)
|
||||
|
||||
@classmethod
|
||||
def home(cls) -> "Control":
|
||||
"""Move cursor to 'home' position."""
|
||||
return cls("\033[H")
|
||||
return cls(ControlType.HOME)
|
||||
|
||||
@classmethod
|
||||
def clear(cls) -> "Control":
|
||||
"""Clear the screen."""
|
||||
return cls(ControlType.CLEAR)
|
||||
|
||||
@classmethod
|
||||
def show_cursor(cls, show: bool) -> "Control":
|
||||
"""Show or hide the cursor."""
|
||||
return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR)
|
||||
|
||||
@classmethod
|
||||
def alt_screen(cls, enable: bool) -> "Control":
|
||||
"""Enable or disable alt screen."""
|
||||
if enable:
|
||||
return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME)
|
||||
else:
|
||||
return cls(ControlType.DISABLE_ALT_SCREEN)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._control_codes.text
|
||||
return self._segment.text
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
yield self._control_codes
|
||||
yield self._segment
|
||||
|
||||
|
||||
def strip_control_codes(text: str, _translate_table=_CONTROL_TRANSLATE) -> str:
|
||||
|
@ -69,5 +106,4 @@ def strip_control_codes(text: str, _translate_table=_CONTROL_TRANSLATE) -> str:
|
|||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
print(strip_control_codes("hello\rWorld"))
|
||||
|
|
|
@ -93,7 +93,7 @@ class ReprHighlighter(RegexHighlighter):
|
|||
r"(?P<ellipsis>\.\.\.)",
|
||||
r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
|
||||
r"(?P<path>\B(\/[\w\.\-\_\+]+)*\/)(?P<filename>[\w\.\-\_\+]*)?",
|
||||
r"(?<!\\)(?P<str>b?\'\'\'.*?(?<!\\)\'\'\'|b?\'.*?(?<!\\)\'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
|
||||
r"(?<![\\\w])(?P<str>b?\'\'\'.*?(?<!\\)\'\'\'|b?\'.*?(?<!\\)\'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
|
||||
r"(?P<uuid>[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12})",
|
||||
r"(?P<url>(https|http|ws|wss):\/\/[0-9a-zA-Z\$\-\_\+\!`\(\)\,\.\?\/\;\:\&\=\%\#]*)",
|
||||
),
|
||||
|
|
|
@ -47,8 +47,8 @@ def _render_segments(segments: Iterable[Segment]) -> str:
|
|||
fragments: List[str] = []
|
||||
append_fragment = fragments.append
|
||||
theme = DEFAULT_TERMINAL_THEME
|
||||
for text, style, is_control in Segment.simplify(segments):
|
||||
if is_control:
|
||||
for text, style, control in Segment.simplify(segments):
|
||||
if control:
|
||||
continue
|
||||
text = escape(text)
|
||||
if style:
|
||||
|
|
|
@ -225,12 +225,12 @@ class Live(JupyterMixin, RenderHook):
|
|||
self.console.print(self._live_render.renderable)
|
||||
elif self.console.is_terminal and not self.console.is_dumb_terminal:
|
||||
with self._lock, self.console:
|
||||
self.console.print(Control(""))
|
||||
self.console.print(Control())
|
||||
elif (
|
||||
not self._started and not self.transient
|
||||
): # if it is finished allow files or dumb-terminals to see final result
|
||||
with self.console:
|
||||
self.console.print(Control(""))
|
||||
self.console.print(Control())
|
||||
|
||||
def process_renderables(
|
||||
self, renderables: List[ConsoleRenderable]
|
||||
|
|
|
@ -5,7 +5,7 @@ from typing_extensions import Literal
|
|||
from ._loop import loop_last
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
from .control import Control
|
||||
from .segment import Segment
|
||||
from .segment import ControlCode, ControlType, Segment
|
||||
from .style import StyleType
|
||||
from .text import Text
|
||||
|
||||
|
@ -47,8 +47,18 @@ class LiveRender:
|
|||
"""
|
||||
if self._shape is not None:
|
||||
_, height = self._shape
|
||||
return Control("\r\x1b[2K" + "\x1b[1A\x1b[2K" * (height - 1))
|
||||
return Control("")
|
||||
return Control(
|
||||
ControlType.CARRIAGE_RETURN,
|
||||
(ControlType.ERASE_IN_LINE, 2),
|
||||
*(
|
||||
(
|
||||
(ControlType.CURSOR_UP, 1),
|
||||
(ControlType.ERASE_IN_LINE, 2),
|
||||
)
|
||||
* (height - 1)
|
||||
)
|
||||
)
|
||||
return Control()
|
||||
|
||||
def restore_cursor(self) -> Control:
|
||||
"""Get control codes to clear the render and restore the cursor to its previous position.
|
||||
|
@ -58,8 +68,11 @@ class LiveRender:
|
|||
"""
|
||||
if self._shape is not None:
|
||||
_, height = self._shape
|
||||
return Control("\r" + "\x1b[1A\x1b[2K" * height)
|
||||
return Control("")
|
||||
return Control(
|
||||
ControlType.CARRIAGE_RETURN,
|
||||
*((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height
|
||||
)
|
||||
return Control()
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from abc import ABC, abstractmethod
|
||||
import pydoc
|
||||
|
||||
|
||||
class Pager(ABC):
|
||||
|
@ -17,7 +16,7 @@ class Pager(ABC):
|
|||
class SystemPager(Pager):
|
||||
"""Uses the pager installed on the system."""
|
||||
|
||||
_pager = lambda self, content: pydoc.pager(content)
|
||||
_pager = lambda self, content: __import__("pydoc").pager(content)
|
||||
|
||||
def show(self, content: str) -> None:
|
||||
"""Use the same pager used by pydoc."""
|
||||
|
|
124
rich/segment.py
124
rich/segment.py
|
@ -1,43 +1,32 @@
|
|||
from enum import IntEnm
|
||||
from enum import IntEnum
|
||||
|
||||
from typing import Dict, NamedTuple, Optional
|
||||
from typing import Callable, Dict, NamedTuple, Optional
|
||||
|
||||
from .cells import cell_len, set_cell_size
|
||||
from .style import Style
|
||||
|
||||
from itertools import filterfalse, zip_longest
|
||||
from itertools import filterfalse
|
||||
from operator import attrgetter
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
|
||||
class ControlCodeEnum(IntEnum):
|
||||
HOME = 1
|
||||
CURSOR_UP = 2
|
||||
CURSOR_DOWN = 3
|
||||
CURSOR_FORWARD = 4
|
||||
CURSOR_BACKWARD = 5
|
||||
ERASE_IN_LINE = 5
|
||||
class ControlType(IntEnum):
|
||||
BELL = 1
|
||||
CARRIAGE_RETURN = 2
|
||||
HOME = 3
|
||||
CLEAR = 4
|
||||
SHOW_CURSOR = 5
|
||||
HIDE_CURSOR = 6
|
||||
ENABLE_ALT_SCREEN = 7
|
||||
DISABLE_ALT_SCREEN = 8
|
||||
CURSOR_UP = 9
|
||||
CURSOR_DOWN = 10
|
||||
CURSOR_FORWARD = 11
|
||||
CURSOR_BACKWARD = 12
|
||||
ERASE_IN_LINE = 13
|
||||
|
||||
|
||||
class ControlCode(NamedTuple):
|
||||
code: ControlCodeEnum
|
||||
param: int
|
||||
|
||||
|
||||
CONTROL_CODES_FORMAT = {
|
||||
ControlCodeEnum.HOME: lambda param: "\x1b[H",
|
||||
ControlCodeEnum.CURSOR_UP: lambda param: f"\x1b[{param}A",
|
||||
ControlCodeEnum.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
|
||||
ControlCodeEnum.CURSOR_FORWARD: lambda param: "\x1b[{param}C",
|
||||
ControlCodeEnum.CURSOR_BACKWARD: lambda param: "\x1b[{param}D",
|
||||
ControlCodeEnum.ERASE_IN_LINE: lambda param: "\x1b[{param}K",
|
||||
}
|
||||
|
||||
|
||||
def render_control_codes(codes: Iterable[ControlCode]) -> str:
|
||||
_format_map = CONTROL_CODES_FORMAT
|
||||
ansi_codes = "".join(_format_map[code](param) for code, param in codes)
|
||||
return ansi_codes
|
||||
ControlCode = Tuple[ControlType, int]
|
||||
|
||||
|
||||
class Segment(NamedTuple):
|
||||
|
@ -47,20 +36,20 @@ class Segment(NamedTuple):
|
|||
Args:
|
||||
text (str): A piece of text.
|
||||
style (:class:`~rich.style.Style`, optional): An optional style to apply to the text.
|
||||
is_control (bool, optional): Boolean that marks segment as containing non-printable control codes.
|
||||
control (Tuple[ControlCode..], optional): Optional tuple of control codes.
|
||||
"""
|
||||
|
||||
text: str = ""
|
||||
"""Raw text."""
|
||||
style: Optional[Style] = None
|
||||
"""An optional style."""
|
||||
is_control: bool = False
|
||||
control: Optional[List[ControlCode]] = None
|
||||
"""True if the segment contains control codes, otherwise False."""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Simplified repr."""
|
||||
if self.is_control:
|
||||
return f"Segment.control({self.text!r}, {self.style!r})"
|
||||
if self.control:
|
||||
return f"Segment({self.text!r}, {self.style!r}, {self.control!r})"
|
||||
else:
|
||||
return f"Segment({self.text!r}, {self.style!r})"
|
||||
|
||||
|
@ -71,34 +60,17 @@ class Segment(NamedTuple):
|
|||
@property
|
||||
def cell_length(self) -> int:
|
||||
"""Get cell length of segment."""
|
||||
return 0 if self.is_control else cell_len(self.text)
|
||||
return 0 if self.control else cell_len(self.text)
|
||||
|
||||
@property
|
||||
def is_control(self) -> bool:
|
||||
"""Check if the segment contains control codes."""
|
||||
return self.control is not None
|
||||
|
||||
@classmethod
|
||||
def control(cls, text: str, style: Optional[Style] = None) -> "Segment":
|
||||
"""Create a Segment with control codes.
|
||||
|
||||
Args:
|
||||
text (str): Text containing non-printable control codes.
|
||||
style (Optional[style]): Optional style.
|
||||
|
||||
Returns:
|
||||
Segment: A Segment instance with ``is_control=True``.
|
||||
"""
|
||||
return cls(text, style, is_control=True)
|
||||
|
||||
@classmethod
|
||||
def make_control(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
|
||||
"""Convert all segments in to control segments.
|
||||
|
||||
Returns:
|
||||
Iterable[Segments]: Segments with is_control=True
|
||||
"""
|
||||
return [cls(text, style, True) for text, style, _ in segments]
|
||||
|
||||
@classmethod
|
||||
def line(cls, is_control: bool = False) -> "Segment":
|
||||
def line(cls) -> "Segment":
|
||||
"""Make a new line segment."""
|
||||
return cls("\n", is_control=is_control)
|
||||
return cls("\n")
|
||||
|
||||
@classmethod
|
||||
def apply_style(
|
||||
|
@ -122,19 +94,19 @@ class Segment(NamedTuple):
|
|||
if style:
|
||||
apply = style.__add__
|
||||
segments = (
|
||||
cls(text, None if is_control else apply(_style), is_control)
|
||||
for text, _style, is_control in segments
|
||||
cls(text, None if control else apply(_style), control)
|
||||
for text, _style, control in segments
|
||||
)
|
||||
if post_style:
|
||||
segments = (
|
||||
cls(
|
||||
text,
|
||||
None
|
||||
if is_control
|
||||
if control
|
||||
else (_style + post_style if _style else post_style),
|
||||
is_control,
|
||||
control,
|
||||
)
|
||||
for text, _style, is_control in segments
|
||||
for text, _style, control in segments
|
||||
)
|
||||
return segments
|
||||
|
||||
|
@ -153,9 +125,9 @@ class Segment(NamedTuple):
|
|||
|
||||
"""
|
||||
if is_control:
|
||||
return filter(attrgetter("is_control"), segments)
|
||||
return filter(attrgetter("control"), segments)
|
||||
else:
|
||||
return filterfalse(attrgetter("is_control"), segments)
|
||||
return filterfalse(attrgetter("control"), segments)
|
||||
|
||||
@classmethod
|
||||
def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]:
|
||||
|
@ -171,7 +143,7 @@ class Segment(NamedTuple):
|
|||
append = line.append
|
||||
|
||||
for segment in segments:
|
||||
if "\n" in segment.text and not segment.is_control:
|
||||
if "\n" in segment.text and not segment.control:
|
||||
text, style, _ = segment
|
||||
while text:
|
||||
_text, new_line, text = text.partition("\n")
|
||||
|
@ -214,7 +186,7 @@ class Segment(NamedTuple):
|
|||
new_line_segment = cls("\n")
|
||||
|
||||
for segment in segments:
|
||||
if "\n" in segment.text and not segment.is_control:
|
||||
if "\n" in segment.text and not segment.control:
|
||||
text, style, _ = segment
|
||||
while text:
|
||||
_text, new_line, text = text.partition("\n")
|
||||
|
@ -262,7 +234,7 @@ class Segment(NamedTuple):
|
|||
line_length = 0
|
||||
for segment in line:
|
||||
segment_length = segment.cell_length
|
||||
if line_length + segment_length < length or segment.is_control:
|
||||
if line_length + segment_length < length or segment.control:
|
||||
append(segment)
|
||||
line_length += segment_length
|
||||
else:
|
||||
|
@ -360,7 +332,7 @@ class Segment(NamedTuple):
|
|||
|
||||
_Segment = Segment
|
||||
for segment in iter_segments:
|
||||
if last_segment.style == segment.style and not segment.is_control:
|
||||
if last_segment.style == segment.style and not segment.control:
|
||||
last_segment = _Segment(
|
||||
last_segment.text + segment.text, last_segment.style
|
||||
)
|
||||
|
@ -380,10 +352,10 @@ class Segment(NamedTuple):
|
|||
Segment: Segments with link removed.
|
||||
"""
|
||||
for segment in segments:
|
||||
if segment.is_control or segment.style is None:
|
||||
if segment.control or segment.style is None:
|
||||
yield segment
|
||||
else:
|
||||
text, style, _is_control = segment
|
||||
text, style, _control = segment
|
||||
yield cls(text, style.update_link(None) if style else None)
|
||||
|
||||
@classmethod
|
||||
|
@ -396,8 +368,8 @@ class Segment(NamedTuple):
|
|||
Yields:
|
||||
Segment: Segments with styles replace with None
|
||||
"""
|
||||
for text, _style, is_control in segments:
|
||||
yield cls(text, None, is_control)
|
||||
for text, _style, control in segments:
|
||||
yield cls(text, None, control)
|
||||
|
||||
@classmethod
|
||||
def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
|
||||
|
@ -411,15 +383,15 @@ class Segment(NamedTuple):
|
|||
"""
|
||||
|
||||
cache: Dict[Style, Style] = {}
|
||||
for text, style, is_control in segments:
|
||||
for text, style, control in segments:
|
||||
if style:
|
||||
colorless_style = cache.get(style)
|
||||
if colorless_style is None:
|
||||
colorless_style = style.without_color
|
||||
cache[style] = colorless_style
|
||||
yield cls(text, colorless_style, is_control)
|
||||
yield cls(text, colorless_style, control)
|
||||
else:
|
||||
yield cls(text, None, is_control)
|
||||
yield cls(text, None, control)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
|
|
@ -66,26 +66,26 @@ def test_get_pulse_segments():
|
|||
)
|
||||
print(repr(segments))
|
||||
expected = [
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("red"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("yellow"), False),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("red")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
Segment("━", Style.parse("yellow")),
|
||||
]
|
||||
assert segments == expected
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ from rich.console import (
|
|||
ConsoleOptions,
|
||||
render_group,
|
||||
)
|
||||
from rich.control import Control
|
||||
from rich.measure import measure_renderables
|
||||
from rich.pager import SystemPager
|
||||
from rich.panel import Panel
|
||||
|
@ -211,9 +212,9 @@ def test_render_error():
|
|||
|
||||
def test_control():
|
||||
console = Console(file=io.StringIO(), force_terminal=True, _environ={})
|
||||
console.control("FOO")
|
||||
console.control(Control.clear())
|
||||
console.print("BAR")
|
||||
assert console.file.getvalue() == "FOOBAR\n"
|
||||
assert console.file.getvalue() == "\x1b[2JBAR\n"
|
||||
|
||||
|
||||
def test_capture():
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from rich.control import Control, strip_control_codes
|
||||
from rich.segment import ControlType
|
||||
|
||||
|
||||
def test_control():
|
||||
control = Control("FOO")
|
||||
assert str(control) == "FOO"
|
||||
control = Control(ControlType.BELL)
|
||||
assert str(control) == "\x07"
|
||||
|
||||
|
||||
def test_strip_control_codes():
|
||||
|
|
|
@ -70,6 +70,7 @@ highlight_tests = [
|
|||
('"hello"', [Span(0, 7, "repr.str")]),
|
||||
('"""hello"""', [Span(0, 11, "repr.str")]),
|
||||
("\\'foo'", []),
|
||||
("it's no 'string'", [Span(8, 16, "repr.str")]),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
from rich.segment import ControlCode, ControlType
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
|
||||
def test_repr():
|
||||
assert repr(Segment("foo")) == "Segment('foo', None)"
|
||||
assert repr(Segment.control("foo")) == "Segment.control('foo', None)"
|
||||
home = (ControlType.HOME, 0)
|
||||
assert (
|
||||
repr(Segment("foo", None, [home]))
|
||||
== "Segment('foo', None, [(<ControlType.HOME: 3>, 0)])"
|
||||
)
|
||||
|
||||
|
||||
def test_line():
|
||||
|
@ -81,10 +86,11 @@ def test_simplify():
|
|||
|
||||
|
||||
def test_filter_control():
|
||||
segments = [Segment("foo"), Segment("bar", is_control=True)]
|
||||
control_code = (ControlType.HOME, 0)
|
||||
segments = [Segment("foo"), Segment("bar", None, (control_code,))]
|
||||
assert list(Segment.filter_control(segments)) == [Segment("foo")]
|
||||
assert list(Segment.filter_control(segments, is_control=True)) == [
|
||||
Segment("bar", is_control=True)
|
||||
Segment("bar", None, (control_code,))
|
||||
]
|
||||
|
||||
|
||||
|
@ -107,11 +113,3 @@ def test_remove_color():
|
|||
Segment("foo", Style(bold=True)),
|
||||
Segment("bar", None),
|
||||
]
|
||||
|
||||
|
||||
def test_make_control():
|
||||
segments = [Segment("foo"), Segment("bar")]
|
||||
assert Segment.make_control(segments) == [
|
||||
Segment.control("foo"),
|
||||
Segment.control("bar"),
|
||||
]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue