Created
May 8, 2015 20:08
-
-
Save anula/5748a896cb0c749257a5 to your computer and use it in GitHub Desktop.
Workaround for colorful prompt in prompt_toolkit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import unicode_literals | |
from prompt_toolkit.contrib.completers import WordCompleter | |
## Almost exact copy of prompt_toolkit.shortcuts | |
from prompt_toolkit.buffer import Buffer | |
from prompt_toolkit.enums import DEFAULT_BUFFER | |
from prompt_toolkit.eventloop.base import EventLoop | |
from prompt_toolkit.filters import IsDone, HasFocus, Always, Never, RendererHeightIsKnown | |
from prompt_toolkit.history import History | |
from prompt_toolkit.interface import CommandLineInterface, AbortAction, AcceptAction | |
from prompt_toolkit.key_binding.manager import KeyBindingManager | |
from prompt_toolkit.layout import Window, HSplit, FloatContainer, Float | |
from prompt_toolkit.layout.controls import BufferControl, TokenListControl | |
from prompt_toolkit.layout.dimension import LayoutDimension | |
from prompt_toolkit.layout.menus import CompletionsMenu | |
from prompt_toolkit.layout.processors import PasswordProcessor, HighlightSearchProcessor, HighlightSelectionProcessor | |
from prompt_toolkit.layout.prompt import DefaultPrompt | |
from prompt_toolkit.layout.screen import Char | |
from prompt_toolkit.layout.toolbars import ValidationToolbar, SystemToolbar | |
from pygments.token import Token | |
import sys | |
__all__ = ( | |
'get_input', | |
'create_cli', | |
'create_default_layout', | |
) | |
def create_eventloop(): | |
""" | |
Create and return a normal `EventLoop` instance for a | |
`CommandLineInterface`. | |
""" | |
if sys.platform == 'win32': | |
from prompt_toolkit.eventloop.win32 import Win32EventLoop as Loop | |
else: | |
from prompt_toolkit.eventloop.posix import PosixEventLoop as Loop | |
return Loop() | |
def create_asyncio_eventloop(loop=None): | |
""" | |
Returns an asyncio `Eventloop` instance for usage in a | |
`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 sys.platform == 'win32': | |
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 create_default_layout(prompt, lexer=None, is_password=False, | |
reserve_space_for_menu=False, get_bottom_toolbar_tokens=None, | |
extra_input_processors=None): | |
""" | |
Generate default layout. | |
Returns a ``Layout`` instance. | |
""" | |
assert get_bottom_toolbar_tokens is None or callable(get_bottom_toolbar_tokens) | |
# Create processors list. | |
# (DefaultPrompt should always be at the end.) | |
input_processors = [HighlightSearchProcessor(preview_search=Always()), | |
HighlightSelectionProcessor()] | |
if extra_input_processors: | |
input_processors.extend(extra_input_processors) | |
if is_password: | |
input_processors.extend([PasswordProcessor(), DefaultPrompt(prompt)]) | |
else: | |
input_processors.append(DefaultPrompt(prompt)) | |
# Create bottom toolbar. | |
if get_bottom_toolbar_tokens: | |
toolbars = [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: | |
return LayoutDimension(min=8) | |
else: | |
return LayoutDimension() | |
# Create and return Layout instance. | |
return HSplit([ | |
FloatContainer( | |
Window( | |
BufferControl( | |
input_processors=input_processors, | |
lexer=lexer, | |
# Enable preview_search, we want to have immediate feedback | |
# in reverse-i-search mode. | |
preview_search=Always()), | |
get_height=get_height, | |
), | |
[ | |
Float(xcursor=True, | |
ycursor=True, | |
content=CompletionsMenu(max_height=16, | |
extra_filter=HasFocus(DEFAULT_BUFFER))) | |
] | |
), | |
ValidationToolbar(), | |
SystemToolbar(), | |
] + toolbars) | |
def create_cli(eventloop, prompt, | |
multiline=False, | |
is_password=False, | |
vi_mode=False, | |
lexer=None, | |
enable_system_prompt=False, | |
enable_open_in_editor=False, | |
validator=None, | |
completer=None, | |
style=None, | |
history=None, | |
get_bottom_toolbar_tokens=None, | |
extra_input_processors=None, | |
key_bindings_registry=None, | |
output=None, | |
on_abort=AbortAction.RAISE_EXCEPTION, | |
on_exit=AbortAction.RAISE_EXCEPTION, | |
on_accept=AcceptAction.RETURN_DOCUMENT): | |
""" | |
Create a `CommandLineInterface` instance. | |
""" | |
assert isinstance(eventloop, EventLoop) | |
# Create history instance. | |
if history is None: | |
history = History() | |
# Use default registry from KeyBindingManager if none was given. | |
if key_bindings_registry is None: | |
key_bindings_registry = KeyBindingManager( | |
enable_vi_mode=vi_mode, | |
enable_system_prompt=enable_system_prompt, | |
enable_open_in_editor=enable_open_in_editor).registry | |
# Create interface. | |
return CommandLineInterface( | |
eventloop=eventloop, | |
layout=create_default_layout(prompt=prompt, lexer=lexer, is_password=is_password, | |
reserve_space_for_menu=(completer is not None), | |
get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, | |
extra_input_processors=extra_input_processors), | |
buffer=Buffer( | |
is_multiline=(Always() if multiline else Never()), | |
history=history, | |
validator=validator, | |
completer=completer, | |
), | |
key_bindings_registry=key_bindings_registry, | |
style=style, | |
output=output, | |
on_abort=on_abort, | |
on_exit=on_exit) | |
def get_input(prompt, | |
on_abort=AbortAction.RAISE_EXCEPTION, | |
on_exit=AbortAction.RAISE_EXCEPTION, | |
on_accept=AcceptAction.RETURN_DOCUMENT, | |
multiline=False, | |
is_password=False, | |
vi_mode=False, | |
lexer=None, | |
validator=None, | |
completer=None, | |
style=None, | |
enable_system_prompt=False, | |
enable_open_in_editor=False, | |
history=None, | |
get_bottom_toolbar_tokens=None, | |
key_bindings_registry=None): | |
""" | |
Get input from the user and return it. This wrapper builds the most obvious | |
configuration of a `CommandLineInterface`. This can be a replacement for | |
`raw_input`. (or GNU readline.) | |
This returns `None` when Ctrl-D was pressed. | |
If you want to keep your history across several ``get_input`` calls, you | |
have to create a :class:`History` instance and pass it every time. | |
: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 is_password: Show asterisks instead of the actual typed characters. | |
:param vi_mode: If True, use Vi key bindings. | |
:param lexer: Pygments lexer to be used for the syntax highlighting. | |
:param validator: `Validator` instance for input validation. | |
:param completer: `Completer` instance for input completion. | |
:param style: Pygments style class for the color scheme. | |
:param enable_system_prompt: Pressing Meta+'!' will show a system prompt. | |
:param enable_open_in_editor: Pressing 'v' in Vi mode or C-X C-E in emacs | |
mode will open an external editor. | |
:param history: `History` instance. (e.g. `FileHistory`) | |
:param get_bottom_toolbar_tokens: Optional callable which takes a | |
:class:`CommandLineInterface` and returns a list of tokens for the | |
bottom toolbar. | |
""" | |
eventloop = create_eventloop() | |
cli = create_cli( | |
eventloop, | |
prompt=prompt, | |
multiline=multiline, | |
is_password=is_password, | |
vi_mode=vi_mode, | |
lexer=lexer, | |
enable_system_prompt=enable_system_prompt, | |
enable_open_in_editor=enable_open_in_editor, | |
validator=validator, | |
completer=completer, | |
style=style, | |
history=history, | |
get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, | |
key_bindings_registry=key_bindings_registry, | |
on_abort=on_abort, | |
on_exit=on_exit, | |
on_accept=on_accept) | |
# Read input and return it. | |
try: | |
document = cli.read_input() | |
if document: | |
return document.text | |
finally: | |
eventloop.close() | |
## End of almost exact copy of prompt_toolkit.shortcuts | |
class FakeChar(str): | |
"""Class that holds a single char and ANSI escape sequences that surround it. | |
It behaves like normal string created with prefix + char + suffix, but has | |
two differences: | |
- len() always returns 2 | |
- when iterating over instance of this class iterator of the single char | |
is returned, it omits escape sequences from prefix and sufix. | |
""" | |
def __new__(cls, prefix, char, sufix = ''): | |
return str.__new__(cls, prefix + char + sufix) | |
def __init__(self, prefix, char, sufix = ''): | |
self.char = char | |
self.prefix = prefix | |
self.sufix = sufix | |
self.length = 2 | |
self.iterated = False | |
def __len__(self): | |
return self.length | |
def __iter__(self): | |
return iter(self.char) | |
animal_completer = WordCompleter([ | |
'alligator', | |
'ant', | |
'bear', | |
'butterfly', | |
'dragon', | |
'duck', | |
'hummingbird', | |
'horse', | |
], ignore_case=True) | |
def main(): | |
prompt = [FakeChar('\x01\x1b[1;32m\x02', 'c'), 'o', 'l', 'o', 'r', 'f', 'u', 'l', '@', 'p', 'r', 'o', 'm', 'p', 't', ' ', FakeChar('\x01\x1b[1;34m\x02', '$'), FakeChar('\x01\x1b[0m\x02', ' ')] | |
def get_tokens(cli): | |
return [(Token.Prompt, prompt)] | |
history = History() | |
while True: | |
try: | |
text = get_input(get_tokens, history=history, completer=animal_completer) | |
print('You said: %s' % text) | |
except EOFError: | |
break | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment