diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2ddfa2..b3466bb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ 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). +## [0.4.0] - Unreleased + +### Added + +- Added Traceback rendering and handler +- Added rich.constrain + +### Fixed + +- Fixed unnecessary padding + ## [0.3.3] - 2020-02-04 ### Fixed diff --git a/rich/console.py b/rich/console.py index 2c78d19f..2c406f79 100644 --- a/rich/console.py +++ b/rich/console.py @@ -830,7 +830,7 @@ class Console: if self.record: self._record_buffer.extend(buffer) del self.buffer[:] - for line in Segment.split_and_crop_lines(buffer, self.width): + for line in Segment.split_and_crop_lines(buffer, self.width, pad=False): for text, style in line: if style: append(style.render(text, color_system=color_system, reset=True)) diff --git a/rich/segment.py b/rich/segment.py index d2a80fea..2b9a0c45 100644 --- a/rich/segment.py +++ b/rich/segment.py @@ -41,14 +41,20 @@ class Segment(NamedTuple): @classmethod def split_and_crop_lines( - cls, segments: Iterable["Segment"], length: int, style: Style = None + cls, + segments: Iterable["Segment"], + length: int, + style: Style = None, + pad: bool = True, ) -> Iterable[List["Segment"]]: """Split segments in to lines, and crop lines greater than a given length. Args: segments (Iterable[Segment]): An iterable of segments, probably generated from console.render. - length (Optional[int]): Desired line length. + length (int): Desired line length. + style (Style, optional): Style to use for any padding. + pad (bool): Enable padding of lines that are less than `length`. Returns: Iterable[List[Segment]]: An iterable of lines of segments. @@ -64,31 +70,37 @@ class Segment(NamedTuple): if _text: append(cls(_text, style)) if new_line: - yield cls.adjust_line_length(lines[-1], length, style=style) + yield cls.adjust_line_length( + lines[-1], length, style=style, pad=pad + ) lines.append([]) append = lines[-1].append else: append(segment) if lines[-1]: - yield cls.adjust_line_length(lines[-1], length, style=style) + yield cls.adjust_line_length(lines[-1], length, style=style, pad=pad) @classmethod def adjust_line_length( - cls, line: List["Segment"], length: int, style: Style = None + cls, line: List["Segment"], length: int, style: Style = None, pad: bool = True ) -> List["Segment"]: - """Adjust a line to a given width (cropping or padding as required. + """Adjust a line to a given width (cropping or padding as required). Args: segments (Iterable[Segment]): A list of segments in a single line. length (int): The desired width of the line. style (Style, optional): The style of padding if used (space on the end). Defaults to None. + pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True. Returns: List[Segment]: A line of segments with the desired length. """ line_length = sum(len(text) for text, _style in line) if line_length < length: - return line + [Segment(" " * (length - line_length), style)] + if pad: + return line + [cls(" " * (length - line_length), style)] + else: + return line[:] elif line_length > length: line_length = 0 new_line: List[Segment] = [] @@ -100,7 +112,7 @@ class Segment(NamedTuple): line_length += segment_length else: text, style = segment - append(Segment(text[: length - line_length], style)) + append(cls(text[: length - line_length], style)) break return new_line return line[:] diff --git a/rich/syntax.py b/rich/syntax.py index 9406b034..a6be2683 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -1,3 +1,4 @@ +import platform import textwrap from typing import Any, Dict, Optional, Set, Tuple, Union @@ -14,7 +15,8 @@ from .style import Style from .text import Text from ._tools import iter_first -DEFAULT_THEME = "monokai" +WINDOWS = platform.system() == "Windows" +DEFAULT_THEME = "dark" class Syntax: @@ -166,6 +168,24 @@ class Syntax: return len(str(self.start_line + self.code.count("\n"))) + 2 return 0 + def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]: + """Get background, number, and highlight styles for line numbers.""" + background_style = Style(bgcolor=self._pygments_style_class.background_color) + if console.color_system in ("256", "truecolor"): + number_style = Style.chain( + background_style, + self._get_theme_style(Token.Text), + Style(color=self._get_line_numbers_color()), + ) + highlight_number_style = Style.chain( + background_style, + self._get_theme_style(Token.Text), + Style(bold=True, color=self._get_line_numbers_color(0.9)), + ) + else: + number_style = highlight_number_style = Style() + return background_style, number_style, highlight_number_style + def __console_width__(self, max_width: int) -> "RenderWidth": if self.code_width is not None: width = self.code_width + self._numbers_column_width @@ -199,23 +219,20 @@ class Syntax: numbers_column_width = self._numbers_column_width render_options = options.update(width=code_width + numbers_column_width) - background_style = Style(bgcolor=self._pygments_style_class.background_color) - number_style = Style.chain( + ( background_style, - self._get_theme_style(Token.Text), - Style(color=self._get_line_numbers_color()), - ) - highlight_number_style = Style.chain( - background_style, - self._get_theme_style(Token.Text), - Style(bold=True, color=self._get_line_numbers_color(0.9)), - ) + number_style, + highlight_number_style, + ) = self._get_number_styles(console) highlight_line = self.highlight_lines.__contains__ _Segment = Segment padding = _Segment(" " * numbers_column_width, background_style) new_line = _Segment("\n") + + line_pointer = "->" if WINDOWS else "❱ " + for line_no, line in enumerate(lines, self.start_line + line_offset): wrapped_lines = console.render_lines( line, render_options, style=background_style @@ -224,7 +241,7 @@ class Syntax: if first: line_column = str(line_no).rjust(numbers_column_width - 2) + " " if highlight_line(line_no): - yield _Segment("❱ ", number_style) + yield _Segment(line_pointer, number_style) yield _Segment( line_column, highlight_number_style, ) @@ -252,9 +269,9 @@ def __init__(self): syntax = Syntax(CODE, "python", line_numbers=False, theme="monokai") syntax = Syntax.from_path( "rich/segment.py", - theme="monokai", + theme="fruity", line_numbers=True, - line_range=(190, 300), + # line_range=(190, 300), highlight_lines={194}, ) console = Console(record=True) diff --git a/rich/table.py b/rich/table.py index a6da76a7..02e00476 100644 --- a/rich/table.py +++ b/rich/table.py @@ -488,11 +488,13 @@ if __name__ == "__main__": # pragma: no cover expand=True, show_footer=True, show_edge=True, + width=100, + style="on blue", ) # table.columns[0].width = 50 # table.columns[1].ratio = 1 - table.add_row("Hello, World! " * 8, "cake" * 10) + table.add_row("Hello, World! " * 3, "cake" * 10) from .markdown import Markdown table.add_row(Markdown("# This is *Markdown*!"), "More text", "Hello WOrld") diff --git a/rich/traceback.py b/rich/traceback.py index 4c5c3d22..d98e39e8 100644 --- a/rich/traceback.py +++ b/rich/traceback.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from dataclasses import dataclass, field import sys +import platform from traceback import extract_tb from types import TracebackType from typing import Optional, Type, List @@ -17,6 +18,7 @@ from .constrain import Constrain from .highlighter import RegexHighlighter, ReprHighlighter from .padding import Padding from .panel import Panel +from .pygments_themes.base16_dark import base16_default_dark from .rule import Rule from .segment import Segment from .text import Text @@ -24,6 +26,9 @@ from ._tools import iter_last from .syntax import Syntax +WINDOWS = platform.system() == "Windows" + + def install(width: Optional[int] = 100, extra_lines: int = 2) -> None: """Install a rich traceback handler. @@ -205,6 +210,7 @@ class Traceback: @render_group() def _render_stack(self, stack: Stack) -> RenderResult: path_highlighter = PathHighlighter() + theme = "fruity" if WINDOWS else "monokai" for frame in stack.frames: text = Text.assemble( (" File ", "traceback.text"), @@ -220,6 +226,7 @@ class Traceback: try: syntax = Syntax.from_path( frame.filename, + theme=theme, line_numbers=True, line_range=( frame.lineno - self.extra_lines,