Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/prompt_toolkit/shortcuts/choice_input.py: 33%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

69 statements  

1from __future__ import annotations 

2 

3from typing import Generic, Sequence, TypeVar 

4 

5from prompt_toolkit.application import Application 

6from prompt_toolkit.filters import ( 

7 Condition, 

8 FilterOrBool, 

9 is_done, 

10 renderer_height_is_known, 

11 to_filter, 

12) 

13from prompt_toolkit.formatted_text import AnyFormattedText 

14from prompt_toolkit.key_binding.key_bindings import ( 

15 DynamicKeyBindings, 

16 KeyBindings, 

17 KeyBindingsBase, 

18 merge_key_bindings, 

19) 

20from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

21from prompt_toolkit.layout import ( 

22 AnyContainer, 

23 ConditionalContainer, 

24 HSplit, 

25 Layout, 

26 Window, 

27) 

28from prompt_toolkit.layout.controls import FormattedTextControl 

29from prompt_toolkit.layout.dimension import Dimension 

30from prompt_toolkit.styles import BaseStyle, Style 

31from prompt_toolkit.utils import suspend_to_background_supported 

32from prompt_toolkit.widgets import Box, Frame, Label, RadioList 

33 

34__all__ = [ 

35 "ChoiceInput", 

36 "choice", 

37] 

38 

39_T = TypeVar("_T") 

40E = KeyPressEvent 

41 

42 

43def create_default_choice_input_style() -> BaseStyle: 

44 return Style.from_dict( 

45 { 

46 "frame.border": "#884444", 

47 "selected-option": "bold", 

48 } 

49 ) 

50 

51 

52class ChoiceInput(Generic[_T]): 

53 """ 

54 Input selection prompt. Ask the user to choose among a set of options. 

55 

56 Example usage:: 

57 

58 input_selection = ChoiceInput( 

59 message="Please select a dish:", 

60 options=[ 

61 ("pizza", "Pizza with mushrooms"), 

62 ("salad", "Salad with tomatoes"), 

63 ("sushi", "Sushi"), 

64 ], 

65 default="pizza", 

66 ) 

67 result = input_selection.prompt() 

68 

69 :param message: Plain text or formatted text to be shown before the options. 

70 :param options: Sequence of ``(value, label)`` tuples. The labels can be 

71 formatted text. 

72 :param default: Default value. If none is given, the first option is 

73 considered the default. 

74 :param mouse_support: Enable mouse support. 

75 :param style: :class:`.Style` instance for the color scheme. 

76 :param symbol: Symbol to be displayed in front of the selected choice. 

77 :param bottom_toolbar: Formatted text or callable that returns formatted 

78 text to be displayed at the bottom of the screen. 

79 :param show_frame: `bool` or 

80 :class:`~prompt_toolkit.filters.Filter`. When True, surround the input 

81 with a frame. 

82 :param enable_interrupt: `bool` or 

83 :class:`~prompt_toolkit.filters.Filter`. When True, raise 

84 the ``interrupt_exception`` (``KeyboardInterrupt`` by default) when 

85 control-c has been pressed. 

86 :param interrupt_exception: The exception type that will be raised when 

87 there is a keyboard interrupt (control-c keypress). 

88 """ 

89 

90 def __init__( 

91 self, 

92 *, 

93 message: AnyFormattedText, 

94 options: Sequence[tuple[_T, AnyFormattedText]], 

95 default: _T | None = None, 

96 mouse_support: bool = False, 

97 style: BaseStyle | None = None, 

98 symbol: str = ">", 

99 bottom_toolbar: AnyFormattedText = None, 

100 show_frame: FilterOrBool = False, 

101 enable_suspend: FilterOrBool = False, 

102 enable_interrupt: FilterOrBool = True, 

103 interrupt_exception: type[BaseException] = KeyboardInterrupt, 

104 key_bindings: KeyBindingsBase | None = None, 

105 ) -> None: 

106 if style is None: 

107 style = create_default_choice_input_style() 

108 

109 self.message = message 

110 self.default = default 

111 self.options = options 

112 self.mouse_support = mouse_support 

113 self.style = style 

114 self.symbol = symbol 

115 self.show_frame = show_frame 

116 self.enable_suspend = enable_suspend 

117 self.interrupt_exception = interrupt_exception 

118 self.enable_interrupt = enable_interrupt 

119 self.bottom_toolbar = bottom_toolbar 

120 self.key_bindings = key_bindings 

121 

122 def _create_application(self) -> Application[_T]: 

123 radio_list = RadioList( 

124 values=self.options, 

125 default=self.default, 

126 select_on_focus=True, 

127 open_character="", 

128 select_character=self.symbol, 

129 close_character="", 

130 show_cursor=False, 

131 show_numbers=True, 

132 container_style="class:input-selection", 

133 default_style="class:option", 

134 selected_style="", 

135 checked_style="class:selected-option", 

136 number_style="class:number", 

137 show_scrollbar=False, 

138 ) 

139 container: AnyContainer = HSplit( 

140 [ 

141 Box( 

142 Label(text=self.message, dont_extend_height=True), 

143 padding_top=0, 

144 padding_left=1, 

145 padding_right=1, 

146 padding_bottom=0, 

147 ), 

148 Box( 

149 radio_list, 

150 padding_top=0, 

151 padding_left=3, 

152 padding_right=1, 

153 padding_bottom=0, 

154 ), 

155 ] 

156 ) 

157 

158 @Condition 

159 def show_frame_filter() -> bool: 

160 return to_filter(self.show_frame)() 

161 

162 show_bottom_toolbar = ( 

163 Condition(lambda: self.bottom_toolbar is not None) 

164 & ~is_done 

165 & renderer_height_is_known 

166 ) 

167 

168 container = ConditionalContainer( 

169 Frame(container), 

170 alternative_content=container, 

171 filter=show_frame_filter, 

172 ) 

173 

174 bottom_toolbar = ConditionalContainer( 

175 Window( 

176 FormattedTextControl( 

177 lambda: self.bottom_toolbar, style="class:bottom-toolbar.text" 

178 ), 

179 style="class:bottom-toolbar", 

180 dont_extend_height=True, 

181 height=Dimension(min=1), 

182 ), 

183 filter=show_bottom_toolbar, 

184 ) 

185 

186 layout = Layout( 

187 HSplit( 

188 [ 

189 container, 

190 # Add an empty window between the selection input and the 

191 # bottom toolbar, if the bottom toolbar is visible, in 

192 # order to allow the bottom toolbar to be displayed at the 

193 # bottom of the screen. 

194 ConditionalContainer(Window(), filter=show_bottom_toolbar), 

195 bottom_toolbar, 

196 ] 

197 ), 

198 focused_element=radio_list, 

199 ) 

200 

201 kb = KeyBindings() 

202 

203 @kb.add("enter", eager=True) 

204 def _accept_input(event: E) -> None: 

205 "Accept input when enter has been pressed." 

206 event.app.exit(result=radio_list.current_value, style="class:accepted") 

207 

208 @Condition 

209 def enable_interrupt() -> bool: 

210 return to_filter(self.enable_interrupt)() 

211 

212 @kb.add("c-c", filter=enable_interrupt) 

213 @kb.add("<sigint>", filter=enable_interrupt) 

214 def _keyboard_interrupt(event: E) -> None: 

215 "Abort when Control-C has been pressed." 

216 event.app.exit(exception=self.interrupt_exception(), style="class:aborting") 

217 

218 suspend_supported = Condition(suspend_to_background_supported) 

219 

220 @Condition 

221 def enable_suspend() -> bool: 

222 return to_filter(self.enable_suspend)() 

223 

224 @kb.add("c-z", filter=suspend_supported & enable_suspend) 

225 def _suspend(event: E) -> None: 

226 """ 

227 Suspend process to background. 

228 """ 

229 event.app.suspend_to_background() 

230 

231 return Application( 

232 layout=layout, 

233 full_screen=False, 

234 mouse_support=self.mouse_support, 

235 key_bindings=merge_key_bindings( 

236 [kb, DynamicKeyBindings(lambda: self.key_bindings)] 

237 ), 

238 style=self.style, 

239 ) 

240 

241 def prompt(self) -> _T: 

242 return self._create_application().run() 

243 

244 async def prompt_async(self) -> _T: 

245 return await self._create_application().run_async() 

246 

247 

248def choice( 

249 message: AnyFormattedText, 

250 *, 

251 options: Sequence[tuple[_T, AnyFormattedText]], 

252 default: _T | None = None, 

253 mouse_support: bool = False, 

254 style: BaseStyle | None = None, 

255 symbol: str = ">", 

256 bottom_toolbar: AnyFormattedText = None, 

257 show_frame: bool = False, 

258 enable_suspend: FilterOrBool = False, 

259 enable_interrupt: FilterOrBool = True, 

260 interrupt_exception: type[BaseException] = KeyboardInterrupt, 

261 key_bindings: KeyBindingsBase | None = None, 

262) -> _T: 

263 """ 

264 Choice selection prompt. Ask the user to choose among a set of options. 

265 

266 Example usage:: 

267 

268 result = choice( 

269 message="Please select a dish:", 

270 options=[ 

271 ("pizza", "Pizza with mushrooms"), 

272 ("salad", "Salad with tomatoes"), 

273 ("sushi", "Sushi"), 

274 ], 

275 default="pizza", 

276 ) 

277 

278 :param message: Plain text or formatted text to be shown before the options. 

279 :param options: Sequence of ``(value, label)`` tuples. The labels can be 

280 formatted text. 

281 :param default: Default value. If none is given, the first option is 

282 considered the default. 

283 :param mouse_support: Enable mouse support. 

284 :param style: :class:`.Style` instance for the color scheme. 

285 :param symbol: Symbol to be displayed in front of the selected choice. 

286 :param bottom_toolbar: Formatted text or callable that returns formatted 

287 text to be displayed at the bottom of the screen. 

288 :param show_frame: `bool` or 

289 :class:`~prompt_toolkit.filters.Filter`. When True, surround the input 

290 with a frame. 

291 :param enable_interrupt: `bool` or 

292 :class:`~prompt_toolkit.filters.Filter`. When True, raise 

293 the ``interrupt_exception`` (``KeyboardInterrupt`` by default) when 

294 control-c has been pressed. 

295 :param interrupt_exception: The exception type that will be raised when 

296 there is a keyboard interrupt (control-c keypress). 

297 """ 

298 return ChoiceInput[_T]( 

299 message=message, 

300 options=options, 

301 default=default, 

302 mouse_support=mouse_support, 

303 style=style, 

304 symbol=symbol, 

305 bottom_toolbar=bottom_toolbar, 

306 show_frame=show_frame, 

307 enable_suspend=enable_suspend, 

308 enable_interrupt=enable_interrupt, 

309 interrupt_exception=interrupt_exception, 

310 key_bindings=key_bindings, 

311 ).prompt()