Skip to content

Commit fd94a78

Browse files
Add support for rich formatted text using the 'rich' library.
This allows for using rich objects anywhere in prompt_toolkit where formatted text is expected.
1 parent d8adbe9 commit fd94a78

File tree

7 files changed

+178
-0
lines changed

7 files changed

+178
-0
lines changed

examples/rich/choices.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python
2+
from rich.markdown import Markdown
3+
from rich.text import Text
4+
5+
from prompt_toolkit import choice
6+
from prompt_toolkit.formatted_text.rich import Rich
7+
8+
# For the header, we wrap the `Markdown` object from `rich` in a `Rich` object
9+
# from `prompt_toolkit`, so that we can explicitly set a width.
10+
header = Rich(
11+
Markdown(
12+
"""
13+
# Please select a dish
14+
15+
Choose *one* item please.
16+
17+
```python
18+
def some_example_function() -> None: "test"
19+
```
20+
""".strip()
21+
),
22+
width=50,
23+
style="black on blue",
24+
)
25+
26+
27+
def main():
28+
answer = choice(
29+
message=header,
30+
options=[
31+
("pizza", "Pizza with mushrooms"),
32+
(
33+
"salad",
34+
Text.from_markup(
35+
":warning: [green]Salad[/green] with [red]tomatoes[/red]"
36+
),
37+
),
38+
("sushi", "Sushi"),
39+
],
40+
show_frame=True,
41+
)
42+
print(f"You said: {answer}")
43+
44+
45+
if __name__ == "__main__":
46+
main()

examples/rich/dialog.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python
2+
"""
3+
Example of an input box dialog.
4+
"""
5+
6+
from rich.text import Text
7+
8+
from prompt_toolkit.shortcuts import input_dialog
9+
10+
11+
def main():
12+
result = input_dialog(
13+
title=Text.from_markup("[red]Input[/red] dialog [b]example[b]"),
14+
text=Text.from_markup("Please type your [green]name[/green]:"),
15+
).run()
16+
17+
print(f"Result = {result}")
18+
19+
20+
if __name__ == "__main__":
21+
main()

examples/rich/multiline-prompt.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env python
2+
from rich.markdown import Markdown
3+
4+
from prompt_toolkit import prompt
5+
from prompt_toolkit.formatted_text import merge_formatted_text
6+
from prompt_toolkit.formatted_text.rich import Rich
7+
8+
# For the header, we wrap the `Markdown` object from `rich` in a `Rich` object
9+
# from `prompt_toolkit`, so that we can explicitly set a width.
10+
header = Rich(
11+
Markdown(
12+
"""
13+
# Type the name of the following function:
14+
15+
```python
16+
def fibonacci(number: int) -> int:
17+
"compute Fibonacci number"
18+
```
19+
20+
"""
21+
),
22+
width=50,
23+
)
24+
25+
26+
def main():
27+
answer = prompt(merge_formatted_text([header, "> "]))
28+
print(f"You said: {answer}")
29+
30+
31+
if __name__ == "__main__":
32+
main()

examples/rich/prompt.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
from rich.text import Text
3+
4+
from prompt_toolkit import prompt
5+
6+
7+
def main():
8+
answer = prompt(Text.from_markup("[green]Say[/green] [b]something[/b] > "))
9+
print(f"You said: {answer}")
10+
11+
12+
if __name__ == "__main__":
13+
main()

src/prompt_toolkit/formatted_text/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from .html import HTML
2828
from .pygments import PygmentsTokens
29+
from .rich import Rich
2930
from .utils import (
3031
fragment_list_len,
3132
fragment_list_to_text,
@@ -50,6 +51,8 @@
5051
"ANSI",
5152
# Pygments.
5253
"PygmentsTokens",
54+
# Rich.
55+
"Rich",
5356
# Utils.
5457
"fragment_list_len",
5558
"fragment_list_width",

src/prompt_toolkit/formatted_text/base.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,19 @@ class MagicFormattedText(Protocol):
4040

4141
def __pt_formatted_text__(self) -> StyleAndTextTuples: ...
4242

43+
class RichFormattedText(Protocol):
44+
"""
45+
Any rich text object from the rich library that implements
46+
``__rich_console__``.
47+
"""
48+
49+
def __rich_console__(self, console: Any = ..., options: Any = ...) -> Any: ...
50+
4351

4452
AnyFormattedText = Union[
4553
str,
4654
"MagicFormattedText",
55+
"RichFormattedText",
4756
StyleAndTextTuples,
4857
# Callable[[], 'AnyFormattedText'] # Recursive definition not supported by mypy.
4958
Callable[[], Any],
@@ -78,6 +87,10 @@ def to_formatted_text(
7887
result = value # StyleAndTextTuples
7988
elif hasattr(value, "__pt_formatted_text__"):
8089
result = cast("MagicFormattedText", value).__pt_formatted_text__()
90+
elif hasattr(value, "__rich_console__"):
91+
from .rich import Rich
92+
93+
result = Rich(value).__pt_formatted_text__()
8194
elif callable(value):
8295
return to_formatted_text(value(), style=style)
8396
elif auto_convert:
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from __future__ import annotations
2+
3+
from io import StringIO
4+
from typing import TYPE_CHECKING, Any
5+
6+
from .ansi import ANSI
7+
from .base import StyleAndTextTuples
8+
9+
if TYPE_CHECKING:
10+
from rich.style import StyleType
11+
12+
__all__ = [
13+
"Rich",
14+
]
15+
16+
17+
class Rich:
18+
"""
19+
Turn any rich text object from the `rich` library into prompt_toolkit
20+
formatted text, so that it can be used in a prompt or anywhere else.
21+
22+
Note that `to_formatted_text` automatically recognizes objects that have a
23+
`__rich_console__` attribute and will wrap them in a `Rich` instance.
24+
"""
25+
26+
def __init__(
27+
self,
28+
rich_object: Any,
29+
width: int = None,
30+
style: StyleType | None = None,
31+
) -> None:
32+
self.rich_object = rich_object
33+
self.width = width
34+
self.style = style
35+
36+
def __pt_formatted_text__(self) -> StyleAndTextTuples:
37+
from rich.console import Console
38+
39+
file = StringIO()
40+
41+
console = Console(
42+
file=file,
43+
force_terminal=True,
44+
color_system="truecolor",
45+
width=self.width,
46+
style=self.style,
47+
)
48+
console.print(self.rich_object, end="")
49+
ansi = file.getvalue()
50+
return ANSI(ansi).__pt_formatted_text__()

0 commit comments

Comments
 (0)