From 066c024be2d6faa7eae2f3dd2ea1cb4543a3d454 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Sep 2022 14:59:40 +0100 Subject: [PATCH 1/3] fix for text in panel title --- rich/panel.py | 69 +++++++++++++++++++++++++++++++++++++++++---- rich/text.py | 27 +++++++++++++++++- tests/test_log.py | 2 +- tests/test_panel.py | 6 ++-- 4 files changed, 94 insertions(+), 10 deletions(-) diff --git a/rich/panel.py b/rich/panel.py index fc2807c31..1ee1e84b8 100644 --- a/rich/panel.py +++ b/rich/panel.py @@ -2,11 +2,12 @@ from .align import AlignMethod from .box import ROUNDED, Box +from .cells import cell_len from .jupyter import JupyterMixin from .measure import Measurement, measure_renderables from .padding import Padding, PaddingDimensions from .segment import Segment -from .style import StyleType +from .style import Style, StyleType from .text import Text, TextType if TYPE_CHECKING: @@ -149,9 +150,53 @@ def __rich_console__( safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box box = self.box.substitute(options, safe=safe_box) + def align_text( + text: Text, width: int, align: str, character: str, style: Style + ) -> Text: + """Gets new aligned text. + + Args: + text (Text): Title or subtitle text. + width (int): Desired width. + align (str): Alignment. + character (str): Character for alignment. + style (Style): Border style + + Returns: + Text: New text instance + """ + text = text.copy() + text.truncate(width) + excess_space = width - cell_len(text.plain) + if excess_space: + if align == "left": + return Text.assemble( + text, + (character * excess_space, style), + no_wrap=True, + end="", + ) + elif align == "center": + left = excess_space // 2 + return Text.assemble( + (character * left, style), + text, + (character * (excess_space - left), style), + no_wrap=True, + end="", + ) + else: + return Text.assemble( + (character * excess_space, style), + text, + no_wrap=True, + end="", + ) + return text + title_text = self._title if title_text is not None: - title_text.style = border_style + title_text.stylize_before(border_style) child_width = ( width - 2 @@ -180,7 +225,14 @@ def __rich_console__( if title_text is None or width <= 4: yield Segment(box.get_top([width - 2]), border_style) else: - title_text.align(self.title_align, width - 4, character=box.top) + # title_text.align(self.title_align, width - 4, character=box.top) + title_text = align_text( + title_text, + width - 4, + self.title_align, + box.top, + border_style, + ) yield Segment(box.top_left + box.top, border_style) yield from console.render(title_text, child_options.update_width(width - 4)) yield Segment(box.top + box.top_right, border_style) @@ -194,12 +246,19 @@ def __rich_console__( subtitle_text = self._subtitle if subtitle_text is not None: - subtitle_text.style = border_style + subtitle_text.stylize_before(border_style) if subtitle_text is None or width <= 4: yield Segment(box.get_bottom([width - 2]), border_style) else: - subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom) + # subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom) + subtitle_text = align_text( + subtitle_text, + width - 4, + self.subtitle_align, + box.bottom, + border_style, + ) yield Segment(box.bottom_left + box.bottom, border_style) yield from console.render( subtitle_text, child_options.update_width(width - 4) diff --git a/rich/text.py b/rich/text.py index 1471fac2d..5b5ead0f6 100644 --- a/rich/text.py +++ b/rich/text.py @@ -450,7 +450,6 @@ def stylize( style (Union[str, Style]): Style instance or style definition to apply. start (int): Start offset (negative indexing is supported). Defaults to 0. end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. - """ if style: length = len(self) @@ -465,6 +464,32 @@ def stylize( return self._spans.append(Span(start, min(length, end), style)) + def stylize_before( + self, + style: Union[str, Style], + start: int = 0, + end: Optional[int] = None, + ) -> None: + """Apply a style to the text, or a portion of the text. Styles will be applied before other styles already present. + + Args: + style (Union[str, Style]): Style instance or style definition to apply. + start (int): Start offset (negative indexing is supported). Defaults to 0. + end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. + """ + if style: + length = len(self) + if start < 0: + start = length + start + if end is None: + end = length + if end < 0: + end = length + end + if start >= length or end <= start: + # Span not in text or not valid + return + self._spans.insert(0, Span(start, min(length, end), style)) + def apply_meta( self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None ) -> None: diff --git a/tests/test_log.py b/tests/test_log.py index 22bb8b7b8..37fdda462 100644 --- a/tests/test_log.py +++ b/tests/test_log.py @@ -37,7 +37,7 @@ def render_log(): def test_log(): expected = replace_link_ids( - "\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" + "\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" ) rendered = render_log() print(repr(rendered)) diff --git a/tests/test_panel.py b/tests/test_panel.py index 5ae3babee..a79a4a918 100644 --- a/tests/test_panel.py +++ b/tests/test_panel.py @@ -63,9 +63,9 @@ def test_render_size(): expected = [ [ Segment("╭─", Style()), - Segment( - "────────────────────────────────── Hello ───────────────────────────────────" - ), + Segment("──────────────────────────────────", Style()), + Segment(" Hello ", Style()), + Segment("───────────────────────────────────", Style()), Segment("─╮", Style()), ], [ From e7bede14258fd9fb7db990d00b573bd092cc84fe Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Sep 2022 15:02:55 +0100 Subject: [PATCH 2/3] added text --- tests/test_text.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_text.py b/tests/test_text.py index 169931a76..5f06715db 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -144,6 +144,13 @@ def test_stylize(): assert text._spans == [Span(7, 11, "bold")] +def test_stylize_before(): + text = Text("Hello, World!") + text.stylize("bold", 0, 5) + text.stylize_before("italic", 2, 7) + assert text._spans == [Span(2, 7, "italic"), Span(0, 5, "bold")] + + def test_stylize_negative_index(): text = Text("Hello, World!") text.stylize("bold", -6, -1) From 32d6e99a8ff514eb8db60367626fa40b998a7a94 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Sep 2022 15:04:40 +0100 Subject: [PATCH 3/3] remove comments --- rich/panel.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rich/panel.py b/rich/panel.py index 1ee1e84b8..d522d80b5 100644 --- a/rich/panel.py +++ b/rich/panel.py @@ -225,7 +225,6 @@ def align_text( if title_text is None or width <= 4: yield Segment(box.get_top([width - 2]), border_style) else: - # title_text.align(self.title_align, width - 4, character=box.top) title_text = align_text( title_text, width - 4, @@ -251,7 +250,6 @@ def align_text( if subtitle_text is None or width <= 4: yield Segment(box.get_bottom([width - 2]), border_style) else: - # subtitle_text.align(self.subtitle_align, width - 4, character=box.bottom) subtitle_text = align_text( subtitle_text, width - 4,