diff --git a/beaupy/_internals.py b/beaupy/_internals.py index dc528f7..f76a5f1 100644 --- a/beaupy/_internals.py +++ b/beaupy/_internals.py @@ -1,3 +1,4 @@ +import re from ast import literal_eval from contextlib import contextmanager from typing import Any, Callable, Iterator, List, Type, Union @@ -5,6 +6,7 @@ import emoji from rich.console import Console, ConsoleRenderable from rich.live import Live +from rich.style import Style from yakh.key import Key TargetType = Any @@ -36,6 +38,18 @@ def _format_option_select(i: int, cursor_index: int, option: str, cursor_style: ) +def _wrap_style(string_w_styles: str, global_style_str: str) -> str: + RE_STYLE_PATTERN = r'\[(.*?)\]' + + global_style = Style.parse(global_style_str) + style_strings = list(set([i.replace('/', '') for i in re.findall(RE_STYLE_PATTERN, string_w_styles)])) + for style_string in style_strings: + style = Style.combine([Style.parse(style_string), global_style]) + string_w_styles = string_w_styles.replace(f'{style_string}', f'{style}') + + return f'[{global_style_str}]{string_w_styles}[/{global_style_str}]' + + def _render_option_select_multiple( option: str, ticked: bool, tick_character: str, tick_style: str, selected: bool, cursor_style: str ) -> str: @@ -43,7 +57,7 @@ def _render_option_select_multiple( if ticked: prefix = f'\[[{tick_style}]{tick_character}[/{tick_style}]]' # noqa: W605 if selected: - option = f'[{cursor_style}]{option}[/{cursor_style}]' + option = _wrap_style(option, cursor_style) return f'{prefix} {option}' diff --git a/test/beaupy_select_multiple_test.py b/test/beaupy_select_multiple_test.py index f9471d2..3627207 100644 --- a/test/beaupy_select_multiple_test.py +++ b/test/beaupy_select_multiple_test.py @@ -713,3 +713,28 @@ def _(): assert Live.update.call_count == 3 assert res == ["test1"] + + +@test("`select_multiple` with 2 options, second of which is styled, starting from first selecting going down and selecting second also") +def _(): + steps = iter([" ", Keys.DOWN_ARROW, " ", Keys.ENTER]) + + b.get_key = lambda: next(steps) + Live.update = mock.MagicMock() + res = select_multiple(options=["test1", "[yellow1]test2[/yellow1]"], tick_character="😋") + assert Live.update.call_args_list == [ + mock.call( + renderable="\\[ ] [pink1]test1[/pink1]\n\\[ ] [yellow1]test2[/yellow1]\n\n(Mark with [bold]space[/bold], confirm with [bold]enter[/bold])" + ), + mock.call( + renderable="\\[[pink1]😋[/pink1]] [pink1]test1[/pink1]\n\\[ ] [yellow1]test2[/yellow1]\n\n(Mark with [bold]space[/bold], confirm with [bold]enter[/bold])" + ), + mock.call( + renderable="\\[[pink1]😋[/pink1]] test1\n\\[ ] [pink1][pink1]test2[/pink1][/pink1]\n\n(Mark with [bold]space[/bold], confirm with [bold]enter[/bold])" + ), + mock.call( + renderable="\\[[pink1]😋[/pink1]] test1\n\\[[pink1]😋[/pink1]] [pink1][pink1]test2[/pink1][/pink1]\n\n(Mark with [bold]space[/bold], confirm with [bold]enter[/bold])" + ), + ] + assert Live.update.call_count == 4 + assert res == ["test1", "[yellow1]test2[/yellow1]"]