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

70 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 enable_interrupt: `bool` or 

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

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

86 control-c has been pressed. 

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

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

89 """ 

90 

91 def __init__( 

92 self, 

93 *, 

94 message: AnyFormattedText, 

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

96 default: _T | None = None, 

97 mouse_support: bool = False, 

98 style: BaseStyle | None = None, 

99 symbol: str = ">", 

100 bottom_toolbar: AnyFormattedText = None, 

101 show_frame: FilterOrBool = False, 

102 enable_suspend: FilterOrBool = False, 

103 enable_interrupt: FilterOrBool = True, 

104 interrupt_exception: type[BaseException] = KeyboardInterrupt, 

105 key_bindings: KeyBindingsBase | None = None, 

106 ) -> None: 

107 if style is None: 

108 style = create_default_choice_input_style() 

109 

110 self.message = message 

111 self.default = default 

112 self.options = options 

113 self.mouse_support = mouse_support 

114 self.style = style 

115 self.symbol = symbol 

116 self.show_frame = show_frame 

117 self.enable_suspend = enable_suspend 

118 self.interrupt_exception = interrupt_exception 

119 self.enable_interrupt = enable_interrupt 

120 self.bottom_toolbar = bottom_toolbar 

121 self.key_bindings = key_bindings 

122 

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

124 radio_list = RadioList( 

125 values=self.options, 

126 default=self.default, 

127 select_on_focus=True, 

128 open_character="", 

129 select_character=self.symbol, 

130 close_character="", 

131 show_cursor=False, 

132 show_numbers=True, 

133 container_style="class:input-selection", 

134 default_style="class:option", 

135 selected_style="", 

136 checked_style="class:selected-option", 

137 number_style="class:number", 

138 show_scrollbar=False, 

139 ) 

140 container: AnyContainer = HSplit( 

141 [ 

142 Box( 

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

144 padding_top=0, 

145 padding_left=1, 

146 padding_right=1, 

147 padding_bottom=0, 

148 ), 

149 Box( 

150 radio_list, 

151 padding_top=0, 

152 padding_left=3, 

153 padding_right=1, 

154 padding_bottom=0, 

155 ), 

156 ] 

157 ) 

158 

159 @Condition 

160 def show_frame_filter() -> bool: 

161 return to_filter(self.show_frame)() 

162 

163 show_bottom_toolbar = ( 

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

165 & ~is_done 

166 & renderer_height_is_known 

167 ) 

168 

169 container = ConditionalContainer( 

170 Frame(container), 

171 alternative_content=container, 

172 filter=show_frame_filter, 

173 ) 

174 

175 bottom_toolbar = ConditionalContainer( 

176 Window( 

177 FormattedTextControl( 

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

179 ), 

180 style="class:bottom-toolbar", 

181 dont_extend_height=True, 

182 height=Dimension(min=1), 

183 ), 

184 filter=show_bottom_toolbar, 

185 ) 

186 

187 layout = Layout( 

188 HSplit( 

189 [ 

190 container, 

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

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

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

194 # bottom of the screen. 

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

196 bottom_toolbar, 

197 ] 

198 ), 

199 focused_element=radio_list, 

200 ) 

201 

202 kb = KeyBindings() 

203 

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

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

206 "Accept input when enter has been pressed." 

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

208 

209 @Condition 

210 def enable_interrupt() -> bool: 

211 return to_filter(self.enable_interrupt)() 

212 

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

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

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

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

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

218 

219 suspend_supported = Condition(suspend_to_background_supported) 

220 

221 @Condition 

222 def enable_suspend() -> bool: 

223 return to_filter(self.enable_suspend)() 

224 

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

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

227 """ 

228 Suspend process to background. 

229 """ 

230 event.app.suspend_to_background() 

231 

232 return Application( 

233 layout=layout, 

234 full_screen=False, 

235 mouse_support=self.mouse_support, 

236 key_bindings=merge_key_bindings( 

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

238 ), 

239 style=self.style, 

240 ) 

241 

242 def prompt(self) -> _T: 

243 return self._create_application().run() 

244 

245 async def prompt_async(self) -> _T: 

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

247 

248 

249def choice( 

250 message: AnyFormattedText, 

251 *, 

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

253 default: _T | None = None, 

254 mouse_support: bool = False, 

255 style: BaseStyle | None = None, 

256 symbol: str = ">", 

257 bottom_toolbar: AnyFormattedText = None, 

258 show_frame: bool = False, 

259 enable_suspend: FilterOrBool = False, 

260 enable_interrupt: FilterOrBool = True, 

261 interrupt_exception: type[BaseException] = KeyboardInterrupt, 

262 key_bindings: KeyBindingsBase | None = None, 

263) -> _T: 

264 """ 

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

266 

267 Example usage:: 

268 

269 result = choice( 

270 message="Please select a dish:", 

271 options=[ 

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

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

274 ("sushi", "Sushi"), 

275 ], 

276 default="pizza", 

277 ) 

278 

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

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

281 formatted text. 

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

283 considered the default. 

284 :param mouse_support: Enable mouse support. 

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

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

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

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

289 :param show_frame: `bool` or 

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

291 with a frame. 

292 :param enable_interrupt: `bool` or 

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

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

295 control-c has been pressed. 

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

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

298 """ 

299 return ChoiceInput[_T]( 

300 message=message, 

301 options=options, 

302 default=default, 

303 mouse_support=mouse_support, 

304 style=style, 

305 symbol=symbol, 

306 bottom_toolbar=bottom_toolbar, 

307 show_frame=show_frame, 

308 enable_suspend=enable_suspend, 

309 enable_interrupt=enable_interrupt, 

310 interrupt_exception=interrupt_exception, 

311 key_bindings=key_bindings, 

312 ).prompt()