Skip to content

Commit 221b280

Browse files
authored
Merge pull request #2543 from Textualize/panel-text
fix for text in panel title
2 parents c6001e5 + 32d6e99 commit 221b280

File tree

5 files changed

+99
-10
lines changed

5 files changed

+99
-10
lines changed

rich/panel.py

+62-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
from .align import AlignMethod
44
from .box import ROUNDED, Box
5+
from .cells import cell_len
56
from .jupyter import JupyterMixin
67
from .measure import Measurement, measure_renderables
78
from .padding import Padding, PaddingDimensions
89
from .segment import Segment
9-
from .style import StyleType
10+
from .style import Style, StyleType
1011
from .text import Text, TextType
1112

1213
if TYPE_CHECKING:
@@ -149,9 +150,53 @@ def __rich_console__(
149150
safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
150151
box = self.box.substitute(options, safe=safe_box)
151152

153+
def align_text(
154+
text: Text, width: int, align: str, character: str, style: Style
155+
) -> Text:
156+
"""Gets new aligned text.
157+
158+
Args:
159+
text (Text): Title or subtitle text.
160+
width (int): Desired width.
161+
align (str): Alignment.
162+
character (str): Character for alignment.
163+
style (Style): Border style
164+
165+
Returns:
166+
Text: New text instance
167+
"""
168+
text = text.copy()
169+
text.truncate(width)
170+
excess_space = width - cell_len(text.plain)
171+
if excess_space:
172+
if align == "left":
173+
return Text.assemble(
174+
text,
175+
(character * excess_space, style),
176+
no_wrap=True,
177+
end="",
178+
)
179+
elif align == "center":
180+
left = excess_space // 2
181+
return Text.assemble(
182+
(character * left, style),
183+
text,
184+
(character * (excess_space - left), style),
185+
no_wrap=True,
186+
end="",
187+
)
188+
else:
189+
return Text.assemble(
190+
(character * excess_space, style),
191+
text,
192+
no_wrap=True,
193+
end="",
194+
)
195+
return text
196+
152197
title_text = self._title
153198
if title_text is not None:
154-
title_text.style = border_style
199+
title_text.stylize_before(border_style)
155200

156201
child_width = (
157202
width - 2
@@ -180,7 +225,13 @@ def __rich_console__(
180225
if title_text is None or width <= 4:
181226
yield Segment(box.get_top([width - 2]), border_style)
182227
else:
183-
title_text.align(self.title_align, width - 4, character=box.top)
228+
title_text = align_text(
229+
title_text,
230+
width - 4,
231+
self.title_align,
232+
box.top,
233+
border_style,
234+
)
184235
yield Segment(box.top_left + box.top, border_style)
185236
yield from console.render(title_text, child_options.update_width(width - 4))
186237
yield Segment(box.top + box.top_right, border_style)
@@ -194,12 +245,18 @@ def __rich_console__(
194245

195246
subtitle_text = self._subtitle
196247
if subtitle_text is not None:
197-
subtitle_text.style = border_style
248+
subtitle_text.stylize_before(border_style)
198249

199250
if subtitle_text is None or width <= 4:
200251
yield Segment(box.get_bottom([width - 2]), border_style)
201252
else:
202-
subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom)
253+
subtitle_text = align_text(
254+
subtitle_text,
255+
width - 4,
256+
self.subtitle_align,
257+
box.bottom,
258+
border_style,
259+
)
203260
yield Segment(box.bottom_left + box.bottom, border_style)
204261
yield from console.render(
205262
subtitle_text, child_options.update_width(width - 4)

rich/text.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,6 @@ def stylize(
450450
style (Union[str, Style]): Style instance or style definition to apply.
451451
start (int): Start offset (negative indexing is supported). Defaults to 0.
452452
end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.
453-
454453
"""
455454
if style:
456455
length = len(self)
@@ -465,6 +464,32 @@ def stylize(
465464
return
466465
self._spans.append(Span(start, min(length, end), style))
467466

467+
def stylize_before(
468+
self,
469+
style: Union[str, Style],
470+
start: int = 0,
471+
end: Optional[int] = None,
472+
) -> None:
473+
"""Apply a style to the text, or a portion of the text. Styles will be applied before other styles already present.
474+
475+
Args:
476+
style (Union[str, Style]): Style instance or style definition to apply.
477+
start (int): Start offset (negative indexing is supported). Defaults to 0.
478+
end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.
479+
"""
480+
if style:
481+
length = len(self)
482+
if start < 0:
483+
start = length + start
484+
if end is None:
485+
end = length
486+
if end < 0:
487+
end = length + end
488+
if start >= length or end <= start:
489+
# Span not in text or not valid
490+
return
491+
self._spans.insert(0, Span(start, min(length, end), style))
492+
468493
def apply_meta(
469494
self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None
470495
) -> None:

tests/test_log.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def render_log():
3737

3838
def test_log():
3939
expected = replace_link_ids(
40-
"\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m32\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0mHello from \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m ! \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m33\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0m\x1b[1m[\x1b[0m\x1b[1;36m1\x1b[0m, \x1b[1;36m2\x1b[0m, \x1b[1;36m3\x1b[0m\x1b[1m]\x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m34\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[34m╭─\x1b[0m\x1b[34m───────────────────── \x1b[0m\x1b[3;34mlocals\x1b[0m\x1b[34m ─────────────────────\x1b[0m\x1b[34m─╮\x1b[0m \x1b[2m \x1b[0m\n\x1b[2;36m \x1b[0m\x1b[34m│\x1b[0m \x1b[3;33mconsole\x1b[0m\x1b[31m =\x1b[0m \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m \x1b[34m│\x1b[0m \x1b[2m \x1b[0m\n\x1b[2;36m \x1b[0m\x1b[34m╰────────────────────────────────────────────────────╯\x1b[0m \x1b[2m \x1b[0m\n"
40+
"\x1b[2;36m[TIME]\x1b[0m\x1b[2;36m \x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m32\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0mHello from \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m ! \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m33\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[2;36m \x1b[0m\x1b[1m[\x1b[0m\x1b[1;36m1\x1b[0m, \x1b[1;36m2\x1b[0m, \x1b[1;36m3\x1b[0m\x1b[1m]\x1b[0m \x1b]8;id=0;foo\x1b\\\x1b[2msource.py\x1b[0m\x1b]8;;\x1b\\\x1b[2m:\x1b[0m\x1b]8;id=0;foo\x1b\\\x1b[2m34\x1b[0m\x1b]8;;\x1b\\\n\x1b[2;36m \x1b[0m\x1b[34m╭─\x1b[0m\x1b[34m─────────────────────\x1b[0m\x1b[34m \x1b[0m\x1b[3;34mlocals\x1b[0m\x1b[34m \x1b[0m\x1b[34m─────────────────────\x1b[0m\x1b[34m─╮\x1b[0m \x1b[2m \x1b[0m\n\x1b[2;36m \x1b[0m\x1b[34m│\x1b[0m \x1b[3;33mconsole\x1b[0m\x1b[31m =\x1b[0m \x1b[1m<\x1b[0m\x1b[1;95mconsole\x1b[0m\x1b[39m \x1b[0m\x1b[33mwidth\x1b[0m\x1b[39m=\x1b[0m\x1b[1;36m80\x1b[0m\x1b[39m ColorSystem.TRUECOLOR\x1b[0m\x1b[1m>\x1b[0m \x1b[34m│\x1b[0m \x1b[2m \x1b[0m\n\x1b[2;36m \x1b[0m\x1b[34m╰────────────────────────────────────────────────────╯\x1b[0m \x1b[2m \x1b[0m\n"
4141
)
4242
rendered = render_log()
4343
print(repr(rendered))

tests/test_panel.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ def test_render_size():
6363
expected = [
6464
[
6565
Segment("╭─", Style()),
66-
Segment(
67-
"────────────────────────────────── Hello ───────────────────────────────────"
68-
),
66+
Segment("──────────────────────────────────", Style()),
67+
Segment(" Hello ", Style()),
68+
Segment("───────────────────────────────────", Style()),
6969
Segment("─╮", Style()),
7070
],
7171
[

tests/test_text.py

+7
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ def test_stylize():
144144
assert text._spans == [Span(7, 11, "bold")]
145145

146146

147+
def test_stylize_before():
148+
text = Text("Hello, World!")
149+
text.stylize("bold", 0, 5)
150+
text.stylize_before("italic", 2, 7)
151+
assert text._spans == [Span(2, 7, "italic"), Span(0, 5, "bold")]
152+
153+
147154
def test_stylize_negative_index():
148155
text = Text("Hello, World!")
149156
text.stylize("bold", -6, -1)

0 commit comments

Comments
 (0)