mirror of
https://github.com/Textualize/rich.git
synced 2025-08-04 01:58:24 +00:00
terminal fixes, thems
This commit is contained in:
parent
6261ca23bf
commit
ccf18b938f
10 changed files with 273 additions and 124 deletions
|
@ -9,9 +9,10 @@ from typing import Iterable, List, NamedTuple, Optional, Sequence, Tuple, TYPE_C
|
|||
|
||||
from ._palettes import STANDARD_PALETTE, EIGHT_BIT_PALETTE
|
||||
from .color_triplet import ColorTriplet
|
||||
from .terminal_theme import DEFAULT_TERMINAL_THEME
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .theme import Theme
|
||||
from .terminal_theme import TerminalTheme
|
||||
|
||||
|
||||
WINDOWS = platform.system() == "Windows"
|
||||
|
@ -283,7 +284,11 @@ class Color(NamedTuple):
|
|||
return ColorSystem.STANDARD
|
||||
return ColorSystem(int(self.type))
|
||||
|
||||
def get_truecolor(self, theme: "Theme", foreground=True) -> ColorTriplet:
|
||||
def get_truecolor(
|
||||
self, theme: "TerminalTheme" = None, foreground=True
|
||||
) -> ColorTriplet:
|
||||
if theme is None:
|
||||
theme = DEFAULT_TERMINAL_THEME
|
||||
"""Get a color triplet for this color."""
|
||||
if self.type == ColorType.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
|
|
|
@ -43,8 +43,9 @@ from .tabulate import tabulate_mapping
|
|||
from . import highlighter
|
||||
from . import themes
|
||||
from .pretty import Pretty
|
||||
from .theme import Theme
|
||||
from .terminal_theme import TerminalTheme, DEFAULT_TERMINAL_THEME
|
||||
from .segment import Segment
|
||||
from .theme import Theme
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .text import Text
|
||||
|
@ -166,7 +167,7 @@ class Console:
|
|||
Args:
|
||||
color_system (str, optional): The color system supported by your terminal,
|
||||
either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect.
|
||||
styles (Dict[str, Style], optional): An optional mapping of style name strings to :class:`~rich.style.Style` objects.
|
||||
theme (Theme, optional): An optional style theme object, or ``None`` for default theme.
|
||||
file (IO, optional): A file object where the console should write to. Defaults to stdoutput.
|
||||
width (int, optional): The width of the terminal. Leave as default to auto-detect width.
|
||||
height (int, optional): The height of the terminal. Leave as default to auto-detect height.
|
||||
|
@ -184,7 +185,7 @@ class Console:
|
|||
color_system: Optional[
|
||||
Literal["auto", "standard", "256", "truecolor", "windows"]
|
||||
] = "auto",
|
||||
styles: Dict[str, Style] = None,
|
||||
theme: Theme = None,
|
||||
file: IO = None,
|
||||
width: int = None,
|
||||
height: int = None,
|
||||
|
@ -195,7 +196,9 @@ class Console:
|
|||
log_time_format: str = "[%X] ",
|
||||
highlighter: Optional["HighlighterType"] = ReprHighlighter(),
|
||||
):
|
||||
self._styles = ChainMap(DEFAULT_STYLES if styles is None else styles)
|
||||
self._styles = ChainMap(
|
||||
themes.DEFAULT.styles if theme is None else theme.styles
|
||||
)
|
||||
self.file = file or sys.stdout
|
||||
self._width = width
|
||||
self._height = height
|
||||
|
@ -262,6 +265,10 @@ class Console:
|
|||
"""
|
||||
self._styles.maps.append(styles)
|
||||
|
||||
def pop_styles(self) -> None:
|
||||
"""Restore styles to state before `push_styles`."""
|
||||
self._styles.maps.pop()
|
||||
|
||||
@property
|
||||
def color_system(self) -> Optional[str]:
|
||||
"""Get color system string.
|
||||
|
@ -379,7 +386,13 @@ class Console:
|
|||
"A str, Segment or object with __console__ method is required"
|
||||
)
|
||||
|
||||
for render_output in render_iterable:
|
||||
try:
|
||||
iter_render = iter(render_iterable)
|
||||
except TypeError:
|
||||
raise errors.NotRenderableError(
|
||||
f"object {render_iterable!r} is not renderable"
|
||||
)
|
||||
for render_output in iter_render:
|
||||
if isinstance(render_output, Segment):
|
||||
yield render_output
|
||||
else:
|
||||
|
@ -808,7 +821,7 @@ class Console:
|
|||
|
||||
def export_html(
|
||||
self,
|
||||
theme: Theme = None,
|
||||
theme: TerminalTheme = None,
|
||||
clear: bool = True,
|
||||
code_format: str = None,
|
||||
inline_styles: bool = False,
|
||||
|
@ -816,7 +829,7 @@ class Console:
|
|||
"""Generate HTML from console contents (requires record=True argument in constructor).
|
||||
|
||||
Args:
|
||||
theme (Theme, optional): Theme object containing console colors.
|
||||
theme (TerminalTheme, optional): TerminalTheme object containing console colors.
|
||||
clear (bool, optional): Set to ``True`` to clear the record buffer after generating the HTML.
|
||||
code_format (str, optional): Format string to render HTML, should contain {foreground}
|
||||
{background} and {code}.
|
||||
|
@ -832,7 +845,7 @@ class Console:
|
|||
), "To export console contents set record=True in the constructor or instance"
|
||||
fragments: List[str] = []
|
||||
append = fragments.append
|
||||
_theme = theme or themes.DEFAULT
|
||||
_theme = theme or DEFAULT_TERMINAL_THEME
|
||||
stylesheet = ""
|
||||
|
||||
def escape(text: str) -> str:
|
||||
|
@ -882,7 +895,7 @@ class Console:
|
|||
def save_html(
|
||||
self,
|
||||
path: str,
|
||||
theme: Theme = None,
|
||||
theme: TerminalTheme = None,
|
||||
clear: bool = True,
|
||||
code_format=CONSOLE_HTML_FORMAT,
|
||||
inline_styles: bool = False,
|
||||
|
@ -891,7 +904,7 @@ class Console:
|
|||
|
||||
Args:
|
||||
path (str): Path to write html file.
|
||||
theme (Theme, optional): Theme object containing console colors.
|
||||
theme (TerminalTheme, optional): TerminalTheme object containing console colors.
|
||||
clear (bool, optional): Set to True to clear the record buffer after generating the HTML.
|
||||
code_format (str, optional): Format string to render HTML, should contain {foreground}
|
||||
{background} and {code}.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Iterator, Iterable, List, TypeVar, TYPE_CHECKING, Union
|
||||
from typing import Iterator, Iterable, List, overload, TypeVar, TYPE_CHECKING, Union
|
||||
from typing_extensions import Literal
|
||||
|
||||
from .segment import Segment
|
||||
|
@ -57,7 +57,15 @@ class Lines:
|
|||
def __iter__(self) -> Iterator["Text"]:
|
||||
return iter(self._lines)
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> "Text":
|
||||
...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> "Lines":
|
||||
...
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._lines[index]
|
||||
|
||||
def __setitem__(self, index: int, value: "Text") -> "Lines":
|
||||
|
|
|
@ -100,4 +100,86 @@ MARKDOWN_STYLES = {
|
|||
"markdown.link": Style(bold=True),
|
||||
"markdown.link_url": Style(underline=True),
|
||||
}
|
||||
|
||||
|
||||
PYTHON_STYLES = {
|
||||
"python.background": Style(),
|
||||
"python.foreground": Style(),
|
||||
"python.keyword": Style(),
|
||||
"python.operator": Style(),
|
||||
"python.endmarker": Style(),
|
||||
"python.name": Style(),
|
||||
"python.number": Style(),
|
||||
"python.string": Style(),
|
||||
"python.newline": Style(),
|
||||
"python.indent": Style(),
|
||||
"python.dedent": Style(),
|
||||
"python.lpar": Style(),
|
||||
"python.rpar": Style(),
|
||||
"python.lsqb": Style(),
|
||||
"python.rsqb": Style(),
|
||||
"python.colon": Style(),
|
||||
"python.comma": Style(),
|
||||
"python.semi": Style(),
|
||||
"python.plus": Style(),
|
||||
"python.minus": Style(),
|
||||
"python.star": Style(),
|
||||
"python.slash": Style(),
|
||||
"python.vbar": Style(),
|
||||
"python.amper": Style(),
|
||||
"python.less": Style(),
|
||||
"python.greater": Style(),
|
||||
"python.equal": Style(),
|
||||
"python.dot": Style(),
|
||||
"python.percent": Style(),
|
||||
"python.lbrace": Style(),
|
||||
"python.rbrace": Style(),
|
||||
"python.eqequal": Style(),
|
||||
"python.notequal": Style(),
|
||||
"python.lessequal": Style(),
|
||||
"python.greaterequal": Style(),
|
||||
"python.tilde": Style(),
|
||||
"python.circumflex": Style(),
|
||||
"python.leftshift": Style(),
|
||||
"python.rightshift": Style(),
|
||||
"python.doublestar": Style(),
|
||||
"python.plusequal": Style(),
|
||||
"python.minequal": Style(),
|
||||
"python.starequal": Style(),
|
||||
"python.slashequal": Style(),
|
||||
"python.percentequal": Style(),
|
||||
"python.amperequal": Style(),
|
||||
"python.vbarequal": Style(),
|
||||
"python.circumflexequal": Style(),
|
||||
"python.leftshiftequal": Style(),
|
||||
"python.rightshiftequal": Style(),
|
||||
"python.doublestarequal": Style(),
|
||||
"python.doubleslash": Style(),
|
||||
"python.doubleslashequal": Style(),
|
||||
"python.at": Style(),
|
||||
"python.atequal": Style(),
|
||||
"python.rarrow": Style(),
|
||||
"python.ellipsis": Style(),
|
||||
"python.colonequal": Style(),
|
||||
"python.op": Style(),
|
||||
"python.await": Style(),
|
||||
"python.async": Style(),
|
||||
"python.type_ignore": Style(),
|
||||
"python.type_comment": Style(),
|
||||
"python.errortoken": Style(),
|
||||
"python.comment": Style(),
|
||||
"python.nl": Style(),
|
||||
"python.encoding": Style(),
|
||||
"python.n_tokens": Style(),
|
||||
"python.nt_offset": Style(),
|
||||
}
|
||||
|
||||
DEFAULT_STYLES.update(MARKDOWN_STYLES)
|
||||
DEFAULT_STYLES.update(PYTHON_STYLES)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import token
|
||||
|
||||
for name in token.tok_name.values():
|
||||
print(f'"python.{name.lower()}" : Style(),')
|
||||
|
|
|
@ -4,8 +4,7 @@ from typing import Any, Dict, Iterable, List, Mapping, Optional, Type, Union
|
|||
|
||||
from . import errors
|
||||
from .color import blend_rgb, Color, ColorParseError, ColorSystem
|
||||
from . import themes
|
||||
from .theme import Theme
|
||||
from .terminal_theme import TerminalTheme, DEFAULT_TERMINAL_THEME
|
||||
|
||||
|
||||
class _Bit:
|
||||
|
@ -31,8 +30,8 @@ class Style:
|
|||
def __init__(
|
||||
self,
|
||||
*,
|
||||
color: str = None,
|
||||
bgcolor: str = None,
|
||||
color: Union[Color, str] = None,
|
||||
bgcolor: Union[Color, str] = None,
|
||||
bold: bool = None,
|
||||
dim: bool = None,
|
||||
italic: bool = None,
|
||||
|
@ -43,8 +42,11 @@ class Style:
|
|||
conceal: bool = None,
|
||||
strike: bool = None,
|
||||
):
|
||||
self._color = None if color is None else Color.parse(color)
|
||||
self._bgcolor = None if bgcolor is None else Color.parse(bgcolor)
|
||||
def _make_color(color: Union[Color, str]) -> Color:
|
||||
return color if isinstance(color, Color) else Color.parse(color)
|
||||
|
||||
self._color = None if color is None else _make_color(color)
|
||||
self._bgcolor = None if bgcolor is None else _make_color(bgcolor)
|
||||
self._attributes = (
|
||||
(bold or 0)
|
||||
| (dim or 0) << 1
|
||||
|
@ -218,9 +220,9 @@ class Style:
|
|||
return style
|
||||
|
||||
@lru_cache(maxsize=1000)
|
||||
def get_html_style(self, theme: Theme = None) -> str:
|
||||
def get_html_style(self, theme: TerminalTheme = None) -> str:
|
||||
"""Get a CSS style rule."""
|
||||
theme = theme or themes.DEFAULT
|
||||
theme = theme or DEFAULT_TERMINAL_THEME
|
||||
css: List[str] = []
|
||||
append = css.append
|
||||
|
||||
|
|
108
rich/syntax.py
108
rich/syntax.py
|
@ -1,8 +1,9 @@
|
|||
import textwrap
|
||||
from typing import Any, Dict, Union
|
||||
from typing import Any, Dict, Set, Tuple, Union
|
||||
|
||||
from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
|
||||
from pygments.styles import get_style_by_name
|
||||
from pygments.style import Style as PygmentsStyle
|
||||
from pygments.token import Token
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
|
@ -12,6 +13,8 @@ from .style import Style
|
|||
from .text import Text
|
||||
from ._tools import iter_first
|
||||
|
||||
DEFAULT_THEME = "monokai"
|
||||
|
||||
|
||||
class Syntax:
|
||||
"""Construct a Syntax object to render syntax highlighted code.
|
||||
|
@ -23,6 +26,8 @@ class Syntax:
|
|||
dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True.
|
||||
line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
|
||||
start_line (int, optional): Starting number for line numbers. Defaults to 1.
|
||||
line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render.
|
||||
highlight_lines (Set[int]): A set of line numbers to highlight.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
@ -30,36 +35,41 @@ class Syntax:
|
|||
code: str,
|
||||
lexer_name: str,
|
||||
*,
|
||||
theme: str = "emacs",
|
||||
dedent: bool = True,
|
||||
theme: Union[str, PygmentsStyle] = DEFAULT_THEME,
|
||||
dedent: bool = False,
|
||||
line_numbers: bool = False,
|
||||
start_line: int = 1,
|
||||
line_range: Tuple[int, int] = None,
|
||||
highlight_lines: Set[int] = None,
|
||||
) -> None:
|
||||
|
||||
if dedent:
|
||||
code = textwrap.dedent(code)
|
||||
self.code = code
|
||||
self.lexer_name = lexer_name
|
||||
self.theme = theme
|
||||
self.dedent = dedent
|
||||
self.line_numbers = line_numbers
|
||||
self.start_line = start_line
|
||||
self.line_range = line_range
|
||||
self.highlight_lines = highlight_lines or set()
|
||||
|
||||
self._style_cache: Dict[Any, Style] = {}
|
||||
try:
|
||||
self._pygments_style_class = get_style_by_name(theme)
|
||||
except ClassNotFound:
|
||||
self._pygments_style_class = get_style_by_name("default")
|
||||
if not isinstance(theme, str) and issubclass(theme, PygmentsStyle):
|
||||
self._pygments_style_class = theme
|
||||
else:
|
||||
try:
|
||||
self._pygments_style_class = get_style_by_name(theme)
|
||||
except ClassNotFound:
|
||||
self._pygments_style_class = get_style_by_name("default")
|
||||
self._background_color = self._pygments_style_class.background_color
|
||||
|
||||
@classmethod
|
||||
def from_path(
|
||||
cls,
|
||||
path: str,
|
||||
theme: str = "emacs",
|
||||
theme: Union[str, PygmentsStyle] = DEFAULT_THEME,
|
||||
dedent: bool = True,
|
||||
line_numbers: bool = False,
|
||||
line_range: Tuple[int, int] = None,
|
||||
start_line: int = 1,
|
||||
highlight_lines: Set[int] = None,
|
||||
) -> "Syntax":
|
||||
"""Construct a Syntax object from a file.
|
||||
|
||||
|
@ -70,6 +80,8 @@ class Syntax:
|
|||
dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True.
|
||||
line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
|
||||
start_line (int, optional): Starting number for line numbers. Defaults to 1.
|
||||
line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render.
|
||||
highlight_lines (Set[int]): A set of line numbers to highlight.
|
||||
|
||||
Returns:
|
||||
[Syntax]: A Syntax object that may be printed to the console
|
||||
|
@ -87,7 +99,9 @@ class Syntax:
|
|||
theme=theme,
|
||||
dedent=dedent,
|
||||
line_numbers=line_numbers,
|
||||
line_range=line_range,
|
||||
start_line=start_line,
|
||||
highlight_lines=highlight_lines,
|
||||
)
|
||||
|
||||
def _get_theme_style(self, token_type) -> Style:
|
||||
|
@ -95,7 +109,6 @@ class Syntax:
|
|||
style = self._style_cache[token_type]
|
||||
else:
|
||||
pygments_style = self._pygments_style_class.style_for_token(token_type)
|
||||
|
||||
color = pygments_style["color"]
|
||||
bgcolor = pygments_style["bgcolor"]
|
||||
style = Style(
|
||||
|
@ -127,7 +140,7 @@ class Syntax:
|
|||
append(token, _get_theme_style(token_type))
|
||||
return text
|
||||
|
||||
def _get_line_numbers_color(self) -> Color:
|
||||
def _get_line_numbers_color(self, blend: float = 0.5) -> Color:
|
||||
background_color = parse_rgb_hex(
|
||||
self._pygments_style_class.background_color[1:]
|
||||
)
|
||||
|
@ -136,7 +149,9 @@ class Syntax:
|
|||
return Color.default()
|
||||
# TODO: Handle no full colors here
|
||||
assert foreground_color.triplet is not None
|
||||
new_color = blend_rgb(background_color, foreground_color.triplet)
|
||||
new_color = blend_rgb(
|
||||
background_color, foreground_color.triplet, cross_fade=blend
|
||||
)
|
||||
return Color.from_triplet(new_color)
|
||||
|
||||
def __console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
||||
|
@ -149,14 +164,32 @@ class Syntax:
|
|||
return
|
||||
|
||||
lines = text.split("\n")
|
||||
|
||||
start_line = 0
|
||||
if self.line_range:
|
||||
start_line, end_line = self.line_range
|
||||
start_line = start_line - 1
|
||||
lines = lines[start_line:end_line]
|
||||
|
||||
numbers_column_width = len(str(self.start_line + len(lines))) + 2
|
||||
render_options = options.update(width=options.max_width - numbers_column_width)
|
||||
background_style = Style(bgcolor=self._pygments_style_class.background_color)
|
||||
number_style = background_style + self._get_theme_style(Token.Text)
|
||||
number_style._color = self._get_line_numbers_color()
|
||||
number_styles = {
|
||||
False: (
|
||||
background_style
|
||||
+ self._get_theme_style(Token.Text)
|
||||
+ Style(color=self._get_line_numbers_color())
|
||||
),
|
||||
True: (
|
||||
background_style
|
||||
+ self._get_theme_style(Token.Text)
|
||||
+ Style(bold=True, color=self._get_line_numbers_color(0.9))
|
||||
),
|
||||
}
|
||||
highlight_line = self.highlight_lines.__contains__
|
||||
padding = Segment(" " * numbers_column_width, background_style)
|
||||
new_line = Segment("\n")
|
||||
for line_no, line in enumerate(lines, self.start_line):
|
||||
for line_no, line in enumerate(lines, self.start_line + start_line):
|
||||
wrapped_lines = console.render_lines(
|
||||
line, render_options, style=background_style
|
||||
)
|
||||
|
@ -164,7 +197,7 @@ class Syntax:
|
|||
if first:
|
||||
yield Segment(
|
||||
f" {str(line_no).rjust(numbers_column_width - 2)} ",
|
||||
number_style,
|
||||
number_styles[highlight_line(line_no)],
|
||||
)
|
||||
else:
|
||||
yield padding
|
||||
|
@ -173,40 +206,17 @@ class Syntax:
|
|||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
CODE = r'''
|
||||
def __console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
||||
"""This is a docstring."""
|
||||
code = self.code
|
||||
if self.dedent:
|
||||
code = textwrap.dedent(code)
|
||||
text = highlight(code, self.lexer_name, theme=self.theme)
|
||||
if not self.line_numbers:
|
||||
yield text
|
||||
return
|
||||
CODE = r"""
|
||||
def __init__(self):
|
||||
self.b = self.
|
||||
|
||||
# This is a comment
|
||||
lines = text.split("\n")
|
||||
numbers_column_width = len(str(len(lines))) + 1
|
||||
render_options = options.update(width=options.max_width - numbers_column_width)
|
||||
|
||||
padding = Segment(" " * numbers_column_width)
|
||||
new_line = Segment("\n")
|
||||
for line_no, line in enumerate(lines, self.start_line):
|
||||
|
||||
wrapped_lines = console.render_lines([line], render_options)
|
||||
for first, wrapped_line in iter_first(wrapped_lines):
|
||||
if first:
|
||||
yield Segment(f"{line_no}".ljust(numbers_column_width))
|
||||
else:
|
||||
yield padding
|
||||
yield from wrapped_line
|
||||
yield new_line
|
||||
'''
|
||||
syntax = Syntax(CODE, "python", dedent=True, line_numbers=True, start_line=990)
|
||||
"""
|
||||
# syntax = Syntax(CODE, "python", dedent=True, line_numbers=True, start_line=990)
|
||||
|
||||
from time import time
|
||||
|
||||
syntax = Syntax.from_path("./rich/syntax.py", theme="monokai", line_numbers=True)
|
||||
syntax = Syntax(CODE, "python", line_numbers=False, theme="monokai")
|
||||
syntax = Syntax.from_path("rich/segment.py", theme="monokai", line_numbers=True)
|
||||
console = Console(record=True)
|
||||
start = time()
|
||||
console.print(syntax)
|
||||
|
|
51
rich/text.py
51
rich/text.py
|
@ -190,7 +190,39 @@ class Text:
|
|||
return
|
||||
self._spans.append(Span(max(0, start), min(length, end), style))
|
||||
|
||||
def highlight_words(self, words: Iterable[str], style: Union[str, Style]) -> int:
|
||||
def highlight_regex(
|
||||
self, re_highlight: str, style: Union[str, Style] = None
|
||||
) -> int:
|
||||
"""Highlight text with a regular expression, where group names are
|
||||
translated to styles.
|
||||
|
||||
Args:
|
||||
re_highlight (str): A regular expression
|
||||
|
||||
Returns:
|
||||
int: Number of regex matches
|
||||
"""
|
||||
count = 0
|
||||
append_span = self._spans.append
|
||||
_Span = Span
|
||||
for match in re.finditer(re_highlight, self.text):
|
||||
_span = match.span
|
||||
if style:
|
||||
start, end = _span()
|
||||
append_span(_Span(start, end, style))
|
||||
count += 1
|
||||
for name, _ in match.groupdict().items():
|
||||
start, end = _span(name)
|
||||
if start != -1:
|
||||
append_span(_Span(start, end, name))
|
||||
return count
|
||||
|
||||
def highlight_words(
|
||||
self,
|
||||
words: Iterable[str],
|
||||
style: Union[str, Style],
|
||||
case_sensitive: bool = True,
|
||||
) -> int:
|
||||
"""Highlight words with a style.
|
||||
|
||||
Args:
|
||||
|
@ -204,7 +236,9 @@ class Text:
|
|||
add_span = self._spans.append
|
||||
count = 0
|
||||
_Span = Span
|
||||
for match in re.finditer(re_words, self.text):
|
||||
for match in re.finditer(
|
||||
re_words, self.text, flags=0 if case_sensitive else re.IGNORECASE
|
||||
):
|
||||
start, end = match.span(0)
|
||||
add_span(_Span(start, end, style))
|
||||
count += 1
|
||||
|
@ -266,6 +300,7 @@ class Text:
|
|||
return console.get_style(style, default=null_style)
|
||||
|
||||
enumerated_spans = list(enumerate(line._spans, 1))
|
||||
|
||||
style_map = {index: get_style(span.style) for index, span in enumerated_spans}
|
||||
style_map[0] = get_style(self.style)
|
||||
|
||||
|
@ -334,7 +369,7 @@ class Text:
|
|||
append(span)
|
||||
continue
|
||||
if span.start >= new_length:
|
||||
break
|
||||
continue
|
||||
append(span.right_crop(new_length))
|
||||
self._spans[:] = spans
|
||||
|
||||
|
@ -529,7 +564,11 @@ if __name__ == "__main__": # pragma: no cover
|
|||
from .console import Console
|
||||
|
||||
console = Console()
|
||||
text = Text("[12:42:33] ")
|
||||
lines = text.wrap(10)
|
||||
console.print(lines)
|
||||
text = Text(
|
||||
"""Hello, self! hello World
|
||||
|
||||
hello worhello\n"""
|
||||
)
|
||||
text.highlight_regex("self", "reverse")
|
||||
console.print(text)
|
||||
|
||||
|
|
|
@ -1,21 +1,32 @@
|
|||
from typing import List, Tuple
|
||||
import configparser
|
||||
from typing import Dict, IO
|
||||
|
||||
from .color_triplet import ColorTriplet
|
||||
from .palette import Palette
|
||||
|
||||
_ColorTuple = Tuple[int, int, int]
|
||||
from .style import Style
|
||||
|
||||
|
||||
class Theme:
|
||||
"""A terminal theme.
|
||||
|
||||
This object is used when exporting console contents as HTML.
|
||||
|
||||
"""
|
||||
def __init__(self, styles: Dict[str, Style] = None):
|
||||
self.styles = styles or {}
|
||||
|
||||
def __init__(
|
||||
self, background: _ColorTuple, foreground: _ColorTuple, ansi: List[_ColorTuple]
|
||||
) -> None:
|
||||
self.background_color = ColorTriplet(*background)
|
||||
self.foreground_color = ColorTriplet(*foreground)
|
||||
self.ansi_colors = Palette(ansi)
|
||||
@property
|
||||
def config(self) -> str:
|
||||
"""Get contents of a config file for this theme."""
|
||||
config_lines = ["[styles]"]
|
||||
append = config_lines.append
|
||||
for name, style in sorted(self.styles.items()):
|
||||
append(f"{name} = {style}")
|
||||
config = "\n".join(config_lines)
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, config_file: IO[str], source: str = None) -> "Theme":
|
||||
config = configparser.ConfigParser()
|
||||
config.read_file(config_file, source=source)
|
||||
styles = {name: Style.parse(value) for name, value in config.items("styles")}
|
||||
theme = Theme(styles)
|
||||
return theme
|
||||
|
||||
@classmethod
|
||||
def read(cls, path: str) -> "Theme":
|
||||
with open(path, "rt") as config_file:
|
||||
return cls.from_file(config_file, source=path)
|
||||
|
|
|
@ -1,24 +1,5 @@
|
|||
from .default_styles import DEFAULT_STYLES
|
||||
from .theme import Theme
|
||||
|
||||
DEFAULT = Theme(
|
||||
(255, 255, 255),
|
||||
(0, 0, 0),
|
||||
[
|
||||
(0, 0, 0),
|
||||
(128, 0, 0),
|
||||
(0, 128, 0),
|
||||
(128, 128, 0),
|
||||
(0, 0, 128),
|
||||
(128, 0, 128),
|
||||
(0, 128, 128),
|
||||
(192, 192, 192),
|
||||
(128, 128, 128),
|
||||
(255, 0, 0),
|
||||
(0, 255, 0),
|
||||
(255, 255, 0),
|
||||
(0, 0, 255),
|
||||
(255, 0, 255),
|
||||
(0, 255, 255),
|
||||
(255, 255, 255),
|
||||
],
|
||||
)
|
||||
|
||||
DEFAULT = Theme(DEFAULT_STYLES)
|
||||
|
|
|
@ -8,7 +8,7 @@ from rich.color import (
|
|||
ColorTriplet,
|
||||
)
|
||||
|
||||
from rich import themes
|
||||
from rich import terminal_theme
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -28,16 +28,14 @@ def test_system() -> None:
|
|||
|
||||
|
||||
def test_truecolor() -> None:
|
||||
assert Color.parse("#ff0000").get_truecolor(themes.DEFAULT) == ColorTriplet(
|
||||
255, 0, 0
|
||||
assert Color.parse("#ff0000").get_truecolor() == ColorTriplet(255, 0, 0)
|
||||
assert Color.parse("red").get_truecolor() == ColorTriplet(128, 0, 0)
|
||||
assert Color.parse("1").get_truecolor() == ColorTriplet(128, 0, 0)
|
||||
assert Color.parse("17").get_truecolor() == ColorTriplet(0, 0, 95)
|
||||
assert Color.parse("default").get_truecolor() == ColorTriplet(0, 0, 0)
|
||||
assert Color.parse("default").get_truecolor(foreground=False) == ColorTriplet(
|
||||
255, 255, 255
|
||||
)
|
||||
assert Color.parse("red").get_truecolor(themes.DEFAULT) == ColorTriplet(128, 0, 0)
|
||||
assert Color.parse("1").get_truecolor(themes.DEFAULT) == ColorTriplet(128, 0, 0)
|
||||
assert Color.parse("17").get_truecolor(themes.DEFAULT) == ColorTriplet(0, 0, 95)
|
||||
assert Color.parse("default").get_truecolor(themes.DEFAULT) == ColorTriplet(0, 0, 0)
|
||||
assert Color.parse("default").get_truecolor(
|
||||
themes.DEFAULT, foreground=False
|
||||
) == ColorTriplet(255, 255, 255)
|
||||
|
||||
|
||||
def test_parse_success() -> None:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue