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

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

71 statements  

1from __future__ import annotations 

2 

3from collections.abc import Sequence 

4from typing import Generic, TypeVar 

5 

6from prompt_toolkit.application import Application 

7from prompt_toolkit.filters import ( 

8 Condition, 

9 FilterOrBool, 

10 is_done, 

11 renderer_height_is_known, 

12 to_filter, 

13) 

14from prompt_toolkit.formatted_text import AnyFormattedText 

15from prompt_toolkit.key_binding.key_bindings import ( 

16 DynamicKeyBindings, 

17 KeyBindings, 

18 KeyBindingsBase, 

19 merge_key_bindings, 

20) 

21from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

22from prompt_toolkit.layout import ( 

23 AnyContainer, 

24 ConditionalContainer, 

25 HSplit, 

26 Layout, 

27 Window, 

28) 

29from prompt_toolkit.layout.controls import FormattedTextControl 

30from prompt_toolkit.layout.dimension import Dimension 

31from prompt_toolkit.styles import BaseStyle, Style 

32from prompt_toolkit.utils import suspend_to_background_supported 

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

34 

35__all__ = [ 

36 "ChoiceInput", 

37 "choice", 

38] 

39 

40_T = TypeVar("_T") 

41E = KeyPressEvent 

42 

43 

44def create_default_choice_input_style() -> BaseStyle: 

45 return Style.from_dict( 

46 { 

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

48 "selected-option": "bold", 

49 } 

50 ) 

51 

52 

53class ChoiceInput(Generic[_T]): 

54 """ 

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

56 

57 Example usage:: 

58 

59 input_selection = ChoiceInput( 

60 message="Please select a dish:", 

61 options=[ 

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

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

64 ("sushi", "Sushi"), 

65 ], 

66 default="pizza", 

67 ) 

68 result = input_selection.prompt() 

69 

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

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

72 formatted text. 

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

74 considered the default. 

75 :param mouse_support: Enable mouse support. 

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

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

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

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

80 :param show_frame: `bool` or 

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

82 with a frame. 

83 :param show_numbers: Whether to show the option numbers in front of each choice. 

84 :param enable_interrupt: `bool` or 

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

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

87 control-c has been pressed. 

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

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

90 """ 

91 

92 def __init__( 

93 self, 

94 *, 

95 message: AnyFormattedText, 

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

97 default: _T | None = None, 

98 mouse_support: bool = False, 

99 style: BaseStyle | None = None, 

100 symbol: str = ">", 

101 bottom_toolbar: AnyFormattedText = None, 

102 show_frame: FilterOrBool = False, 

103 show_numbers: bool = True, 

104 enable_suspend: FilterOrBool = False, 

105 enable_interrupt: FilterOrBool = True, 

106 interrupt_exception: type[BaseException] = KeyboardInterrupt, 

107 key_bindings: KeyBindingsBase | None = None, 

108 ) -> None: 

109 if style is None: 

110 style = create_default_choice_input_style() 

111 

112 self.message = message 

113 self.default = default 

114 self.options = options 

115 self.mouse_support = mouse_support 

116 self.style = style 

117 self.symbol = symbol 

118 self.show_frame = show_frame 

119 self.show_numbers = show_numbers 

120 self.enable_suspend = enable_suspend 

121 self.interrupt_exception = interrupt_exception 

122 self.enable_interrupt = enable_interrupt 

123 self.bottom_toolbar = bottom_toolbar 

124 self.key_bindings = key_bindings 

125 

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

127 radio_list = RadioList( 

128 values=self.options, 

129 default=self.default, 

130 select_on_focus=True, 

131 open_character="", 

132 select_character=self.symbol, 

133 close_character="", 

134 show_cursor=False, 

135 show_numbers=self.show_numbers, 

136 container_style="class:input-selection", 

137 default_style="class:option", 

138 selected_style="", 

139 checked_style="class:selected-option", 

140 number_style="class:number", 

141 show_scrollbar=False, 

142 ) 

143 container: AnyContainer = HSplit( 

144 [ 

145 Box( 

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

147 padding_top=0, 

148 padding_left=1, 

149 padding_right=1, 

150 padding_bottom=0, 

151 ), 

152 Box( 

153 radio_list, 

154 padding_top=0, 

155 padding_left=3, 

156 padding_right=1, 

157 padding_bottom=0, 

158 ), 

159 ] 

160 ) 

161 

162 @Condition 

163 def show_frame_filter() -> bool: 

164 return to_filter(self.show_frame)() 

165 

166 show_bottom_toolbar = ( 

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

168 & ~is_done 

169 & renderer_height_is_known 

170 ) 

171 

172 container = ConditionalContainer( 

173 Frame(container), 

174 alternative_content=container, 

175 filter=show_frame_filter, 

176 ) 

177 

178 bottom_toolbar = ConditionalContainer( 

179 Window( 

180 FormattedTextControl( 

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

182 ), 

183 style="class:bottom-toolbar", 

184 dont_extend_height=True, 

185 height=Dimension(min=1), 

186 ), 

187 filter=show_bottom_toolbar, 

188 ) 

189 

190 layout = Layout( 

191 HSplit( 

192 [ 

193 container, 

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

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

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

197 # bottom of the screen. 

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

199 bottom_toolbar, 

200 ] 

201 ), 

202 focused_element=radio_list, 

203 ) 

204 

205 kb = KeyBindings() 

206 

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

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

209 "Accept input when enter has been pressed." 

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

211 

212 @Condition 

213 def enable_interrupt() -> bool: 

214 return to_filter(self.enable_interrupt)() 

215 

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

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

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

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

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

221 

222 suspend_supported = Condition(suspend_to_background_supported) 

223 

224 @Condition 

225 def enable_suspend() -> bool: 

226 return to_filter(self.enable_suspend)() 

227 

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

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

230 """ 

231 Suspend process to background. 

232 """ 

233 event.app.suspend_to_background() 

234 

235 return Application( 

236 layout=layout, 

237 full_screen=False, 

238 mouse_support=self.mouse_support, 

239 key_bindings=merge_key_bindings( 

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

241 ), 

242 style=self.style, 

243 ) 

244 

245 def prompt(self) -> _T: 

246 return self._create_application().run() 

247 

248 async def prompt_async(self) -> _T: 

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

250 

251 

252def choice( 

253 message: AnyFormattedText, 

254 *, 

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

256 default: _T | None = None, 

257 mouse_support: bool = False, 

258 style: BaseStyle | None = None, 

259 symbol: str = ">", 

260 bottom_toolbar: AnyFormattedText = None, 

261 show_frame: bool = False, 

262 enable_suspend: FilterOrBool = False, 

263 enable_interrupt: FilterOrBool = True, 

264 interrupt_exception: type[BaseException] = KeyboardInterrupt, 

265 key_bindings: KeyBindingsBase | None = None, 

266) -> _T: 

267 """ 

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

269 

270 Example usage:: 

271 

272 result = choice( 

273 message="Please select a dish:", 

274 options=[ 

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

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

277 ("sushi", "Sushi"), 

278 ], 

279 default="pizza", 

280 ) 

281 

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

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

284 formatted text. 

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

286 considered the default. 

287 :param mouse_support: Enable mouse support. 

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

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

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

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

292 :param show_frame: `bool` or 

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

294 with a frame. 

295 :param enable_interrupt: `bool` or 

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

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

298 control-c has been pressed. 

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

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

301 """ 

302 return ChoiceInput[_T]( 

303 message=message, 

304 options=options, 

305 default=default, 

306 mouse_support=mouse_support, 

307 style=style, 

308 symbol=symbol, 

309 bottom_toolbar=bottom_toolbar, 

310 show_frame=show_frame, 

311 enable_suspend=enable_suspend, 

312 enable_interrupt=enable_interrupt, 

313 interrupt_exception=interrupt_exception, 

314 key_bindings=key_bindings, 

315 ).prompt()