Skip to content

Commit 92abd70

Browse files
authored
Merge pull request #3509 from Textualize/panel-title-style
respect text.style in panels
2 parents 7022e20 + 7db6b63 commit 92abd70

14 files changed

+377
-388
lines changed

.github/workflows/codespell.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ jobs:
77
- uses: actions/checkout@v4
88
- run: python3 -m pip install codespell
99
- run: codespell --ignore-words-list="ba,fo,hel,revered,womens"
10-
--skip="./README.*.md,*.svg,*.ai,./benchmarks/snippets.py,./tests,./tools"
10+
--skip="./README.*.md,*.svg,*.ai,./benchmarks/snippets.py,./tests,./tools,*.lock"

CHANGELOG.md

+4-7
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,18 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
9-
8+
## [13.9.0]
109

1110
### Changed
1211

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

15-
1615
### Fixed
1716

1817
- Fixed issue with Segment._split_cells https://github.com/Textualize/rich/pull/3506
1918
- Fix auto detection of terminal size on Windows https://github.com/Textualize/rich/pull/2916
20-
21-
### Added
22-
23-
- Add a new `column` object `IterationSpeedColumn`. https://github.com/Textualize/rich/pull/3332
19+
- `Text.style` now respected in Panel title/subtitle https://github.com/Textualize/rich/pull/3509
2420

2521
## [13.8.1] - 2024-09-10
2622

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

20892085
- First official release, API still to be stabilized
20902086

2087+
[13.9.0]: https://github.com/textualize/rich/compare/v13.8.1...v13.9.0
20912088
[13.8.1]: https://github.com/textualize/rich/compare/v13.8.0...v13.8.1
20922089
[13.8.0]: https://github.com/textualize/rich/compare/v13.7.1...v13.8.0
20932090
[13.7.1]: https://github.com/textualize/rich/compare/v13.7.0...v13.7.1

poetry.lock

+313-330
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "rich"
33
homepage = "https://github.com/Textualize/rich"
44
documentation = "https://rich.readthedocs.io/en/latest/"
5-
version = "13.8.1"
5+
version = "13.9.0"
66
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
77
authors = ["Will McGugan <[email protected]>"]
88
license = "MIT"
@@ -28,7 +28,7 @@ include = ["rich/py.typed"]
2828

2929

3030
[tool.poetry.dependencies]
31-
python = ">=3.7.0"
31+
python = ">=3.8.0"
3232
typing-extensions = { version = ">=4.0.0, <5.0", python = "<3.9" }
3333
pygments = "^2.13.0"
3434
ipywidgets = { version = ">=7.5.1,<9", optional = true }
@@ -40,7 +40,7 @@ jupyter = ["ipywidgets"]
4040
[tool.poetry.dev-dependencies]
4141
pytest = "^7.0.0"
4242
black = "^22.6"
43-
mypy = "^0.971"
43+
mypy = "^1.11"
4444
pytest-cov = "^3.0.0"
4545
attrs = "^21.4.0"
4646
pre-commit = "^2.17.0"

rich/_null_file.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __iter__(self) -> Iterator[str]:
4646
return iter([""])
4747

4848
def __enter__(self) -> IO[str]:
49-
pass
49+
return self
5050

5151
def __exit__(
5252
self,

rich/_win32_console.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
44
"""
5+
56
import ctypes
67
import sys
78
from typing import Any
@@ -380,7 +381,7 @@ def cursor_position(self) -> WindowsCoordinates:
380381
WindowsCoordinates: The current cursor position.
381382
"""
382383
coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition
383-
return WindowsCoordinates(row=cast(int, coord.Y), col=cast(int, coord.X))
384+
return WindowsCoordinates(row=coord.Y, col=coord.X)
384385

385386
@property
386387
def screen_size(self) -> WindowsCoordinates:
@@ -390,9 +391,7 @@ def screen_size(self) -> WindowsCoordinates:
390391
WindowsCoordinates: The width and height of the screen as WindowsCoordinates.
391392
"""
392393
screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize
393-
return WindowsCoordinates(
394-
row=cast(int, screen_size.Y), col=cast(int, screen_size.X)
395-
)
394+
return WindowsCoordinates(row=screen_size.Y, col=screen_size.X)
396395

397396
def write_text(self, text: str) -> None:
398397
"""Write text directly to the terminal without any modification of styles

rich/console.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ class NoChange:
8989
NO_CHANGE = NoChange()
9090

9191
try:
92-
_STDIN_FILENO = sys.__stdin__.fileno()
92+
_STDIN_FILENO = sys.__stdin__.fileno() # type: ignore[union-attr]
9393
except Exception:
9494
_STDIN_FILENO = 0
9595
try:
96-
_STDOUT_FILENO = sys.__stdout__.fileno()
96+
_STDOUT_FILENO = sys.__stdout__.fileno() # type: ignore[union-attr]
9797
except Exception:
9898
_STDOUT_FILENO = 1
9999
try:
100-
_STDERR_FILENO = sys.__stderr__.fileno()
100+
_STDERR_FILENO = sys.__stderr__.fileno() # type: ignore[union-attr]
101101
except Exception:
102102
_STDERR_FILENO = 2
103103

@@ -1005,7 +1005,8 @@ def size(self) -> ConsoleDimensions:
10051005
width: Optional[int] = None
10061006
height: Optional[int] = None
10071007

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

13031304
renderable = rich_cast(renderable)
13041305
if hasattr(renderable, "__rich_console__") and not isclass(renderable):
1305-
render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr]
1306+
render_iterable = renderable.__rich_console__(self, _options)
13061307
elif isinstance(renderable, str):
13071308
text_renderable = self.render_str(
13081309
renderable, highlight=_options.highlight, markup=_options.markup

rich/padding.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union
1+
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
22

33
if TYPE_CHECKING:
44
from .console import (
@@ -7,11 +7,11 @@
77
RenderableType,
88
RenderResult,
99
)
10+
1011
from .jupyter import JupyterMixin
1112
from .measure import Measurement
12-
from .style import Style
1313
from .segment import Segment
14-
14+
from .style import Style
1515

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

@@ -66,10 +66,10 @@ def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]:
6666
_pad = pad[0]
6767
return (_pad, _pad, _pad, _pad)
6868
if len(pad) == 2:
69-
pad_top, pad_right = cast(Tuple[int, int], pad)
69+
pad_top, pad_right = pad
7070
return (pad_top, pad_right, pad_top, pad_right)
7171
if len(pad) == 4:
72-
top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
72+
top, right, bottom, left = pad
7373
return (top, right, bottom, left)
7474
raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")
7575

