mirror of https://github.com/Textualize/rich.git
fixes
This commit is contained in:
parent
1c1d72ccad
commit
83cc870695
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -5,6 +5,18 @@ 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.12.0] - 2021-02-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue with Syntax and missing lines in Layout https://github.com/willmcgugan/rich/issues/1050
|
||||
- Fixed issue with nested markdown elements https://github.com/willmcgugan/rich/issues/1036
|
||||
- Fixed new lines not invoking render hooks https://github.com/willmcgugan/rich/issues/1052
|
||||
|
||||
### Changed
|
||||
|
||||
- Printing a table with no columns now result in a blank line https://github.com/willmcgugan/rich/issues/1044
|
||||
|
||||
## [9.11.1] - 2021-02-20
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -52,7 +52,18 @@ See :ref:`appendix_box` for other box styles.
|
|||
The :class:`~rich.table.Table` class offers a number of configuration options to set the look and feel of the table, including how borders are rendered and the style and alignment of the columns.
|
||||
|
||||
|
||||
Adding columns
|
||||
Empty Tables
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Printing a table with no columns results in a blank line. If you are building a table dynamically and the data source has no columns, you might want to print something different. Here's how you might do that::
|
||||
|
||||
if table.columns:
|
||||
print(table)
|
||||
else:
|
||||
print("[i]No data...[/i]")
|
||||
|
||||
|
||||
Adding Columns
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You may also add columns by specifying them in the positional arguments of the :class:`~rich.table.Table` constructor. For example, we could construct a table with three columns like this::
|
||||
|
|
|
@ -49,7 +49,7 @@ class Clock:
|
|||
|
||||
layout["header"].update(Clock())
|
||||
|
||||
with Live(layout, screen=True) as live:
|
||||
with Live(layout, screen=True, redirect_stderr=False) as live:
|
||||
try:
|
||||
while True:
|
||||
sleep(1)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "rich"
|
||||
homepage = "https://github.com/willmcgugan/rich"
|
||||
documentation = "https://rich.readthedocs.io/en/latest/"
|
||||
version = "9.11.1"
|
||||
version = "9.12.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 @@ from typing import (
|
|||
NamedTuple,
|
||||
Optional,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
@ -32,7 +31,7 @@ from typing_extensions import Literal, Protocol, runtime_checkable
|
|||
|
||||
from . import errors, themes
|
||||
from ._emoji_replace import _emoji_replace
|
||||
from ._log_render import LogRender, FormatTimeCallable
|
||||
from ._log_render import FormatTimeCallable, LogRender
|
||||
from .align import Align, AlignMethod
|
||||
from .color import ColorSystem
|
||||
from .control import Control
|
||||
|
@ -148,7 +147,7 @@ class ConsoleOptions:
|
|||
"""Update values, return a copy."""
|
||||
options = replace(self)
|
||||
if not isinstance(width, NoChange):
|
||||
options.min_width = options.max_width = width
|
||||
options.min_width = options.max_width = max(0, width)
|
||||
if not isinstance(min_width, NoChange):
|
||||
options.min_width = min_width
|
||||
if not isinstance(max_width, NoChange):
|
||||
|
@ -162,7 +161,7 @@ class ConsoleOptions:
|
|||
if not isinstance(highlight, NoChange):
|
||||
options.highlight = highlight
|
||||
if not isinstance(height, NoChange):
|
||||
options.height = height
|
||||
options.height = None if height is None else max(0, height)
|
||||
return options
|
||||
|
||||
def update_width(self, width: int) -> "ConsoleOptions":
|
||||
|
@ -174,6 +173,7 @@ class ConsoleOptions:
|
|||
Returns:
|
||||
~ConsoleOptions: New console options instance
|
||||
"""
|
||||
width = max(0, width)
|
||||
options = replace(self, min_width=width, max_width=width)
|
||||
return options
|
||||
|
||||
|
@ -210,6 +210,18 @@ class CaptureError(Exception):
|
|||
"""An error in the Capture context manager."""
|
||||
|
||||
|
||||
class NewLine:
|
||||
"""A renderable to generate new line(s)"""
|
||||
|
||||
def __init__(self, count: int = 1) -> None:
|
||||
self.count = count
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Iterable[Segment]:
|
||||
yield Segment("\n" * self.count)
|
||||
|
||||
|
||||
class Capture:
|
||||
"""Context manager to capture the result of printing to the console.
|
||||
See :meth:`~rich.console.Console.capture` for how to use.
|
||||
|
@ -913,9 +925,7 @@ class Console:
|
|||
"""
|
||||
|
||||
assert count >= 0, "count must be >= 0"
|
||||
if count:
|
||||
self._buffer.append(Segment("\n" * count))
|
||||
self._check_buffer()
|
||||
self.print(NewLine(count))
|
||||
|
||||
def clear(self, home: bool = True) -> None:
|
||||
"""Clear the screen.
|
||||
|
@ -1072,7 +1082,7 @@ class Console:
|
|||
options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``.
|
||||
style (Style, optional): Optional style to apply to renderables. Defaults to ``None``.
|
||||
pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``.
|
||||
new_lines (bool, optional): Include "\n" characters at end of line.
|
||||
new_lines (bool, optional): Include "\n" characters at end of lines.
|
||||
|
||||
Returns:
|
||||
List[List[Segment]]: A list of lines, where a line is a list of Segment objects.
|
||||
|
@ -1082,17 +1092,27 @@ class Console:
|
|||
if style is not None:
|
||||
_rendered = Segment.apply_style(_rendered, style)
|
||||
lines = list(
|
||||
islice(
|
||||
Segment.split_and_crop_lines(
|
||||
_rendered,
|
||||
render_options.max_width,
|
||||
include_new_lines=new_lines,
|
||||
pad=pad,
|
||||
),
|
||||
None,
|
||||
render_options.height,
|
||||
)
|
||||
)
|
||||
if render_options.height is not None:
|
||||
lines = Segment.set_shape(
|
||||
lines, render_options.max_width, render_options.height, style=style
|
||||
)
|
||||
extra_lines = render_options.height - len(lines)
|
||||
if extra_lines > 0:
|
||||
pad_line = [
|
||||
[Segment(" " * render_options.max_width, style), Segment("\n")]
|
||||
if new_lines
|
||||
else [Segment(" " * render_options.max_width, style)]
|
||||
]
|
||||
lines.extend(pad_line * extra_lines)
|
||||
|
||||
return lines
|
||||
|
||||
def render_str(
|
||||
|
@ -1357,8 +1377,7 @@ class Console:
|
|||
Console default. Defaults to ``None``.
|
||||
"""
|
||||
if not objects:
|
||||
self.line()
|
||||
return
|
||||
objects = (NewLine(),)
|
||||
|
||||
if soft_wrap is None:
|
||||
soft_wrap = self.soft_wrap
|
||||
|
@ -1470,8 +1489,8 @@ class Console:
|
|||
_stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1.
|
||||
"""
|
||||
if not objects:
|
||||
self.line()
|
||||
return
|
||||
objects = (NewLine(),)
|
||||
|
||||
with self:
|
||||
renderables = self._collect_renderables(
|
||||
objects,
|
||||
|
|
|
@ -130,14 +130,14 @@ class Live(JupyterMixin, RenderHook):
|
|||
self._refresh_thread.stop()
|
||||
# allow it to fully render on the last even if overflow
|
||||
self.vertical_overflow = "visible"
|
||||
if not self._alt_screen:
|
||||
if not self.console.is_jupyter:
|
||||
if not self._alt_screen and not self.console.is_jupyter:
|
||||
self.refresh()
|
||||
if self.console.is_terminal:
|
||||
self.console.line()
|
||||
|
||||
finally:
|
||||
self._disable_redirect_io()
|
||||
self.console.pop_render_hook()
|
||||
if not self._alt_screen and self.console.is_terminal:
|
||||
self.console.line()
|
||||
self.console.show_cursor(True)
|
||||
if self._alt_screen:
|
||||
self.console.set_alt_screen(False)
|
||||
|
|
|
@ -367,7 +367,7 @@ class MarkdownContext:
|
|||
|
||||
def on_text(self, text: str, node_type: str) -> None:
|
||||
"""Called when the parser visits text."""
|
||||
if node_type == "code" and self._syntax is not None:
|
||||
if node_type in "code" and self._syntax is not None:
|
||||
highlight_text = self._syntax.highlight(text)
|
||||
highlight_text.rstrip()
|
||||
self.stack.top.on_text(
|
||||
|
@ -517,10 +517,13 @@ class Markdown(JupyterMixin):
|
|||
if current.literal:
|
||||
element.on_text(context, current.literal.rstrip())
|
||||
context.stack.pop()
|
||||
if context.stack.top.on_child_close(context, element):
|
||||
if new_line:
|
||||
yield Segment("\n")
|
||||
yield from console.render(element, context.options)
|
||||
element.on_leave(context)
|
||||
else:
|
||||
element.on_leave(context)
|
||||
new_line = element.new_line
|
||||
|
||||
|
||||
|
|
|
@ -275,6 +275,7 @@ class Segment(NamedTuple):
|
|||
width: int,
|
||||
height: int = None,
|
||||
style: Style = None,
|
||||
new_lines: bool = False,
|
||||
) -> List[List["Segment"]]:
|
||||
"""Set the shape of a list of lines (enclosing rectangle).
|
||||
|
||||
|
@ -283,15 +284,21 @@ class Segment(NamedTuple):
|
|||
width (int): Desired width.
|
||||
height (int, optional): Desired height or None for no change.
|
||||
style (Style, optional): Style of any padding added. Defaults to None.
|
||||
new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
|
||||
|
||||
Returns:
|
||||
List[List[Segment]]: New list of lines that fits width x height.
|
||||
"""
|
||||
if height is None:
|
||||
height = len(lines)
|
||||
new_lines: List[List[Segment]] = []
|
||||
pad_line = [Segment(" " * width, style)]
|
||||
append = new_lines.append
|
||||
shaped_lines: List[List[Segment]] = []
|
||||
pad_line = (
|
||||
[Segment(" " * width, style), Segment("\n")]
|
||||
if new_lines
|
||||
else [Segment(" " * width, style)]
|
||||
)
|
||||
|
||||
append = shaped_lines.append
|
||||
adjust_line_length = cls.adjust_line_length
|
||||
line: Optional[List[Segment]]
|
||||
iter_lines = iter(lines)
|
||||
|
@ -301,7 +308,7 @@ class Segment(NamedTuple):
|
|||
append(pad_line)
|
||||
else:
|
||||
append(adjust_line_length(line, width, style=style))
|
||||
return new_lines
|
||||
return shaped_lines
|
||||
|
||||
@classmethod
|
||||
def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
|
||||
|
|
|
@ -530,10 +530,11 @@ class Syntax(JupyterMixin):
|
|||
if self.word_wrap:
|
||||
wrapped_lines = console.render_lines(
|
||||
line,
|
||||
render_options,
|
||||
render_options.update(height=None),
|
||||
style=background_style,
|
||||
pad=not transparent_background,
|
||||
)
|
||||
|
||||
else:
|
||||
segments = list(line.render(console, end=""))
|
||||
if options.no_wrap:
|
||||
|
|
|
@ -410,6 +410,10 @@ class Table(JupyterMixin):
|
|||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
|
||||
if not self.columns:
|
||||
yield Segment("\n")
|
||||
return
|
||||
|
||||
max_width = options.max_width
|
||||
if self.width is not None:
|
||||
max_width = self.width
|
||||
|
|
|
@ -38,7 +38,7 @@ def render_log():
|
|||
|
||||
def test_log():
|
||||
expected = replace_link_ids(
|
||||
"\n\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0mHello from \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;34m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m ! \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:34\x1b[0m\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0m\x1b[1m[\x1b[0m\x1b[1;34m1\x1b[0m, \x1b[1;34m2\x1b[0m, \x1b[1;34m3\x1b[0m\x1b[1m]\x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:35\x1b[0m\n \x1b[34m╭─\x1b[0m\x1b[34m───────────────────── \x1b[0m\x1b[3;34mlocals\x1b[0m\x1b[34m ─────────────────────\x1b[0m\x1b[34m─╮\x1b[0m \n \x1b[34m│\x1b[0m \x1b[3;33mconsole\x1b[0m\x1b[31m =\x1b[0m \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;34m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m \x1b[34m│\x1b[0m \n \x1b[34m╰────────────────────────────────────────────────────╯\x1b[0m \n"
|
||||
"\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:33\x1b[0m\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0mHello from \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;34m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m ! \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:34\x1b[0m\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0m\x1b[1m[\x1b[0m\x1b[1;34m1\x1b[0m, \x1b[1;34m2\x1b[0m, \x1b[1;34m3\x1b[0m\x1b[1m]\x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2mtest_log.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:35\x1b[0m\n \x1b[34m╭─\x1b[0m\x1b[34m───────────────────── \x1b[0m\x1b[3;34mlocals\x1b[0m\x1b[34m ─────────────────────\x1b[0m\x1b[34m─╮\x1b[0m \n \x1b[34m│\x1b[0m \x1b[3;33mconsole\x1b[0m\x1b[31m =\x1b[0m \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;34m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m \x1b[34m│\x1b[0m \n \x1b[34m╰────────────────────────────────────────────────────╯\x1b[0m \n"
|
||||
)
|
||||
rendered = render_log()
|
||||
print(repr(rendered))
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -141,6 +141,15 @@ def test_min_width():
|
|||
assert all(len(line) == 30 for line in output.splitlines())
|
||||
|
||||
|
||||
def test_no_columns():
|
||||
console = Console(color_system=None)
|
||||
console.begin_capture()
|
||||
console.print(Table())
|
||||
output = console.end_capture()
|
||||
print(repr(output))
|
||||
assert output == "\n"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
render = render_tables()
|
||||
print(render)
|
||||
|
|
Loading…
Reference in New Issue