1"""
2Line editing functionality.
3---------------------------
4
5This provides a UI for a line input, similar to GNU Readline, libedit and
6linenoise.
7
8Either call the `prompt` function for every line input. Or create an instance
9of the :class:`.PromptSession` class and call the `prompt` method from that
10class. In the second case, we'll have a 'session' that keeps all the state like
11the history in between several calls.
12
13There is a lot of overlap between the arguments taken by the `prompt` function
14and the `PromptSession` (like `completer`, `style`, etcetera). There we have
15the freedom to decide which settings we want for the whole 'session', and which
16we want for an individual `prompt`.
17
18Example::
19
20 # Simple `prompt` call.
21 result = prompt('Say something: ')
22
23 # Using a 'session'.
24 s = PromptSession()
25 result = s.prompt('Say something: ')
26"""
27
28from __future__ import annotations
29
30from asyncio import get_running_loop
31from contextlib import contextmanager
32from enum import Enum
33from functools import partial
34from typing import TYPE_CHECKING, Callable, Generic, Iterator, TypeVar, Union, cast
35
36from prompt_toolkit.application import Application
37from prompt_toolkit.application.current import get_app
38from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest
39from prompt_toolkit.buffer import Buffer
40from prompt_toolkit.clipboard import Clipboard, DynamicClipboard, InMemoryClipboard
41from prompt_toolkit.completion import Completer, DynamicCompleter, ThreadedCompleter
42from prompt_toolkit.cursor_shapes import (
43 AnyCursorShapeConfig,
44 CursorShapeConfig,
45 DynamicCursorShapeConfig,
46)
47from prompt_toolkit.document import Document
48from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
49from prompt_toolkit.eventloop import InputHook
50from prompt_toolkit.filters import (
51 Condition,
52 FilterOrBool,
53 has_arg,
54 has_focus,
55 is_done,
56 is_true,
57 renderer_height_is_known,
58 to_filter,
59)
60from prompt_toolkit.formatted_text import (
61 AnyFormattedText,
62 StyleAndTextTuples,
63 fragment_list_to_text,
64 merge_formatted_text,
65 to_formatted_text,
66)
67from prompt_toolkit.history import History, InMemoryHistory
68from prompt_toolkit.input.base import Input
69from prompt_toolkit.key_binding.bindings.auto_suggest import load_auto_suggest_bindings
70from prompt_toolkit.key_binding.bindings.completion import (
71 display_completions_like_readline,
72)
73from prompt_toolkit.key_binding.bindings.open_in_editor import (
74 load_open_in_editor_bindings,
75)
76from prompt_toolkit.key_binding.key_bindings import (
77 ConditionalKeyBindings,
78 DynamicKeyBindings,
79 KeyBindings,
80 KeyBindingsBase,
81 merge_key_bindings,
82)
83from prompt_toolkit.key_binding.key_processor import KeyPressEvent
84from prompt_toolkit.keys import Keys
85from prompt_toolkit.layout import Float, FloatContainer, HSplit, Window
86from prompt_toolkit.layout.containers import ConditionalContainer, WindowAlign
87from prompt_toolkit.layout.controls import (
88 BufferControl,
89 FormattedTextControl,
90 SearchBufferControl,
91)
92from prompt_toolkit.layout.dimension import Dimension
93from prompt_toolkit.layout.layout import Layout
94from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu
95from prompt_toolkit.layout.processors import (
96 AfterInput,
97 AppendAutoSuggestion,
98 ConditionalProcessor,
99 DisplayMultipleCursors,
100 DynamicProcessor,
101 HighlightIncrementalSearchProcessor,
102 HighlightSelectionProcessor,
103 PasswordProcessor,
104 Processor,
105 ReverseSearchProcessor,
106 merge_processors,
107)
108from prompt_toolkit.layout.utils import explode_text_fragments
109from prompt_toolkit.lexers import DynamicLexer, Lexer
110from prompt_toolkit.output import ColorDepth, DummyOutput, Output
111from prompt_toolkit.styles import (
112 BaseStyle,
113 ConditionalStyleTransformation,
114 DynamicStyle,
115 DynamicStyleTransformation,
116 StyleTransformation,
117 SwapLightAndDarkStyleTransformation,
118 merge_style_transformations,
119)
120from prompt_toolkit.utils import (
121 get_cwidth,
122 is_dumb_terminal,
123 suspend_to_background_supported,
124 to_str,
125)
126from prompt_toolkit.validation import DynamicValidator, Validator
127from prompt_toolkit.widgets.toolbars import (
128 SearchToolbar,
129 SystemToolbar,
130 ValidationToolbar,
131)
132
133if TYPE_CHECKING:
134 from prompt_toolkit.formatted_text.base import MagicFormattedText
135
136__all__ = [
137 "PromptSession",
138 "prompt",
139 "confirm",
140 "create_confirm_session", # Used by '_display_completions_like_readline'.
141 "CompleteStyle",
142]
143
144_StyleAndTextTuplesCallable = Callable[[], StyleAndTextTuples]
145E = KeyPressEvent
146
147
148def _split_multiline_prompt(
149 get_prompt_text: _StyleAndTextTuplesCallable,
150) -> tuple[
151 Callable[[], bool], _StyleAndTextTuplesCallable, _StyleAndTextTuplesCallable
152]:
153 """
154 Take a `get_prompt_text` function and return three new functions instead.
155 One that tells whether this prompt consists of multiple lines; one that
156 returns the fragments to be shown on the lines above the input; and another
157 one with the fragments to be shown at the first line of the input.
158 """
159
160 def has_before_fragments() -> bool:
161 for fragment, char, *_ in get_prompt_text():
162 if "\n" in char:
163 return True
164 return False
165
166 def before() -> StyleAndTextTuples:
167 result: StyleAndTextTuples = []
168 found_nl = False
169 for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())):
170 if found_nl:
171 result.insert(0, (fragment, char))
172 elif char == "\n":
173 found_nl = True
174 return result
175
176 def first_input_line() -> StyleAndTextTuples:
177 result: StyleAndTextTuples = []
178 for fragment, char, *_ in reversed(explode_text_fragments(get_prompt_text())):
179 if char == "\n":
180 break
181 else:
182 result.insert(0, (fragment, char))
183 return result
184
185 return has_before_fragments, before, first_input_line
186
187
188class _RPrompt(Window):
189 """
190 The prompt that is displayed on the right side of the Window.
191 """
192
193 def __init__(self, text: AnyFormattedText) -> None:
194 super().__init__(
195 FormattedTextControl(text=text),
196 align=WindowAlign.RIGHT,
197 style="class:rprompt",
198 )
199
200
201class CompleteStyle(str, Enum):
202 """
203 How to display autocompletions for the prompt.
204 """
205
206 value: str
207
208 COLUMN = "COLUMN"
209 MULTI_COLUMN = "MULTI_COLUMN"
210 READLINE_LIKE = "READLINE_LIKE"
211
212
213# Formatted text for the continuation prompt. It's the same like other
214# formatted text, except that if it's a callable, it takes three arguments.
215PromptContinuationText = Union[
216 str,
217 "MagicFormattedText",
218 StyleAndTextTuples,
219 # (prompt_width, line_number, wrap_count) -> AnyFormattedText.
220 Callable[[int, int, int], AnyFormattedText],
221]
222
223_T = TypeVar("_T")
224
225
226class PromptSession(Generic[_T]):
227 """
228 PromptSession for a prompt application, which can be used as a GNU Readline
229 replacement.
230
231 This is a wrapper around a lot of ``prompt_toolkit`` functionality and can
232 be a replacement for `raw_input`.
233
234 All parameters that expect "formatted text" can take either just plain text
235 (a unicode object), a list of ``(style_str, text)`` tuples or an HTML object.
236
237 Example usage::
238
239 s = PromptSession(message='>')
240 text = s.prompt()
241
242 :param message: Plain text or formatted text to be shown before the prompt.
243 This can also be a callable that returns formatted text.
244 :param multiline: `bool` or :class:`~prompt_toolkit.filters.Filter`.
245 When True, prefer a layout that is more adapted for multiline input.
246 Text after newlines is automatically indented, and search/arg input is
247 shown below the input, instead of replacing the prompt.
248 :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.Filter`.
249 When True (the default), automatically wrap long lines instead of
250 scrolling horizontally.
251 :param is_password: Show asterisks instead of the actual typed characters.
252 :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``.
253 :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``.
254 :param complete_while_typing: `bool` or
255 :class:`~prompt_toolkit.filters.Filter`. Enable autocompletion while
256 typing.
257 :param validate_while_typing: `bool` or
258 :class:`~prompt_toolkit.filters.Filter`. Enable input validation while
259 typing.
260 :param enable_history_search: `bool` or
261 :class:`~prompt_toolkit.filters.Filter`. Enable up-arrow parting
262 string matching.
263 :param search_ignore_case:
264 :class:`~prompt_toolkit.filters.Filter`. Search case insensitive.
265 :param lexer: :class:`~prompt_toolkit.lexers.Lexer` to be used for the
266 syntax highlighting.
267 :param validator: :class:`~prompt_toolkit.validation.Validator` instance
268 for input validation.
269 :param completer: :class:`~prompt_toolkit.completion.Completer` instance
270 for input completion.
271 :param complete_in_thread: `bool` or
272 :class:`~prompt_toolkit.filters.Filter`. Run the completer code in a
273 background thread in order to avoid blocking the user interface.
274 For ``CompleteStyle.READLINE_LIKE``, this setting has no effect. There
275 we always run the completions in the main thread.
276 :param reserve_space_for_menu: Space to be reserved for displaying the menu.
277 (0 means that no space needs to be reserved.)
278 :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
279 instance for input suggestions.
280 :param style: :class:`.Style` instance for the color scheme.
281 :param include_default_pygments_style: `bool` or
282 :class:`~prompt_toolkit.filters.Filter`. Tell whether the default
283 styling for Pygments lexers has to be included. By default, this is
284 true, but it is recommended to be disabled if another Pygments style is
285 passed as the `style` argument, otherwise, two Pygments styles will be
286 merged.
287 :param style_transformation:
288 :class:`~prompt_toolkit.style.StyleTransformation` instance.
289 :param swap_light_and_dark_colors: `bool` or
290 :class:`~prompt_toolkit.filters.Filter`. When enabled, apply
291 :class:`~prompt_toolkit.style.SwapLightAndDarkStyleTransformation`.
292 This is useful for switching between dark and light terminal
293 backgrounds.
294 :param enable_system_prompt: `bool` or
295 :class:`~prompt_toolkit.filters.Filter`. Pressing Meta+'!' will show
296 a system prompt.
297 :param enable_suspend: `bool` or :class:`~prompt_toolkit.filters.Filter`.
298 Enable Control-Z style suspension.
299 :param enable_open_in_editor: `bool` or
300 :class:`~prompt_toolkit.filters.Filter`. Pressing 'v' in Vi mode or
301 C-X C-E in emacs mode will open an external editor.
302 :param history: :class:`~prompt_toolkit.history.History` instance.
303 :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` instance.
304 (e.g. :class:`~prompt_toolkit.clipboard.InMemoryClipboard`)
305 :param rprompt: Text or formatted text to be displayed on the right side.
306 This can also be a callable that returns (formatted) text.
307 :param bottom_toolbar: Formatted text or callable which is supposed to
308 return formatted text.
309 :param prompt_continuation: Text that needs to be displayed for a multiline
310 prompt continuation. This can either be formatted text or a callable
311 that takes a `prompt_width`, `line_number` and `wrap_count` as input
312 and returns formatted text. When this is `None` (the default), then
313 `prompt_width` spaces will be used.
314 :param complete_style: ``CompleteStyle.COLUMN``,
315 ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``.
316 :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter`
317 to enable mouse support.
318 :param placeholder: Text to be displayed when no input has been given
319 yet. Unlike the `default` parameter, this won't be returned as part of
320 the output ever. This can be formatted text or a callable that returns
321 formatted text.
322 :param refresh_interval: (number; in seconds) When given, refresh the UI
323 every so many seconds.
324 :param input: `Input` object. (Note that the preferred way to change the
325 input/output is by creating an `AppSession`.)
326 :param output: `Output` object.
327 :param interrupt_exception: The exception type that will be raised when
328 there is a keyboard interrupt (control-c keypress).
329 :param eof_exception: The exception type that will be raised when there is
330 an end-of-file/exit event (control-d keypress).
331 """
332
333 _fields = (
334 "message",
335 "lexer",
336 "completer",
337 "complete_in_thread",
338 "is_password",
339 "editing_mode",
340 "key_bindings",
341 "is_password",
342 "bottom_toolbar",
343 "style",
344 "style_transformation",
345 "swap_light_and_dark_colors",
346 "color_depth",
347 "cursor",
348 "include_default_pygments_style",
349 "rprompt",
350 "multiline",
351 "prompt_continuation",
352 "wrap_lines",
353 "enable_history_search",
354 "search_ignore_case",
355 "complete_while_typing",
356 "validate_while_typing",
357 "complete_style",
358 "mouse_support",
359 "auto_suggest",
360 "clipboard",
361 "validator",
362 "refresh_interval",
363 "input_processors",
364 "placeholder",
365 "enable_system_prompt",
366 "enable_suspend",
367 "enable_open_in_editor",
368 "reserve_space_for_menu",
369 "tempfile_suffix",
370 "tempfile",
371 )
372
373 def __init__(
374 self,
375 message: AnyFormattedText = "",
376 *,
377 multiline: FilterOrBool = False,
378 wrap_lines: FilterOrBool = True,
379 is_password: FilterOrBool = False,
380 vi_mode: bool = False,
381 editing_mode: EditingMode = EditingMode.EMACS,
382 complete_while_typing: FilterOrBool = True,
383 validate_while_typing: FilterOrBool = True,
384 enable_history_search: FilterOrBool = False,
385 search_ignore_case: FilterOrBool = False,
386 lexer: Lexer | None = None,
387 enable_system_prompt: FilterOrBool = False,
388 enable_suspend: FilterOrBool = False,
389 enable_open_in_editor: FilterOrBool = False,
390 validator: Validator | None = None,
391 completer: Completer | None = None,
392 complete_in_thread: bool = False,
393 reserve_space_for_menu: int = 8,
394 complete_style: CompleteStyle = CompleteStyle.COLUMN,
395 auto_suggest: AutoSuggest | None = None,
396 style: BaseStyle | None = None,
397 style_transformation: StyleTransformation | None = None,
398 swap_light_and_dark_colors: FilterOrBool = False,
399 color_depth: ColorDepth | None = None,
400 cursor: AnyCursorShapeConfig = None,
401 include_default_pygments_style: FilterOrBool = True,
402 history: History | None = None,
403 clipboard: Clipboard | None = None,
404 prompt_continuation: PromptContinuationText | None = None,
405 rprompt: AnyFormattedText = None,
406 bottom_toolbar: AnyFormattedText = None,
407 mouse_support: FilterOrBool = False,
408 input_processors: list[Processor] | None = None,
409 placeholder: AnyFormattedText | None = None,
410 key_bindings: KeyBindingsBase | None = None,
411 erase_when_done: bool = False,
412 tempfile_suffix: str | Callable[[], str] | None = ".txt",
413 tempfile: str | Callable[[], str] | None = None,
414 refresh_interval: float = 0,
415 input: Input | None = None,
416 output: Output | None = None,
417 interrupt_exception: type[BaseException] = KeyboardInterrupt,
418 eof_exception: type[BaseException] = EOFError,
419 ) -> None:
420 history = history or InMemoryHistory()
421 clipboard = clipboard or InMemoryClipboard()
422
423 # Ensure backwards-compatibility, when `vi_mode` is passed.
424 if vi_mode:
425 editing_mode = EditingMode.VI
426
427 # Store all settings in this class.
428 self._input = input
429 self._output = output
430
431 # Store attributes.
432 # (All except 'editing_mode'.)
433 self.message = message
434 self.lexer = lexer
435 self.completer = completer
436 self.complete_in_thread = complete_in_thread
437 self.is_password = is_password
438 self.key_bindings = key_bindings
439 self.bottom_toolbar = bottom_toolbar
440 self.style = style
441 self.style_transformation = style_transformation
442 self.swap_light_and_dark_colors = swap_light_and_dark_colors
443 self.color_depth = color_depth
444 self.cursor = cursor
445 self.include_default_pygments_style = include_default_pygments_style
446 self.rprompt = rprompt
447 self.multiline = multiline
448 self.prompt_continuation = prompt_continuation
449 self.wrap_lines = wrap_lines
450 self.enable_history_search = enable_history_search
451 self.search_ignore_case = search_ignore_case
452 self.complete_while_typing = complete_while_typing
453 self.validate_while_typing = validate_while_typing
454 self.complete_style = complete_style
455 self.mouse_support = mouse_support
456 self.auto_suggest = auto_suggest
457 self.clipboard = clipboard
458 self.validator = validator
459 self.refresh_interval = refresh_interval
460 self.input_processors = input_processors
461 self.placeholder = placeholder
462 self.enable_system_prompt = enable_system_prompt
463 self.enable_suspend = enable_suspend
464 self.enable_open_in_editor = enable_open_in_editor
465 self.reserve_space_for_menu = reserve_space_for_menu
466 self.tempfile_suffix = tempfile_suffix
467 self.tempfile = tempfile
468 self.interrupt_exception = interrupt_exception
469 self.eof_exception = eof_exception
470
471 # Create buffers, layout and Application.
472 self.history = history
473 self.default_buffer = self._create_default_buffer()
474 self.search_buffer = self._create_search_buffer()
475 self.layout = self._create_layout()
476 self.app = self._create_application(editing_mode, erase_when_done)
477
478 def _dyncond(self, attr_name: str) -> Condition:
479 """
480 Dynamically take this setting from this 'PromptSession' class.
481 `attr_name` represents an attribute name of this class. Its value
482 can either be a boolean or a `Filter`.
483
484 This returns something that can be used as either a `Filter`
485 or `Filter`.
486 """
487
488 @Condition
489 def dynamic() -> bool:
490 value = cast(FilterOrBool, getattr(self, attr_name))
491 return to_filter(value)()
492
493 return dynamic
494
495 def _create_default_buffer(self) -> Buffer:
496 """
497 Create and return the default input buffer.
498 """
499 dyncond = self._dyncond
500
501 # Create buffers list.
502 def accept(buff: Buffer) -> bool:
503 """Accept the content of the default buffer. This is called when
504 the validation succeeds."""
505 cast(Application[str], get_app()).exit(result=buff.document.text)
506 return True # Keep text, we call 'reset' later on.
507
508 return Buffer(
509 name=DEFAULT_BUFFER,
510 # Make sure that complete_while_typing is disabled when
511 # enable_history_search is enabled. (First convert to Filter,
512 # to avoid doing bitwise operations on bool objects.)
513 complete_while_typing=Condition(
514 lambda: is_true(self.complete_while_typing)
515 and not is_true(self.enable_history_search)
516 and not self.complete_style == CompleteStyle.READLINE_LIKE
517 ),
518 validate_while_typing=dyncond("validate_while_typing"),
519 enable_history_search=dyncond("enable_history_search"),
520 validator=DynamicValidator(lambda: self.validator),
521 completer=DynamicCompleter(
522 lambda: ThreadedCompleter(self.completer)
523 if self.complete_in_thread and self.completer
524 else self.completer
525 ),
526 history=self.history,
527 auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
528 accept_handler=accept,
529 tempfile_suffix=lambda: to_str(self.tempfile_suffix or ""),
530 tempfile=lambda: to_str(self.tempfile or ""),
531 )
532
533 def _create_search_buffer(self) -> Buffer:
534 return Buffer(name=SEARCH_BUFFER)
535
536 def _create_layout(self) -> Layout:
537 """
538 Create `Layout` for this prompt.
539 """
540 dyncond = self._dyncond
541
542 # Create functions that will dynamically split the prompt. (If we have
543 # a multiline prompt.)
544 (
545 has_before_fragments,
546 get_prompt_text_1,
547 get_prompt_text_2,
548 ) = _split_multiline_prompt(self._get_prompt)
549
550 default_buffer = self.default_buffer
551 search_buffer = self.search_buffer
552
553 # Create processors list.
554 @Condition
555 def display_placeholder() -> bool:
556 return self.placeholder is not None and self.default_buffer.text == ""
557
558 all_input_processors = [
559 HighlightIncrementalSearchProcessor(),
560 HighlightSelectionProcessor(),
561 ConditionalProcessor(
562 AppendAutoSuggestion(), has_focus(default_buffer) & ~is_done
563 ),
564 ConditionalProcessor(PasswordProcessor(), dyncond("is_password")),
565 DisplayMultipleCursors(),
566 # Users can insert processors here.
567 DynamicProcessor(lambda: merge_processors(self.input_processors or [])),
568 ConditionalProcessor(
569 AfterInput(lambda: self.placeholder),
570 filter=display_placeholder,
571 ),
572 ]
573
574 # Create bottom toolbars.
575 bottom_toolbar = ConditionalContainer(
576 Window(
577 FormattedTextControl(
578 lambda: self.bottom_toolbar, style="class:bottom-toolbar.text"
579 ),
580 style="class:bottom-toolbar",
581 dont_extend_height=True,
582 height=Dimension(min=1),
583 ),
584 filter=Condition(lambda: self.bottom_toolbar is not None)
585 & ~is_done
586 & renderer_height_is_known,
587 )
588
589 search_toolbar = SearchToolbar(
590 search_buffer, ignore_case=dyncond("search_ignore_case")
591 )
592
593 search_buffer_control = SearchBufferControl(
594 buffer=search_buffer,
595 input_processors=[ReverseSearchProcessor()],
596 ignore_case=dyncond("search_ignore_case"),
597 )
598
599 system_toolbar = SystemToolbar(
600 enable_global_bindings=dyncond("enable_system_prompt")
601 )
602
603 def get_search_buffer_control() -> SearchBufferControl:
604 "Return the UIControl to be focused when searching start."
605 if is_true(self.multiline):
606 return search_toolbar.control
607 else:
608 return search_buffer_control
609
610 default_buffer_control = BufferControl(
611 buffer=default_buffer,
612 search_buffer_control=get_search_buffer_control,
613 input_processors=all_input_processors,
614 include_default_input_processors=False,
615 lexer=DynamicLexer(lambda: self.lexer),
616 preview_search=True,
617 )
618
619 default_buffer_window = Window(
620 default_buffer_control,
621 height=self._get_default_buffer_control_height,
622 get_line_prefix=partial(
623 self._get_line_prefix, get_prompt_text_2=get_prompt_text_2
624 ),
625 wrap_lines=dyncond("wrap_lines"),
626 )
627
628 @Condition
629 def multi_column_complete_style() -> bool:
630 return self.complete_style == CompleteStyle.MULTI_COLUMN
631
632 # Build the layout.
633 layout = HSplit(
634 [
635 # The main input, with completion menus floating on top of it.
636 FloatContainer(
637 HSplit(
638 [
639 ConditionalContainer(
640 Window(
641 FormattedTextControl(get_prompt_text_1),
642 dont_extend_height=True,
643 ),
644 Condition(has_before_fragments),
645 ),
646 ConditionalContainer(
647 default_buffer_window,
648 Condition(
649 lambda: get_app().layout.current_control
650 != search_buffer_control
651 ),
652 ),
653 ConditionalContainer(
654 Window(search_buffer_control),
655 Condition(
656 lambda: get_app().layout.current_control
657 == search_buffer_control
658 ),
659 ),
660 ]
661 ),
662 [
663 # Completion menus.
664 # NOTE: Especially the multi-column menu needs to be
665 # transparent, because the shape is not always
666 # rectangular due to the meta-text below the menu.
667 Float(
668 xcursor=True,
669 ycursor=True,
670 transparent=True,
671 content=CompletionsMenu(
672 max_height=16,
673 scroll_offset=1,
674 extra_filter=has_focus(default_buffer)
675 & ~multi_column_complete_style,
676 ),
677 ),
678 Float(
679 xcursor=True,
680 ycursor=True,
681 transparent=True,
682 content=MultiColumnCompletionsMenu(
683 show_meta=True,
684 extra_filter=has_focus(default_buffer)
685 & multi_column_complete_style,
686 ),
687 ),
688 # The right prompt.
689 Float(
690 right=0,
691 top=0,
692 hide_when_covering_content=True,
693 content=_RPrompt(lambda: self.rprompt),
694 ),
695 ],
696 ),
697 ConditionalContainer(ValidationToolbar(), filter=~is_done),
698 ConditionalContainer(
699 system_toolbar, dyncond("enable_system_prompt") & ~is_done
700 ),
701 # In multiline mode, we use two toolbars for 'arg' and 'search'.
702 ConditionalContainer(
703 Window(FormattedTextControl(self._get_arg_text), height=1),
704 dyncond("multiline") & has_arg,
705 ),
706 ConditionalContainer(search_toolbar, dyncond("multiline") & ~is_done),
707 bottom_toolbar,
708 ]
709 )
710
711 return Layout(layout, default_buffer_window)
712
713 def _create_application(
714 self, editing_mode: EditingMode, erase_when_done: bool
715 ) -> Application[_T]:
716 """
717 Create the `Application` object.
718 """
719 dyncond = self._dyncond
720
721 # Default key bindings.
722 auto_suggest_bindings = load_auto_suggest_bindings()
723 open_in_editor_bindings = load_open_in_editor_bindings()
724 prompt_bindings = self._create_prompt_bindings()
725
726 # Create application
727 application: Application[_T] = Application(
728 layout=self.layout,
729 style=DynamicStyle(lambda: self.style),
730 style_transformation=merge_style_transformations(
731 [
732 DynamicStyleTransformation(lambda: self.style_transformation),
733 ConditionalStyleTransformation(
734 SwapLightAndDarkStyleTransformation(),
735 dyncond("swap_light_and_dark_colors"),
736 ),
737 ]
738 ),
739 include_default_pygments_style=dyncond("include_default_pygments_style"),
740 clipboard=DynamicClipboard(lambda: self.clipboard),
741 key_bindings=merge_key_bindings(
742 [
743 merge_key_bindings(
744 [
745 auto_suggest_bindings,
746 ConditionalKeyBindings(
747 open_in_editor_bindings,
748 dyncond("enable_open_in_editor")
749 & has_focus(DEFAULT_BUFFER),
750 ),
751 prompt_bindings,
752 ]
753 ),
754 DynamicKeyBindings(lambda: self.key_bindings),
755 ]
756 ),
757 mouse_support=dyncond("mouse_support"),
758 editing_mode=editing_mode,
759 erase_when_done=erase_when_done,
760 reverse_vi_search_direction=True,
761 color_depth=lambda: self.color_depth,
762 cursor=DynamicCursorShapeConfig(lambda: self.cursor),
763 refresh_interval=self.refresh_interval,
764 input=self._input,
765 output=self._output,
766 )
767
768 # During render time, make sure that we focus the right search control
769 # (if we are searching). - This could be useful if people make the
770 # 'multiline' property dynamic.
771 """
772 def on_render(app):
773 multiline = is_true(self.multiline)
774 current_control = app.layout.current_control
775
776 if multiline:
777 if current_control == search_buffer_control:
778 app.layout.current_control = search_toolbar.control
779 app.invalidate()
780 else:
781 if current_control == search_toolbar.control:
782 app.layout.current_control = search_buffer_control
783 app.invalidate()
784
785 app.on_render += on_render
786 """
787
788 return application
789
790 def _create_prompt_bindings(self) -> KeyBindings:
791 """
792 Create the KeyBindings for a prompt application.
793 """
794 kb = KeyBindings()
795 handle = kb.add
796 default_focused = has_focus(DEFAULT_BUFFER)
797
798 @Condition
799 def do_accept() -> bool:
800 return not is_true(self.multiline) and self.app.layout.has_focus(
801 DEFAULT_BUFFER
802 )
803
804 @handle("enter", filter=do_accept & default_focused)
805 def _accept_input(event: E) -> None:
806 "Accept input when enter has been pressed."
807 self.default_buffer.validate_and_handle()
808
809 @Condition
810 def readline_complete_style() -> bool:
811 return self.complete_style == CompleteStyle.READLINE_LIKE
812
813 @handle("tab", filter=readline_complete_style & default_focused)
814 def _complete_like_readline(event: E) -> None:
815 "Display completions (like Readline)."
816 display_completions_like_readline(event)
817
818 @handle("c-c", filter=default_focused)
819 @handle("<sigint>")
820 def _keyboard_interrupt(event: E) -> None:
821 "Abort when Control-C has been pressed."
822 event.app.exit(exception=self.interrupt_exception(), style="class:aborting")
823
824 @Condition
825 def ctrl_d_condition() -> bool:
826 """Ctrl-D binding is only active when the default buffer is selected
827 and empty."""
828 app = get_app()
829 return (
830 app.current_buffer.name == DEFAULT_BUFFER
831 and not app.current_buffer.text
832 )
833
834 @handle("c-d", filter=ctrl_d_condition & default_focused)
835 def _eof(event: E) -> None:
836 "Exit when Control-D has been pressed."
837 event.app.exit(exception=self.eof_exception(), style="class:exiting")
838
839 suspend_supported = Condition(suspend_to_background_supported)
840
841 @Condition
842 def enable_suspend() -> bool:
843 return to_filter(self.enable_suspend)()
844
845 @handle("c-z", filter=suspend_supported & enable_suspend)
846 def _suspend(event: E) -> None:
847 """
848 Suspend process to background.
849 """
850 event.app.suspend_to_background()
851
852 return kb
853
854 def prompt(
855 self,
856 # When any of these arguments are passed, this value is overwritten
857 # in this PromptSession.
858 message: AnyFormattedText | None = None,
859 # `message` should go first, because people call it as
860 # positional argument.
861 *,
862 editing_mode: EditingMode | None = None,
863 refresh_interval: float | None = None,
864 vi_mode: bool | None = None,
865 lexer: Lexer | None = None,
866 completer: Completer | None = None,
867 complete_in_thread: bool | None = None,
868 is_password: bool | None = None,
869 key_bindings: KeyBindingsBase | None = None,
870 bottom_toolbar: AnyFormattedText | None = None,
871 style: BaseStyle | None = None,
872 color_depth: ColorDepth | None = None,
873 cursor: AnyCursorShapeConfig | None = None,
874 include_default_pygments_style: FilterOrBool | None = None,
875 style_transformation: StyleTransformation | None = None,
876 swap_light_and_dark_colors: FilterOrBool | None = None,
877 rprompt: AnyFormattedText | None = None,
878 multiline: FilterOrBool | None = None,
879 prompt_continuation: PromptContinuationText | None = None,
880 wrap_lines: FilterOrBool | None = None,
881 enable_history_search: FilterOrBool | None = None,
882 search_ignore_case: FilterOrBool | None = None,
883 complete_while_typing: FilterOrBool | None = None,
884 validate_while_typing: FilterOrBool | None = None,
885 complete_style: CompleteStyle | None = None,
886 auto_suggest: AutoSuggest | None = None,
887 validator: Validator | None = None,
888 clipboard: Clipboard | None = None,
889 mouse_support: FilterOrBool | None = None,
890 input_processors: list[Processor] | None = None,
891 placeholder: AnyFormattedText | None = None,
892 reserve_space_for_menu: int | None = None,
893 enable_system_prompt: FilterOrBool | None = None,
894 enable_suspend: FilterOrBool | None = None,
895 enable_open_in_editor: FilterOrBool | None = None,
896 tempfile_suffix: str | Callable[[], str] | None = None,
897 tempfile: str | Callable[[], str] | None = None,
898 # Following arguments are specific to the current `prompt()` call.
899 default: str | Document = "",
900 accept_default: bool = False,
901 pre_run: Callable[[], None] | None = None,
902 set_exception_handler: bool = True,
903 handle_sigint: bool = True,
904 in_thread: bool = False,
905 inputhook: InputHook | None = None,
906 ) -> _T:
907 """
908 Display the prompt.
909
910 The first set of arguments is a subset of the :class:`~.PromptSession`
911 class itself. For these, passing in ``None`` will keep the current
912 values that are active in the session. Passing in a value will set the
913 attribute for the session, which means that it applies to the current,
914 but also to the next prompts.
915
916 Note that in order to erase a ``Completer``, ``Validator`` or
917 ``AutoSuggest``, you can't use ``None``. Instead pass in a
918 ``DummyCompleter``, ``DummyValidator`` or ``DummyAutoSuggest`` instance
919 respectively. For a ``Lexer`` you can pass in an empty ``SimpleLexer``.
920
921 Additional arguments, specific for this prompt:
922
923 :param default: The default input text to be shown. (This can be edited
924 by the user).
925 :param accept_default: When `True`, automatically accept the default
926 value without allowing the user to edit the input.
927 :param pre_run: Callable, called at the start of `Application.run`.
928 :param in_thread: Run the prompt in a background thread; block the
929 current thread. This avoids interference with an event loop in the
930 current thread. Like `Application.run(in_thread=True)`.
931
932 This method will raise ``KeyboardInterrupt`` when control-c has been
933 pressed (for abort) and ``EOFError`` when control-d has been pressed
934 (for exit).
935 """
936 # NOTE: We used to create a backup of the PromptSession attributes and
937 # restore them after exiting the prompt. This code has been
938 # removed, because it was confusing and didn't really serve a use
939 # case. (People were changing `Application.editing_mode`
940 # dynamically and surprised that it was reset after every call.)
941
942 # NOTE 2: YES, this is a lot of repeation below...
943 # However, it is a very convenient for a user to accept all
944 # these parameters in this `prompt` method as well. We could
945 # use `locals()` and `setattr` to avoid the repetition, but
946 # then we loose the advantage of mypy and pyflakes to be able
947 # to verify the code.
948 if message is not None:
949 self.message = message
950 if editing_mode is not None:
951 self.editing_mode = editing_mode
952 if refresh_interval is not None:
953 self.refresh_interval = refresh_interval
954 if vi_mode:
955 self.editing_mode = EditingMode.VI
956 if lexer is not None:
957 self.lexer = lexer
958 if completer is not None:
959 self.completer = completer
960 if complete_in_thread is not None:
961 self.complete_in_thread = complete_in_thread
962 if is_password is not None:
963 self.is_password = is_password
964 if key_bindings is not None:
965 self.key_bindings = key_bindings
966 if bottom_toolbar is not None:
967 self.bottom_toolbar = bottom_toolbar
968 if style is not None:
969 self.style = style
970 if color_depth is not None:
971 self.color_depth = color_depth
972 if cursor is not None:
973 self.cursor = cursor
974 if include_default_pygments_style is not None:
975 self.include_default_pygments_style = include_default_pygments_style
976 if style_transformation is not None:
977 self.style_transformation = style_transformation
978 if swap_light_and_dark_colors is not None:
979 self.swap_light_and_dark_colors = swap_light_and_dark_colors
980 if rprompt is not None:
981 self.rprompt = rprompt
982 if multiline is not None:
983 self.multiline = multiline
984 if prompt_continuation is not None:
985 self.prompt_continuation = prompt_continuation
986 if wrap_lines is not None:
987 self.wrap_lines = wrap_lines
988 if enable_history_search is not None:
989 self.enable_history_search = enable_history_search
990 if search_ignore_case is not None:
991 self.search_ignore_case = search_ignore_case
992 if complete_while_typing is not None:
993 self.complete_while_typing = complete_while_typing
994 if validate_while_typing is not None:
995 self.validate_while_typing = validate_while_typing
996 if complete_style is not None:
997 self.complete_style = complete_style
998 if auto_suggest is not None:
999 self.auto_suggest = auto_suggest
1000 if validator is not None:
1001 self.validator = validator
1002 if clipboard is not None:
1003 self.clipboard = clipboard
1004 if mouse_support is not None:
1005 self.mouse_support = mouse_support
1006 if input_processors is not None:
1007 self.input_processors = input_processors
1008 if placeholder is not None:
1009 self.placeholder = placeholder
1010 if reserve_space_for_menu is not None:
1011 self.reserve_space_for_menu = reserve_space_for_menu
1012 if enable_system_prompt is not None:
1013 self.enable_system_prompt = enable_system_prompt
1014 if enable_suspend is not None:
1015 self.enable_suspend = enable_suspend
1016 if enable_open_in_editor is not None:
1017 self.enable_open_in_editor = enable_open_in_editor
1018 if tempfile_suffix is not None:
1019 self.tempfile_suffix = tempfile_suffix
1020 if tempfile is not None:
1021 self.tempfile = tempfile
1022
1023 self._add_pre_run_callables(pre_run, accept_default)
1024 self.default_buffer.reset(
1025 default if isinstance(default, Document) else Document(default)
1026 )
1027 self.app.refresh_interval = self.refresh_interval # This is not reactive.
1028
1029 # If we are using the default output, and have a dumb terminal. Use the
1030 # dumb prompt.
1031 if self._output is None and is_dumb_terminal():
1032 with self._dumb_prompt(self.message) as dump_app:
1033 return dump_app.run(in_thread=in_thread, handle_sigint=handle_sigint)
1034
1035 return self.app.run(
1036 set_exception_handler=set_exception_handler,
1037 in_thread=in_thread,
1038 handle_sigint=handle_sigint,
1039 inputhook=inputhook,
1040 )
1041
1042 @contextmanager
1043 def _dumb_prompt(self, message: AnyFormattedText = "") -> Iterator[Application[_T]]:
1044 """
1045 Create prompt `Application` for prompt function for dumb terminals.
1046
1047 Dumb terminals have minimum rendering capabilities. We can only print
1048 text to the screen. We can't use colors, and we can't do cursor
1049 movements. The Emacs inferior shell is an example of a dumb terminal.
1050
1051 We will show the prompt, and wait for the input. We still handle arrow
1052 keys, and all custom key bindings, but we don't really render the
1053 cursor movements. Instead we only print the typed character that's
1054 right before the cursor.
1055 """
1056 # Send prompt to output.
1057 self.output.write(fragment_list_to_text(to_formatted_text(self.message)))
1058 self.output.flush()
1059
1060 # Key bindings for the dumb prompt: mostly the same as the full prompt.
1061 key_bindings: KeyBindingsBase = self._create_prompt_bindings()
1062 if self.key_bindings:
1063 key_bindings = merge_key_bindings([self.key_bindings, key_bindings])
1064
1065 # Create and run application.
1066 application = cast(
1067 Application[_T],
1068 Application(
1069 input=self.input,
1070 output=DummyOutput(),
1071 layout=self.layout,
1072 key_bindings=key_bindings,
1073 ),
1074 )
1075
1076 def on_text_changed(_: object) -> None:
1077 self.output.write(self.default_buffer.document.text_before_cursor[-1:])
1078 self.output.flush()
1079
1080 self.default_buffer.on_text_changed += on_text_changed
1081
1082 try:
1083 yield application
1084 finally:
1085 # Render line ending.
1086 self.output.write("\r\n")
1087 self.output.flush()
1088
1089 self.default_buffer.on_text_changed -= on_text_changed
1090
1091 async def prompt_async(
1092 self,
1093 # When any of these arguments are passed, this value is overwritten
1094 # in this PromptSession.
1095 message: AnyFormattedText | None = None,
1096 # `message` should go first, because people call it as
1097 # positional argument.
1098 *,
1099 editing_mode: EditingMode | None = None,
1100 refresh_interval: float | None = None,
1101 vi_mode: bool | None = None,
1102 lexer: Lexer | None = None,
1103 completer: Completer | None = None,
1104 complete_in_thread: bool | None = None,
1105 is_password: bool | None = None,
1106 key_bindings: KeyBindingsBase | None = None,
1107 bottom_toolbar: AnyFormattedText | None = None,
1108 style: BaseStyle | None = None,
1109 color_depth: ColorDepth | None = None,
1110 cursor: CursorShapeConfig | None = None,
1111 include_default_pygments_style: FilterOrBool | None = None,
1112 style_transformation: StyleTransformation | None = None,
1113 swap_light_and_dark_colors: FilterOrBool | None = None,
1114 rprompt: AnyFormattedText | None = None,
1115 multiline: FilterOrBool | None = None,
1116 prompt_continuation: PromptContinuationText | None = None,
1117 wrap_lines: FilterOrBool | None = None,
1118 enable_history_search: FilterOrBool | None = None,
1119 search_ignore_case: FilterOrBool | None = None,
1120 complete_while_typing: FilterOrBool | None = None,
1121 validate_while_typing: FilterOrBool | None = None,
1122 complete_style: CompleteStyle | None = None,
1123 auto_suggest: AutoSuggest | None = None,
1124 validator: Validator | None = None,
1125 clipboard: Clipboard | None = None,
1126 mouse_support: FilterOrBool | None = None,
1127 input_processors: list[Processor] | None = None,
1128 placeholder: AnyFormattedText | None = None,
1129 reserve_space_for_menu: int | None = None,
1130 enable_system_prompt: FilterOrBool | None = None,
1131 enable_suspend: FilterOrBool | None = None,
1132 enable_open_in_editor: FilterOrBool | None = None,
1133 tempfile_suffix: str | Callable[[], str] | None = None,
1134 tempfile: str | Callable[[], str] | None = None,
1135 # Following arguments are specific to the current `prompt()` call.
1136 default: str | Document = "",
1137 accept_default: bool = False,
1138 pre_run: Callable[[], None] | None = None,
1139 set_exception_handler: bool = True,
1140 handle_sigint: bool = True,
1141 ) -> _T:
1142 if message is not None:
1143 self.message = message
1144 if editing_mode is not None:
1145 self.editing_mode = editing_mode
1146 if refresh_interval is not None:
1147 self.refresh_interval = refresh_interval
1148 if vi_mode:
1149 self.editing_mode = EditingMode.VI
1150 if lexer is not None:
1151 self.lexer = lexer
1152 if completer is not None:
1153 self.completer = completer
1154 if complete_in_thread is not None:
1155 self.complete_in_thread = complete_in_thread
1156 if is_password is not None:
1157 self.is_password = is_password
1158 if key_bindings is not None:
1159 self.key_bindings = key_bindings
1160 if bottom_toolbar is not None:
1161 self.bottom_toolbar = bottom_toolbar
1162 if style is not None:
1163 self.style = style
1164 if color_depth is not None:
1165 self.color_depth = color_depth
1166 if cursor is not None:
1167 self.cursor = cursor
1168 if include_default_pygments_style is not None:
1169 self.include_default_pygments_style = include_default_pygments_style
1170 if style_transformation is not None:
1171 self.style_transformation = style_transformation
1172 if swap_light_and_dark_colors is not None:
1173 self.swap_light_and_dark_colors = swap_light_and_dark_colors
1174 if rprompt is not None:
1175 self.rprompt = rprompt
1176 if multiline is not None:
1177 self.multiline = multiline
1178 if prompt_continuation is not None:
1179 self.prompt_continuation = prompt_continuation
1180 if wrap_lines is not None:
1181 self.wrap_lines = wrap_lines
1182 if enable_history_search is not None:
1183 self.enable_history_search = enable_history_search
1184 if search_ignore_case is not None:
1185 self.search_ignore_case = search_ignore_case
1186 if complete_while_typing is not None:
1187 self.complete_while_typing = complete_while_typing
1188 if validate_while_typing is not None:
1189 self.validate_while_typing = validate_while_typing
1190 if complete_style is not None:
1191 self.complete_style = complete_style
1192 if auto_suggest is not None:
1193 self.auto_suggest = auto_suggest
1194 if validator is not None:
1195 self.validator = validator
1196 if clipboard is not None:
1197 self.clipboard = clipboard
1198 if mouse_support is not None:
1199 self.mouse_support = mouse_support
1200 if input_processors is not None:
1201 self.input_processors = input_processors
1202 if placeholder is not None:
1203 self.placeholder = placeholder
1204 if reserve_space_for_menu is not None:
1205 self.reserve_space_for_menu = reserve_space_for_menu
1206 if enable_system_prompt is not None:
1207 self.enable_system_prompt = enable_system_prompt
1208 if enable_suspend is not None:
1209 self.enable_suspend = enable_suspend
1210 if enable_open_in_editor is not None:
1211 self.enable_open_in_editor = enable_open_in_editor
1212 if tempfile_suffix is not None:
1213 self.tempfile_suffix = tempfile_suffix
1214 if tempfile is not None:
1215 self.tempfile = tempfile
1216
1217 self._add_pre_run_callables(pre_run, accept_default)
1218 self.default_buffer.reset(
1219 default if isinstance(default, Document) else Document(default)
1220 )
1221 self.app.refresh_interval = self.refresh_interval # This is not reactive.
1222
1223 # If we are using the default output, and have a dumb terminal. Use the
1224 # dumb prompt.
1225 if self._output is None and is_dumb_terminal():
1226 with self._dumb_prompt(self.message) as dump_app:
1227 return await dump_app.run_async(handle_sigint=handle_sigint)
1228
1229 return await self.app.run_async(
1230 set_exception_handler=set_exception_handler, handle_sigint=handle_sigint
1231 )
1232
1233 def _add_pre_run_callables(
1234 self, pre_run: Callable[[], None] | None, accept_default: bool
1235 ) -> None:
1236 def pre_run2() -> None:
1237 if pre_run:
1238 pre_run()
1239
1240 if accept_default:
1241 # Validate and handle input. We use `call_from_executor` in
1242 # order to run it "soon" (during the next iteration of the
1243 # event loop), instead of right now. Otherwise, it won't
1244 # display the default value.
1245 get_running_loop().call_soon(self.default_buffer.validate_and_handle)
1246
1247 self.app.pre_run_callables.append(pre_run2)
1248
1249 @property
1250 def editing_mode(self) -> EditingMode:
1251 return self.app.editing_mode
1252
1253 @editing_mode.setter
1254 def editing_mode(self, value: EditingMode) -> None:
1255 self.app.editing_mode = value
1256
1257 def _get_default_buffer_control_height(self) -> Dimension:
1258 # If there is an autocompletion menu to be shown, make sure that our
1259 # layout has at least a minimal height in order to display it.
1260 if (
1261 self.completer is not None
1262 and self.complete_style != CompleteStyle.READLINE_LIKE
1263 ):
1264 space = self.reserve_space_for_menu
1265 else:
1266 space = 0
1267
1268 if space and not get_app().is_done:
1269 buff = self.default_buffer
1270
1271 # Reserve the space, either when there are completions, or when
1272 # `complete_while_typing` is true and we expect completions very
1273 # soon.
1274 if buff.complete_while_typing() or buff.complete_state is not None:
1275 return Dimension(min=space)
1276
1277 return Dimension()
1278
1279 def _get_prompt(self) -> StyleAndTextTuples:
1280 return to_formatted_text(self.message, style="class:prompt")
1281
1282 def _get_continuation(
1283 self, width: int, line_number: int, wrap_count: int
1284 ) -> StyleAndTextTuples:
1285 """
1286 Insert the prompt continuation.
1287
1288 :param width: The width that was used for the prompt. (more or less can
1289 be used.)
1290 :param line_number:
1291 :param wrap_count: Amount of times that the line has been wrapped.
1292 """
1293 prompt_continuation = self.prompt_continuation
1294
1295 if callable(prompt_continuation):
1296 continuation: AnyFormattedText = prompt_continuation(
1297 width, line_number, wrap_count
1298 )
1299 else:
1300 continuation = prompt_continuation
1301
1302 # When the continuation prompt is not given, choose the same width as
1303 # the actual prompt.
1304 if continuation is None and is_true(self.multiline):
1305 continuation = " " * width
1306
1307 return to_formatted_text(continuation, style="class:prompt-continuation")
1308
1309 def _get_line_prefix(
1310 self,
1311 line_number: int,
1312 wrap_count: int,
1313 get_prompt_text_2: _StyleAndTextTuplesCallable,
1314 ) -> StyleAndTextTuples:
1315 """
1316 Return whatever needs to be inserted before every line.
1317 (the prompt, or a line continuation.)
1318 """
1319 # First line: display the "arg" or the prompt.
1320 if line_number == 0 and wrap_count == 0:
1321 if not is_true(self.multiline) and get_app().key_processor.arg is not None:
1322 return self._inline_arg()
1323 else:
1324 return get_prompt_text_2()
1325
1326 # For the next lines, display the appropriate continuation.
1327 prompt_width = get_cwidth(fragment_list_to_text(get_prompt_text_2()))
1328 return self._get_continuation(prompt_width, line_number, wrap_count)
1329
1330 def _get_arg_text(self) -> StyleAndTextTuples:
1331 "'arg' toolbar, for in multiline mode."
1332 arg = self.app.key_processor.arg
1333 if arg is None:
1334 # Should not happen because of the `has_arg` filter in the layout.
1335 return []
1336
1337 if arg == "-":
1338 arg = "-1"
1339
1340 return [("class:arg-toolbar", "Repeat: "), ("class:arg-toolbar.text", arg)]
1341
1342 def _inline_arg(self) -> StyleAndTextTuples:
1343 "'arg' prefix, for in single line mode."
1344 app = get_app()
1345 if app.key_processor.arg is None:
1346 return []
1347 else:
1348 arg = app.key_processor.arg
1349
1350 return [
1351 ("class:prompt.arg", "(arg: "),
1352 ("class:prompt.arg.text", str(arg)),
1353 ("class:prompt.arg", ") "),
1354 ]
1355
1356 # Expose the Input and Output objects as attributes, mainly for
1357 # backward-compatibility.
1358
1359 @property
1360 def input(self) -> Input:
1361 return self.app.input
1362
1363 @property
1364 def output(self) -> Output:
1365 return self.app.output
1366
1367
1368def prompt(
1369 message: AnyFormattedText | None = None,
1370 *,
1371 history: History | None = None,
1372 editing_mode: EditingMode | None = None,
1373 refresh_interval: float | None = None,
1374 vi_mode: bool | None = None,
1375 lexer: Lexer | None = None,
1376 completer: Completer | None = None,
1377 complete_in_thread: bool | None = None,
1378 is_password: bool | None = None,
1379 key_bindings: KeyBindingsBase | None = None,
1380 bottom_toolbar: AnyFormattedText | None = None,
1381 style: BaseStyle | None = None,
1382 color_depth: ColorDepth | None = None,
1383 cursor: AnyCursorShapeConfig = None,
1384 include_default_pygments_style: FilterOrBool | None = None,
1385 style_transformation: StyleTransformation | None = None,
1386 swap_light_and_dark_colors: FilterOrBool | None = None,
1387 rprompt: AnyFormattedText | None = None,
1388 multiline: FilterOrBool | None = None,
1389 prompt_continuation: PromptContinuationText | None = None,
1390 wrap_lines: FilterOrBool | None = None,
1391 enable_history_search: FilterOrBool | None = None,
1392 search_ignore_case: FilterOrBool | None = None,
1393 complete_while_typing: FilterOrBool | None = None,
1394 validate_while_typing: FilterOrBool | None = None,
1395 complete_style: CompleteStyle | None = None,
1396 auto_suggest: AutoSuggest | None = None,
1397 validator: Validator | None = None,
1398 clipboard: Clipboard | None = None,
1399 mouse_support: FilterOrBool | None = None,
1400 input_processors: list[Processor] | None = None,
1401 placeholder: AnyFormattedText | None = None,
1402 reserve_space_for_menu: int | None = None,
1403 enable_system_prompt: FilterOrBool | None = None,
1404 enable_suspend: FilterOrBool | None = None,
1405 enable_open_in_editor: FilterOrBool | None = None,
1406 tempfile_suffix: str | Callable[[], str] | None = None,
1407 tempfile: str | Callable[[], str] | None = None,
1408 # Following arguments are specific to the current `prompt()` call.
1409 default: str = "",
1410 accept_default: bool = False,
1411 pre_run: Callable[[], None] | None = None,
1412 set_exception_handler: bool = True,
1413 handle_sigint: bool = True,
1414 in_thread: bool = False,
1415 inputhook: InputHook | None = None,
1416) -> str:
1417 """
1418 The global `prompt` function. This will create a new `PromptSession`
1419 instance for every call.
1420 """
1421 # The history is the only attribute that has to be passed to the
1422 # `PromptSession`, it can't be passed into the `prompt()` method.
1423 session: PromptSession[str] = PromptSession(history=history)
1424
1425 return session.prompt(
1426 message,
1427 editing_mode=editing_mode,
1428 refresh_interval=refresh_interval,
1429 vi_mode=vi_mode,
1430 lexer=lexer,
1431 completer=completer,
1432 complete_in_thread=complete_in_thread,
1433 is_password=is_password,
1434 key_bindings=key_bindings,
1435 bottom_toolbar=bottom_toolbar,
1436 style=style,
1437 color_depth=color_depth,
1438 cursor=cursor,
1439 include_default_pygments_style=include_default_pygments_style,
1440 style_transformation=style_transformation,
1441 swap_light_and_dark_colors=swap_light_and_dark_colors,
1442 rprompt=rprompt,
1443 multiline=multiline,
1444 prompt_continuation=prompt_continuation,
1445 wrap_lines=wrap_lines,
1446 enable_history_search=enable_history_search,
1447 search_ignore_case=search_ignore_case,
1448 complete_while_typing=complete_while_typing,
1449 validate_while_typing=validate_while_typing,
1450 complete_style=complete_style,
1451 auto_suggest=auto_suggest,
1452 validator=validator,
1453 clipboard=clipboard,
1454 mouse_support=mouse_support,
1455 input_processors=input_processors,
1456 placeholder=placeholder,
1457 reserve_space_for_menu=reserve_space_for_menu,
1458 enable_system_prompt=enable_system_prompt,
1459 enable_suspend=enable_suspend,
1460 enable_open_in_editor=enable_open_in_editor,
1461 tempfile_suffix=tempfile_suffix,
1462 tempfile=tempfile,
1463 default=default,
1464 accept_default=accept_default,
1465 pre_run=pre_run,
1466 set_exception_handler=set_exception_handler,
1467 handle_sigint=handle_sigint,
1468 in_thread=in_thread,
1469 inputhook=inputhook,
1470 )
1471
1472
1473prompt.__doc__ = PromptSession.prompt.__doc__
1474
1475
1476def create_confirm_session(
1477 message: str, suffix: str = " (y/n) "
1478) -> PromptSession[bool]:
1479 """
1480 Create a `PromptSession` object for the 'confirm' function.
1481 """
1482 bindings = KeyBindings()
1483
1484 @bindings.add("y")
1485 @bindings.add("Y")
1486 def yes(event: E) -> None:
1487 session.default_buffer.text = "y"
1488 event.app.exit(result=True)
1489
1490 @bindings.add("n")
1491 @bindings.add("N")
1492 def no(event: E) -> None:
1493 session.default_buffer.text = "n"
1494 event.app.exit(result=False)
1495
1496 @bindings.add(Keys.Any)
1497 def _(event: E) -> None:
1498 "Disallow inserting other text."
1499 pass
1500
1501 complete_message = merge_formatted_text([message, suffix])
1502 session: PromptSession[bool] = PromptSession(
1503 complete_message, key_bindings=bindings
1504 )
1505 return session
1506
1507
1508def confirm(message: str = "Confirm?", suffix: str = " (y/n) ") -> bool:
1509 """
1510 Display a confirmation prompt that returns True/False.
1511 """
1512 session = create_confirm_session(message, suffix)
1513 return session.prompt()