Skip to content

Commit 3a12cfa

Browse files
Added input_selection shortcut.
1 parent 3374ae9 commit 3a12cfa

File tree

6 files changed

+353
-29
lines changed

6 files changed

+353
-29
lines changed

examples/input_selection/color.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from __future__ import annotations
2+
3+
from prompt_toolkit.formatted_text import HTML
4+
from prompt_toolkit.shortcuts.input_selection import select_input
5+
from prompt_toolkit.styles import Style
6+
7+
8+
def main() -> None:
9+
style = Style.from_dict(
10+
{
11+
"input-selection": "fg:#ff0000",
12+
"number": "fg:#884444 bold",
13+
"selected-option": "underline",
14+
"frame.border": "#884444",
15+
}
16+
)
17+
18+
result = select_input(
19+
message=HTML("<u>Please select a dish</u>:"),
20+
options=[
21+
("pizza", "Pizza with mushrooms"),
22+
(
23+
"salad",
24+
HTML("<ansigreen>Salad</ansigreen> with <ansired>tomatoes</ansired>"),
25+
),
26+
("sushi", "Sushi"),
27+
],
28+
style=style,
29+
)
30+
print(result)
31+
32+
33+
if __name__ == "__main__":
34+
main()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
from prompt_toolkit.shortcuts.input_selection import select_input
4+
5+
6+
def main() -> None:
7+
result = select_input(
8+
message="Please select an option:",
9+
options=[(i, f"Option {i}") for i in range(1, 100)],
10+
)
11+
print(result)
12+
13+
14+
if __name__ == "__main__":
15+
main()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from __future__ import annotations
2+
3+
from prompt_toolkit.shortcuts.input_selection import select_input
4+
5+
6+
def main() -> None:
7+
result = select_input(
8+
message="Please select a dish:",
9+
options=[
10+
("pizza", "Pizza with mushrooms"),
11+
("salad", "Salad with tomatoes"),
12+
("sushi", "Sushi"),
13+
],
14+
)
15+
print(result)
16+
17+
18+
if __name__ == "__main__":
19+
main()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import annotations
2+
3+
from prompt_toolkit.formatted_text import HTML
4+
from prompt_toolkit.shortcuts.input_selection import select_input
5+
from prompt_toolkit.styles import Style
6+
7+
8+
def main() -> None:
9+
style = Style.from_dict(
10+
{
11+
"frame.border": "#884444",
12+
}
13+
)
14+
15+
result = select_input(
16+
message=HTML("<u>Please select a dish</u>:"),
17+
options=[
18+
("pizza", "Pizza with mushrooms"),
19+
("salad", "Salad with tomatoes"),
20+
("sushi", "Sushi"),
21+
],
22+
style=style,
23+
show_frame=1,
24+
)
25+
print(result)
26+
27+
28+
if __name__ == "__main__":
29+
main()
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
from __future__ import annotations
2+
3+
from typing import Generic, Sequence, TypeVar
4+
5+
from prompt_toolkit.application import Application
6+
from prompt_toolkit.filters import Condition, FilterOrBool, to_filter
7+
from prompt_toolkit.formatted_text import AnyFormattedText
8+
from prompt_toolkit.key_binding.key_bindings import KeyBindings
9+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
10+
from prompt_toolkit.layout import AnyContainer, HSplit, Layout
11+
from prompt_toolkit.styles import BaseStyle
12+
from prompt_toolkit.utils import suspend_to_background_supported
13+
from prompt_toolkit.widgets import Box, Frame, Label, RadioList
14+
15+
_T = TypeVar("_T")
16+
E = KeyPressEvent
17+
18+
19+
class InputSelection(Generic[_T]):
20+
def __init__(
21+
self,
22+
*,
23+
message: AnyFormattedText,
24+
options: Sequence[tuple[_T, AnyFormattedText]],
25+
default: _T | None = None,
26+
mouse_support: bool = True,
27+
style: BaseStyle | None = None,
28+
symbol: str = ">",
29+
show_frame: bool = False,
30+
enable_suspend: FilterOrBool = False,
31+
enable_abort: FilterOrBool = True,
32+
interrupt_exception: type[BaseException] = KeyboardInterrupt,
33+
) -> None:
34+
self.message = message
35+
self.default = default
36+
self.options = options
37+
self.mouse_support = mouse_support
38+
self.style = style
39+
self.symbol = symbol
40+
self.show_frame = show_frame
41+
self.enable_suspend = enable_suspend
42+
self.interrupt_exception = interrupt_exception
43+
self.enable_abort = enable_abort
44+
45+
def _create_application(self) -> Application[_T]:
46+
radio_list = RadioList(
47+
values=self.options,
48+
default=self.default,
49+
select_on_focus=True,
50+
open_character="",
51+
select_character=self.symbol,
52+
close_character="",
53+
show_cursor=False,
54+
show_numbers=True,
55+
container_style="class:input-selection",
56+
default_style="class:option",
57+
selected_style="",
58+
checked_style="class:selected-option",
59+
number_style="class:number",
60+
)
61+
container: AnyContainer = HSplit(
62+
[
63+
Box(
64+
Label(text=self.message, dont_extend_height=True),
65+
padding_top=0,
66+
padding_left=1,
67+
padding_right=1,
68+
padding_bottom=0,
69+
),
70+
Box(
71+
radio_list,
72+
padding_top=0,
73+
padding_left=3,
74+
padding_right=1,
75+
padding_bottom=0,
76+
),
77+
]
78+
)
79+
if self.show_frame:
80+
container = Frame(container)
81+
layout = Layout(container, radio_list)
82+
83+
kb = KeyBindings()
84+
85+
@kb.add("enter", eager=True)
86+
def _accept_input(event: E) -> None:
87+
"Accept input when enter has been pressed."
88+
event.app.exit(result=radio_list.current_value)
89+
90+
@Condition
91+
def enable_abort() -> bool:
92+
return to_filter(self.enable_abort)()
93+
94+
@kb.add("c-c", filter=enable_abort)
95+
@kb.add("<sigint>", filter=enable_abort)
96+
def _keyboard_interrupt(event: E) -> None:
97+
"Abort when Control-C has been pressed."
98+
event.app.exit(exception=self.interrupt_exception(), style="class:aborting")
99+
100+
suspend_supported = Condition(suspend_to_background_supported)
101+
102+
@Condition
103+
def enable_suspend() -> bool:
104+
return to_filter(self.enable_suspend)()
105+
106+
@kb.add("c-z", filter=suspend_supported & enable_suspend)
107+
def _suspend(event: E) -> None:
108+
"""
109+
Suspend process to background.
110+
"""
111+
event.app.suspend_to_background()
112+
113+
return Application(
114+
layout=layout,
115+
full_screen=False,
116+
mouse_support=self.mouse_support,
117+
key_bindings=kb,
118+
style=self.style,
119+
)
120+
121+
def prompt(self) -> _T:
122+
return self._create_application().run()
123+
124+
async def prompt_async(self) -> _T:
125+
return await self._create_application().run_async()
126+
127+
128+
def select_input(
129+
message: AnyFormattedText,
130+
options: Sequence[tuple[_T, AnyFormattedText]],
131+
default: _T | None = None,
132+
mouse_support: bool = True,
133+
style: BaseStyle | None = None,
134+
symbol: str = ">",
135+
show_frame: bool = False,
136+
enable_suspend: FilterOrBool = False,
137+
enable_abort: FilterOrBool = True,
138+
) -> _T:
139+
return InputSelection(
140+
message=message,
141+
options=options,
142+
default=default,
143+
mouse_support=mouse_support,
144+
show_frame=show_frame,
145+
symbol=symbol,
146+
style=style,
147+
enable_suspend=enable_suspend,
148+
enable_abort=enable_abort,
149+
).prompt()

0 commit comments

Comments
 (0)