Skip to content

Commit c869087

Browse files
Added input_selection shortcut.
1 parent af26eec commit c869087

File tree

6 files changed

+361
-29
lines changed

6 files changed

+361
-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: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
__all__ = [
16+
"InputSelection",
17+
"select_input",
18+
]
19+
20+
_T = TypeVar("_T")
21+
E = KeyPressEvent
22+
23+
24+
class InputSelection(Generic[_T]):
25+
def __init__(
26+
self,
27+
*,
28+
message: AnyFormattedText,
29+
options: Sequence[tuple[_T, AnyFormattedText]],
30+
default: _T | None = None,
31+
mouse_support: bool = True,
32+
style: BaseStyle | None = None,
33+
symbol: str = ">",
34+
show_frame: bool = False,
35+
enable_suspend: FilterOrBool = False,
36+
enable_abort: FilterOrBool = True,
37+
interrupt_exception: type[BaseException] = KeyboardInterrupt,
38+
) -> None:
39+
self.message = message
40+
self.default = default
41+
self.options = options
42+
self.mouse_support = mouse_support
43+
self.style = style
44+
self.symbol = symbol
45+
self.show_frame = show_frame
46+
self.enable_suspend = enable_suspend
47+
self.interrupt_exception = interrupt_exception
48+
self.enable_abort = enable_abort
49+
50+
def _create_application(self) -> Application[_T]:
51+
radio_list = RadioList(
52+
values=self.options,
53+
default=self.default,
54+
select_on_focus=True,
55+
open_character="",
56+
select_character=self.symbol,
57+
close_character="",
58+
show_cursor=False,
59+
show_numbers=True,
60+
container_style="class:input-selection",
61+
default_style="class:option",
62+
selected_style="",
63+
checked_style="class:selected-option",
64+
number_style="class:number",
65+
show_scrollbar=False,
66+
)
67+
container: AnyContainer = HSplit(
68+
[
69+
Box(
70+
Label(text=self.message, dont_extend_height=True),
71+
padding_top=0,
72+
padding_left=1,
73+
padding_right=1,
74+
padding_bottom=0,
75+
),
76+
Box(
77+
radio_list,
78+
padding_top=0,
79+
padding_left=3,
80+
padding_right=1,
81+
padding_bottom=0,
82+
),
83+
]
84+
)
85+
if self.show_frame:
86+
container = Frame(container)
87+
layout = Layout(container, radio_list)
88+
89+
kb = KeyBindings()
90+
91+
@kb.add("enter", eager=True)
92+
def _accept_input(event: E) -> None:
93+
"Accept input when enter has been pressed."
94+
event.app.exit(result=radio_list.current_value)
95+
96+
@Condition
97+
def enable_abort() -> bool:
98+
return to_filter(self.enable_abort)()
99+
100+
@kb.add("c-c", filter=enable_abort)
101+
@kb.add("<sigint>", filter=enable_abort)
102+
def _keyboard_interrupt(event: E) -> None:
103+
"Abort when Control-C has been pressed."
104+
event.app.exit(exception=self.interrupt_exception(), style="class:aborting")
105+
106+
suspend_supported = Condition(suspend_to_background_supported)
107+
108+
@Condition
109+
def enable_suspend() -> bool:
110+
return to_filter(self.enable_suspend)()
111+
112+
@kb.add("c-z", filter=suspend_supported & enable_suspend)
113+
def _suspend(event: E) -> None:
114+
"""
115+
Suspend process to background.
116+
"""
117+
event.app.suspend_to_background()
118+
119+
return Application(
120+
layout=layout,
121+
full_screen=False,
122+
mouse_support=self.mouse_support,
123+
key_bindings=kb,
124+
style=self.style,
125+
)
126+
127+
def prompt(self) -> _T:
128+
return self._create_application().run()
129+
130+
async def prompt_async(self) -> _T:
131+
return await self._create_application().run_async()
132+
133+
134+
def select_input(
135+
message: AnyFormattedText,
136+
options: Sequence[tuple[_T, AnyFormattedText]],
137+
default: _T | None = None,
138+
mouse_support: bool = True,
139+
style: BaseStyle | None = None,
140+
symbol: str = ">",
141+
show_frame: bool = False,
142+
enable_suspend: FilterOrBool = False,
143+
enable_abort: FilterOrBool = True,
144+
) -> _T:
145+
return InputSelection[_T](
146+
message=message,
147+
options=options,
148+
default=default,
149+
mouse_support=mouse_support,
150+
show_frame=show_frame,
151+
symbol=symbol,
152+
style=style,
153+
enable_suspend=enable_suspend,
154+
enable_abort=enable_abort,
155+
).prompt()

0 commit comments

Comments
 (0)