diff options
Diffstat (limited to 'contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py')
-rw-r--r-- | contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py new file mode 100644 index 0000000000..9893624c6e --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py @@ -0,0 +1,717 @@ +""" +Shortcuts for retrieving input from the user. + +If you are using this library for retrieving some input from the user (as a +pure Python replacement for GNU readline), probably for 90% of the use cases, +the :func:`.prompt` function is all you need. It's the easiest shortcut which +does a lot of the underlying work like creating a +:class:`~prompt_toolkit.interface.CommandLineInterface` instance for you. + +When is this not sufficient: + - When you want to have more complicated layouts (maybe with sidebars or + multiple toolbars. Or visibility of certain user interface controls + according to some conditions.) + - When you wish to have multiple input buffers. (If you would create an + editor like a Vi clone.) + - Something else that requires more customization than what is possible + with the parameters of `prompt`. + +In that case, study the code in this file and build your own +`CommandLineInterface` instance. It's not too complicated. +""" +from __future__ import unicode_literals + +from .buffer import Buffer, AcceptAction +from .document import Document +from .enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode +from .filters import IsDone, HasFocus, RendererHeightIsKnown, to_simple_filter, to_cli_filter, Condition +from .history import InMemoryHistory +from .interface import CommandLineInterface, Application, AbortAction +from .key_binding.defaults import load_key_bindings_for_prompt +from .key_binding.registry import Registry +from .keys import Keys +from .layout import Window, HSplit, FloatContainer, Float +from .layout.containers import ConditionalContainer +from .layout.controls import BufferControl, TokenListControl +from .layout.dimension import LayoutDimension +from .layout.lexers import PygmentsLexer +from .layout.margins import PromptMargin, ConditionalMargin +from .layout.menus import CompletionsMenu, MultiColumnCompletionsMenu +from .layout.processors import PasswordProcessor, ConditionalProcessor, AppendAutoSuggestion, HighlightSearchProcessor, HighlightSelectionProcessor, DisplayMultipleCursors +from .layout.prompt import DefaultPrompt +from .layout.screen import Char +from .layout.toolbars import ValidationToolbar, SystemToolbar, ArgToolbar, SearchToolbar +from .layout.utils import explode_tokens +from .renderer import print_tokens as renderer_print_tokens +from .styles import DEFAULT_STYLE, Style, style_from_dict +from .token import Token +from .utils import is_conemu_ansi, is_windows, DummyContext + +from six import text_type, exec_, PY2 + +import os +import sys +import textwrap +import threading +import time + +try: + from pygments.lexer import Lexer as pygments_Lexer + from pygments.style import Style as pygments_Style +except ImportError: + pygments_Lexer = None + pygments_Style = None + +if is_windows(): + from .terminal.win32_output import Win32Output + from .terminal.conemu_output import ConEmuOutput +else: + from .terminal.vt100_output import Vt100_Output + + +__all__ = ( + 'create_eventloop', + 'create_output', + 'create_prompt_layout', + 'create_prompt_application', + 'prompt', + 'prompt_async', + 'create_confirm_application', + 'run_application', + 'confirm', + 'print_tokens', + 'clear', +) + + +def create_eventloop(inputhook=None, recognize_win32_paste=True): + """ + Create and return an + :class:`~prompt_toolkit.eventloop.base.EventLoop` instance for a + :class:`~prompt_toolkit.interface.CommandLineInterface`. + """ + if is_windows(): + from prompt_toolkit.eventloop.win32 import Win32EventLoop as Loop + return Loop(inputhook=inputhook, recognize_paste=recognize_win32_paste) + else: + from prompt_toolkit.eventloop.posix import PosixEventLoop as Loop + return Loop(inputhook=inputhook) + + +def create_output(stdout=None, true_color=False, ansi_colors_only=None): + """ + Return an :class:`~prompt_toolkit.output.Output` instance for the command + line. + + :param true_color: When True, use 24bit colors instead of 256 colors. + (`bool` or :class:`~prompt_toolkit.filters.SimpleFilter`.) + :param ansi_colors_only: When True, restrict to 16 ANSI colors only. + (`bool` or :class:`~prompt_toolkit.filters.SimpleFilter`.) + """ + stdout = stdout or sys.__stdout__ + true_color = to_simple_filter(true_color) + + if is_windows(): + if is_conemu_ansi(): + return ConEmuOutput(stdout) + else: + return Win32Output(stdout) + else: + term = os.environ.get('TERM', '') + if PY2: + term = term.decode('utf-8') + + return Vt100_Output.from_pty( + stdout, true_color=true_color, + ansi_colors_only=ansi_colors_only, term=term) + + +def create_asyncio_eventloop(loop=None): + """ + Returns an asyncio :class:`~prompt_toolkit.eventloop.EventLoop` instance + for usage in a :class:`~prompt_toolkit.interface.CommandLineInterface`. It + is a wrapper around an asyncio loop. + + :param loop: The asyncio eventloop (or `None` if the default asyncioloop + should be used.) + """ + # Inline import, to make sure the rest doesn't break on Python 2. (Where + # asyncio is not available.) + if is_windows(): + from prompt_toolkit.eventloop.asyncio_win32 import Win32AsyncioEventLoop as AsyncioEventLoop + else: + from prompt_toolkit.eventloop.asyncio_posix import PosixAsyncioEventLoop as AsyncioEventLoop + + return AsyncioEventLoop(loop) + + +def _split_multiline_prompt(get_prompt_tokens): + """ + Take a `get_prompt_tokens` function and return three new functions instead. + One that tells whether this prompt consists of multiple lines; one that + returns the tokens to be shown on the lines above the input; and another + one with the tokens to be shown at the first line of the input. + """ + def has_before_tokens(cli): + for token, char in get_prompt_tokens(cli): + if '\n' in char: + return True + return False + + def before(cli): + result = [] + found_nl = False + for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): + if found_nl: + result.insert(0, (token, char)) + elif char == '\n': + found_nl = True + return result + + def first_input_line(cli): + result = [] + for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): + if char == '\n': + break + else: + result.insert(0, (token, char)) + return result + + return has_before_tokens, before, first_input_line + + +class _RPrompt(Window): + " The prompt that is displayed on the right side of the Window. " + def __init__(self, get_tokens=None): + get_tokens = get_tokens or (lambda cli: []) + + super(_RPrompt, self).__init__( + TokenListControl(get_tokens, align_right=True)) + + +def create_prompt_layout(message='', lexer=None, is_password=False, + reserve_space_for_menu=8, + get_prompt_tokens=None, get_continuation_tokens=None, + get_rprompt_tokens=None, + get_bottom_toolbar_tokens=None, + display_completions_in_columns=False, + extra_input_processors=None, multiline=False, + wrap_lines=True): + """ + Create a :class:`.Container` instance for a prompt. + + :param message: Text to be used as prompt. + :param lexer: :class:`~prompt_toolkit.layout.lexers.Lexer` to be used for + the highlighting. + :param is_password: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. + When True, display input as '*'. + :param reserve_space_for_menu: Space to be reserved for the menu. When >0, + make sure that a minimal height is allocated in the terminal, in order + to display the completion menu. + :param get_prompt_tokens: An optional callable that returns the tokens to be + shown in the menu. (To be used instead of a `message`.) + :param get_continuation_tokens: An optional callable that takes a + CommandLineInterface and width as input and returns a list of (Token, + text) tuples to be used for the continuation. + :param get_bottom_toolbar_tokens: An optional callable that returns the + tokens for a toolbar at the bottom. + :param display_completions_in_columns: `bool` or + :class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in + multiple columns. + :param multiline: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. + When True, prefer a layout that is more adapted for multiline input. + Text after newlines is automatically indented, and search/arg input is + shown below the input, instead of replacing the prompt. + :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. + When True (the default), automatically wrap long lines instead of + scrolling horizontally. + """ + assert isinstance(message, text_type), 'Please provide a unicode string.' + assert get_bottom_toolbar_tokens is None or callable(get_bottom_toolbar_tokens) + assert get_prompt_tokens is None or callable(get_prompt_tokens) + assert get_rprompt_tokens is None or callable(get_rprompt_tokens) + assert not (message and get_prompt_tokens) + + display_completions_in_columns = to_cli_filter(display_completions_in_columns) + multiline = to_cli_filter(multiline) + + if get_prompt_tokens is None: + get_prompt_tokens = lambda _: [(Token.Prompt, message)] + + has_before_tokens, get_prompt_tokens_1, get_prompt_tokens_2 = \ + _split_multiline_prompt(get_prompt_tokens) + + # `lexer` is supposed to be a `Lexer` instance. But if a Pygments lexer + # class is given, turn it into a PygmentsLexer. (Important for + # backwards-compatibility.) + try: + if pygments_Lexer and issubclass(lexer, pygments_Lexer): + lexer = PygmentsLexer(lexer, sync_from_start=True) + except TypeError: # Happens when lexer is `None` or an instance of something else. + pass + + # Create processors list. + input_processors = [ + ConditionalProcessor( + # By default, only highlight search when the search + # input has the focus. (Note that this doesn't mean + # there is no search: the Vi 'n' binding for instance + # still allows to jump to the next match in + # navigation mode.) + HighlightSearchProcessor(preview_search=True), + HasFocus(SEARCH_BUFFER)), + HighlightSelectionProcessor(), + ConditionalProcessor(AppendAutoSuggestion(), HasFocus(DEFAULT_BUFFER) & ~IsDone()), + ConditionalProcessor(PasswordProcessor(), is_password), + DisplayMultipleCursors(DEFAULT_BUFFER), + ] + + if extra_input_processors: + input_processors.extend(extra_input_processors) + + # Show the prompt before the input (using the DefaultPrompt processor. + # This also replaces it with reverse-i-search and 'arg' when required. + # (Only for single line mode.) + # (DefaultPrompt should always be at the end of the processors.) + input_processors.append(ConditionalProcessor( + DefaultPrompt(get_prompt_tokens_2), ~multiline)) + + # Create bottom toolbar. + if get_bottom_toolbar_tokens: + toolbars = [ConditionalContainer( + Window(TokenListControl(get_bottom_toolbar_tokens, + default_char=Char(' ', Token.Toolbar)), + height=LayoutDimension.exact(1)), + filter=~IsDone() & RendererHeightIsKnown())] + else: + toolbars = [] + + def get_height(cli): + # If there is an autocompletion menu to be shown, make sure that our + # layout has at least a minimal height in order to display it. + if reserve_space_for_menu and not cli.is_done: + buff = cli.current_buffer + + # Reserve the space, either when there are completions, or when + # `complete_while_typing` is true and we expect completions very + # soon. + if buff.complete_while_typing() or buff.complete_state is not None: + return LayoutDimension(min=reserve_space_for_menu) + + return LayoutDimension() + + # Create and return Container instance. + return HSplit([ + # The main input, with completion menus floating on top of it. + FloatContainer( + HSplit([ + ConditionalContainer( + Window( + TokenListControl(get_prompt_tokens_1), + dont_extend_height=True), + Condition(has_before_tokens) + ), + Window( + BufferControl( + input_processors=input_processors, + lexer=lexer, + # Enable preview_search, we want to have immediate feedback + # in reverse-i-search mode. + preview_search=True), + get_height=get_height, + left_margins=[ + # In multiline mode, use the window margin to display + # the prompt and continuation tokens. + ConditionalMargin( + PromptMargin(get_prompt_tokens_2, get_continuation_tokens), + filter=multiline + ) + ], + wrap_lines=wrap_lines, + ), + ]), + [ + # Completion menus. + Float(xcursor=True, + ycursor=True, + content=CompletionsMenu( + max_height=16, + scroll_offset=1, + extra_filter=HasFocus(DEFAULT_BUFFER) & + ~display_completions_in_columns)), + Float(xcursor=True, + ycursor=True, + content=MultiColumnCompletionsMenu( + extra_filter=HasFocus(DEFAULT_BUFFER) & + display_completions_in_columns, + show_meta=True)), + + # The right prompt. + Float(right=0, top=0, hide_when_covering_content=True, + content=_RPrompt(get_rprompt_tokens)), + ] + ), + ValidationToolbar(), + SystemToolbar(), + + # In multiline mode, we use two toolbars for 'arg' and 'search'. + ConditionalContainer(ArgToolbar(), multiline), + ConditionalContainer(SearchToolbar(), multiline), + ] + toolbars) + + +def create_prompt_application( + message='', + multiline=False, + wrap_lines=True, + is_password=False, + vi_mode=False, + editing_mode=EditingMode.EMACS, + complete_while_typing=True, + enable_history_search=False, + lexer=None, + enable_system_bindings=False, + enable_open_in_editor=False, + validator=None, + completer=None, + reserve_space_for_menu=8, + auto_suggest=None, + style=None, + history=None, + clipboard=None, + get_prompt_tokens=None, + get_continuation_tokens=None, + get_rprompt_tokens=None, + get_bottom_toolbar_tokens=None, + display_completions_in_columns=False, + get_title=None, + mouse_support=False, + extra_input_processors=None, + key_bindings_registry=None, + on_abort=AbortAction.RAISE_EXCEPTION, + on_exit=AbortAction.RAISE_EXCEPTION, + accept_action=AcceptAction.RETURN_DOCUMENT, + erase_when_done=False, + default=''): + """ + Create an :class:`~Application` instance for a prompt. + + (It is meant to cover 90% of the prompt use cases, where no extreme + customization is required. For more complex input, it is required to create + a custom :class:`~Application` instance.) + + :param message: Text to be shown before the prompt. + :param mulitiline: Allow multiline input. Pressing enter will insert a + newline. (This requires Meta+Enter to accept the input.) + :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. + When True (the default), automatically wrap long lines instead of + scrolling horizontally. + :param is_password: Show asterisks instead of the actual typed characters. + :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``. + :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``. + :param complete_while_typing: `bool` or + :class:`~prompt_toolkit.filters.SimpleFilter`. Enable autocompletion + while typing. + :param enable_history_search: `bool` or + :class:`~prompt_toolkit.filters.SimpleFilter`. Enable up-arrow parting + string matching. + :param lexer: :class:`~prompt_toolkit.layout.lexers.Lexer` to be used for + the syntax highlighting. + :param validator: :class:`~prompt_toolkit.validation.Validator` instance + for input validation. + :param completer: :class:`~prompt_toolkit.completion.Completer` instance + for input completion. + :param reserve_space_for_menu: Space to be reserved for displaying the menu. + (0 means that no space needs to be reserved.) + :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` + instance for input suggestions. + :param style: :class:`.Style` instance for the color scheme. + :param enable_system_bindings: `bool` or + :class:`~prompt_toolkit.filters.CLIFilter`. Pressing Meta+'!' will show + a system prompt. + :param enable_open_in_editor: `bool` or + :class:`~prompt_toolkit.filters.CLIFilter`. Pressing 'v' in Vi mode or + C-X C-E in emacs mode will open an external editor. + :param history: :class:`~prompt_toolkit.history.History` instance. + :param clipboard: :class:`~prompt_toolkit.clipboard.base.Clipboard` instance. + (e.g. :class:`~prompt_toolkit.clipboard.in_memory.InMemoryClipboard`) + :param get_bottom_toolbar_tokens: Optional callable which takes a + :class:`~prompt_toolkit.interface.CommandLineInterface` and returns a + list of tokens for the bottom toolbar. + :param display_completions_in_columns: `bool` or + :class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in + multiple columns. + :param get_title: Callable that returns the title to be displayed in the + terminal. + :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.CLIFilter` + to enable mouse support. + :param default: The default text to be shown in the input buffer. (This can + be edited by the user.) + """ + if key_bindings_registry is None: + key_bindings_registry = load_key_bindings_for_prompt( + enable_system_bindings=enable_system_bindings, + enable_open_in_editor=enable_open_in_editor) + + # Ensure backwards-compatibility, when `vi_mode` is passed. + if vi_mode: + editing_mode = EditingMode.VI + + # Make sure that complete_while_typing is disabled when enable_history_search + # is enabled. (First convert to SimpleFilter, to avoid doing bitwise operations + # on bool objects.) + complete_while_typing = to_simple_filter(complete_while_typing) + enable_history_search = to_simple_filter(enable_history_search) + multiline = to_simple_filter(multiline) + + complete_while_typing = complete_while_typing & ~enable_history_search + + # Accept Pygments styles as well for backwards compatibility. + try: + if pygments_Style and issubclass(style, pygments_Style): + style = style_from_dict(style.styles) + except TypeError: # Happens when style is `None` or an instance of something else. + pass + + # Create application + return Application( + layout=create_prompt_layout( + message=message, + lexer=lexer, + is_password=is_password, + reserve_space_for_menu=(reserve_space_for_menu if completer is not None else 0), + multiline=Condition(lambda cli: multiline()), + get_prompt_tokens=get_prompt_tokens, + get_continuation_tokens=get_continuation_tokens, + get_rprompt_tokens=get_rprompt_tokens, + get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, + display_completions_in_columns=display_completions_in_columns, + extra_input_processors=extra_input_processors, + wrap_lines=wrap_lines), + buffer=Buffer( + enable_history_search=enable_history_search, + complete_while_typing=complete_while_typing, + is_multiline=multiline, + history=(history or InMemoryHistory()), + validator=validator, + completer=completer, + auto_suggest=auto_suggest, + accept_action=accept_action, + initial_document=Document(default), + ), + style=style or DEFAULT_STYLE, + clipboard=clipboard, + key_bindings_registry=key_bindings_registry, + get_title=get_title, + mouse_support=mouse_support, + editing_mode=editing_mode, + erase_when_done=erase_when_done, + reverse_vi_search_direction=True, + on_abort=on_abort, + on_exit=on_exit) + + +def prompt(message='', **kwargs): + """ + Get input from the user and return it. + + This is a wrapper around a lot of ``prompt_toolkit`` functionality and can + be a replacement for `raw_input`. (or GNU readline.) + + If you want to keep your history across several calls, create one + :class:`~prompt_toolkit.history.History` instance and pass it every time. + + This function accepts many keyword arguments. Except for the following, + they are a proxy to the arguments of :func:`.create_prompt_application`. + + :param patch_stdout: Replace ``sys.stdout`` by a proxy that ensures that + print statements from other threads won't destroy the prompt. (They + will be printed above the prompt instead.) + :param return_asyncio_coroutine: When True, return a asyncio coroutine. (Python >3.3) + :param true_color: When True, use 24bit colors instead of 256 colors. + :param refresh_interval: (number; in seconds) When given, refresh the UI + every so many seconds. + """ + patch_stdout = kwargs.pop('patch_stdout', False) + return_asyncio_coroutine = kwargs.pop('return_asyncio_coroutine', False) + true_color = kwargs.pop('true_color', False) + refresh_interval = kwargs.pop('refresh_interval', 0) + eventloop = kwargs.pop('eventloop', None) + + application = create_prompt_application(message, **kwargs) + + return run_application(application, + patch_stdout=patch_stdout, + return_asyncio_coroutine=return_asyncio_coroutine, + true_color=true_color, + refresh_interval=refresh_interval, + eventloop=eventloop) + + +def run_application( + application, patch_stdout=False, return_asyncio_coroutine=False, + true_color=False, refresh_interval=0, eventloop=None): + """ + Run a prompt toolkit application. + + :param patch_stdout: Replace ``sys.stdout`` by a proxy that ensures that + print statements from other threads won't destroy the prompt. (They + will be printed above the prompt instead.) + :param return_asyncio_coroutine: When True, return a asyncio coroutine. (Python >3.3) + :param true_color: When True, use 24bit colors instead of 256 colors. + :param refresh_interval: (number; in seconds) When given, refresh the UI + every so many seconds. + """ + assert isinstance(application, Application) + + if return_asyncio_coroutine: + eventloop = create_asyncio_eventloop() + else: + eventloop = eventloop or create_eventloop() + + # Create CommandLineInterface. + cli = CommandLineInterface( + application=application, + eventloop=eventloop, + output=create_output(true_color=true_color)) + + # Set up refresh interval. + if refresh_interval: + done = [False] + def start_refresh_loop(cli): + def run(): + while not done[0]: + time.sleep(refresh_interval) + cli.request_redraw() + t = threading.Thread(target=run) + t.daemon = True + t.start() + + def stop_refresh_loop(cli): + done[0] = True + + cli.on_start += start_refresh_loop + cli.on_stop += stop_refresh_loop + + # Replace stdout. + patch_context = cli.patch_stdout_context(raw=True) if patch_stdout else DummyContext() + + # Read input and return it. + if return_asyncio_coroutine: + # Create an asyncio coroutine and call it. + exec_context = {'patch_context': patch_context, 'cli': cli, + 'Document': Document} + exec_(textwrap.dedent(''' + def prompt_coro(): + # Inline import, because it slows down startup when asyncio is not + # needed. + import asyncio + + @asyncio.coroutine + def run(): + with patch_context: + result = yield from cli.run_async() + + if isinstance(result, Document): # Backwards-compatibility. + return result.text + return result + return run() + '''), exec_context) + + return exec_context['prompt_coro']() + else: + try: + with patch_context: + result = cli.run() + + if isinstance(result, Document): # Backwards-compatibility. + return result.text + return result + finally: + eventloop.close() + + +def prompt_async(message='', **kwargs): + """ + Similar to :func:`.prompt`, but return an asyncio coroutine instead. + """ + kwargs['return_asyncio_coroutine'] = True + return prompt(message, **kwargs) + + +def create_confirm_application(message): + """ + Create a confirmation `Application` that returns True/False. + """ + registry = Registry() + + @registry.add_binding('y') + @registry.add_binding('Y') + def _(event): + event.cli.buffers[DEFAULT_BUFFER].text = 'y' + event.cli.set_return_value(True) + + @registry.add_binding('n') + @registry.add_binding('N') + @registry.add_binding(Keys.ControlC) + def _(event): + event.cli.buffers[DEFAULT_BUFFER].text = 'n' + event.cli.set_return_value(False) + + return create_prompt_application(message, key_bindings_registry=registry) + + +def confirm(message='Confirm (y or n) '): + """ + Display a confirmation prompt. + """ + assert isinstance(message, text_type) + + app = create_confirm_application(message) + return run_application(app) + + +def print_tokens(tokens, style=None, true_color=False, file=None): + """ + Print a list of (Token, text) tuples in the given style to the output. + E.g.:: + + style = style_from_dict({ + Token.Hello: '#ff0066', + Token.World: '#884444 italic', + }) + tokens = [ + (Token.Hello, 'Hello'), + (Token.World, 'World'), + ] + print_tokens(tokens, style=style) + + :param tokens: List of ``(Token, text)`` tuples. + :param style: :class:`.Style` instance for the color scheme. + :param true_color: When True, use 24bit colors instead of 256 colors. + :param file: The output file. This can be `sys.stdout` or `sys.stderr`. + """ + if style is None: + style = DEFAULT_STYLE + assert isinstance(style, Style) + + output = create_output(true_color=true_color, stdout=file) + renderer_print_tokens(output, tokens, style) + + +def clear(): + """ + Clear the screen. + """ + out = create_output() + out.erase_screen() + out.cursor_goto(0, 0) + out.flush() + + +# Deprecated alias for `prompt`. +get_input = prompt +# Deprecated alias for create_prompt_layout +create_default_layout = create_prompt_layout +# Deprecated alias for create_prompt_application +create_default_application = create_prompt_application |