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