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
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1from __future__ import annotations
3from typing import Any
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
48__all__ = [
49 "ArgToolbar",
50 "CompletionsToolbar",
51 "FormattedTextToolbar",
52 "SearchToolbar",
53 "SystemToolbar",
54 "ValidationToolbar",
55]
57E = KeyPressEvent
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 )
72class SystemToolbar:
73 """
74 Toolbar for a system prompt.
76 :param prompt: Prompt to be displayed to the user.
77 """
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)
87 self.system_buffer = Buffer(name=SYSTEM_BUFFER)
89 self._bindings = self._build_key_bindings()
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 )
100 self.window = Window(
101 self.buffer_control, height=1, style="class:system-toolbar"
102 )
104 self.container = ConditionalContainer(
105 content=self.window, filter=has_focus(self.system_buffer)
106 )
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 ]
115 def _build_key_bindings(self) -> KeyBindingsBase:
116 focused = has_focus(self.system_buffer)
118 # Emacs
119 emacs_bindings = KeyBindings()
120 handle = emacs_bindings.add
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()
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()
140 # Vi.
141 vi_bindings = KeyBindings()
142 handle = vi_bindings.add
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()
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()
163 # Global bindings. (Listen to these bindings, even when this widget is
164 # not focussed.)
165 global_bindings = KeyBindings()
166 handle = global_bindings.add
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)
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)
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 )
187 def __pt_container__(self) -> Container:
188 return self.container
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"
198 return [
199 ("class:arg-toolbar", "Repeat: "),
200 ("class:arg-toolbar.text", arg),
201 ]
203 self.window = Window(FormattedTextControl(get_formatted_text), height=1)
205 self.container = ConditionalContainer(content=self.window, filter=has_arg)
207 def __pt_container__(self) -> Container:
208 return self.container
211class SearchToolbar:
212 """
213 :param vi_mode: Display '/' and '?' instead of I-search.
214 :param ignore_case: Search case insensitive.
215 """
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()
229 @Condition
230 def is_searching() -> bool:
231 return self.control in get_app().layout.search_links
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
243 self.search_buffer = search_buffer
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 )
254 self.container = ConditionalContainer(
255 content=Window(self.control, height=1, style="class:search-toolbar"),
256 filter=is_searching,
257 )
259 def __pt_container__(self) -> Container:
260 return self.container
263class _CompletionsToolbarControl(UIControl):
264 def create_content(self, width: int, height: int) -> UIContent:
265 all_fragments: StyleAndTextTuples = []
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!
272 # Width of the completions without the left/right arrows in the margins.
273 content_width = width - 6
275 # Booleans indicating whether we stripped from the left/right
276 cut_left = False
277 cut_right = False
279 # Create Menu content.
280 fragments: StyleAndTextTuples = []
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
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(("", " "))
306 # Extend/strip until the content width.
307 fragments.append(("", " " * (content_width - fragment_list_len(fragments))))
308 fragments = fragments[:content_width]
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(("", " "))
317 all_fragments.extend(fragments)
319 all_fragments.append(("", " "))
320 all_fragments.append(
321 ("class:completion-toolbar.arrow", ">" if cut_right else " ")
322 )
323 all_fragments.append(("", " "))
325 def get_line(i: int) -> StyleAndTextTuples:
326 return all_fragments
328 return UIContent(get_line=get_line, line_count=1)
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 )
340 def __pt_container__(self) -> Container:
341 return self.container
344class ValidationToolbar:
345 def __init__(self, show_position: bool = False) -> None:
346 def get_formatted_text() -> StyleAndTextTuples:
347 buff = get_app().current_buffer
349 if buff.validation_error:
350 row, column = buff.document.translate_index_to_position(
351 buff.validation_error.cursor_position
352 )
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
363 return [("class:validation-toolbar", text)]
364 else:
365 return []
367 self.control = FormattedTextControl(get_formatted_text)
369 self.container = ConditionalContainer(
370 content=Window(self.control, height=1), filter=has_validation_error
371 )
373 def __pt_container__(self) -> Container:
374 return self.container