Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/widgets/toolbars.py: 26%

155 statements  

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

1from __future__ import annotations 

2 

3from typing import Any 

4 

5from prompt_toolkit.application.current import get_app 

6from prompt_toolkit.buffer import Buffer 

7from prompt_toolkit.enums import SYSTEM_BUFFER 

8from prompt_toolkit.filters import ( 

9 Condition, 

10 FilterOrBool, 

11 emacs_mode, 

12 has_arg, 

13 has_completions, 

14 has_focus, 

15 has_validation_error, 

16 to_filter, 

17 vi_mode, 

18 vi_navigation_mode, 

19) 

20from prompt_toolkit.formatted_text import ( 

21 AnyFormattedText, 

22 StyleAndTextTuples, 

23 fragment_list_len, 

24 to_formatted_text, 

25) 

26from prompt_toolkit.key_binding.key_bindings import ( 

27 ConditionalKeyBindings, 

28 KeyBindings, 

29 KeyBindingsBase, 

30 merge_key_bindings, 

31) 

32from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

33from prompt_toolkit.key_binding.vi_state import InputMode 

34from prompt_toolkit.keys import Keys 

35from prompt_toolkit.layout.containers import ConditionalContainer, Container, Window 

36from prompt_toolkit.layout.controls import ( 

37 BufferControl, 

38 FormattedTextControl, 

39 SearchBufferControl, 

40 UIContent, 

41 UIControl, 

42) 

43from prompt_toolkit.layout.dimension import Dimension 

44from prompt_toolkit.layout.processors import BeforeInput 

45from prompt_toolkit.lexers import SimpleLexer 

46from prompt_toolkit.search import SearchDirection 

47 

48__all__ = [ 

49 "ArgToolbar", 

50 "CompletionsToolbar", 

51 "FormattedTextToolbar", 

52 "SearchToolbar", 

53 "SystemToolbar", 

54 "ValidationToolbar", 

55] 

56 

57E = KeyPressEvent 

58 

59 

60class FormattedTextToolbar(Window): 

61 def __init__(self, text: AnyFormattedText, style: str = "", **kw: Any) -> None: 

62 # Note: The style needs to be applied to the toolbar as a whole, not 

63 # just the `FormattedTextControl`. 

64 super().__init__( 

65 FormattedTextControl(text, **kw), 

66 style=style, 

67 dont_extend_height=True, 

68 height=Dimension(min=1), 

69 ) 

70 

71 

72class SystemToolbar: 

73 """ 

74 Toolbar for a system prompt. 

75 

76 :param prompt: Prompt to be displayed to the user. 

77 """ 

78 

79 def __init__( 

80 self, 

81 prompt: AnyFormattedText = "Shell command: ", 

82 enable_global_bindings: FilterOrBool = True, 

83 ) -> None: 

84 self.prompt = prompt 

85 self.enable_global_bindings = to_filter(enable_global_bindings) 

86 

87 self.system_buffer = Buffer(name=SYSTEM_BUFFER) 

88 

89 self._bindings = self._build_key_bindings() 

90 

91 self.buffer_control = BufferControl( 

92 buffer=self.system_buffer, 

93 lexer=SimpleLexer(style="class:system-toolbar.text"), 

94 input_processors=[ 

95 BeforeInput(lambda: self.prompt, style="class:system-toolbar") 

96 ], 

97 key_bindings=self._bindings, 

98 ) 

99 

100 self.window = Window( 

101 self.buffer_control, height=1, style="class:system-toolbar" 

102 ) 

103 

104 self.container = ConditionalContainer( 

105 content=self.window, filter=has_focus(self.system_buffer) 

106 ) 

107 

108 def _get_display_before_text(self) -> StyleAndTextTuples: 

109 return [ 

110 ("class:system-toolbar", "Shell command: "), 

111 ("class:system-toolbar.text", self.system_buffer.text), 

112 ("", "\n"), 

113 ] 

114 

115 def _build_key_bindings(self) -> KeyBindingsBase: 

116 focused = has_focus(self.system_buffer) 

117 

118 # Emacs 

119 emacs_bindings = KeyBindings() 

120 handle = emacs_bindings.add 

121 

122 @handle("escape", filter=focused) 

123 @handle("c-g", filter=focused) 

124 @handle("c-c", filter=focused) 

125 def _cancel(event: E) -> None: 

126 "Hide system prompt." 

127 self.system_buffer.reset() 

128 event.app.layout.focus_last() 

129 

130 @handle("enter", filter=focused) 

131 async def _accept(event: E) -> None: 

132 "Run system command." 

133 await event.app.run_system_command( 

134 self.system_buffer.text, 

135 display_before_text=self._get_display_before_text(), 

136 ) 

137 self.system_buffer.reset(append_to_history=True) 

138 event.app.layout.focus_last() 

139 

140 # Vi. 

141 vi_bindings = KeyBindings() 

142 handle = vi_bindings.add 

143 

144 @handle("escape", filter=focused) 

145 @handle("c-c", filter=focused) 

146 def _cancel_vi(event: E) -> None: 

147 "Hide system prompt." 

148 event.app.vi_state.input_mode = InputMode.NAVIGATION 

149 self.system_buffer.reset() 

150 event.app.layout.focus_last() 

151 

152 @handle("enter", filter=focused) 

153 async def _accept_vi(event: E) -> None: 

154 "Run system command." 

155 event.app.vi_state.input_mode = InputMode.NAVIGATION 

156 await event.app.run_system_command( 

157 self.system_buffer.text, 

158 display_before_text=self._get_display_before_text(), 

159 ) 

160 self.system_buffer.reset(append_to_history=True) 

161 event.app.layout.focus_last() 

162 

163 # Global bindings. (Listen to these bindings, even when this widget is 

164 # not focussed.) 

165 global_bindings = KeyBindings() 

166 handle = global_bindings.add 

167 

168 @handle(Keys.Escape, "!", filter=~focused & emacs_mode, is_global=True) 

169 def _focus_me(event: E) -> None: 

170 "M-'!' will focus this user control." 

171 event.app.layout.focus(self.window) 

172 

173 @handle("!", filter=~focused & vi_mode & vi_navigation_mode, is_global=True) 

174 def _focus_me_vi(event: E) -> None: 

175 "Focus." 

176 event.app.vi_state.input_mode = InputMode.INSERT 

177 event.app.layout.focus(self.window) 

178 

179 return merge_key_bindings( 

180 [ 

181 ConditionalKeyBindings(emacs_bindings, emacs_mode), 

182 ConditionalKeyBindings(vi_bindings, vi_mode), 

183 ConditionalKeyBindings(global_bindings, self.enable_global_bindings), 

184 ] 

185 ) 

186 

187 def __pt_container__(self) -> Container: 

188 return self.container 

189 

190 

191class ArgToolbar: 

192 def __init__(self) -> None: 

193 def get_formatted_text() -> StyleAndTextTuples: 

194 arg = get_app().key_processor.arg or "" 

195 if arg == "-": 

196 arg = "-1" 

197 

198 return [ 

199 ("class:arg-toolbar", "Repeat: "), 

200 ("class:arg-toolbar.text", arg), 

201 ] 

202 

203 self.window = Window(FormattedTextControl(get_formatted_text), height=1) 

204 

205 self.container = ConditionalContainer(content=self.window, filter=has_arg) 

206 

207 def __pt_container__(self) -> Container: 

208 return self.container 

209 

210 

211class SearchToolbar: 

212 """ 

213 :param vi_mode: Display '/' and '?' instead of I-search. 

214 :param ignore_case: Search case insensitive. 

215 """ 

216 

217 def __init__( 

218 self, 

219 search_buffer: Buffer | None = None, 

220 vi_mode: bool = False, 

221 text_if_not_searching: AnyFormattedText = "", 

222 forward_search_prompt: AnyFormattedText = "I-search: ", 

223 backward_search_prompt: AnyFormattedText = "I-search backward: ", 

224 ignore_case: FilterOrBool = False, 

225 ) -> None: 

226 if search_buffer is None: 

227 search_buffer = Buffer() 

228 

229 @Condition 

230 def is_searching() -> bool: 

231 return self.control in get_app().layout.search_links 

232 

233 def get_before_input() -> AnyFormattedText: 

234 if not is_searching(): 

235 return text_if_not_searching 

236 elif ( 

237 self.control.searcher_search_state.direction == SearchDirection.BACKWARD 

238 ): 

239 return "?" if vi_mode else backward_search_prompt 

240 else: 

241 return "/" if vi_mode else forward_search_prompt 

242 

243 self.search_buffer = search_buffer 

244 

245 self.control = SearchBufferControl( 

246 buffer=search_buffer, 

247 input_processors=[ 

248 BeforeInput(get_before_input, style="class:search-toolbar.prompt") 

249 ], 

250 lexer=SimpleLexer(style="class:search-toolbar.text"), 

251 ignore_case=ignore_case, 

252 ) 

253 

254 self.container = ConditionalContainer( 

255 content=Window(self.control, height=1, style="class:search-toolbar"), 

256 filter=is_searching, 

257 ) 

258 

259 def __pt_container__(self) -> Container: 

260 return self.container 

261 

262 

263class _CompletionsToolbarControl(UIControl): 

264 def create_content(self, width: int, height: int) -> UIContent: 

265 all_fragments: StyleAndTextTuples = [] 

266 

267 complete_state = get_app().current_buffer.complete_state 

268 if complete_state: 

269 completions = complete_state.completions 

270 index = complete_state.complete_index # Can be None! 

271 

272 # Width of the completions without the left/right arrows in the margins. 

273 content_width = width - 6 

274 

275 # Booleans indicating whether we stripped from the left/right 

276 cut_left = False 

277 cut_right = False 

278 

279 # Create Menu content. 

280 fragments: StyleAndTextTuples = [] 

281 

282 for i, c in enumerate(completions): 

283 # When there is no more place for the next completion 

284 if fragment_list_len(fragments) + len(c.display_text) >= content_width: 

285 # If the current one was not yet displayed, page to the next sequence. 

286 if i <= (index or 0): 

287 fragments = [] 

288 cut_left = True 

289 # If the current one is visible, stop here. 

290 else: 

291 cut_right = True 

292 break 

293 

294 fragments.extend( 

295 to_formatted_text( 

296 c.display_text, 

297 style=( 

298 "class:completion-toolbar.completion.current" 

299 if i == index 

300 else "class:completion-toolbar.completion" 

301 ), 

302 ) 

303 ) 

304 fragments.append(("", " ")) 

305 

306 # Extend/strip until the content width. 

307 fragments.append(("", " " * (content_width - fragment_list_len(fragments)))) 

308 fragments = fragments[:content_width] 

309 

310 # Return fragments 

311 all_fragments.append(("", " ")) 

312 all_fragments.append( 

313 ("class:completion-toolbar.arrow", "<" if cut_left else " ") 

314 ) 

315 all_fragments.append(("", " ")) 

316 

317 all_fragments.extend(fragments) 

318 

319 all_fragments.append(("", " ")) 

320 all_fragments.append( 

321 ("class:completion-toolbar.arrow", ">" if cut_right else " ") 

322 ) 

323 all_fragments.append(("", " ")) 

324 

325 def get_line(i: int) -> StyleAndTextTuples: 

326 return all_fragments 

327 

328 return UIContent(get_line=get_line, line_count=1) 

329 

330 

331class CompletionsToolbar: 

332 def __init__(self) -> None: 

333 self.container = ConditionalContainer( 

334 content=Window( 

335 _CompletionsToolbarControl(), height=1, style="class:completion-toolbar" 

336 ), 

337 filter=has_completions, 

338 ) 

339 

340 def __pt_container__(self) -> Container: 

341 return self.container 

342 

343 

344class ValidationToolbar: 

345 def __init__(self, show_position: bool = False) -> None: 

346 def get_formatted_text() -> StyleAndTextTuples: 

347 buff = get_app().current_buffer 

348 

349 if buff.validation_error: 

350 row, column = buff.document.translate_index_to_position( 

351 buff.validation_error.cursor_position 

352 ) 

353 

354 if show_position: 

355 text = "{} (line={} column={})".format( 

356 buff.validation_error.message, 

357 row + 1, 

358 column + 1, 

359 ) 

360 else: 

361 text = buff.validation_error.message 

362 

363 return [("class:validation-toolbar", text)] 

364 else: 

365 return [] 

366 

367 self.control = FormattedTextControl(get_formatted_text) 

368 

369 self.container = ConditionalContainer( 

370 content=Window(self.control, height=1), filter=has_validation_error 

371 ) 

372 

373 def __pt_container__(self) -> Container: 

374 return self.container