Support ptk 2.0

This commit is contained in:
Tyler Goodlet 2017-12-19 12:30:25 -05:00
parent e9476a9734
commit a5b5b9acf5
4 changed files with 217 additions and 30 deletions

View file

@ -6,6 +6,7 @@ from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import (Condition, IsMultiline, HasSelection,
EmacsInsertMode, ViInsertMode)
from prompt_toolkit.keys import Keys
from prompt_toolkit.application.current import get_app
from xonsh.aliases import xonsh_exit
from xonsh.tools import check_for_partial_string, get_line_continuation
@ -84,34 +85,36 @@ def can_compile(src):
@Condition
def tab_insert_indent(cli):
def tab_insert_indent():
"""Check if <Tab> should insert indent instead of starting autocompletion.
Checks if there are only whitespaces before the cursor - if so indent
should be inserted, otherwise autocompletion.
"""
before_cursor = cli.current_buffer.document.current_line_before_cursor
before_cursor = get_app(
).current_buffer.document.current_line_before_cursor
return bool(before_cursor.isspace())
@Condition
def beginning_of_line(cli):
def beginning_of_line():
"""Check if cursor is at beginning of a line other than the first line in a
multiline document
"""
before_cursor = cli.current_buffer.document.current_line_before_cursor
app = get_app()
before_cursor = app.current_buffer.document.current_line_before_cursor
return bool(len(before_cursor) == 0 and
not cli.current_buffer.document.on_first_line)
not app.current_buffer.document.on_first_line)
@Condition
def end_of_line(cli):
def end_of_line():
"""Check if cursor is at the end of a line other than the last line in a
multiline document
"""
d = cli.current_buffer.document
d = get_app().current_buffer.document
at_end = d.is_cursor_at_the_end_of_line
last_line = d.is_cursor_at_the_end
@ -119,56 +122,57 @@ def end_of_line(cli):
@Condition
def should_confirm_completion(cli):
def should_confirm_completion():
"""Check if completion needs confirmation"""
return (builtins.__xonsh_env__.get('COMPLETIONS_CONFIRM') and
cli.current_buffer.complete_state)
get_app().current_buffer.complete_state)
# Copied from prompt-toolkit's key_binding/bindings/basic.py
@Condition
def ctrl_d_condition(cli):
def ctrl_d_condition():
"""Ctrl-D binding is only active when the default buffer is selected and
empty.
"""
if builtins.__xonsh_env__.get("IGNOREEOF"):
raise EOFError
else:
return (cli.current_buffer_name == DEFAULT_BUFFER and
not cli.current_buffer.text)
app = get_app()
return (app.current_buffer_name == DEFAULT_BUFFER and
not app.current_buffer.text)
@Condition
def autopair_condition(cli):
def autopair_condition():
"""Check if XONSH_AUTOPAIR is set"""
return builtins.__xonsh_env__.get("XONSH_AUTOPAIR", False)
@Condition
def whitespace_or_bracket_before(cli):
def whitespace_or_bracket_before():
"""Check if there is whitespace or an opening
bracket to the left of the cursor"""
d = cli.current_buffer.document
d = get_app().current_buffer.document
return bool(d.cursor_position == 0
or d.char_before_cursor.isspace()
or d.char_before_cursor in '([{')
@Condition
def whitespace_or_bracket_after(cli):
def whitespace_or_bracket_after():
"""Check if there is whitespace or a closing
bracket to the right of the cursor"""
d = cli.current_buffer.document
d = get_app().current_buffer.document
return bool(d.is_cursor_at_the_end_of_line
or d.current_char.isspace()
or d.current_char in ')]}')
def load_xonsh_bindings(key_bindings_manager):
def load_xonsh_bindings(key_bindings):
"""
Load custom key bindings.
"""
handle = key_bindings_manager.registry.add_binding
handle = key_bindings.add
has_selection = HasSelection()
insert_mode = ViInsertMode() | EmacsInsertMode()

View file

@ -3,24 +3,37 @@
import sys
import builtins
from prompt_toolkit.key_binding.manager import KeyBindingManager
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.layout.lexers import PygmentsLexer
from prompt_toolkit.shortcuts import print_tokens
from prompt_toolkit.styles import PygmentsStyle, style_from_dict
from xonsh.platform import ptk_version_info
from xonsh.base_shell import BaseShell
from xonsh.tools import print_exception, carriage_return
from xonsh.ptk.completer import PromptToolkitCompleter
from xonsh.ptk.history import PromptToolkitHistory
from xonsh.ptk.key_bindings import load_xonsh_bindings
from xonsh.ptk.shortcuts import Prompter
from xonsh.ptk.shortcuts import get_prompter
from xonsh.events import events
from xonsh.shell import transform_command
from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS
from xonsh.style_tools import partial_color_tokenize, _TokenType, DEFAULT_STYLE_DICT
from xonsh.lazyimps import pygments, pyghooks, winutils
if ptk_version_info()[:2] < (2, 0):
from prompt_toolkit.key_binding.manager import KeyBindingManager
from prompt_toolkit.shortcuts import print_tokens as ptk_print
from prompt_toolkit.styles import style_from_dict
from prompt_toolkit.styles import PygmentsStyle as style_from_pygments
else: # ptk 2.0
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.shortcuts import print_formatted_text as ptk_print
from prompt_toolkit.shortcuts import CompleteStyle
from prompt_toolkit.formatted_text import PygmentsTokens
from prompt_toolkit.styles.pygments import (
style_from_pygments, Style, pygments_token_to_classname)
Token = _TokenType()
events.transmogrify('on_ptk_create', 'LoadEvent')
@ -39,7 +52,7 @@ class PromptToolkitShell(BaseShell):
if ON_WINDOWS:
winutils.enable_virtual_terminal_processing()
self._first_prompt = True
self.prompter = Prompter()
self.prompter = get_prompter()
self.history = PromptToolkitHistory()
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
key_bindings_manager_args = {
@ -270,10 +283,10 @@ class PromptToolkitShell(BaseShell):
if HAS_PYGMENTS:
env = builtins.__xonsh_env__
self.styler.style_name = env.get('XONSH_COLOR_STYLE')
proxy_style = PygmentsStyle(pyghooks.xonsh_style_proxy(self.styler))
proxy_style = style_from_pygments(pyghooks.xonsh_style_proxy(self.styler))
else:
proxy_style = style_from_dict(DEFAULT_STYLE_DICT)
print_tokens(tokens, style=proxy_style)
ptk_print(tokens, style=proxy_style)
def color_style_names(self):
"""Returns an iterable of all available style names."""
@ -312,3 +325,118 @@ class PromptToolkitShell(BaseShell):
# if not ON_POSIX:
# return
# sys.stdout.write('\033[9999999C\n')
class PromptToolkitShell2(PromptToolkitShell):
"""The xonsh shell for ptk 2.0.
"""
def __init__(self, **kwargs):
super(PromptToolkitShell, self).__init__(**kwargs)
if ON_WINDOWS:
winutils.enable_virtual_terminal_processing()
self._first_prompt = True
self.prompter = get_prompter()
self.history = PromptToolkitHistory()
self.pt_completer = PromptToolkitCompleter(
self.completer, self.ctx, self)
self.key_bindings = KeyBindings()
load_xonsh_bindings(self.key_bindings)
# This assumes that PromptToolkitShell is a singleton
events.on_ptk_create.fire(
prompter=self.prompter,
history=self.history,
completer=self.pt_completer,
bindings=self.key_bindings,
)
def singleline(self, store_in_history=True, auto_suggest=None,
enable_history_search=True, multiline=True, **kwargs):
"""Reads a single line of input from the shell. The store_in_history
kwarg flags whether the input should be stored in PTK's in-memory
history.
"""
events.on_pre_prompt.fire()
env = builtins.__xonsh_env__
mouse_support = env.get('MOUSE_SUPPORT')
if store_in_history:
history = self.history
else:
history = None
enable_history_search = False
auto_suggest = auto_suggest if env.get('AUTO_SUGGEST') else None
completions_display = env.get('COMPLETIONS_DISPLAY')
if completions_display == 'multi':
complete_style = CompleteStyle.MULTI_COLUMN
complete_while_typing = env.get('UPDATE_COMPLETIONS_ON_KEYPRESS')
if complete_while_typing:
# PTK requires history search to be none when completing while typing
enable_history_search = False
if HAS_PYGMENTS:
self.styler.style_name = env.get('XONSH_COLOR_STYLE')
completer = None if completions_display == 'none' else self.pt_completer
if not env.get('UPDATE_PROMPT_ON_KEYPRESS'):
get_prompt_tokens = self.prompt_tokens(None)
get_rprompt_tokens = self.rprompt_tokens(None)
get_bottom_toolbar_tokens = self.bottom_toolbar_tokens(None)
else:
get_prompt_tokens = self.prompt_tokens
get_rprompt_tokens = self.rprompt_tokens
get_bottom_toolbar_tokens = self.bottom_toolbar_tokens
with self.prompter:
prompt_args = {
'mouse_support': mouse_support,
'auto_suggest': auto_suggest,
'message': get_prompt_tokens,
'rprompt': get_rprompt_tokens,
'bottom_toolbar': get_bottom_toolbar_tokens,
'completer': completer,
'multiline': multiline,
'prompt_continuation': self.continuation_tokens,
'history': history,
'enable_history_search': enable_history_search,
'reserve_space_for_menu': 0,
'extra_key_bindings': self.key_bindings,
'complete_style': complete_style,
'complete_while_typing': complete_while_typing,
}
if builtins.__xonsh_env__.get('COLOR_INPUT'):
if HAS_PYGMENTS:
prompt_args['lexer'] = PygmentsLexer(pyghooks.XonshLexer)
style = style_from_pygments(
pyghooks.xonsh_style_proxy(self.styler))
else:
style_dict = {
pygments_token_to_classname(key.__name__): value
for key, value in DEFAULT_STYLE_DICT
}
style = Style.from_dict(style_dict)
prompt_args['style'] = style
line = self.prompter.prompt(**prompt_args)
events.on_post_prompt.fire()
return line
def prompt_tokens(self, cli):
return PygmentsTokens(super().prompt_tokens(cli))
def rprompt_tokens(self, cli):
return PygmentsTokens(super().rprompt_tokens(cli))
def bottom_toolbar_tokens(self, cli):
return PygmentsTokens(super().bottom_toolbar_tokens(cli))
def continuation_tokens(self, width, cli=None):
return PygmentsTokens(
super().continuation_tokens(width=width, cli=cli))
def get_ptk_shell(**kwargs):
"""Return a new ptk shell wrapper based on the installed version.
"""
if ptk_version_info()[:2] < (2, 0):
return PromptToolkitShell(**kwargs)
else:
return PromptToolkitShell2(**kwargs)

View file

@ -2,15 +2,20 @@
import builtins
import textwrap
from prompt_toolkit.interface import CommandLineInterface
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.utils import DummyContext
from prompt_toolkit.shortcuts import (create_prompt_application,
create_eventloop, create_asyncio_eventloop, create_output)
from xonsh.platform import ptk_version_info
import xonsh.tools as xt
if ptk_version_info()[:2] < (2, 0):
from prompt_toolkit.interface import CommandLineInterface
from prompt_toolkit.shortcuts import (
create_prompt_application, create_eventloop, create_asyncio_eventloop,
create_output)
else:
from prompt_toolkit.shortcuts import Prompt
class Prompter(object):
@ -120,3 +125,53 @@ class Prompter(object):
def reset(self):
"""Resets the prompt and cli to a pristine state on this object."""
self.cli = None
class Prompter2(object):
"""Prompter for ptk 2.0
"""
def __init__(self, cli=None, *args, **kwargs):
"""Implements a prompt that statefully holds a command-line
interface. When used as a context manager, it will return itself
on entry and reset itself on exit.
Parameters
----------
cli : CommandLineInterface or None, optional
If this is not a CommandLineInterface object, such an object
will be created when the prompt() method is called.
"""
# TODO: maybe call this ``.prompt`` now since
# ``CommandLineInterface`` is gone?
self.cli = cli or Prompt(**kwargs)
self.major_minor = ptk_version_info()[:2]
def __enter__(self):
self.reset()
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def prompt(self, message='', **kwargs):
"""Get input from the user and return it.
"""
if builtins.__xonsh_env__.get('VI_MODE'):
editing_mode = EditingMode.VI
else:
editing_mode = EditingMode.EMACS
kwargs['editing_mode'] = editing_mode
self.cli.prompt(message=message, **kwargs)
def reset(self):
"""Resets the prompt and cli to a pristine state on this object."""
# XXX Is this necessary any more?
# self.prompt = None
def get_prompter():
if ptk_version_info()[:2] < (2, 0):
return Prompter()
else:
return Prompter2()

View file

@ -142,7 +142,7 @@ class Shell(object):
if shell_type == 'none':
from xonsh.base_shell import BaseShell as shell_class
elif shell_type == 'prompt_toolkit':
from xonsh.ptk.shell import PromptToolkitShell as shell_class
from xonsh.ptk.shell import get_ptk_shell as shell_class
elif shell_type == 'readline':
from xonsh.readline_shell import ReadlineShell as shell_class
else: