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