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

View file

@ -3,24 +3,37 @@
import sys import sys
import builtins import builtins
from prompt_toolkit.key_binding.manager import KeyBindingManager
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.layout.lexers import PygmentsLexer 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.base_shell import BaseShell
from xonsh.tools import print_exception, carriage_return from xonsh.tools import print_exception, carriage_return
from xonsh.ptk.completer import PromptToolkitCompleter from xonsh.ptk.completer import PromptToolkitCompleter
from xonsh.ptk.history import PromptToolkitHistory from xonsh.ptk.history import PromptToolkitHistory
from xonsh.ptk.key_bindings import load_xonsh_bindings 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.events import events
from xonsh.shell import transform_command from xonsh.shell import transform_command
from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS
from xonsh.style_tools import partial_color_tokenize, _TokenType, DEFAULT_STYLE_DICT from xonsh.style_tools import partial_color_tokenize, _TokenType, DEFAULT_STYLE_DICT
from xonsh.lazyimps import pygments, pyghooks, winutils 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() Token = _TokenType()
events.transmogrify('on_ptk_create', 'LoadEvent') events.transmogrify('on_ptk_create', 'LoadEvent')
@ -39,7 +52,7 @@ class PromptToolkitShell(BaseShell):
if ON_WINDOWS: if ON_WINDOWS:
winutils.enable_virtual_terminal_processing() winutils.enable_virtual_terminal_processing()
self._first_prompt = True self._first_prompt = True
self.prompter = Prompter() self.prompter = get_prompter()
self.history = PromptToolkitHistory() self.history = PromptToolkitHistory()
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self) self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
key_bindings_manager_args = { key_bindings_manager_args = {
@ -270,10 +283,10 @@ class PromptToolkitShell(BaseShell):
if HAS_PYGMENTS: if HAS_PYGMENTS:
env = builtins.__xonsh_env__ env = builtins.__xonsh_env__
self.styler.style_name = env.get('XONSH_COLOR_STYLE') 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: else:
proxy_style = style_from_dict(DEFAULT_STYLE_DICT) 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): def color_style_names(self):
"""Returns an iterable of all available style names.""" """Returns an iterable of all available style names."""
@ -312,3 +325,118 @@ class PromptToolkitShell(BaseShell):
# if not ON_POSIX: # if not ON_POSIX:
# return # return
# sys.stdout.write('\033[9999999C\n') # 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 builtins
import textwrap import textwrap
from prompt_toolkit.interface import CommandLineInterface
from prompt_toolkit.enums import EditingMode from prompt_toolkit.enums import EditingMode
from prompt_toolkit.utils import DummyContext 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 from xonsh.platform import ptk_version_info
import xonsh.tools as xt 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): class Prompter(object):
@ -120,3 +125,53 @@ class Prompter(object):
def reset(self): def reset(self):
"""Resets the prompt and cli to a pristine state on this object.""" """Resets the prompt and cli to a pristine state on this object."""
self.cli = None 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': if shell_type == 'none':
from xonsh.base_shell import BaseShell as shell_class from xonsh.base_shell import BaseShell as shell_class
elif shell_type == 'prompt_toolkit': 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': elif shell_type == 'readline':
from xonsh.readline_shell import ReadlineShell as shell_class from xonsh.readline_shell import ReadlineShell as shell_class
else: else: