mirror of
https://github.com/Textualize/rich.git
synced 2025-07-07 21:04:58 +00:00
rich measure
This commit is contained in:
parent
27379a8f1a
commit
be5e9dca2d
43 changed files with 301 additions and 185 deletions
|
@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Changed Layout.split to use new Splitter class
|
||||
- Improved layout.tree
|
||||
- Changed default theme color for repr.number to cyan
|
||||
- `__rich_measure__` signature changed to accept ConsoleOptions rather than max_width
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -29,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Added Layout.add_split, Layout.split_column, Layout.split_row, layout.refresh
|
||||
- Added new Rich repr protocol `__rich_repr__`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed table style taking precedence over row style https://github.com/willmcgugan/rich/issues/1129
|
||||
- Fixed incorrect measurement of Text with new lines and whitespace https://github.com/willmcgugan/rich/issues/1133
|
||||
|
||||
## [9.13.0] - 2021-03-06
|
||||
|
||||
### Added
|
||||
|
|
|
@ -18,25 +18,22 @@ To define a layout, construct a Layout object and print it::
|
|||
layout = Layout()
|
||||
print(layout)
|
||||
|
||||
This will draw a box the size of the terminal with some information regarding the layout. The box is a "placeholder" because we have yet to add any content to it. Before we do that, let's create a more interesting layout by calling the :meth:`~rich.layout.Layout.split` method to divide the layout in to two sub-layouts::
|
||||
This will draw a box the size of the terminal with some information regarding the layout. The box is a "placeholder" because we have yet to add any content to it. Before we do that, let's create a more interesting layout by calling the :meth:`~rich.layout.Layout.split_column` method to divide the layout in to two sub-layouts::
|
||||
|
||||
layout.split(
|
||||
layout.split_column(
|
||||
Layout(name="upper"),
|
||||
Layout(name="lower")
|
||||
)
|
||||
print(layout)
|
||||
|
||||
This will divide the terminal screen in to two equal sized portions, one on top of the other. The ``name`` attribute is an internal identifier we can use to look up the sub-layout later. Let's use that to create another split::
|
||||
This will divide the terminal screen in to two equal sized portions, one on top of the other. The ``name`` attribute is an internal identifier we can use to look up the sub-layout later. Let's use that to create another split, this time we will call :meth:`~rich.layout.Layout.split_row` to split the lower layout in to a row of two sub-layouts.
|
||||
|
||||
layout["lower"].split(
|
||||
layout["lower"].split_row(
|
||||
Layout(name="left"),
|
||||
Layout(name="right"),
|
||||
direction="horizontal"
|
||||
Layout(name="right"),
|
||||
)
|
||||
print(layout)
|
||||
|
||||
The addition of the ``direction="horizontal"`` tells the Layout class to split left-to-right, rather than the default of top-to-bottom.
|
||||
|
||||
You should now see the screen area divided in to 3 portions; an upper half and a lower half that is split in to two quarters.
|
||||
|
||||
.. raw:: html
|
||||
|
|
|
@ -64,10 +64,10 @@ For complete control over how a custom object is rendered to the terminal, you c
|
|||
Measuring Renderables
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sometimes Rich needs to know how many characters an object will take up when rendering. The :class:`~rich.table.Table` class, for instance, will use this information to calculate the optimal dimensions for the columns. If you aren't using one of the renderable objects in the Rich module, you will need to supply a ``__rich_measure__`` method which accepts a :class:`~rich.console.Console` and the maximum width and returns a :class:`~rich.measure.Measurement` object. The Measurement object should contain the *minimum* and *maximum* number of characters required to render.
|
||||
Sometimes Rich needs to know how many characters an object will take up when rendering. The :class:`~rich.table.Table` class, for instance, will use this information to calculate the optimal dimensions for the columns. If you aren't using one of the renderable objects in the Rich module, you will need to supply a ``__rich_measure__`` method which accepts a :class:`~rich.console.Console` and :class:`~rich.console.ConsoleOptions` and returns a :class:`~rich.measure.Measurement` object. The Measurement object should contain the *minimum* and *maximum* number of characters required to render.
|
||||
|
||||
For example, if we are rendering a chess board, it would require a minimum of 8 characters to render. The maximum can be left as the maximum available width (assuming a centered board)::
|
||||
|
||||
class ChessBoard:
|
||||
def __rich_measure__(self, console: Console, max_width: int) -> Measurement:
|
||||
return Measurement(8, max_width)
|
||||
def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
|
||||
return Measurement(8, options.max_width)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "rich"
|
||||
homepage = "https://github.com/willmcgugan/rich"
|
||||
documentation = "https://rich.readthedocs.io/en/latest/"
|
||||
version = "9.13.0"
|
||||
version = "10.0.0"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
|
|
@ -30,8 +30,10 @@ class ColorBox:
|
|||
yield Segment("▄", Style(color=color, bgcolor=bgcolor))
|
||||
yield Segment.line()
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
return Measurement(1, max_width)
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return Measurement(1, options.max_width)
|
||||
|
||||
|
||||
def make_test_card() -> Table:
|
||||
|
|
|
@ -133,7 +133,7 @@ class Align(JupyterMixin):
|
|||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
align = self.align
|
||||
width = Measurement.get(console, self.renderable).maximum
|
||||
width = Measurement.get(console, options, self.renderable).maximum
|
||||
rendered = console.render(
|
||||
Constrain(
|
||||
self.renderable, width if self.width is None else min(width, self.width)
|
||||
|
@ -221,8 +221,10 @@ class Align(JupyterMixin):
|
|||
iter_segments = Segment.apply_style(iter_segments, style)
|
||||
yield from iter_segments
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
measurement = Measurement.get(console, self.renderable, max_width)
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
measurement = Measurement.get(console, options, self.renderable)
|
||||
return measurement
|
||||
|
||||
|
||||
|
@ -275,8 +277,10 @@ class VerticalCenter(JupyterMixin):
|
|||
if bottom_space > 0:
|
||||
yield from blank_lines(bottom_space)
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
measurement = Measurement.get(console, self.renderable, max_width)
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
measurement = Measurement.get(console, options, self.renderable)
|
||||
return measurement
|
||||
|
||||
|
||||
|
|
11
rich/bar.py
11
rich/bar.py
|
@ -49,7 +49,10 @@ class Bar(JupyterMixin):
|
|||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
|
||||
width = min(self.width or options.max_width, options.max_width)
|
||||
width = min(
|
||||
self.width if self.width is not None else options.max_width,
|
||||
options.max_width,
|
||||
)
|
||||
|
||||
if self.begin >= self.end:
|
||||
yield Segment(" " * width, self.style)
|
||||
|
@ -81,9 +84,11 @@ class Bar(JupyterMixin):
|
|||
yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
|
||||
yield Segment.line()
|
||||
|
||||
def __rich_measure__(self, console: Console, max_width: int) -> Measurement:
|
||||
def __rich_measure__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return (
|
||||
Measurement(self.width, self.width)
|
||||
if self.width is not None
|
||||
else Measurement(4, max_width)
|
||||
else Measurement(4, options.max_width)
|
||||
)
|
||||
|
|
|
@ -77,7 +77,7 @@ class Columns(JupyterMixin):
|
|||
|
||||
get_measurement = Measurement.get
|
||||
renderable_widths = [
|
||||
get_measurement(console, renderable, max_width).maximum
|
||||
get_measurement(console, options, renderable).maximum
|
||||
for renderable in renderables
|
||||
]
|
||||
if self.equal:
|
||||
|
|
|
@ -127,6 +127,8 @@ class ConsoleOptions:
|
|||
"""Disable wrapping for text."""
|
||||
highlight: Optional[bool] = None
|
||||
"""Highlight override for render_str."""
|
||||
markup: Optional[bool] = None
|
||||
"""Enable markup when rendering strings."""
|
||||
height: Optional[int] = None
|
||||
"""Height available, or None for no height limit."""
|
||||
|
||||
|
@ -155,6 +157,7 @@ class ConsoleOptions:
|
|||
overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE,
|
||||
no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE,
|
||||
highlight: Union[Optional[bool], NoChange] = NO_CHANGE,
|
||||
markup: Union[Optional[bool], NoChange] = NO_CHANGE,
|
||||
height: Union[Optional[int], NoChange] = NO_CHANGE,
|
||||
) -> "ConsoleOptions":
|
||||
"""Update values, return a copy."""
|
||||
|
@ -173,6 +176,8 @@ class ConsoleOptions:
|
|||
options.no_wrap = no_wrap
|
||||
if not isinstance(highlight, NoChange):
|
||||
options.highlight = highlight
|
||||
if not isinstance(markup, NoChange):
|
||||
options.markup = markup
|
||||
if not isinstance(height, NoChange):
|
||||
options.height = None if height is None else max(0, height)
|
||||
return options
|
||||
|
@ -405,11 +410,13 @@ class RenderGroup:
|
|||
self._render = list(self._renderables)
|
||||
return self._render
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
if self.fit:
|
||||
return measure_renderables(console, self.renderables, max_width)
|
||||
return measure_renderables(console, options, self.renderables)
|
||||
else:
|
||||
return Measurement(max_width, max_width)
|
||||
return Measurement(options.max_width, options.max_width)
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
|
@ -1105,7 +1112,9 @@ class Console:
|
|||
if hasattr(renderable, "__rich_console__"):
|
||||
render_iterable = renderable.__rich_console__(self, _options) # type: ignore
|
||||
elif isinstance(renderable, str):
|
||||
text_renderable = self.render_str(renderable, highlight=_options.highlight)
|
||||
text_renderable = self.render_str(
|
||||
renderable, highlight=_options.highlight, markup=_options.markup
|
||||
)
|
||||
render_iterable = text_renderable.__rich_console__(self, _options) # type: ignore
|
||||
else:
|
||||
raise errors.NotRenderableError(
|
||||
|
@ -1467,9 +1476,10 @@ class Console:
|
|||
render_options = self.options.update(
|
||||
justify=justify,
|
||||
overflow=overflow,
|
||||
width=min(width, self.width) if width else NO_CHANGE,
|
||||
width=min(width, self.width) if width is not None else NO_CHANGE,
|
||||
height=height,
|
||||
no_wrap=no_wrap,
|
||||
markup=markup,
|
||||
)
|
||||
|
||||
new_segments: List[Segment] = []
|
||||
|
|
|
@ -28,8 +28,10 @@ class Constrain(JupyterMixin):
|
|||
child_options = options.update(width=min(self.width, options.max_width))
|
||||
yield from console.render(self.renderable, child_options)
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
if self.width is not None:
|
||||
max_width = min(self.width, max_width)
|
||||
measurement = Measurement.get(console, self.renderable, max_width)
|
||||
max_width = min(self.width, options.max_width)
|
||||
measurement = Measurement.get(console, options, self.renderable)
|
||||
return measurement
|
||||
|
|
|
@ -39,9 +39,11 @@ class Renderables:
|
|||
"""Console render method to insert line-breaks."""
|
||||
yield from self._renderables
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
dimensions = [
|
||||
Measurement.get(console, renderable, max_width)
|
||||
Measurement.get(console, options, renderable)
|
||||
for renderable in self._renderables
|
||||
]
|
||||
if not dimensions:
|
||||
|
|
|
@ -27,8 +27,9 @@ CONTROL_CODES_FORMAT: Dict[int, Callable] = {
|
|||
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.CURSOR_MOVE_TO_ROW: lambda param: f"\x1b[{param+1}G",
|
||||
ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
|
||||
ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y};{x}H",
|
||||
ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
|
||||
}
|
||||
|
||||
|
||||
|
@ -40,7 +41,7 @@ class Control:
|
|||
tuple of ControlType and an integer parameter
|
||||
"""
|
||||
|
||||
__slots__ = ["_segment"]
|
||||
__slots__ = ["segment"]
|
||||
|
||||
def __init__(self, *codes: Union[ControlType, ControlCode]) -> None:
|
||||
control_codes: List[ControlCode] = [
|
||||
|
@ -50,11 +51,7 @@ class Control:
|
|||
rendered_codes = "".join(
|
||||
_format_map[code](*parameters) for code, *parameters in control_codes
|
||||
)
|
||||
self._segment = Segment(rendered_codes, None, control_codes)
|
||||
|
||||
@property
|
||||
def segment(self) -> "Segment":
|
||||
return self._segment
|
||||
self.segment = Segment(rendered_codes, None, control_codes)
|
||||
|
||||
@classmethod
|
||||
def bell(cls) -> "Control":
|
||||
|
@ -67,7 +64,7 @@ class Control:
|
|||
return cls(ControlType.HOME)
|
||||
|
||||
@classmethod
|
||||
def move(cls, x: int, y: int) -> "Control":
|
||||
def move(cls, x: int = 0, y: int = 0) -> "Control":
|
||||
"""Move cursor relative to current position.
|
||||
|
||||
Args:
|
||||
|
@ -84,18 +81,41 @@ class Control:
|
|||
if x:
|
||||
yield (
|
||||
control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD,
|
||||
x,
|
||||
abs(x),
|
||||
)
|
||||
|
||||
if y:
|
||||
yield (
|
||||
control.CURSOR_DOWN if y > 0 else control.CURSOR_UP,
|
||||
y,
|
||||
abs(y),
|
||||
)
|
||||
|
||||
control = cls(*get_codes())
|
||||
return control
|
||||
|
||||
@classmethod
|
||||
def move_to_row(cls, x: int, y: int = 0) -> "Control":
|
||||
"""Move to the given row, optionally add offset to column.
|
||||
|
||||
Returns:
|
||||
x (int): absolute x (column)
|
||||
y (int): optional y offset (row)
|
||||
|
||||
Returns:
|
||||
~Control: Control object.
|
||||
"""
|
||||
|
||||
return (
|
||||
cls(
|
||||
(ControlType.CURSOR_MOVE_TO_ROW, x + 1),
|
||||
(
|
||||
ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP,
|
||||
abs(y),
|
||||
),
|
||||
)
|
||||
if y
|
||||
else cls((ControlType.CURSOR_MOVE_TO_ROW, x))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def move_to(cls, x: int, y: int) -> "Control":
|
||||
"""Move cursor to absolute position.
|
||||
|
@ -107,7 +127,7 @@ class Control:
|
|||
Returns:
|
||||
~Control: Control object.
|
||||
"""
|
||||
return cls((ControlType.CURSOR_MOVE_TO, x + 1, y + 1))
|
||||
return cls((ControlType.CURSOR_MOVE_TO, x, y))
|
||||
|
||||
@classmethod
|
||||
def clear(cls) -> "Control":
|
||||
|
@ -128,12 +148,12 @@ class Control:
|
|||
return cls(ControlType.DISABLE_ALT_SCREEN)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._segment.text
|
||||
return self.segment.text
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
yield self._segment
|
||||
yield self.segment
|
||||
|
||||
|
||||
def strip_control_codes(text: str, _translate_table=_CONTROL_TRANSLATE) -> str:
|
||||
|
|
|
@ -4,7 +4,6 @@ from operator import itemgetter
|
|||
from threading import RLock
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
|
@ -17,7 +16,6 @@ from typing import (
|
|||
|
||||
from typing_extensions import Literal
|
||||
|
||||
from ._loop import loop_last
|
||||
from ._ratio import ratio_resolve
|
||||
from .align import Align
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
|
@ -327,7 +325,7 @@ class Layout:
|
|||
with self._lock:
|
||||
self._renderable = renderable
|
||||
|
||||
def refresh(self, console: "Console", layout_name: str) -> None:
|
||||
def refresh_screen(self, console: "Console", layout_name: str) -> None:
|
||||
"""Refresh a sub-layout.
|
||||
|
||||
Args:
|
||||
|
@ -376,7 +374,7 @@ class Layout:
|
|||
RenderMap: A dict that maps Layout on to a tuple of Region, lines
|
||||
"""
|
||||
render_width = options.max_width
|
||||
render_height = options.height or 1
|
||||
render_height = options.height or console.height
|
||||
region_map = self._make_region_map(render_width, render_height)
|
||||
layout_regions = [
|
||||
(layout, region)
|
||||
|
@ -402,7 +400,7 @@ class Layout:
|
|||
height = options.height or console.height
|
||||
render_map = self.render(console, options.update_dimensions(width, height))
|
||||
self._render_map = render_map
|
||||
layout_lines: List[List[Segment]] = [[] for _ in range(options.height or 1)]
|
||||
layout_lines: List[List[Segment]] = [[] for _ in range(height)]
|
||||
_islice = islice
|
||||
for (region, lines) in render_map.values():
|
||||
_x, y, _layout_width, layout_height = region
|
||||
|
@ -442,19 +440,4 @@ if __name__ == "__main__": # type: ignore
|
|||
|
||||
layout["content"].update("foo")
|
||||
|
||||
from rich.live import Live
|
||||
from time import sleep
|
||||
|
||||
from rich import print
|
||||
|
||||
from rich.pretty import Pretty
|
||||
|
||||
# l = Layout()
|
||||
# # l.split(Layout(), Layout())
|
||||
# print(l.tree)
|
||||
|
||||
with Live(layout, console=console, screen=True, refresh_per_second=1) as live:
|
||||
for n in range(100):
|
||||
layout["top"].update("[on red]" + str(n))
|
||||
sleep(0.1)
|
||||
layout.refresh(console, "top")
|
||||
console.print(layout)
|
||||
|
|
|
@ -5,7 +5,7 @@ from . import errors
|
|||
from .protocol import is_renderable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, RenderableType
|
||||
from .console import Console, ConsoleOptions, RenderableType
|
||||
|
||||
|
||||
class Measurement(NamedTuple):
|
||||
|
@ -75,15 +75,14 @@ class Measurement(NamedTuple):
|
|||
|
||||
@classmethod
|
||||
def get(
|
||||
cls, console: "Console", renderable: "RenderableType", max_width: int = None
|
||||
cls, console: "Console", options: "ConsoleOptions", renderable: "RenderableType"
|
||||
) -> "Measurement":
|
||||
"""Get a measurement for a renderable.
|
||||
|
||||
Args:
|
||||
console (~rich.console.Console): Console instance.
|
||||
options (~rich.console.ConsoleOptions): Console options.
|
||||
renderable (RenderableType): An object that may be rendered with Rich.
|
||||
max_width (int, optional): The maximum width available, or None to use console.width.
|
||||
Defaults to None.
|
||||
|
||||
Raises:
|
||||
errors.NotRenderableError: If the object is not renderable.
|
||||
|
@ -91,20 +90,18 @@ class Measurement(NamedTuple):
|
|||
Returns:
|
||||
Measurement: Measurement object containing range of character widths required to render the object.
|
||||
"""
|
||||
_max_width = console.width if max_width is None else max_width
|
||||
_max_width = options.max_width
|
||||
if _max_width < 1:
|
||||
return Measurement(0, 0)
|
||||
if isinstance(renderable, str):
|
||||
renderable = console.render_str(renderable)
|
||||
|
||||
renderable = console.render_str(renderable, markup=options.markup)
|
||||
if hasattr(renderable, "__rich__"):
|
||||
renderable = renderable.__rich__() # type: ignore
|
||||
|
||||
if is_renderable(renderable):
|
||||
get_console_width = getattr(renderable, "__rich_measure__", None)
|
||||
if get_console_width is not None:
|
||||
render_width = (
|
||||
get_console_width(console, _max_width)
|
||||
get_console_width(console, options)
|
||||
.normalize()
|
||||
.with_maximum(_max_width)
|
||||
)
|
||||
|
@ -121,7 +118,9 @@ class Measurement(NamedTuple):
|
|||
|
||||
|
||||
def measure_renderables(
|
||||
console: "Console", renderables: Iterable["RenderableType"], max_width: int
|
||||
console: "Console",
|
||||
options: "ConsoleOptions",
|
||||
renderables: Iterable["RenderableType"],
|
||||
) -> "Measurement":
|
||||
"""Get a measurement that would fit a number of renderables.
|
||||
|
||||
|
@ -138,7 +137,7 @@ def measure_renderables(
|
|||
return Measurement(0, 0)
|
||||
get_measurement = Measurement.get
|
||||
measurements = [
|
||||
get_measurement(console, renderable, max_width) for renderable in renderables
|
||||
get_measurement(console, options, renderable) for renderable in renderables
|
||||
]
|
||||
measured_width = Measurement(
|
||||
max(measurements, key=itemgetter(0)).minimum,
|
||||
|
|
|
@ -84,7 +84,7 @@ class Padding(JupyterMixin):
|
|||
width = options.max_width
|
||||
else:
|
||||
width = min(
|
||||
Measurement.get(console, self.renderable, options.max_width).maximum
|
||||
Measurement.get(console, options, self.renderable).maximum
|
||||
+ self.left
|
||||
+ self.right,
|
||||
options.max_width,
|
||||
|
@ -120,13 +120,14 @@ class Padding(JupyterMixin):
|
|||
blank_line = blank_line or [_Segment(f'{" " * width}\n', style)]
|
||||
yield from blank_line * self.bottom
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
max_width = options.max_width
|
||||
extra_width = self.left + self.right
|
||||
if max_width - extra_width < 1:
|
||||
return Measurement(max_width, max_width)
|
||||
measure_min, measure_max = Measurement.get(
|
||||
console, self.renderable, max(0, max_width - extra_width)
|
||||
)
|
||||
measure_min, measure_max = Measurement.get(console, options, self.renderable)
|
||||
measurement = Measurement(measure_min + extra_width, measure_max + extra_width)
|
||||
measurement = measurement.with_maximum(max_width)
|
||||
return measurement
|
||||
|
|
|
@ -133,7 +133,9 @@ class Panel(JupyterMixin):
|
|||
child_width = (
|
||||
width - 2
|
||||
if self.expand
|
||||
else Measurement.get(console, renderable, width - 2).maximum
|
||||
else Measurement.get(
|
||||
console, options.update_width(width - 2), renderable
|
||||
).maximum
|
||||
)
|
||||
child_height = self.height or options.height or None
|
||||
if child_height:
|
||||
|
@ -169,7 +171,9 @@ class Panel(JupyterMixin):
|
|||
yield Segment(box.get_bottom([width - 2]), border_style)
|
||||
yield new_line
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
_title = self._title
|
||||
_, right, _, left = Padding.unpack(self.padding)
|
||||
padding = left + right
|
||||
|
@ -178,7 +182,9 @@ class Panel(JupyterMixin):
|
|||
if self.width is None:
|
||||
width = (
|
||||
measure_renderables(
|
||||
console, renderables, max_width - padding - 2
|
||||
console,
|
||||
options.update_width(options.max_width - padding - 2),
|
||||
renderables,
|
||||
).maximum
|
||||
+ padding
|
||||
+ 2
|
||||
|
|
|
@ -212,10 +212,12 @@ class Pretty:
|
|||
yield ""
|
||||
yield pretty_text
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
pretty_str = pretty_repr(
|
||||
self._object,
|
||||
max_width=max_width,
|
||||
max_width=options.max_width,
|
||||
indent_size=self.indent_size,
|
||||
max_length=self.max_length,
|
||||
max_string=self.max_string,
|
||||
|
@ -449,24 +451,26 @@ def traverse(_object: Any, max_length: int = None, max_string: int = None) -> No
|
|||
py_version = (sys.version_info.major, sys.version_info.minor)
|
||||
children: List[Node]
|
||||
|
||||
def iter_tokens(tokens) -> Iterable[Union[Any, Tuple[str, Any]]]:
|
||||
for token in tokens:
|
||||
if isinstance(token, tuple):
|
||||
if len(token) == 3:
|
||||
key, child, default = token
|
||||
def iter_rich_args(rich_args) -> Iterable[Union[Any, Tuple[str, Any]]]:
|
||||
for arg in rich_args:
|
||||
if isinstance(arg, tuple):
|
||||
if len(arg) == 3:
|
||||
key, child, default = arg
|
||||
if default == child:
|
||||
continue
|
||||
yield key, child
|
||||
elif len(token) == 2:
|
||||
key, child = token
|
||||
elif len(arg) == 2:
|
||||
key, child = arg
|
||||
yield key, child
|
||||
elif len(arg) == 1:
|
||||
yield arg[0]
|
||||
else:
|
||||
yield token
|
||||
yield arg
|
||||
|
||||
if hasattr(obj, "__rich_repr__"):
|
||||
tokens = list(iter_tokens(obj.__rich_repr__()))
|
||||
args = list(iter_rich_args(obj.__rich_repr__()))
|
||||
|
||||
if tokens:
|
||||
if args:
|
||||
children = []
|
||||
append = children.append
|
||||
node = Node(
|
||||
|
@ -475,9 +479,9 @@ def traverse(_object: Any, max_length: int = None, max_string: int = None) -> No
|
|||
children=children,
|
||||
last=root,
|
||||
)
|
||||
for last, token in loop_last(tokens):
|
||||
if isinstance(token, tuple):
|
||||
key, child = token
|
||||
for last, arg in loop_last(args):
|
||||
if isinstance(arg, tuple):
|
||||
key, child = arg
|
||||
child_node = _traverse(child)
|
||||
child_node.last = last
|
||||
child_node.key_repr = key
|
||||
|
@ -485,7 +489,7 @@ def traverse(_object: Any, max_length: int = None, max_string: int = None) -> No
|
|||
child_node.key_separator = "="
|
||||
append(child_node)
|
||||
else:
|
||||
child_node = _traverse(token)
|
||||
child_node = _traverse(arg)
|
||||
child_node.last = last
|
||||
append(child_node)
|
||||
else:
|
||||
|
|
|
@ -190,11 +190,13 @@ class ProgressBar(JupyterMixin):
|
|||
if remaining_bars:
|
||||
yield _Segment(bar * remaining_bars, style)
|
||||
|
||||
def __rich_measure__(self, console: Console, max_width: int) -> Measurement:
|
||||
def __rich_measure__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return (
|
||||
Measurement(self.width, self.width)
|
||||
if self.width is not None
|
||||
else Measurement(4, max_width)
|
||||
else Measurement(4, options.max_width)
|
||||
)
|
||||
|
||||
|
||||
|
|
17
rich/repr.py
17
rich/repr.py
|
@ -13,14 +13,17 @@ def rich_repr(cls: Type[T]) -> Type[T]:
|
|||
def auto_repr(self) -> str:
|
||||
repr_str: List[str] = []
|
||||
append = repr_str.append
|
||||
for token in self.__rich_repr__():
|
||||
if isinstance(token, tuple):
|
||||
key, value, *default = token
|
||||
if len(default) and default[0] == value:
|
||||
continue
|
||||
append(f"{key}={value!r}")
|
||||
for arg in self.__rich_repr__():
|
||||
if isinstance(arg, tuple):
|
||||
if len(arg) == 1:
|
||||
append(repr(arg[0]))
|
||||
else:
|
||||
key, value, *default = arg
|
||||
if len(default) and default[0] == value:
|
||||
continue
|
||||
append(f"{key}={value!r}")
|
||||
else:
|
||||
append(repr(token))
|
||||
append(repr(arg))
|
||||
return f"{self.__class__.__name__}({', '.join(repr_str)})"
|
||||
|
||||
auto_repr.__doc__ = "Return repr(self)"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from .measure import Measurement
|
||||
from .segment import Segment
|
||||
from .style import StyleType
|
||||
from ._loop import loop_last
|
||||
|
|
|
@ -25,8 +25,9 @@ class ControlType(IntEnum):
|
|||
CURSOR_DOWN = 10
|
||||
CURSOR_FORWARD = 11
|
||||
CURSOR_BACKWARD = 12
|
||||
CURSOR_MOVE_TO = 13
|
||||
ERASE_IN_LINE = 14
|
||||
CURSOR_MOVE_TO_ROW = 13
|
||||
CURSOR_MOVE_TO = 14
|
||||
ERASE_IN_LINE = 15
|
||||
|
||||
|
||||
ControlCode = Union[
|
||||
|
|
|
@ -46,9 +46,11 @@ class Spinner:
|
|||
text = self.render(time - self.start_time)
|
||||
yield text
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
text = self.render(0)
|
||||
return Measurement.get(console, text, max_width)
|
||||
return Measurement.get(console, options, text)
|
||||
|
||||
def render(self, time: float) -> Text:
|
||||
"""Render the spinner for a given time.
|
||||
|
|
|
@ -28,8 +28,10 @@ class Styled:
|
|||
segments = Segment.apply_style(rendered_segments, style)
|
||||
return segments
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
return Measurement.get(console, self.renderable, max_width)
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
return Measurement.get(console, options, self.renderable)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
|
|
@ -468,11 +468,13 @@ class Syntax(JupyterMixin):
|
|||
highlight_number_style = background_style + Style(dim=False)
|
||||
return background_style, number_style, highlight_number_style
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
if self.code_width is not None:
|
||||
width = self.code_width + self._numbers_column_width
|
||||
return Measurement(self._numbers_column_width, width)
|
||||
return Measurement(self._numbers_column_width, max_width)
|
||||
return Measurement(self._numbers_column_width, options.max_width)
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
|
|
|
@ -281,18 +281,26 @@ class Table(JupyterMixin):
|
|||
style += console.get_style(row_style)
|
||||
return style
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
max_width = options.max_width
|
||||
if self.width is not None:
|
||||
max_width = self.width
|
||||
if max_width < 0:
|
||||
return Measurement(0, 0)
|
||||
|
||||
extra_width = self._extra_width
|
||||
max_width = sum(self._calculate_column_widths(console, max_width - extra_width))
|
||||
max_width = sum(
|
||||
self._calculate_column_widths(
|
||||
console, options.update_width(max_width - extra_width)
|
||||
)
|
||||
)
|
||||
_measure_column = self._measure_column
|
||||
|
||||
measurements = [
|
||||
_measure_column(console, column, max_width) for column in self.columns
|
||||
_measure_column(console, options.update_width(max_width), column)
|
||||
for column in self.columns
|
||||
]
|
||||
minimum_width = (
|
||||
sum(measurement.minimum for measurement in measurements) + extra_width
|
||||
|
@ -428,7 +436,9 @@ class Table(JupyterMixin):
|
|||
max_width = self.width
|
||||
|
||||
extra_width = self._extra_width
|
||||
widths = self._calculate_column_widths(console, max_width - extra_width)
|
||||
widths = self._calculate_column_widths(
|
||||
console, options.update_width(max_width - extra_width)
|
||||
)
|
||||
table_width = sum(widths) + extra_width
|
||||
|
||||
render_options = options.update(
|
||||
|
@ -461,11 +471,14 @@ class Table(JupyterMixin):
|
|||
justify=self.caption_justify,
|
||||
)
|
||||
|
||||
def _calculate_column_widths(self, console: "Console", max_width: int) -> List[int]:
|
||||
def _calculate_column_widths(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> List[int]:
|
||||
"""Calculate the widths of each column, including padding, not including borders."""
|
||||
max_width = options.max_width
|
||||
columns = self.columns
|
||||
width_ranges = [
|
||||
self._measure_column(console, column, max_width) for column in columns
|
||||
self._measure_column(console, options, column) for column in columns
|
||||
]
|
||||
widths = [_range.maximum or 1 for _range in width_ranges]
|
||||
get_padding_width = self._get_padding_width
|
||||
|
@ -504,7 +517,7 @@ class Table(JupyterMixin):
|
|||
table_width = sum(widths)
|
||||
|
||||
width_ranges = [
|
||||
self._measure_column(console, column, width)
|
||||
self._measure_column(console, options.update_width(width), column)
|
||||
for width, column in zip(widths, columns)
|
||||
]
|
||||
widths = [_range.maximum or 0 for _range in width_ranges]
|
||||
|
@ -609,7 +622,7 @@ class Table(JupyterMixin):
|
|||
column.header_style
|
||||
)
|
||||
_append((header_style, column.header))
|
||||
cell_style = get_style(self.style or "") + get_style(column.style or "")
|
||||
cell_style = get_style(column.style or "")
|
||||
for cell in column.cells:
|
||||
_append((cell_style, cell))
|
||||
if self.show_footer:
|
||||
|
@ -635,10 +648,14 @@ class Table(JupyterMixin):
|
|||
return pad_left + pad_right
|
||||
|
||||
def _measure_column(
|
||||
self, console: "Console", column: Column, max_width: int
|
||||
self,
|
||||
console: "Console",
|
||||
options: "ConsoleOptions",
|
||||
column: Column,
|
||||
) -> Measurement:
|
||||
"""Get the minimum and maximum width of the column."""
|
||||
|
||||
max_width = options.max_width
|
||||
if max_width < 1:
|
||||
return Measurement(0, 0)
|
||||
|
||||
|
@ -656,7 +673,7 @@ class Table(JupyterMixin):
|
|||
append_max = max_widths.append
|
||||
get_render_width = Measurement.get
|
||||
for cell in self._get_cells(console, column._index, column):
|
||||
_min, _max = get_render_width(console, cell.renderable, max_width)
|
||||
_min, _max = get_render_width(console, options, cell.renderable)
|
||||
append_min(_min)
|
||||
append_max(_max)
|
||||
|
||||
|
|
14
rich/text.py
14
rich/text.py
|
@ -525,12 +525,16 @@ class Text(JupyterMixin):
|
|||
all_lines = Text("\n").join(lines)
|
||||
yield from all_lines.render(console, end=self.end)
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
text = self.plain
|
||||
if not text.strip():
|
||||
return Measurement(cell_len(text), cell_len(text))
|
||||
max_text_width = max(cell_len(line) for line in text.splitlines())
|
||||
min_text_width = max(cell_len(word) for word in text.split())
|
||||
lines = text.splitlines()
|
||||
max_text_width = max(cell_len(line) for line in lines) if lines else 0
|
||||
words = text.split()
|
||||
min_text_width = (
|
||||
max(cell_len(word) for word in words) if words else max_text_width
|
||||
)
|
||||
return Measurement(min_text_width, max_text_width)
|
||||
|
||||
def render(self, console: "Console", end: str = "") -> Iterable["Segment"]:
|
||||
|
|
|
@ -158,7 +158,9 @@ class Tree(JupyterMixin):
|
|||
guide_style_stack.push(get_style(node.guide_style))
|
||||
push(iter(loop_last(node.children)))
|
||||
|
||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "Measurement":
|
||||
stack: List[Iterator[Tree]] = [iter([self])]
|
||||
pop = stack.pop
|
||||
push = stack.append
|
||||
|
@ -174,7 +176,7 @@ class Tree(JupyterMixin):
|
|||
level -= 1
|
||||
continue
|
||||
push(iter_tree)
|
||||
min_measure, max_measure = measure(console, tree.label, max_width)
|
||||
min_measure, max_measure = measure(console, options, tree.label)
|
||||
indent = level * 4
|
||||
minimum = max(min_measure + indent, minimum)
|
||||
maximum = max(max_measure + indent, maximum)
|
||||
|
|
|
@ -107,7 +107,7 @@ def test_align_right_style():
|
|||
|
||||
def test_measure():
|
||||
console = Console(file=io.StringIO(), width=20)
|
||||
_min, _max = Measurement.get(console, Align("foo bar", "left"), 20)
|
||||
_min, _max = Measurement.get(console, console.options, Align("foo bar", "left"))
|
||||
assert _min == 3
|
||||
assert _max == 7
|
||||
|
||||
|
@ -147,4 +147,6 @@ def test_vertical_center():
|
|||
print(repr(result))
|
||||
expected = " \n \nfoo\n \n \n \n"
|
||||
assert result == expected
|
||||
assert Measurement.get(console, vertical_center) == Measurement(3, 3)
|
||||
assert Measurement.get(console, console.options, vertical_center) == Measurement(
|
||||
3, 3
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from rich.console import Console
|
||||
from rich.progress_bar import ProgressBar
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
@ -39,8 +40,9 @@ def test_render():
|
|||
|
||||
|
||||
def test_measure():
|
||||
console = Console(width=120)
|
||||
bar = ProgressBar()
|
||||
measurement = bar.__rich_measure__(None, 120)
|
||||
measurement = bar.__rich_measure__(console, console.options)
|
||||
assert measurement.minimum == 4
|
||||
assert measurement.maximum == 120
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from rich.bar import Bar
|
||||
from rich.console import Console
|
||||
|
||||
from .render import render
|
||||
|
||||
|
@ -29,8 +30,9 @@ def test_render():
|
|||
|
||||
|
||||
def test_measure():
|
||||
console = Console(width=120)
|
||||
bar = Bar(size=100, begin=11, end=62)
|
||||
measurement = bar.__rich_measure__(None, 120)
|
||||
measurement = bar.__rich_measure__(console, console.options)
|
||||
assert measurement.minimum == 4
|
||||
assert measurement.maximum == 120
|
||||
|
||||
|
|
|
@ -475,7 +475,7 @@ def test_render_group() -> None:
|
|||
|
||||
renderables = [renderable() for _ in range(4)]
|
||||
console = Console(width=42)
|
||||
min_width, _ = measure_renderables(console, renderables, 42)
|
||||
min_width, _ = measure_renderables(console, console.options, renderables)
|
||||
assert min_width == 42
|
||||
|
||||
|
||||
|
@ -491,7 +491,7 @@ def test_render_group_fit() -> None:
|
|||
|
||||
console = Console(width=42)
|
||||
|
||||
min_width, _ = measure_renderables(console, renderables, 42)
|
||||
min_width, _ = measure_renderables(console, console.options, renderables)
|
||||
assert min_width == 5
|
||||
|
||||
|
||||
|
@ -633,3 +633,17 @@ def test_update_screen_lines():
|
|||
console = Console(force_terminal=True, width=20, height=5)
|
||||
with pytest.raises(errors.NoAltScreen):
|
||||
console.update_screen_lines([])
|
||||
|
||||
|
||||
def test_update_options_markup():
|
||||
console = Console()
|
||||
options = console.options
|
||||
assert options.update(markup=False).markup == False
|
||||
assert options.update(markup=True).markup == True
|
||||
|
||||
|
||||
def test_print_width_zero():
|
||||
console = Console()
|
||||
with console.capture() as capture:
|
||||
console.print("Hello", width=0)
|
||||
assert capture.get() == ""
|
||||
|
|
|
@ -6,6 +6,8 @@ from rich.text import Text
|
|||
def test_width_of_none():
|
||||
console = Console()
|
||||
constrain = Constrain(Text("foo"), width=None)
|
||||
min_width, max_width = constrain.__rich_measure__(console, 80)
|
||||
min_width, max_width = constrain.__rich_measure__(
|
||||
console, console.options.update_width(80)
|
||||
)
|
||||
assert min_width == 3
|
||||
assert max_width == 3
|
||||
|
|
|
@ -9,7 +9,7 @@ def test_renderables_measure():
|
|||
text = Text("foo")
|
||||
renderables = Renderables([text])
|
||||
|
||||
result = renderables.__rich_measure__(console, console.width)
|
||||
result = renderables.__rich_measure__(console, console.options)
|
||||
_min, _max = result
|
||||
assert _min == 3
|
||||
assert _max == 3
|
||||
|
@ -21,7 +21,7 @@ def test_renderables_empty():
|
|||
console = Console()
|
||||
renderables = Renderables()
|
||||
|
||||
result = renderables.__rich_measure__(console, console.width)
|
||||
result = renderables.__rich_measure__(console, console.options)
|
||||
_min, _max = result
|
||||
assert _min == 1
|
||||
assert _max == 1
|
||||
|
|
|
@ -17,7 +17,7 @@ def test_control_move_to():
|
|||
control = Control.move_to(5, 10)
|
||||
print(control.segment)
|
||||
assert control.segment == Segment(
|
||||
"\x1b[11;6H", None, [(ControlType.CURSOR_MOVE_TO, 6, 11)]
|
||||
"\x1b[11;6H", None, [(ControlType.CURSOR_MOVE_TO, 5, 10)]
|
||||
)
|
||||
|
||||
|
||||
|
@ -30,3 +30,12 @@ def test_control_move():
|
|||
None,
|
||||
[(ControlType.CURSOR_FORWARD, 3), (ControlType.CURSOR_DOWN, 4)],
|
||||
)
|
||||
|
||||
|
||||
def test_move_to_row():
|
||||
print(repr(Control.move_to_row(10, 20).segment))
|
||||
assert Control.move_to_row(10, 20).segment == Segment(
|
||||
"\x1b[12G\x1b[20B",
|
||||
None,
|
||||
[(ControlType.CURSOR_MOVE_TO_ROW, 11), (ControlType.CURSOR_DOWN, 20)],
|
||||
)
|
||||
|
|
|
@ -76,3 +76,17 @@ def test_tree():
|
|||
expected = "⬍ Layout(name='root') \n├── ⬍ Layout(size=2) \n└── ⬌ Layout(name='bar') \n ├── ⬍ Layout() \n └── ⬍ Layout() \n"
|
||||
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_refresh_screen():
|
||||
layout = Layout()
|
||||
layout.split_row(Layout(name="foo"), Layout(name="bar"))
|
||||
console = Console(force_terminal=True, width=20, height=5)
|
||||
console.print(layout)
|
||||
with console.screen():
|
||||
with console.capture() as capture:
|
||||
layout.refresh_screen(console, "foo")
|
||||
result = capture.get()
|
||||
print(repr(result))
|
||||
expected = "\x1b[1;1H\x1b[34m╭─\x1b[0m\x1b[34m \x1b[0m\x1b[32m'foo'\x1b[0m\x1b[34m─╮\x1b[0m\x1b[2;1H\x1b[34m│\x1b[0m Layout \x1b[34m│\x1b[0m\x1b[3;1H\x1b[34m│\x1b[0m \x1b[1m(\x1b[0m \x1b[34m│\x1b[0m\x1b[4;1H\x1b[34m│\x1b[0m \x1b[33mna\x1b[0m \x1b[34m│\x1b[0m\x1b[5;1H\x1b[34m╰────────╯\x1b[0m"
|
||||
assert result == expected
|
||||
|
|
|
@ -16,21 +16,15 @@ def test_no_renderable():
|
|||
text = Text()
|
||||
|
||||
with pytest.raises(NotRenderableError):
|
||||
Measurement.get(console, None, console.width)
|
||||
|
||||
|
||||
def test_null_get():
|
||||
# Test negative console.width passed into get method
|
||||
assert Measurement.get(Console(width=-1), None) == Measurement(0, 0)
|
||||
# Test negative max_width passed into get method
|
||||
assert Measurement.get(Console(), None, -1) == Measurement(0, 0)
|
||||
Measurement.get(console, console.options, None)
|
||||
|
||||
|
||||
def test_measure_renderables():
|
||||
# Test measure_renderables returning a null Measurement object
|
||||
assert measure_renderables(Console(), None, None) == Measurement(0, 0)
|
||||
# Test measure_renderables returning a valid Measurement object
|
||||
assert measure_renderables(Console(width=1), ["test"], 1) == Measurement(1, 1)
|
||||
console = Console()
|
||||
assert measure_renderables(console, console.options, "") == Measurement(0, 0)
|
||||
assert measure_renderables(
|
||||
console, console.options.update_width(0), "hello"
|
||||
) == Measurement(0, 0)
|
||||
|
||||
|
||||
def test_clamp():
|
||||
|
|
|
@ -38,7 +38,7 @@ def test_render_panel(panel, expected):
|
|||
def test_console_width():
|
||||
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
|
||||
panel = Panel("Hello, World", expand=False)
|
||||
min_width, max_width = panel.__rich_measure__(console, 50)
|
||||
min_width, max_width = panel.__rich_measure__(console, console.options)
|
||||
assert min_width == 16
|
||||
assert max_width == 16
|
||||
|
||||
|
@ -46,7 +46,7 @@ def test_console_width():
|
|||
def test_fixed_width():
|
||||
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
|
||||
panel = Panel("Hello World", width=20)
|
||||
min_width, max_width = panel.__rich_measure__(console, 100)
|
||||
min_width, max_width = panel.__rich_measure__(console, console.options)
|
||||
assert min_width == 20
|
||||
assert max_width == 20
|
||||
|
||||
|
|
|
@ -4,18 +4,21 @@ from rich.repr import rich_repr
|
|||
|
||||
@rich_repr
|
||||
class Foo:
|
||||
def __init__(self, foo: str, bar: int = None):
|
||||
def __init__(self, foo: str, bar: int = None, egg: int = 1):
|
||||
self.foo = foo
|
||||
self.bar = bar
|
||||
self.egg = egg
|
||||
|
||||
def __rich_repr__(self):
|
||||
yield self.foo
|
||||
yield self.foo,
|
||||
yield "bar", self.bar, None
|
||||
yield "egg", self.egg
|
||||
|
||||
|
||||
def test_rich_repr() -> None:
|
||||
assert (repr(Foo("hello"))) == "Foo('hello')"
|
||||
assert (repr(Foo("hello", bar=3))) == "Foo('hello', bar=3)"
|
||||
assert (repr(Foo("hello"))) == "Foo('hello', 'hello', egg=1)"
|
||||
assert (repr(Foo("hello", bar=3))) == "Foo('hello', 'hello', bar=3, egg=1)"
|
||||
|
||||
|
||||
def test_rich_pretty() -> None:
|
||||
|
@ -23,5 +26,5 @@ def test_rich_pretty() -> None:
|
|||
with console.capture() as capture:
|
||||
console.print(Foo("hello", bar=3))
|
||||
result = capture.get()
|
||||
expected = "Foo('hello', bar=3)\n"
|
||||
expected = "Foo('hello', 'hello', bar=3, egg=1)\n"
|
||||
assert result == expected
|
||||
|
|
|
@ -37,6 +37,6 @@ def test_spinner_render():
|
|||
def test_rich_measure():
|
||||
console = Console(width=80, color_system=None, force_terminal=True)
|
||||
spinner = Spinner("dots", "Foo")
|
||||
min_width, max_width = Measurement.get(console, spinner, 80)
|
||||
min_width, max_width = Measurement.get(console, console.options, spinner)
|
||||
assert min_width == 3
|
||||
assert max_width == 5
|
||||
|
|
|
@ -8,7 +8,7 @@ from rich.styled import Styled
|
|||
def test_styled():
|
||||
styled_foo = Styled("foo", "on red")
|
||||
console = Console(file=io.StringIO(), force_terminal=True, _environ={})
|
||||
assert Measurement.get(console, styled_foo, 80) == Measurement(3, 3)
|
||||
assert Measurement.get(console, console.options, styled_foo) == Measurement(3, 3)
|
||||
console.print(styled_foo)
|
||||
result = console.file.getvalue()
|
||||
expected = "\x1b[41mfoo\x1b[0m\n"
|
||||
|
|
|
@ -28,9 +28,9 @@ def render_tables():
|
|||
|
||||
table.add_row("Averlongwordgoeshere", "banana pancakes", None)
|
||||
|
||||
assert Measurement.get(console, table, 80) == Measurement(41, 48)
|
||||
assert Measurement.get(console, console.options, table) == Measurement(41, 48)
|
||||
table.expand = True
|
||||
assert Measurement.get(console, table, 80) == Measurement(41, 48)
|
||||
assert Measurement.get(console, console.options, table) == Measurement(41, 48)
|
||||
|
||||
for width in range(10, 60, 5):
|
||||
console.print(table, width=width)
|
||||
|
@ -64,9 +64,9 @@ def render_tables():
|
|||
console.print(table)
|
||||
|
||||
table.width = 20
|
||||
assert Measurement.get(console, table, 80) == Measurement(20, 20)
|
||||
assert Measurement.get(console, console.options, table) == Measurement(20, 20)
|
||||
table.expand = False
|
||||
assert Measurement.get(console, table, 80) == Measurement(20, 20)
|
||||
assert Measurement.get(console, console.options, table) == Measurement(20, 20)
|
||||
table.expand = True
|
||||
console.print(table)
|
||||
|
||||
|
@ -113,28 +113,24 @@ def test_init_append_column():
|
|||
|
||||
|
||||
def test_rich_measure():
|
||||
# Check __rich_measure__() for a negative width passed as an argument
|
||||
assert Table("test_header", width=None).__rich_measure__(
|
||||
Console(), -1
|
||||
|
||||
console = Console()
|
||||
assert Table("test_header", width=-1).__rich_measure__(
|
||||
console, console.options
|
||||
) == Measurement(0, 0)
|
||||
# Check __rich_measure__() for a negative Table.width attribute
|
||||
assert Table("test_header", width=-1).__rich_measure__(Console(), 1) == Measurement(
|
||||
0, 0
|
||||
)
|
||||
# Check __rich_measure__() for a positive width passed as an argument
|
||||
assert Table("test_header", width=None).__rich_measure__(
|
||||
Console(), 10
|
||||
) == Measurement(10, 10)
|
||||
# Check __rich_measure__() for a positive Table.width attribute
|
||||
assert Table("test_header", width=10).__rich_measure__(
|
||||
Console(), -1
|
||||
console, console.options.update_width(10)
|
||||
) == Measurement(10, 10)
|
||||
|
||||
|
||||
def test_min_width():
|
||||
table = Table("foo", min_width=30)
|
||||
table.add_row("bar")
|
||||
assert table.__rich_measure__(Console(), 100) == Measurement(30, 30)
|
||||
console = Console()
|
||||
assert table.__rich_measure__(
|
||||
console, console.options.update_width(100)
|
||||
) == Measurement(30, 30)
|
||||
console = Console(color_system=None)
|
||||
console.begin_capture()
|
||||
console.print(table)
|
||||
|
|
|
@ -238,6 +238,7 @@ def test_console_width():
|
|||
test = Text("Hello World!\nfoobarbaz")
|
||||
assert test.__rich_measure__(console, 80) == Measurement(9, 12)
|
||||
assert Text(" " * 4).__rich_measure__(console, 80) == Measurement(4, 4)
|
||||
assert Text(" \n \n ").__rich_measure__(console, 80) == Measurement(3, 3)
|
||||
|
||||
|
||||
def test_join():
|
||||
|
|
|
@ -99,5 +99,5 @@ def test_tree_measure():
|
|||
tree.add("bar")
|
||||
tree.add("musroom risotto")
|
||||
console = Console()
|
||||
measurement = Measurement.get(console, tree)
|
||||
measurement = Measurement.get(console, console.options, tree)
|
||||
assert measurement == Measurement(11, 19)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue