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

91 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1from __future__ import annotations 

2 

3import functools 

4from asyncio import get_running_loop 

5from typing import Any, Callable, Sequence, TypeVar 

6 

7from prompt_toolkit.application import Application 

8from prompt_toolkit.application.current import get_app 

9from prompt_toolkit.buffer import Buffer 

10from prompt_toolkit.completion import Completer 

11from prompt_toolkit.eventloop import run_in_executor_with_context 

12from prompt_toolkit.filters import FilterOrBool 

13from prompt_toolkit.formatted_text import AnyFormattedText 

14from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous 

15from prompt_toolkit.key_binding.defaults import load_key_bindings 

16from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings 

17from prompt_toolkit.layout import Layout 

18from prompt_toolkit.layout.containers import AnyContainer, HSplit 

19from prompt_toolkit.layout.dimension import Dimension as D 

20from prompt_toolkit.styles import BaseStyle 

21from prompt_toolkit.validation import Validator 

22from prompt_toolkit.widgets import ( 

23 Box, 

24 Button, 

25 CheckboxList, 

26 Dialog, 

27 Label, 

28 ProgressBar, 

29 RadioList, 

30 TextArea, 

31 ValidationToolbar, 

32) 

33 

34__all__ = [ 

35 "yes_no_dialog", 

36 "button_dialog", 

37 "input_dialog", 

38 "message_dialog", 

39 "radiolist_dialog", 

40 "checkboxlist_dialog", 

41 "progress_dialog", 

42] 

43 

44 

45def yes_no_dialog( 

46 title: AnyFormattedText = "", 

47 text: AnyFormattedText = "", 

48 yes_text: str = "Yes", 

49 no_text: str = "No", 

50 style: BaseStyle | None = None, 

51) -> Application[bool]: 

52 """ 

53 Display a Yes/No dialog. 

54 Return a boolean. 

55 """ 

56 

57 def yes_handler() -> None: 

58 get_app().exit(result=True) 

59 

60 def no_handler() -> None: 

61 get_app().exit(result=False) 

62 

63 dialog = Dialog( 

64 title=title, 

65 body=Label(text=text, dont_extend_height=True), 

66 buttons=[ 

67 Button(text=yes_text, handler=yes_handler), 

68 Button(text=no_text, handler=no_handler), 

69 ], 

70 with_background=True, 

71 ) 

72 

73 return _create_app(dialog, style) 

74 

75 

76_T = TypeVar("_T") 

77 

78 

79def button_dialog( 

80 title: AnyFormattedText = "", 

81 text: AnyFormattedText = "", 

82 buttons: list[tuple[str, _T]] = [], 

83 style: BaseStyle | None = None, 

84) -> Application[_T]: 

85 """ 

86 Display a dialog with button choices (given as a list of tuples). 

87 Return the value associated with button. 

88 """ 

89 

90 def button_handler(v: _T) -> None: 

91 get_app().exit(result=v) 

92 

93 dialog = Dialog( 

94 title=title, 

95 body=Label(text=text, dont_extend_height=True), 

96 buttons=[ 

97 Button(text=t, handler=functools.partial(button_handler, v)) 

98 for t, v in buttons 

99 ], 

100 with_background=True, 

101 ) 

102 

103 return _create_app(dialog, style) 

104 

105 

106def input_dialog( 

107 title: AnyFormattedText = "", 

108 text: AnyFormattedText = "", 

109 ok_text: str = "OK", 

110 cancel_text: str = "Cancel", 

111 completer: Completer | None = None, 

112 validator: Validator | None = None, 

113 password: FilterOrBool = False, 

114 style: BaseStyle | None = None, 

115 default: str = "", 

116) -> Application[str]: 

117 """ 

118 Display a text input box. 

119 Return the given text, or None when cancelled. 

120 """ 

121 

122 def accept(buf: Buffer) -> bool: 

123 get_app().layout.focus(ok_button) 

124 return True # Keep text. 

125 

126 def ok_handler() -> None: 

127 get_app().exit(result=textfield.text) 

128 

129 ok_button = Button(text=ok_text, handler=ok_handler) 

130 cancel_button = Button(text=cancel_text, handler=_return_none) 

131 

132 textfield = TextArea( 

133 text=default, 

134 multiline=False, 

135 password=password, 

136 completer=completer, 

137 validator=validator, 

138 accept_handler=accept, 

139 ) 

140 

141 dialog = Dialog( 

142 title=title, 

143 body=HSplit( 

144 [ 

145 Label(text=text, dont_extend_height=True), 

146 textfield, 

147 ValidationToolbar(), 

148 ], 

149 padding=D(preferred=1, max=1), 

150 ), 

151 buttons=[ok_button, cancel_button], 

152 with_background=True, 

153 ) 

154 

155 return _create_app(dialog, style) 

156 

157 

158def message_dialog( 

159 title: AnyFormattedText = "", 

160 text: AnyFormattedText = "", 

161 ok_text: str = "Ok", 

162 style: BaseStyle | None = None, 

163) -> Application[None]: 

164 """ 

165 Display a simple message box and wait until the user presses enter. 

166 """ 

