Skip to content

Commit c7d83db

Browse files
Added input_selection shortcut.
1 parent af26eec commit c7d83db

File tree

6 files changed

+356
-29
lines changed

6 files changed

+356
-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: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
show_scrollbar=False,
61+
)
62+
container: AnyContainer = HSplit(
63+
[
64+
Box(
65+
Label(text=self.message, dont_extend_height=True),
66+
padding_top=0,
67+
padding_left=1,
68+
padding_right=1,
69+
padding_bottom=0,
70+
),
71+
Box(
72+
radio_list,
73+
padding_top=0,
74+
padding_left=3,
75+
padding_right=1,
76+
padding_bottom=0,
77+
),
78+
]
79+
)
80+
if self.show_frame:
81+
container = Frame(container)
82+
layout = Layout(container, radio_list)
83+
84+
kb = KeyBindings()
85+
86+
@kb.add("enter", eager=True)
87+
def _accept_input(event: E) -> None:
88+
"Accept input when enter has been pressed."
89+
event.app.exit(result=radio_list.current_value)
90+
91+
@Condition
92+
def enable_abort() -> bool:
93+
return to_filter(self.enable_abort)()
94+
95+
@kb.add("c-c", filter=enable_abort)
96+
@kb.add("<sigint>", filter=enable_abort)
97+
def _keyboard_interrupt(event: E) -> None:
98+
"Abort when Control-C has been pressed."
99+
event.app.exit(exception=self.interrupt_exception(), style="class:aborting")
100+
101+
suspend_supported = Condition(suspend_to_background_supported)
102+
103+
@Condition
104+
def enable_suspend() -> bool:
105+
return to_filter(self.enable_suspend)()
106+
107+
@kb.add("c-z", filter=suspend_supported & enable_suspend)
108+
def _suspend(event: E) -> None:
109+
"""
110+
Suspend process to background.
111+
"""
112+
event.app.suspend_to_background()
113+
114+
return Application(
115+
layout=layout,
116+
full_screen=False,
117+
mouse_support=self.mouse_support,
118+
key_bindings=kb,
119+
style=self.style,
120+
)
121+
122+
def prompt(self) -> _T:
123+
return self._create_application().run()
124+
125+
async def prompt_async(self) -> _T:
126+
return await self._create_application().run_async()
127+
128+
129+
def select_input(
130+
message: AnyFormattedText,
131+
options: Sequence[tuple[_T, AnyFormattedText]],
132+
default: _T | None = None,
133+
mouse_support: bool = True,
134+
style: BaseStyle | None = None,
135+
symbol: str = ">",
136+
show_frame: bool = False,
137+
enable_suspend: FilterOrBool = False,
138+
enable_abort: FilterOrBool = True,
139+
) -> _T:
140+
return InputSelection[_T](
141+
message=message,
142+
options=options,
143+
default=default,
144+
mouse_support=mouse_support,
145+
show_frame=show_frame,
146+
symbol=symbol,
147+
style=style,
148+
enable_suspend=enable_suspend,
149+
enable_abort=enable_abort,
150+
).prompt()

0 commit comments

Comments
 (0)