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"""
27from __future__ import unicode_literals
28
29import contextlib
30import threading
31import time
32from functools import partial
33
34from six import text_type
35
36from prompt_toolkit.application import Application
37from prompt_toolkit.application.current import get_app
38from prompt_toolkit.auto_suggest import DynamicAutoSuggest
39from prompt_toolkit.buffer import Buffer
40from prompt_toolkit.clipboard import DynamicClipboard, InMemoryClipboard
41from prompt_toolkit.completion import DynamicCompleter, ThreadedCompleter
42from prompt_toolkit.document import Document
43from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
44from prompt_toolkit.eventloop import (
45    From,
46    Return,
47    ensure_future,
48    get_event_loop,
49)
50from prompt_toolkit.filters import (
51    Condition,
52    has_arg,
53    has_focus,
54    is_done,
55    is_true,
56    renderer_height_is_known,
57    to_filter,
58)
59from prompt_toolkit.formatted_text import (
60    fragment_list_to_text,
61    merge_formatted_text,
62    to_formatted_text,
63)
64from prompt_toolkit.history import InMemoryHistory
65from prompt_toolkit.input.defaults import get_default_input
66from prompt_toolkit.key_binding.bindings.auto_suggest import (
67    load_auto_suggest_bindings,
68)
69from prompt_toolkit.key_binding.bindings.completion import (
70    display_completions_like_readline,
71)
72from prompt_toolkit.key_binding.bindings.open_in_editor import (
73    load_open_in_editor_bindings,
74)
75from prompt_toolkit.key_binding.key_bindings import (
76    ConditionalKeyBindings,
77    DynamicKeyBindings,
78    KeyBindings,
79    KeyBindingsBase,
80    merge_key_bindings,
81)
82from prompt_toolkit.keys import Keys
83from prompt_toolkit.layout import Float, FloatContainer, HSplit, Window
84from prompt_toolkit.layout.containers import ConditionalContainer, WindowAlign
85from prompt_toolkit.layout.controls import (
86    BufferControl,
87    FormattedTextControl,
88    SearchBufferControl,
89)
90from prompt_toolkit.layout.dimension import Dimension
91from prompt_toolkit.layout.layout import Layout
92from prompt_toolkit.layout.menus import (
93    CompletionsMenu,
94    MultiColumnCompletionsMenu,
95)
96from prompt_toolkit.layout.processors import (
97    AppendAutoSuggestion,
98    ConditionalProcessor,
99    DisplayMultipleCursors,
100    DynamicProcessor,
101    HighlightIncrementalSearchProcessor,
102    HighlightSelectionProcessor,
103    PasswordProcessor,
104    ReverseSearchProcessor,
105    merge_processors,
106)
107from prompt_toolkit.layout.utils import explode_text_fragments
108from prompt_toolkit.lexers import DynamicLexer
109from prompt_toolkit.output.defaults import get_default_output
110from prompt_toolkit.styles import (
111    BaseStyle,
112    ConditionalStyleTransformation,
113    DynamicStyle,
114    DynamicStyleTransformation,
115    StyleTransformation,
116    SwapLightAndDarkStyleTransformation,
117    merge_style_transformations,
118)
119from prompt_toolkit.utils import get_cwidth, suspend_to_background_supported
120from prompt_toolkit.validation import DynamicValidator
121from prompt_toolkit.widgets.toolbars import (
122    SearchToolbar,
123    SystemToolbar,
124    ValidationToolbar,
125)
126
127__all__ = [
128    'PromptSession',
129    'prompt',
130    'confirm',
131    'create_confirm_session',  # Used by '_display_completions_like_readline'.
132    'CompleteStyle',
133]
134
135
136def _split_multiline_prompt(get_prompt_text):
137    """
138    Take a `get_prompt_text` function and return three new functions instead.
139    One that tells whether this prompt consists of multiple lines; one that
140    returns the fragments to be shown on the lines above the input; and another
141    one with the fragments to be shown at the first line of the input.
142    """
143    def has_before_fragments():
144        for fragment, char in get_prompt_text():
145            if '\n' in char:
146                return True
147        return False
148
149    def before():
150        result = []
151        found_nl = False
152        for fragment, char in reversed(explode_text_fragments(get_prompt_text())):
153            if found_nl:
154                result.insert(0, (fragment, char))
155            elif char == '\n':
156                found_nl = True
157        return result
158
159    def first_input_line():
160        result = []
161        for fragment, char in reversed(explode_text_fragments(get_prompt_text())):
162            if char == '\n':
163                break
164            else:
165                result.insert(0, (fragment, char))
166        return result
167
168    return has_before_fragments, before, first_input_line
169
170
171class _RPrompt(Window):
172    " The prompt that is displayed on the right side of the Window. "
173    def __init__(self, get_formatted_text):
174        super(_RPrompt, self).__init__(
175            FormattedTextControl(get_formatted_text),
176            align=WindowAlign.RIGHT,
177            style='class:rprompt')
178
179
180class CompleteStyle:
181    " How to display autocompletions for the prompt. "
182    COLUMN = 'COLUMN'
183    MULTI_COLUMN = 'MULTI_COLUMN'
184    READLINE_LIKE = 'READLINE_LIKE'
185
186
187class PromptSession(object):
188    """
189    PromptSession for a prompt application, which can be used as a GNU Readline
190    replacement.
191
192    This is a wrapper around a lot of ``prompt_toolkit`` functionality and can
193    be a replacement for `raw_input`.
194
195    All parameters that expect "formatted text" can take either just plain text
196    (a unicode object), a list of ``(style_str, text)`` tuples or an HTML object.
197
198    Example usage::
199
200        s = PromptSession(message='>')
201        text = s.prompt()
202
203    :param message: Plain text or formatted text to be shown before the prompt.
204        This can also be a callable that returns formatted text.
205    :param multiline: `bool` or :class:`~prompt_toolkit.filters.Filter`.
206        When True, prefer a layout that is more adapted for multiline input.
207        Text after newlines is automatically indented, and search/arg input is
208        shown below the input, instead of replacing the prompt.
209    :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.Filter`.
210        When True (the default), automatically wrap long lines instead of
211        scrolling horizontally.
212    :param is_password: Show asterisks instead of the actual typed characters.
213    :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``.
214    :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``.
215    :param complete_while_typing: `bool` or
216        :class:`~prompt_toolkit.filters.Filter`. Enable autocompletion while
217        typing.
218    :param validate_while_typing: `bool` or
219        :class:`~prompt_toolkit.filters.Filter`. Enable input validation while
220        typing.
221    :param enable_history_search: `bool` or
222        :class:`~prompt_toolkit.filters.Filter`. Enable up-arrow parting
223        string matching.
224    :param search_ignore_case:
225        :class:`~prompt_toolkit.filters.Filter`. Search case insensitive.
226    :param lexer: :class:`~prompt_toolkit.lexers.Lexer` to be used for the
227        syntax highlighting.
228    :param validator: :class:`~prompt_toolkit.validation.Validator` instance
229        for input validation.
230    :param completer: :class:`~prompt_toolkit.completion.Completer` instance
231        for input completion.
232    :param complete_in_thread: `bool` or
233        :class:`~prompt_toolkit.filters.Filter`. Run the completer code in a
234        background thread in order to avoid blocking the user interface.
235        For ``CompleteStyle.READLINE_LIKE``, this setting has no effect. There
236        we always run the completions in the main thread.
237    :param reserve_space_for_menu: Space to be reserved for displaying the menu.
238        (0 means that no space needs to be reserved.)
239    :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
240        instance for input suggestions.
241    :param style: :class:`.Style` instance for the color scheme.
242    :param include_default_pygments_style: `bool` or
243        :class:`~prompt_toolkit.filters.Filter`. Tell whether the default
244        styling for Pygments lexers has to be included. By default, this is
245        true, but it is recommended to be disabled if another Pygments style is
246        passed as the `style` argument, otherwise, two Pygments styles will be
247        merged.
248    :param style_transformation:
249        :class:`~prompt_toolkit.style.StyleTransformation` instance.
250    :param swap_light_and_dark_colors: `bool` or
251        :class:`~prompt_toolkit.filters.Filter`. When enabled, apply
252        :class:`~prompt_toolkit.style.SwapLightAndDarkStyleTransformation`.
253        This is useful for switching between dark and light terminal
254        backgrounds.
255    :param enable_system_prompt: `bool` or
256        :class:`~prompt_toolkit.filters.Filter`. Pressing Meta+'!' will show
257        a system prompt.
258    :param enable_suspend: `bool` or :class:`~prompt_toolkit.filters.Filter`.
259        Enable Control-Z style suspension.
260    :param enable_open_in_editor: `bool` or
261        :class:`~prompt_toolkit.filters.Filter`. Pressing 'v' in Vi mode or
262        C-X C-E in emacs mode will open an external editor.
263    :param history: :class:`~prompt_toolkit.history.History` instance.
264    :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` instance.
265        (e.g. :class:`~prompt_toolkit.clipboard.InMemoryClipboard`)
266    :param rprompt: Text or formatted text to be displayed on the right side.
267        This can also be a callable that returns (formatted) text.
268    :param bottom_toolbar: Formatted text or callable which is supposed to
269        return formatted text.
270    :param prompt_continuation: Text that needs to be displayed for a multiline
271        prompt continuation. This can either be formatted text or a callable
272        that takes a `prompt_width`, `line_number` and `wrap_count` as input
273        and returns formatted text.
274    :param complete_style: ``CompleteStyle.COLUMN``,
275        ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``.
276    :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter`
277        to enable mouse support.
278    :param refresh_interval: (number; in seconds) When given, refresh the UI
279        every so many seconds.
280    :param inputhook: None or an Inputhook callable that takes an
281        `InputHookContext` object.
282    """
283    _fields = (
284        'message', 'lexer', 'completer', 'complete_in_thread', 'is_password',
285        'editing_mode', 'key_bindings', 'is_password', 'bottom_toolbar',
286        'style', 'style_transformation', 'swap_light_and_dark_colors',
287        'color_depth', 'include_default_pygments_style', 'rprompt',
288        'multiline', 'prompt_continuation', 'wrap_lines',
289        'enable_history_search', 'search_ignore_case', 'complete_while_typing',
290        'validate_while_typing', 'complete_style', 'mouse_support',
291        'auto_suggest', 'clipboard', 'validator', 'refresh_interval',
292        'input_processors', 'enable_system_prompt', 'enable_suspend',
293        'enable_open_in_editor', 'reserve_space_for_menu', 'tempfile_suffix',
294        'inputhook')
295
296    def __init__(
297            self,
298            message='',
299            multiline=False,
300            wrap_lines=True,
301            is_password=False,
302            vi_mode=False,
303            editing_mode=EditingMode.EMACS,
304            complete_while_typing=True,
305            validate_while_typing=True,
306            enable_history_search=False,
307            search_ignore_case=False,
308            lexer=None,
309            enable_system_prompt=False,
310            enable_suspend=False,
311            enable_open_in_editor=False,
312            validator=None,
313            completer=None,
314            complete_in_thread=False,
315            reserve_space_for_menu=8,
316            complete_style=None,
317            auto_suggest=None,
318            style=None,
319            style_transformation=None,
320            swap_light_and_dark_colors=False,
321            color_depth=None,
322            include_default_pygments_style=True,
323            history=None,
324            clipboard=None,
325            prompt_continuation=None,
326            rprompt=None,
327            bottom_toolbar=None,
328            mouse_support=False,
329            input_processors=None,
330            key_bindings=None,
331            erase_when_done=False,
332            tempfile_suffix='.txt',
333            inputhook=None,
334
335            refresh_interval=0,
336            input=None,
337            output=None):
338        assert style is None or isinstance(style, BaseStyle)
339        assert style_transformation is None or isinstance(style_transformation, StyleTransformation)
340        assert input_processors is None or isinstance(input_processors, list)
341        assert key_bindings is None or isinstance(key_bindings, KeyBindingsBase)
342
343        # Defaults.
344        output = output or get_default_output()
345        input = input or get_default_input()
346
347        history = history or InMemoryHistory()
348        clipboard = clipboard or InMemoryClipboard()
349
350        # Ensure backwards-compatibility, when `vi_mode` is passed.
351        if vi_mode:
352            editing_mode = EditingMode.VI
353
354        # Store all settings in this class.
355        self.input = input
356        self.output = output
357
358        # Store all settings in this class.
359        for name in self._fields:
360            if name not in ('editing_mode', ):
361                value = locals()[name]
362                setattr(self, name, value)
363
364        # Create buffers, layout and Application.
365        self.history = history
366        self.default_buffer = self._create_default_buffer()
367        self.search_buffer = self._create_search_buffer()
368        self.layout = self._create_layout()
369        self.app = self._create_application(editing_mode, erase_when_done)
370
371    def _dyncond(self, attr_name):
372        """
373        Dynamically take this setting from this 'PromptSession' class.
374        `attr_name` represents an attribute name of this class. Its value
375        can either be a boolean or a `Filter`.
376
377        This returns something that can be used as either a `Filter`
378        or `Filter`.
379        """
380        @Condition
381        def dynamic():
382            value = getattr(self, attr_name)
383            return to_filter(value)()
384        return dynamic
385
386    def _create_default_buffer(self):
387        """
388        Create and return the default input buffer.
389        """
390        dyncond = self._dyncond
391
392        # Create buffers list.
393        def accept(buff):
394            """ Accept the content of the default buffer. This is called when
395            the validation succeeds. """
396            self.app.exit(result=buff.document.text)
397            return True  # Keep text, we call 'reset' later on.
398
399        return Buffer(
400            name=DEFAULT_BUFFER,
401                # Make sure that complete_while_typing is disabled when
402                # enable_history_search is enabled. (First convert to Filter,
403                # to avoid doing bitwise operations on bool objects.)
404            complete_while_typing=Condition(lambda:
405                is_true(self.complete_while_typing) and not
406                is_true(self.enable_history_search) and not
407                self.complete_style == CompleteStyle.READLINE_LIKE),
408            validate_while_typing=dyncond('validate_while_typing'),
409            enable_history_search=dyncond('enable_history_search'),
410            validator=DynamicValidator(lambda: self.validator),
411            completer=DynamicCompleter(lambda:
412                ThreadedCompleter(self.completer)
413                if self.complete_in_thread and self.completer
414                else self.completer),
415            history=self.history,
416            auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
417            accept_handler=accept,
418            tempfile_suffix=lambda: self.tempfile_suffix)
419
420    def _create_search_buffer(self):
421        return Buffer(name=SEARCH_BUFFER)
422
423    def _create_layout(self):
424        """
425        Create `Layout` for this prompt.
426        """
427        dyncond = self._dyncond
428
429        # Create functions that will dynamically split the prompt. (If we have
430        # a multiline prompt.)
431        has_before_fragments, get_prompt_text_1, get_prompt_text_2 = \
432            _split_multiline_prompt(self._get_prompt)
433
434        default_buffer = self.default_buffer
435        search_buffer = self.search_buffer
436
437        # Create processors list.
438        all_input_processors = [
439            HighlightIncrementalSearchProcessor(),
440            HighlightSelectionProcessor(),
441            ConditionalProcessor(AppendAutoSuggestion(),
442                                 has_focus(default_buffer) & ~is_done),
443            ConditionalProcessor(PasswordProcessor(), dyncond('is_password')),
444            DisplayMultipleCursors(),
445
446            # Users can insert processors here.
447            DynamicProcessor(lambda: merge_processors(self.input_processors or [])),
448        ]
449
450        # Create bottom toolbars.
451        bottom_toolbar = ConditionalContainer(
452            Window(FormattedTextControl(
453                        lambda: self.bottom_toolbar,
454                        style='class:bottom-toolbar.text'),
455                   style='class:bottom-toolbar',
456                   dont_extend_height=True,
457                   height=Dimension(min=1)),
458            filter=~is_done & renderer_height_is_known &
459                    Condition(lambda: self.bottom_toolbar is not None))
460
461        search_toolbar = SearchToolbar(
462            search_buffer,
463            ignore_case=dyncond('search_ignore_case'))
464
465        search_buffer_control = SearchBufferControl(
466            buffer=search_buffer,
467            input_processors=[
468                ReverseSearchProcessor(),
469            ],
470            ignore_case=dyncond('search_ignore_case'))
471
472        system_toolbar = SystemToolbar(
473            enable_global_bindings=dyncond('enable_system_prompt'))
474
475        def get_search_buffer_control():
476            " Return the UIControl to be focused when searching start. "
477            if is_true(self.multiline):
478                return search_toolbar.control
479            else:
480                return search_buffer_control
481
482        default_buffer_control = BufferControl(
483            buffer=default_buffer,
484            search_buffer_control=get_search_buffer_control,
485            input_processors=all_input_processors,
486            include_default_input_processors=False,
487            lexer=DynamicLexer(lambda: self.lexer),
488            preview_search=True)
489
490        default_buffer_window = Window(
491            default_buffer_control,
492            height=self._get_default_buffer_control_height,
493            get_line_prefix=partial(
494                self._get_line_prefix, get_prompt_text_2=get_prompt_text_2),
495            wrap_lines=dyncond('wrap_lines'))
496
497        @Condition
498        def multi_column_complete_style():
499            return self.complete_style == CompleteStyle.MULTI_COLUMN
500
501        # Build the layout.
502        layout = HSplit([
503            # The main input, with completion menus floating on top of it.
504            FloatContainer(
505                HSplit([
506                    ConditionalContainer(
507                        Window(
508                            FormattedTextControl(get_prompt_text_1),
509                            dont_extend_height=True),
510                        Condition(has_before_fragments)
511                    ),
512                    ConditionalContainer(
513                        default_buffer_window,
514                        Condition(lambda:
515                            get_app().layout.current_control != search_buffer_control),
516                    ),
517                    ConditionalContainer(
518                        Window(search_buffer_control),
519                        Condition(lambda:
520                            get_app().layout.current_control == search_buffer_control),
521                    ),
522                ]),
523                [
524                    # Completion menus.
525                    # NOTE: Especially the multi-column menu needs to be
526                    #       transparent, because the shape is not always
527                    #       rectangular due to the meta-text below the menu.
528                    Float(xcursor=True,
529                          ycursor=True,
530                          transparent=True,
531                          content=CompletionsMenu(
532                              max_height=16,
533                              scroll_offset=1,
534                              extra_filter=has_focus(default_buffer) &
535                                  ~multi_column_complete_style)),
536                    Float(xcursor=True,
537                          ycursor=True,
538                          transparent=True,
539                          content=MultiColumnCompletionsMenu(
540                              show_meta=True,
541                              extra_filter=has_focus(default_buffer) &
542                                  multi_column_complete_style)),
543                    # The right prompt.
544                    Float(right=0, top=0, hide_when_covering_content=True,
545                          content=_RPrompt(lambda: self.rprompt)),
546                ]
547            ),
548            ConditionalContainer(
549                ValidationToolbar(),
550                filter=~is_done),
551            ConditionalContainer(
552                system_toolbar,
553                dyncond('enable_system_prompt') & ~is_done),
554
555            # In multiline mode, we use two toolbars for 'arg' and 'search'.
556            ConditionalContainer(
557                Window(FormattedTextControl(self._get_arg_text), height=1),
558                dyncond('multiline') & has_arg),
559            ConditionalContainer(search_toolbar, dyncond('multiline') & ~is_done),
560            bottom_toolbar,
561        ])
562
563        return Layout(layout, default_buffer_window)
564
565    def _create_application(self, editing_mode, erase_when_done):
566        """
567        Create the `Application` object.
568        """
569        dyncond = self._dyncond
570
571        # Default key bindings.
572        auto_suggest_bindings = load_auto_suggest_bindings()
573        open_in_editor_bindings = load_open_in_editor_bindings()
574        prompt_bindings = self._create_prompt_bindings()
575
576        # Create application
577        application = Application(
578            layout=self.layout,
579            style=DynamicStyle(lambda: self.style),
580            style_transformation=merge_style_transformations([
581                DynamicStyleTransformation(lambda: self.style_transformation),
582                ConditionalStyleTransformation(
583                    SwapLightAndDarkStyleTransformation(),
584                    dyncond('swap_light_and_dark_colors'),
585                ),
586            ]),
587            include_default_pygments_style=dyncond('include_default_pygments_style'),
588            clipboard=DynamicClipboard(lambda: self.clipboard),
589            key_bindings=merge_key_bindings([
590                merge_key_bindings([
591                    auto_suggest_bindings,
592                    ConditionalKeyBindings(open_in_editor_bindings,
593                        dyncond('enable_open_in_editor') &
594                        has_focus(DEFAULT_BUFFER)),
595                    prompt_bindings
596                ]),
597                DynamicKeyBindings(lambda: self.key_bindings),
598            ]),
599            mouse_support=dyncond('mouse_support'),
600            editing_mode=editing_mode,
601            erase_when_done=erase_when_done,
602            reverse_vi_search_direction=True,
603            color_depth=lambda: self.color_depth,
604
605            # I/O.
606            input=self.input,
607            output=self.output)
608
609        # During render time, make sure that we focus the right search control
610        # (if we are searching). - This could be useful if people make the
611        # 'multiline' property dynamic.
612        '''
613        def on_render(app):
614            multiline = is_true(self.multiline)
615            current_control = app.layout.current_control
616
617            if multiline:
618                if current_control == search_buffer_control:
619                    app.layout.current_control = search_toolbar.control
620                    app.invalidate()
621            else:
622                if current_control == search_toolbar.control:
623                    app.layout.current_control = search_buffer_control
624                    app.invalidate()
625
626        app.on_render += on_render
627        '''
628
629        return application
630
631    def _create_prompt_bindings(self):
632        """
633        Create the KeyBindings for a prompt application.
634        """
635        kb = KeyBindings()
636        handle = kb.add
637        default_focused = has_focus(DEFAULT_BUFFER)
638
639        @Condition
640        def do_accept():
641            return (not is_true(self.multiline) and
642                    self.app.layout.has_focus(DEFAULT_BUFFER))
643
644        @handle('enter', filter=do_accept & default_focused)
645        def _(event):
646            " Accept input when enter has been pressed. "
647            self.default_buffer.validate_and_handle()
648
649        @Condition
650        def readline_complete_style():
651            return self.complete_style == CompleteStyle.READLINE_LIKE
652
653        @handle('tab', filter=readline_complete_style & default_focused)
654        def _(event):
655            " Display completions (like Readline). "
656            display_completions_like_readline(event)
657
658        @handle('c-c', filter=default_focused)
659        def _(event):
660            " Abort when Control-C has been pressed. "
661            event.app.exit(exception=KeyboardInterrupt, style='class:aborting')
662
663        @Condition
664        def ctrl_d_condition():
665            """ Ctrl-D binding is only active when the default buffer is selected
666            and empty. """
667            app = get_app()
668            return (app.current_buffer.name == DEFAULT_BUFFER and
669                    not app.current_buffer.text)
670
671        @handle('c-d', filter=ctrl_d_condition & default_focused)
672        def _(event):
673            " Exit when Control-D has been pressed. "
674            event.app.exit(exception=EOFError, style='class:exiting')
675
676        suspend_supported = Condition(suspend_to_background_supported)
677
678        @Condition
679        def enable_suspend():
680            return to_filter(self.enable_suspend)()
681
682        @handle('c-z', filter=suspend_supported & enable_suspend)
683        def _(event):
684            """
685            Suspend process to background.
686            """
687            event.app.suspend_to_background()
688
689        return kb
690
691    @contextlib.contextmanager
692    def _auto_refresh_context(self):
693        " Return a context manager for the auto-refresh loop. "
694        done = [False]  # nonlocal
695
696        # Enter.
697
698        def run():
699            while not done[0]:
700                time.sleep(self.refresh_interval)
701                self.app.invalidate()
702
703        if self.refresh_interval:
704            t = threading.Thread(target=run)
705            t.daemon = True
706            t.start()
707
708        try:
709            yield
710        finally:
711            # Exit.
712            done[0] = True
713
714    def prompt(
715            self,
716            # When any of these arguments are passed, this value is overwritten
717            # in this PromptSession.
718            message=None,  # `message` should go first, because people call it
719                           # as positional argument.
720            editing_mode=None, refresh_interval=None, vi_mode=None, lexer=None,
721            completer=None, complete_in_thread=None, is_password=None,
722            key_bindings=None, bottom_toolbar=None, style=None,
723            color_depth=None, include_default_pygments_style=None,
724            style_transformation=None, swap_light_and_dark_colors=None,
725            rprompt=None, multiline=None, prompt_continuation=None,
726            wrap_lines=None, enable_history_search=None,
727            search_ignore_case=None, complete_while_typing=None,
728            validate_while_typing=None, complete_style=None, auto_suggest=None,
729            validator=None, clipboard=None, mouse_support=None,
730            input_processors=None, reserve_space_for_menu=None,
731            enable_system_prompt=None, enable_suspend=None,
732            enable_open_in_editor=None, tempfile_suffix=None, inputhook=None,
733
734            # Following arguments are specific to the current `prompt()` call.
735            async_=False, default='', accept_default=False, pre_run=None):
736        """
737        Display the prompt. All the arguments are a subset of the
738        :class:`~.PromptSession` class itself.
739
740        This will raise ``KeyboardInterrupt`` when control-c has been pressed
741        (for abort) and ``EOFError`` when control-d has been pressed (for
742        exit).
743
744        Additional arguments, specific for this prompt:
745
746        :param async_: When `True` return a `Future` instead of waiting for the
747            prompt to finish.
748        :param default: The default input text to be shown. (This can be edited
749            by the user).
750        :param accept_default: When `True`, automatically accept the default
751            value without allowing the user to edit the input.
752        :param pre_run: Callable, called at the start of `Application.run`.
753        """
754        assert isinstance(default, text_type)
755
756        # NOTE: We used to create a backup of the PromptSession attributes and
757        #       restore them after exiting the prompt. This code has been
758        #       removed, because it was confusing and didn't really serve a use
759        #       case. (People were changing `Application.editing_mode`
760        #       dynamically and surprised that it was reset after every call.)
761
762        # Take settings from 'prompt'-arguments.
763        for name in self._fields:
764            value = locals()[name]
765            if value is not None:
766                setattr(self, name, value)
767
768        if vi_mode:
769            self.editing_mode = EditingMode.VI
770
771        def pre_run2():
772            if pre_run:
773                pre_run()
774
775            if accept_default:
776                # Validate and handle input. We use `call_from_executor` in
777                # order to run it "soon" (during the next iteration of the
778                # event loop), instead of right now. Otherwise, it won't
779                # display the default value.
780                get_event_loop().call_from_executor(
781                    self.default_buffer.validate_and_handle)
782
783        def run_sync():
784            with self._auto_refresh_context():
785                self.default_buffer.reset(Document(default))
786                return self.app.run(inputhook=self.inputhook, pre_run=pre_run2)
787
788        def run_async():
789            with self._auto_refresh_context():
790                self.default_buffer.reset(Document(default))
791                result = yield From(self.app.run_async(pre_run=pre_run2))
792                raise Return(result)
793
794        if async_:
795            return ensure_future(run_async())
796        else:
797            return run_sync()
798
799    @property
800    def editing_mode(self):
801        return self.app.editing_mode
802
803    @editing_mode.setter
804    def editing_mode(self, value):
805        self.app.editing_mode = value
806
807    def _get_default_buffer_control_height(self):
808        # If there is an autocompletion menu to be shown, make sure that our
809        # layout has at least a minimal height in order to display it.
810        if (self.completer is not None and
811                self.complete_style != CompleteStyle.READLINE_LIKE):
812            space = self.reserve_space_for_menu
813        else:
814            space = 0
815
816        if space and not get_app().is_done:
817            buff = self.default_buffer
818
819            # Reserve the space, either when there are completions, or when
820            # `complete_while_typing` is true and we expect completions very
821            # soon.
822            if buff.complete_while_typing() or buff.complete_state is not None:
823                return Dimension(min=space)
824
825        return Dimension()
826
827    def _get_prompt(self):
828        return to_formatted_text(self.message, style='class:prompt')
829
830    def _get_continuation(self, width, line_number, wrap_count):
831        """
832        Insert the prompt continuation.
833
834        :param width: The width that was used for the prompt. (more or less can
835            be used.)
836        :param line_number:
837        :param wrap_count: Amount of times that the line has been wrapped.
838        """
839        prompt_continuation = self.prompt_continuation
840
841        if callable(prompt_continuation):
842            prompt_continuation = prompt_continuation(width, line_number, wrap_count)
843
844        # When the continuation prompt is not given, choose the same width as
845        # the actual prompt.
846        if not prompt_continuation and is_true(self.multiline):
847            prompt_continuation = ' ' * width
848
849        return to_formatted_text(
850            prompt_continuation, style='class:prompt-continuation')
851
852    def _get_line_prefix(self, line_number, wrap_count, get_prompt_text_2):
853        """
854        Return whatever needs to be inserted before every line.
855        (the prompt, or a line continuation.)
856        """
857        # First line: display the "arg" or the prompt.
858        if line_number == 0 and wrap_count == 0:
859            if not is_true(self.multiline) and get_app().key_processor.arg is not None:
860                return self._inline_arg()
861            else:
862                return get_prompt_text_2()
863
864        # For the next lines, display the appropriate continuation.
865        prompt_width = get_cwidth(fragment_list_to_text(get_prompt_text_2()))
866        return self._get_continuation(prompt_width, line_number, wrap_count)
867
868    def _get_arg_text(self):
869        " 'arg' toolbar, for in multiline mode. "
870        arg = self.app.key_processor.arg
871        if arg == '-':
872            arg = '-1'
873
874        return [
875            ('class:arg-toolbar', 'Repeat: '),
876            ('class:arg-toolbar.text', arg)
877        ]
878
879    def _inline_arg(self):
880        " 'arg' prefix, for in single line mode. "
881        app = get_app()
882        if app.key_processor.arg is None:
883            return []
884        else:
885            arg = app.key_processor.arg
886
887            return [
888                ('class:prompt.arg', '(arg: '),
889                ('class:prompt.arg.text', str(arg)),
890                ('class:prompt.arg', ') '),
891            ]
892
893
894def prompt(*a, **kw):
895    """ The global `prompt` function. This will create a new `PromptSession`
896    instance for every call.  """
897    # Input and output arguments have to be passed to the 'PromptSession'
898    # class, not its method.
899    input = kw.pop('input', None)
900    output = kw.pop('output', None)
901    history = kw.pop('history', None)
902
903    session = PromptSession(input=input, output=output, history=history)
904    return session.prompt(*a, **kw)
905
906
907prompt.__doc__ = PromptSession.prompt.__doc__
908
909
910def create_confirm_session(message, suffix=' (y/n) '):
911    """
912    Create a `PromptSession` object for the 'confirm' function.
913    """
914    assert isinstance(message, text_type)
915    bindings = KeyBindings()
916
917    @bindings.add('y')
918    @bindings.add('Y')
919    def yes(event):
920        session.default_buffer.text = 'y'
921        event.app.exit(result=True)
922
923    @bindings.add('n')
924    @bindings.add('N')
925    def no(event):
926        session.default_buffer.text = 'n'
927        event.app.exit(result=False)
928
929    @bindings.add(Keys.Any)
930    def _(event):
931        " Disallow inserting other text. "
932        pass
933
934    complete_message = merge_formatted_text([message, suffix])
935    session = PromptSession(complete_message, key_bindings=bindings)
936    return session
937
938
939def confirm(message='Confirm?', suffix=' (y/n) '):
940    """
941    Display a confirmation prompt that returns True/False.
942    """
943    session = create_confirm_session(message, suffix)
944    return session.prompt()
945