718 lines
28 KiB
Python
718 lines
28 KiB
Python
"""
|
|
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
|