Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

respect text.style in panels #3509

Merged
merged 13 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codespell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ jobs:
- uses: actions/checkout@v4
- run: python3 -m pip install codespell
- run: codespell --ignore-words-list="ba,fo,hel,revered,womens"
--skip="./README.*.md,*.svg,*.ai,./benchmarks/snippets.py,./tests,./tools"
--skip="./README.*.md,*.svg,*.ai,./benchmarks/snippets.py,./tests,./tools,*.lock"
11 changes: 4 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

## [13.9.0]

### Changed

- Dropped support for Python3.7 https://github.com/Textualize/rich/pull/3509
- Rich will display tracebacks with finely grained error locations on python 3.11+ https://github.com/Textualize/rich/pull/3486


### Fixed

- Fixed issue with Segment._split_cells https://github.com/Textualize/rich/pull/3506
- Fix auto detection of terminal size on Windows https://github.com/Textualize/rich/pull/2916

### Added

- Add a new `column` object `IterationSpeedColumn`. https://github.com/Textualize/rich/pull/3332
- `Text.style` now respected in Panel title/subtitle https://github.com/Textualize/rich/pull/3509

## [13.8.1] - 2024-09-10

Expand Down Expand Up @@ -2088,6 +2084,7 @@ Major version bump for a breaking change to `Text.stylize signature`, which corr

- First official release, API still to be stabilized

[13.9.0]: https://github.com/textualize/rich/compare/v13.8.1...v13.9.0
[13.8.1]: https://github.com/textualize/rich/compare/v13.8.0...v13.8.1
[13.8.0]: https://github.com/textualize/rich/compare/v13.7.1...v13.8.0
[13.7.1]: https://github.com/textualize/rich/compare/v13.7.0...v13.7.1
Expand Down
643 changes: 313 additions & 330 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "rich"
homepage = "https://github.com/Textualize/rich"
documentation = "https://rich.readthedocs.io/en/latest/"
version = "13.8.1"
version = "13.9.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
license = "MIT"
Expand All @@ -28,7 +28,7 @@ include = ["rich/py.typed"]


[tool.poetry.dependencies]
python = ">=3.7.0"
python = ">=3.8.0"
typing-extensions = { version = ">=4.0.0, <5.0", python = "<3.9" }
pygments = "^2.13.0"
ipywidgets = { version = ">=7.5.1,<9", optional = true }
Expand All @@ -40,7 +40,7 @@ jupyter = ["ipywidgets"]
[tool.poetry.dev-dependencies]
pytest = "^7.0.0"
black = "^22.6"
mypy = "^0.971"
mypy = "^1.11"
pytest-cov = "^3.0.0"
attrs = "^21.4.0"
pre-commit = "^2.17.0"
Expand Down
2 changes: 1 addition & 1 deletion rich/_null_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __iter__(self) -> Iterator[str]:
return iter([""])

def __enter__(self) -> IO[str]:
pass
return self

def __exit__(
self,
Expand Down
7 changes: 3 additions & 4 deletions rich/_win32_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
"""

import ctypes
import sys
from typing import Any
Expand Down Expand Up @@ -380,7 +381,7 @@ def cursor_position(self) -> WindowsCoordinates:
WindowsCoordinates: The current cursor position.
"""
coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition
return WindowsCoordinates(row=cast(int, coord.Y), col=cast(int, coord.X))
return WindowsCoordinates(row=coord.Y, col=coord.X)

@property
def screen_size(self) -> WindowsCoordinates:
Expand All @@ -390,9 +391,7 @@ def screen_size(self) -> WindowsCoordinates:
WindowsCoordinates: The width and height of the screen as WindowsCoordinates.
"""
screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize
return WindowsCoordinates(
row=cast(int, screen_size.Y), col=cast(int, screen_size.X)
)
return WindowsCoordinates(row=screen_size.Y, col=screen_size.X)

def write_text(self, text: str) -> None:
"""Write text directly to the terminal without any modification of styles
Expand Down
11 changes: 6 additions & 5 deletions rich/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ class NoChange:
NO_CHANGE = NoChange()

try:
_STDIN_FILENO = sys.__stdin__.fileno()
_STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr]
except Exception:
_STDIN_FILENO = 0
try:
_STDOUT_FILENO = sys.__stdout__.fileno()
_STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr]
except Exception:
_STDOUT_FILENO = 1
try:
_STDERR_FILENO = sys.__stderr__.fileno()
_STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr]
except Exception:
_STDERR_FILENO = 2

Expand Down Expand Up @@ -1005,7 +1005,8 @@ def size(self) -> ConsoleDimensions:
width: Optional[int] = None
height: Optional[int] = None

for file_descriptor in _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS:
streams = _STD_STREAMS_OUTPUT if WINDOWS else _STD_STREAMS
for file_descriptor in streams:
try:
width, height = os.get_terminal_size(file_descriptor)
except (AttributeError, ValueError, OSError): # Probably not a terminal
Expand Down Expand Up @@ -1302,7 +1303,7 @@ def render(

renderable = rich_cast(renderable)
if hasattr(renderable, "__rich_console__") and not isclass(renderable):
render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr]
render_iterable = renderable.__rich_console__(self, _options)
elif isinstance(renderable, str):
text_renderable = self.render_str(
renderable, highlight=_options.highlight, markup=_options.markup
Expand Down
10 changes: 5 additions & 5 deletions rich/padding.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union
from typing import TYPE_CHECKING, List, Optional, Tuple, Union

if TYPE_CHECKING:
from .console import (
Expand All @@ -7,11 +7,11 @@
RenderableType,
RenderResult,
)

from .jupyter import JupyterMixin
from .measure import Measurement
from .style import Style
from .segment import Segment

from .style import Style

PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]