167 dialog = Dialog( 

168 title=title, 

169 body=Label(text=text, dont_extend_height=True), 

170 buttons=[Button(text=ok_text, handler=_return_none)], 

171 with_background=True, 

172 ) 

173 

174 return _create_app(dialog, style) 

175 

176 

177def radiolist_dialog( 

178 title: AnyFormattedText = "", 

179 text: AnyFormattedText = "", 

180 ok_text: str = "Ok", 

181 cancel_text: str = "Cancel", 

182 values: Sequence[tuple[_T, AnyFormattedText]] | None = None, 

183 default: _T | None = None, 

184 style: BaseStyle | None = None, 

185) -> Application[_T]: 

186 """ 

187 Display a simple list of element the user can choose amongst. 

188 

189 Only one element can be selected at a time using Arrow keys and Enter. 

190 The focus can be moved between the list and the Ok/Cancel button with tab. 

191 """ 

192 if values is None: 

193 values = [] 

194 

195 def ok_handler() -> None: 

196 get_app().exit(result=radio_list.current_value) 

197 

198 radio_list = RadioList(values=values, default=default) 

199 

200 dialog = Dialog( 

201 title=title, 

202 body=HSplit( 

203 [Label(text=text, dont_extend_height=True), radio_list], 

204 padding=1, 

205 ), 

206 buttons=[ 

207 Button(text=ok_text, handler=ok_handler), 

208 Button(text=cancel_text, handler=_return_none), 

209 ], 

210 with_background=True, 

211 ) 

212 

213 return _create_app(dialog, style) 

214 

215 

216def checkboxlist_dialog( 

217 title: AnyFormattedText = "", 

218 text: AnyFormattedText = "", 

219 ok_text: str = "Ok", 

220 cancel_text: str = "Cancel", 

221 values: Sequence[tuple[_T, AnyFormattedText]] | None = None, 

222 default_values: Sequence[_T] | None = None, 

223 style: BaseStyle | None = None, 

224) -> Application[list[_T]]: 

225 """ 

226 Display a simple list of element the user can choose multiple values amongst. 

227 

228 Several elements can be selected at a time using Arrow keys and Enter. 

229 The focus can be moved between the list and the Ok/Cancel button with tab. 

230 """ 

231 if values is None: 

232 values = [] 

233 

234 def ok_handler() -> None: 

235 get_app().exit(result=cb_list.current_values) 

236 

237 cb_list = CheckboxList(values=values, default_values=default_values) 

238 

239 dialog = Dialog( 

240 title=title, 

241 body=HSplit( 

242 [Label(text=text, dont_extend_height=True), cb_list], 

243 padding=1, 

244 ), 

245 buttons=[ 

246 Button(text=ok_text, handler=ok_handler), 

247 Button(text=cancel_text, handler=_return_none), 

248 ], 

249 with_background=True, 

250 ) 

251 

252 return _create_app(dialog, style) 

253 

254 

255def progress_dialog( 

256 title: AnyFormattedText = "", 

257 text: AnyFormattedText = "", 

258 run_callback: Callable[[Callable[[int], None], Callable[[str], None]], None] = ( 

259 lambda *a: None 

260 ), 

261 style: BaseStyle | None = None, 

262) -> Application[None]: 

263 """ 

264 :param run_callback: A function that receives as input a `set_percentage` 

265 function and it does the work. 

266 """ 

267 loop = get_running_loop() 

268 progressbar = ProgressBar() 

269 text_area = TextArea( 

270 focusable=False, 

271 # Prefer this text area as big as possible, to avoid having a window 

272 # that keeps resizing when we add text to it. 

273 height=D(preferred=10**10), 

274 ) 

275 

276 dialog = Dialog( 

277 body=HSplit( 

278 [ 

279 Box(Label(text=text)), 

280 Box(text_area, padding=D.exact(1)), 

281 progressbar, 

282 ] 

283 ), 

284 title=title, 

285 with_background=True, 

286 ) 

287 app = _create_app(dialog, style) 

288 

289 def set_percentage(value: int) -> None: 

290 progressbar.percentage = int(value) 

291 app.invalidate() 

292 

293 def log_text(text: str) -> None: 

294 loop.call_soon_threadsafe(text_area.buffer.insert_text, text) 

295 app.invalidate() 

296 

297 # Run the callback in the executor. When done, set a return value for the 

298 # UI, so that it quits. 

299 def start() -> None: 

300 try: 

301 run_callback(set_percentage, log_text) 

302 finally: 

303 app.exit() 

304 

305 def pre_run() -> None: 

306 run_in_executor_with_context(start) 

307 

308 app.pre_run_callables.append(pre_run) 

309 

310 return app 

311 

312 

313def _create_app(dialog: AnyContainer, style: BaseStyle | None) -> Application[Any]: 

314 # Key bindings. 

315 bindings = KeyBindings() 

316 bindings.add("tab")(focus_next) 

317 bindings.add("s-tab")(focus_previous) 

318 

319 return Application( 

320 layout=Layout(dialog), 

321 key_bindings=merge_key_bindings([load_key_bindings(), bindings]), 

322 mouse_support=True, 

323 style=style, 

324 full_screen=True, 

325 ) 

326 

327 

328def _return_none() -> None: 

329 "Button handler that returns None." 

330 get_app().exit()