Skip to content

Commit

Permalink
Merge pull request #1032 from willmcgugan/fixes-9.11.1
Browse files Browse the repository at this point in the history
Fixes 9.11.1
  • Loading branch information
willmcgugan committed Feb 20, 2021
2 parents d359770 + becaaae commit 3077335
Show file tree
Hide file tree
Showing 21 changed files with 208 additions and 92 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ 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.11.1] - 2021-02-20

### Fixed

- Fixed table with expand=False not expanding when justify="center"
- Fixed single renderable in Layout not respecting height
- Fixed COLUMNS and LINES env var https://github.com/willmcgugan/rich/issues/1019
- Layout now respects minimum_size when fixes sizes are greater than available space
- HTML export now changes link underline score to match terminal https://github.com/willmcgugan/rich/issues/1009

### Changed

- python -m rich.markdown and rich.syntax show usage with no file

### Added

- Added height parameter to Layout
- Added python -m rich.segment

## [9.11.0] - 2021-02-15

### Fixed
Expand Down
13 changes: 4 additions & 9 deletions examples/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@

from time import sleep

from rich.console import Console
from rich.align import Align
from rich.text import Text
from rich.console import Console
from rich.panel import Panel

console = Console()

with console.screen(style="bold white on red") as screen:
for count in range(5, 0, -1):
text = Align.center(
Text.from_markup(f"[blink]Don't Panic![/blink]\n{count}", justify="center"),
vertical="middle",
)
screen.update(Panel(text))
sleep(1)
text = Align.center("[blink]Don't Panic![/blink]", vertical="middle")
screen.update(Panel(text))
sleep(5)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rich"
homepage = "https://github.com/willmcgugan/rich"
documentation = "https://rich.readthedocs.io/en/latest/"
version = "9.11.0"
version = "9.11.1"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
license = "MIT"
Expand Down
5 changes: 4 additions & 1 deletion rich/_ratio.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
remaining = total - sum(size or 0 for size in sizes)
if remaining <= 0:
# No room for flexible edges
return [(size or 1) for size in sizes]
return [
((edge.minimum_size or 1) if size is None else size)
for size, edge in zip(sizes, edges)
]
# Calculate number of characters in a ratio portion
portion = remaining / sum((edge.ratio or 1) for _, edge in flexible_edges)

Expand Down
40 changes: 27 additions & 13 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,18 +299,18 @@ def __init__(
self.screen = Screen(style=style)
self._changed = False

def update(
self, renderable: RenderableType = None, style: StyleType = None
) -> None:
def update(self, *renderables: RenderableType, style: StyleType = None) -> None:
"""Update the screen.
Args:
renderable (RenderableType, optional): Optional renderable to replace current renderable,
or None for no change. Defaults to None.
style: (Style, optional): Replacement style, or None for no change. Defaults to None.
"""
if renderable is not None:
self.screen.renderable = renderable
if renderables:
self.screen.renderable = (
RenderGroup(*renderables) if len(renderables) > 1 else renderables[0]
)
if style is not None:
self.screen.style = style
self.console.print(self.screen, end="")
Expand Down Expand Up @@ -532,6 +532,16 @@ def __init__(
if self.is_jupyter:
width = width or 93
height = height or 100

if width is None:
columns = self._environ.get("COLUMNS")
if columns is not None and columns.isdigit():
width = int(columns)
if height is None:
lines = self._environ.get("LINES")
if lines is not None and lines.isdigit():
height = int(lines)

self.soft_wrap = soft_wrap
self._width = width
self._height = height
Expand Down Expand Up @@ -1050,6 +1060,7 @@ def render_lines(
*,
style: Optional[Style] = None,
pad: bool = True,
new_lines: bool = False,
) -> List[List[Segment]]:
"""Render objects in to a list of lines.
Expand All @@ -1061,7 +1072,7 @@ def render_lines(
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``.
range (Optional[Tuple[int, int]], optional): Range of lines to render, or ``None`` for all line. Defaults to ``None``
new_lines (bool, optional): Include "\n" characters at end of line.
Returns:
List[List[Segment]]: A list of lines, where a line is a list of Segment objects.
Expand All @@ -1072,7 +1083,10 @@ def render_lines(
_rendered = Segment.apply_style(_rendered, style)
lines = list(
Segment.split_and_crop_lines(
_rendered, render_options.max_width, include_new_lines=False, pad=pad
_rendered,
render_options.max_width,
include_new_lines=new_lines,
pad=pad,
)
)
if render_options.height is not None:
Expand Down Expand Up @@ -1368,7 +1382,7 @@ def print(
for hook in self._render_hooks:
renderables = hook.process_renderables(renderables)
render_options = self.options.update(
justify="default",
justify=justify,
overflow=overflow,
width=min(width, self.width) if width else NO_CHANGE,
height=height,
Expand Down Expand Up @@ -1697,9 +1711,9 @@ def escape(text: str) -> str:
text = escape(text)
if style:
rule = style.get_html_style(_theme)
text = f'<span style="{rule}">{text}</span>' if rule else text
if style.link:
text = f'<a href="{style.link}">{text}</a>'
text = f'<span style="{rule}">{text}</span>' if rule else text
append(text)
else:
styles: Dict[str, int] = {}
Expand All @@ -1709,11 +1723,11 @@ def escape(text: str) -> str:
text = escape(text)
if style:
rule = style.get_html_style(_theme)
if rule:
style_number = styles.setdefault(rule, len(styles) + 1)
text = f'<span class="r{style_number}">{text}</span>'
style_number = styles.setdefault(rule, len(styles) + 1)
if style.link:
text = f'<a href="{style.link}">{text}</a>'
text = f'<a class="r{style_number}" href="{style.link}">{text}</a>'
else:
text = f'<span class="r{style_number}">{text}</span>'
append(text)
stylesheet_rules: List[str] = []
stylesheet_append = stylesheet_rules.append
Expand Down
23 changes: 16 additions & 7 deletions rich/layout.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .align import Align
from .console import Console, ConsoleOptions, RenderResult, RenderableType
from .highlighter import ReprHighlighter
from ._loop import loop_last
from .panel import Panel
from .pretty import Pretty
from ._ratio import ratio_resolve
Expand Down Expand Up @@ -77,6 +78,7 @@ def __init__(
ratio: int = 1,
name: str = None,
visible: bool = True,
height: int = None,
) -> None:
self._renderable = renderable or _Placeholder(self)
self.direction = direction
Expand All @@ -85,6 +87,7 @@ def __init__(
self.ratio = ratio
self.name = name
self.visible = visible
self.height = height
self._children: List[Layout] = []

def __repr__(self) -> str:
Expand Down Expand Up @@ -179,13 +182,19 @@ def update(self, renderable: RenderableType) -> None:
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
options = options.update(height=options.height or options.size.height)
render_options = options.update(
height=options.height or self.height or options.size.height
)
if not self.children:
yield from console.render(self._renderable or "", options)
for line in console.render_lines(
self._renderable or "", render_options, new_lines=True
):
yield from line

elif self.direction == "vertical":
yield from self._render_vertical(console, options)
yield from self._render_vertical(console, render_options)
elif self.direction == "horizontal":
yield from self._render_horizontal(console, options)
yield from self._render_horizontal(console, render_options)

def _render_horizontal(
self, console: Console, options: ConsoleOptions
Expand All @@ -206,14 +215,14 @@ def _render_vertical(
) -> RenderResult:
render_heights = ratio_resolve(options.height or console.height, self.children)
renders = [
console.render_lines(child.renderable, options.update(height=render_height))
console.render_lines(
child.renderable, options.update(height=render_height), new_lines=True
)
for child, render_height in zip(self.children, render_heights)
]
new_line = Segment.line()
for render in renders:
for line in render:
yield from line
yield new_line


if __name__ == "__main__": # type: ignore
Expand Down
6 changes: 3 additions & 3 deletions rich/live.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Live(JupyterMixin, RenderHook):
screen (bool, optional): Enable alternate screen mode. Defaults to False.
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True
refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 1.
transient (bool, optional): Clear the renderable on exit. Defaults to False.
transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False.
redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True.
redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True.
vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis".
Expand Down Expand Up @@ -76,7 +76,7 @@ def __init__(
self.ipy_widget: Optional[Any] = None
self.auto_refresh = auto_refresh
self._started: bool = False
self.transient = transient
self.transient = True if screen else transient

self._refresh_thread: Optional[_RefreshThread] = None
self.refresh_per_second = refresh_per_second
Expand Down Expand Up @@ -145,7 +145,7 @@ def stop(self) -> None:
if self._refresh_thread is not None:
self._refresh_thread.join()
self._refresh_thread = None
if self.transient and not self._screen:
if self.transient and not self._alt_screen:
self.console.control(self._live_render.restore_cursor())
if self.ipy_widget is not None: # pragma: no cover
if self.transient:
Expand Down
5 changes: 2 additions & 3 deletions rich/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,7 @@ def __rich_console__(
parser.add_argument(
"path",
metavar="PATH",
nargs="?",
help="path to markdown file",
help="path to markdown file, or - for stdin",
)
parser.add_argument(
"-c",
Expand Down Expand Up @@ -593,7 +592,7 @@ def __rich_console__(

from rich.console import Console

if not args.path or args.path == "-":
if args.path == "-":
markdown_body = sys.stdin.read()
else:
with open(args.path, "rt", encoding="utf-8") as markdown_file:
Expand Down
6 changes: 6 additions & 0 deletions rich/padding.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,9 @@ def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
measurement = Measurement(measure_min + extra_width, measure_max + extra_width)
measurement = measurement.with_maximum(max_width)
return measurement


if __name__ == "__main__": # pragma: no cover
from rich import print

print(Padding("Hello, World", (2, 4), style="on blue"))
13 changes: 5 additions & 8 deletions rich/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,12 @@ def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
from .box import ROUNDED, DOUBLE

p = Panel(
Panel.fit(
Text.from_markup("[bold magenta]Hello World!"),
box=ROUNDED,
safe_box=True,
style="on red",
),
title="[b]Hello, World",
"Hello, World!",
title="rich.Panel",
style="white on blue",
box=DOUBLE,
padding=1,
)

print(p)
c.print()
c.print(p)
10 changes: 8 additions & 2 deletions rich/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ def __rich_console__(
no_wrap=pick_bool(self.no_wrap, options.no_wrap),
style="pretty",
)
pretty_text = self.highlighter(pretty_text)
pretty_text = (
self.highlighter(pretty_text)
if pretty_text
else Text("__repr__ return empty string", style="dim italic")
)
if self.indent_guides and not options.ascii_only:
pretty_text = pretty_text.with_indent_guides(
self.indent_size, style="repr.indent"
Expand All @@ -209,7 +213,9 @@ def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
max_length=self.max_length,
max_string=self.max_string,
)
text_width = max(cell_len(line) for line in pretty_str.splitlines())
text_width = (
max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
)
return Measurement(text_width, text_width)


Expand Down
37 changes: 31 additions & 6 deletions rich/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,34 @@ def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:


if __name__ == "__main__": # pragma: no cover
lines = [[Segment("Hello")]]
lines = Segment.set_shape(lines, 50, 4, style=Style.parse("on blue"))
for line in lines:
print(line)

print(Style.parse("on blue") + Style.parse("on red"))
from rich.syntax import Syntax
from rich.text import Text
from rich.console import Console

code = """from rich.console import Console
console = Console()
text = Text.from_markup("Hello, [bold magenta]World[/]!")
console.print(text)"""

text = Text.from_markup("Hello, [bold magenta]World[/]!")

console = Console()

console.rule("rich.Segment")
console.print(
"A Segment is the last step in the Rich render process before gemerating text with ANSI codes."
)
console.print("\nConsider the following code:\n")
console.print(Syntax(code, "python", line_numbers=True))
console.print()
console.print(
"When you call [b]print()[/b], Rich [i]renders[/i] the object in to the the following:\n"
)
fragments = list(console.render(text))
console.print(fragments)
console.print()
console.print("The Segments are then processed to produce the following output:\n")
console.print(text)
console.print(
"\nYou will only need to know this if you are implementing your own Rich renderables."
)
1 change: 1 addition & 0 deletions rich/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ def get_html_style(self, theme: TerminalTheme = None) -> str:
if color is not None:
theme_color = color.get_truecolor(theme)
append(f"color: {theme_color.hex}")
append(f"text-decoration-color: {theme_color.hex}")
if bgcolor is not None:
theme_color = bgcolor.get_truecolor(theme, foreground=False)
append(f"background-color: {theme_color.hex}")
Expand Down
Loading

0 comments on commit 3077335

Please sign in to comment.