Expand Down Expand Up @@ -66,10 +66,10 @@ def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]:
_pad = pad[0]
return (_pad, _pad, _pad, _pad)
if len(pad) == 2:
pad_top, pad_right = cast(Tuple[int, int], pad)
pad_top, pad_right = pad
return (pad_top, pad_right, pad_top, pad_right)
if len(pad) == 4:
top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
top, right, bottom, left = pad
return (top, right, bottom, left)
raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")

Expand Down
5 changes: 3 additions & 2 deletions rich/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ def align_text(
text = text.copy()
text.truncate(width)
excess_space = width - cell_len(text.plain)
if text.style:
text.stylize(console.get_style(text.style))

if excess_space:
if align == "left":
return Text.assemble(
Expand Down Expand Up @@ -203,8 +206,6 @@ def align_text(

title_text = self._title
if title_text is not None:
if title_text.style is not None:
title_text.stylize_before(title_text.style)
title_text.stylize_before(partial_border_style)

child_width = (
Expand Down
22 changes: 3 additions & 19 deletions rich/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ def tell(self) -> int:
def write(self, s: Any) -> int:
raise UnsupportedOperation("write")

def writelines(self, lines: Iterable[Any]) -> None:
raise UnsupportedOperation("writelines")


class _ReadContext(ContextManager[_I], Generic[_I]):
"""A utility class to handle a context for both a reader and a progress."""
Expand Down Expand Up @@ -922,25 +925,6 @@ def render(self, task: "Task") -> Text:
return Text(f"{data_speed}/s", style="progress.data.speed")


class IterationSpeedColumn(ProgressColumn):
"""Renders iterations per second, e.g. '11.4 it/s'."""

def render(self, task: "Task") -> Text:
last_speed = task.last_speed if hasattr(task, 'last_speed') else None
if task.finished and last_speed is not None:
return Text(f"{last_speed} it/s", style="progress.data.speed")
if task.speed is None:
return Text("", style="progress.data.speed")
unit, suffix = filesize.pick_unit_and_suffix(
int(task.speed),
["", "×10³", "×10⁶", "×10⁹", "×10¹²"],
1000,
)
data_speed = task.speed / unit
task.last_speed = f"{data_speed:.1f}{suffix}"
return Text(f"{task.last_speed} it/s", style="progress.data.speed")


class ProgressSample(NamedTuple):
"""Sample of progress for a given time."""

Expand Down
2 changes: 1 addition & 1 deletion rich/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ def divide(
while True:
cut = next(iter_cuts, -1)
if cut == -1:
return []
return
if cut != 0:
break
yield []
Expand Down
5 changes: 3 additions & 2 deletions rich/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
List,
NamedTuple,
Optional,
Pattern,
Tuple,
Union,
)
Expand Down Expand Up @@ -173,7 +174,7 @@ def __str__(self) -> str:
return self.plain

def __repr__(self) -> str:
return f"<text {self.plain!r} {self._spans!r}>"
return f"<text {self.plain!r} {self._spans!r} {self.style!r}>"

def __add__(self, other: Any) -> "Text":
if isinstance(other, (str, Text)):
Expand Down Expand Up @@ -591,7 +592,7 @@ def extend_style(self, spaces: int) -> None:

def highlight_regex(
self,
re_highlight: Union[re.Pattern, str],
re_highlight: Union[Pattern[str], str],
style: Optional[Union[GetStyleCallable, StyleType]] = None,
*,
style_prefix: str = "",
Expand Down
35 changes: 30 additions & 5 deletions tests/test_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from rich.panel import Panel
from rich.segment import Segment
from rich.style import Style
from rich.text import Text

tests = [
Panel("Hello, World", padding=0),
Expand All @@ -31,31 +32,33 @@
def render(panel, width=50) -> str:
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
console.print(panel)
return console.file.getvalue()
result = console.file.getvalue()
print(result)
return result


@pytest.mark.parametrize("panel,expected", zip(tests, expected))
def test_render_panel(panel, expected):
def test_render_panel(panel, expected) -> None:
assert render(panel) == expected


def test_console_width():
def test_console_width() -> None:
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
panel = Panel("Hello, World", expand=False)
min_width, max_width = panel.__rich_measure__(console, console.options)
assert min_width == 16
assert max_width == 16


def test_fixed_width():
def test_fixed_width() -> None:
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
panel = Panel("Hello World", width=20)
min_width, max_width = panel.__rich_measure__(console, console.options)
assert min_width == 20
assert max_width == 20


def test_render_size():
def test_render_size() -> None:
console = Console(width=63, height=46, legacy_windows=False)
options = console.options.update_dimensions(80, 4)
lines = console.render_lines(Panel("foo", title="Hello"), options=options)
Expand Down Expand Up @@ -99,6 +102,28 @@ def test_render_size():
assert lines == expected


def test_title_text() -> None:
panel = Panel(
"Hello, World",
title=Text("title", style="red"),
subtitle=Text("subtitle", style="magenta bold"),
)
console = Console(
file=io.StringIO(),
width=50,
height=20,
legacy_windows=False,
force_terminal=True,
color_system="truecolor",
)
console.print(panel)

result = console.file.getvalue()
print(repr(result))
expected = "╭────────────────────\x1b[31m title \x1b[0m─────────────────────╮\n│ Hello, World │\n╰───────────────────\x1b[1;35m subtitle \x1b[0m───────────────────╯\n"
assert result == expected


if __name__ == "__main__":
expected = []
for panel in tests:
Expand Down
Loading
Loading