rich/panel.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ def align_text(
175175
text = text.copy()
176176
text.truncate(width)
177177
excess_space = width - cell_len(text.plain)
178+
if text.style:
179+
text.stylize(console.get_style(text.style))
180+
178181
if excess_space:
179182
if align == "left":
180183
return Text.assemble(
@@ -203,8 +206,6 @@ def align_text(
203206

204207
title_text = self._title
205208
if title_text is not None:
206-
if title_text.style is not None:
207-
title_text.stylize_before(title_text.style)
208209
title_text.stylize_before(partial_border_style)
209210

210211
child_width = (

rich/progress.py

+3-19
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ def tell(self) -> int:
280280
def write(self, s: Any) -> int:
281281
raise UnsupportedOperation("write")
282282

283+
def writelines(self, lines: Iterable[Any]) -> None:
284+
raise UnsupportedOperation("writelines")
285+
283286

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

924927

925-
class IterationSpeedColumn(ProgressColumn):
926-
"""Renders iterations per second, e.g. '11.4 it/s'."""
927-
928-
def render(self, task: "Task") -> Text:
929-
last_speed = task.last_speed if hasattr(task, 'last_speed') else None
930-
if task.finished and last_speed is not None:
931-
return Text(f"{last_speed} it/s", style="progress.data.speed")
932-
if task.speed is None:
933-
return Text("", style="progress.data.speed")
934-
unit, suffix = filesize.pick_unit_and_suffix(
935-
int(task.speed),
936-
["", "×10³", "×10⁶", "×10⁹", "×10¹²"],
937-
1000,
938-
)
939-
data_speed = task.speed / unit
940-
task.last_speed = f"{data_speed:.1f}{suffix}"
941-
return Text(f"{task.last_speed} it/s", style="progress.data.speed")
942-
943-
944928
class ProgressSample(NamedTuple):
945929
"""Sample of progress for a given time."""
946930

rich/segment.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ def divide(
617617
while True:
618618
cut = next(iter_cuts, -1)
619619
if cut == -1:
620-
return []
620+
return
621621
if cut != 0:
622622
break
623623
yield []

rich/text.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
List,
1212
NamedTuple,
1313
Optional,
14+
Pattern,
1415
Tuple,
1516
Union,
1617
)
@@ -173,7 +174,7 @@ def __str__(self) -> str:
173174
return self.plain
174175

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

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

592593
def highlight_regex(
593594
self,
594-
re_highlight: Union[re.Pattern, str],
595+
re_highlight: Union[Pattern[str], str],
595596
style: Optional[Union[GetStyleCallable, StyleType]] = None,
596597
*,
597598
style_prefix: str = "",

tests/test_panel.py

+30-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from rich.panel import Panel
77
from rich.segment import Segment
88
from rich.style import Style
9+
from rich.text import Text
910

1011
tests = [
1112
Panel("Hello, World", padding=0),
@@ -31,31 +32,33 @@
3132
def render(panel, width=50) -> str:
3233
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
3334
console.print(panel)
34-
return console.file.getvalue()
35+
result = console.file.getvalue()
36+
print(result)
37+
return result
3538

3639

3740
@pytest.mark.parametrize("panel,expected", zip(tests, expected))
38-
def test_render_panel(panel, expected):
41+
def test_render_panel(panel, expected) -> None:
3942
assert render(panel) == expected
4043

4144

42-
def test_console_width():
45+
def test_console_width() -> None:
4346
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
4447
panel = Panel("Hello, World", expand=False)
4548
min_width, max_width = panel.__rich_measure__(console, console.options)
4649
assert min_width == 16
4750
assert max_width == 16
4851

4952

50-
def test_fixed_width():
53+
def test_fixed_width() -> None:
5154
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
5255
panel = Panel("Hello World", width=20)
5356
min_width, max_width = panel.__rich_measure__(console, console.options)
5457
assert min_width == 20
5558
assert max_width == 20
5659

5760

58-
def test_render_size():
61+
def test_render_size() -> None:
5962
console = Console(width=63, height=46, legacy_windows=False)
6063
options = console.options.update_dimensions(80, 4)
6164
lines = console.render_lines(Panel("foo", title="Hello"), options=options)
@@ -99,6 +102,28 @@ def test_render_size():
99102
assert lines == expected
100103

101104

105+
def test_title_text() -> None:
106+
panel = Panel(
107+
"Hello, World",
108+
title=Text("title", style="red"),
109+
subtitle=Text("subtitle", style="magenta bold"),
110+
)
111+
console = Console(
112+
file=io.StringIO(),
113+
width=50,
114+
height=20,
115+
legacy_windows=False,
116+
force_terminal=True,
117+
color_system="truecolor",
118+
)
119+
console.print(panel)
120+
121+
result = console.file.getvalue()
122+
print(repr(result))
123+
expected = "╭────────────────────\x1b[31m title \x1b[0m─────────────────────╮\n│ Hello, World │\n╰───────────────────\x1b[1;35m subtitle \x1b[0m───────────────────╯\n"
124+
assert result == expected
125+
126+
102127
if __name__ == "__main__":
103128
expected = []
104129
for panel in tests:

0 commit comments

Comments
 (0)