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