Merge branch 'ptk-prompt' of https://github.com/scopatz/xonsh into scopatz-ptk-updates

This commit is contained in:
adam j hartz 2016-01-01 20:25:05 -05:00
commit 61e1ed3178
8 changed files with 570 additions and 14 deletions

70
CHANGELOG.rst Normal file
View file

@ -0,0 +1,70 @@
================
Xonsh Change Log
================
Current Developments
====================
**Added:**
* timeit alias will now complete its arguments.
* $COMPLETIONS_MENU_ROWS environment variable controls the size of the
tab-completion menu in prompt-toolkit.
**Changed:**
* Prompt-toolkit shell will now dynamically allocate space for the
tab-completion menu.
**Deprecated:**
None
**Removed:**
None
**Fixed:**
None
**Security:**
None
v0.2.1 - v0.2.4
===============
You are reading the docs...but you still feel hungry.
v0.2.0
=============
**Added:**
* Rich history recording and replaying
v0.1.0
=============
**Added:**
* Naturally typed environment variables
* Inherits the environment from BASH
* Uses BASH completion for subprocess commands
* Regular expression filename globbing
* Its own PLY-based lexer and parser
* xonsh code parses into a Python AST
* You can do all the normal Python things, like arithmetic and importing
* Captured and uncaptured subprocesses
* Pipes, redirection, and non-blocking subprocess syntax support
* Help and superhelp with ? and ??
* Command aliasing
* Multiline input, unlike ed
* History matching like in IPython
* Color prompts
* Low system overhead
<v0.1.0
=============
The before times, like 65,000,000 BCE.

View file

@ -63,6 +63,11 @@ applicable.
writing ``$COMPLETIONS_DISPLAY = None`` and ``$COMPLETIONS_DISPLAY = 'none'`` is equivalent.
(Only usable with SHELL_TYPE=prompt_toolkit)
* - COMPLETIONS_MENU_ROWS
- ``5``
- Number of rows to reserve for tab-completions menu if
``$COMPLETIONS_DISPLAY`` is ``'single'`` or ``'multi'``. This only
effects the prompt-toolkit shell.
* - DIRSTACK_SIZE
- ``20``
- Maximum size of the directory stack.

View file

@ -0,0 +1 @@
.. include:: ../../CHANGELOG.rst

View file

@ -8,6 +8,7 @@ xonsh:
.. toctree::
:maxdepth: 1
changelog
0.2_release_notes
0.1_release_notes

View file

@ -19,7 +19,7 @@ from xonsh.tools import (
env_path_to_str, is_bool, to_bool, bool_to_str, is_history_tuple, to_history_tuple,
history_tuple_to_str, is_float, string_types, is_string, DEFAULT_ENCODING,
is_completions_display_value, to_completions_display_value, is_string_set,
csv_to_set, set_to_csv, get_sep
csv_to_set, set_to_csv, get_sep, is_int
)
from xonsh.dirstack import _get_cwd
from xonsh.foreign_shells import DEFAULT_SHELLS, load_foreign_envs
@ -58,6 +58,7 @@ DEFAULT_ENSURERS = {
'CASE_SENSITIVE_COMPLETIONS': (is_bool, to_bool, bool_to_str),
re.compile('\w*DIRS$'): (is_env_path, str_to_env_path, env_path_to_str),
'COMPLETIONS_DISPLAY': (is_completions_display_value, to_completions_display_value, str),
'COMPLETIONS_MENU_ROWS': (is_int, int, str),
'FORCE_POSIX_PATHS': (is_bool, to_bool, bool_to_str),
'HISTCONTROL': (is_string_set, csv_to_set, set_to_csv),
'IGNOREEOF': (is_bool, to_bool, bool_to_str),
@ -144,6 +145,7 @@ DEFAULT_VALUES = {
'CASE_SENSITIVE_COMPLETIONS': ON_LINUX,
'CDPATH': (),
'COMPLETIONS_DISPLAY': 'multi',
'COMPLETIONS_MENU_ROWS': 5,
'DIRSTACK_SIZE': 20,
'EXPAND_ENV_VARS': True,
'FORCE_POSIX_PATHS': False,

View file

@ -1,5 +1,9 @@
# -*- coding: utf-8 -*-
"""Completer implementation to use with prompt_toolkit."""
import os
import builtins
from prompt_toolkit.layout.dimension import LayoutDimension
from prompt_toolkit.completion import Completer, Completion
@ -32,5 +36,25 @@ class PromptToolkitCompleter(Completer):
begidx,
endidx,
self.ctx)
if len(completions) <= 1:
pass
elif len(os.path.commonprefix(completions)) <= len(prefix):
self.reserve_space()
for comp in completions:
yield Completion(comp, -l)
def reserve_space(self):
cli = builtins.__xonsh_shell__.shell.prompter.cli
window = cli.application.layout.children[1].children[1].content
h = window.render_info.content_height
r = builtins.__xonsh_env__.get('COMPLETIONS_MENU_ROWS')
size = h + r
def comp_height(cli):
# If there is an autocompletion menu to be shown, make sure that o
# layout has at least a minimal height in order to display it.
if not cli.is_done:
return LayoutDimension(min=size)
else:
return LayoutDimension()
window._height = comp_height

View file

@ -3,7 +3,6 @@
import builtins
from warnings import warn
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.key_binding.manager import KeyBindingManager
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.layout.lexers import PygmentsLexer
@ -18,15 +17,16 @@ from xonsh.tools import format_prompt_for_prompt_toolkit, _make_style
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.pyghooks import XonshLexer
class PromptToolkitShell(BaseShell):
"""The xonsh shell."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.prompter = Prompter()
self.history = PromptToolkitHistory()
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx)
self.key_bindings_manager = KeyBindingManager(
@ -53,17 +53,19 @@ class PromptToolkitShell(BaseShell):
completions_display = builtins.__xonsh_env__.get('COMPLETIONS_DISPLAY')
multicolumn = (completions_display == 'multi')
completer = None if completions_display == 'none' else self.pt_completer
line = prompt(
mouse_support=mouse_support,
auto_suggest=auto_suggest,
get_prompt_tokens=token_func,
style=style_cls,
completer=completer,
lexer=PygmentsLexer(XonshLexer),
history=self.history,
enable_history_search=True,
key_bindings_registry=self.key_bindings_manager.registry,
display_completions_in_columns=multicolumn)
with self.prompter:
line = self.prompter.prompt(
mouse_support=mouse_support,
auto_suggest=auto_suggest,
get_prompt_tokens=token_func,
style=style_cls,
completer=completer,
lexer=PygmentsLexer(XonshLexer),
history=self.history,
enable_history_search=True,
reserve_space_for_menu=0,
key_bindings_registry=self.key_bindings_manager.registry,
display_completions_in_columns=multicolumn)
if not line:
self.emptyline()
else:

451
xonsh/ptk/shortcuts.py Normal file
View file

@ -0,0 +1,451 @@
"""A prompt-toolkit inspired shortcut collection."""
import pygments.lexer
from pygments.token import Token
from prompt_toolkit.buffer import Buffer, AcceptAction
from prompt_toolkit.document import Document
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
from prompt_toolkit.filters import IsDone, HasFocus, RendererHeightIsKnown, to_simple_filter, to_cli_filter, Condition
from prompt_toolkit.history import InMemoryHistory
from prompt_toolkit.interface import CommandLineInterface, Application, AbortAction
from prompt_toolkit.key_binding.manager import KeyBindingManager
from prompt_toolkit.layout import Window, HSplit, VSplit, FloatContainer, Float
from prompt_toolkit.layout.containers import ConditionalContainer
from prompt_toolkit.layout.controls import BufferControl, TokenListControl
from prompt_toolkit.layout.dimension import LayoutDimension
from prompt_toolkit.layout.lexers import PygmentsLexer
from prompt_toolkit.layout.menus import CompletionsMenu, MultiColumnCompletionsMenu
from prompt_toolkit.layout.processors import PasswordProcessor, ConditionalProcessor, AppendAutoSuggestion
from prompt_toolkit.layout.prompt import DefaultPrompt
from prompt_toolkit.layout.screen import Char
from prompt_toolkit.styles import PygmentsStyle
from prompt_toolkit.layout.toolbars import ValidationToolbar, SystemToolbar, ArgToolbar, SearchToolbar
from prompt_toolkit.layout.utils import explode_tokens
from prompt_toolkit.utils import is_conemu_ansi, is_windows, DummyContext
from prompt_toolkit.shortcuts import (create_prompt_application,
create_eventloop, create_asyncio_eventloop, create_output,
_split_multiline_prompt)
try:
from prompt_toolkit.styles import DEFAULT_STYLE
except ImportError:
DEFAULT_STYLE = None
try:
from prompt_toolkit.layout.highlighters import (SearchHighlighter,
SelectionHighlighter, ConditionalHighlighter)
except ImportError:
SearchHighlighter = SelectionHighlighter = ConditionalHighlighter = None
class Prompter(object):
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.
"""
self.cli = cli
def __enter__(self):
self.reset()
return self
def __exit__(self, exc_type, exc_value, traceback):
#self.reset()
pass
def prompt(self, 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
`~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
create_prompt_application().
Parameters
----------
patch_stdout : file-like, optional
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.)
return_asyncio_coroutine : bool, optional
When True, return a asyncio coroutine. (Python >3.3)
Notes
-----
This method was forked from the mainline prompt-toolkit repo.
Copyright (c) 2014, Jonathan Slenders, All rights reserved.
"""
patch_stdout = kwargs.pop('patch_stdout', False)
return_asyncio_coroutine = kwargs.pop('return_asyncio_coroutine', False)
if return_asyncio_coroutine:
eventloop = create_asyncio_eventloop()
else:
eventloop = kwargs.pop('eventloop', None) or create_eventloop()
# Create CommandLineInterface.
if self.cli is None:
cli = CommandLineInterface(
application=self.create_prompt_application(message, **kwargs),
eventloop=eventloop,
output=create_output())
self.cli = cli
else:
cli = self.cli
# Replace stdout.
patch_context = cli.patch_stdout_context() 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}
exec_(textwrap.dedent('''
import asyncio
@asyncio.coroutine
def prompt_coro():
with patch_context:
document = yield from cli.run_async(reset_current_buffer=False)
if document:
return document.text
'''), exec_context)
return exec_context['prompt_coro']()
else:
# Note: We pass `reset_current_buffer=False`, because that way
# it's easy to give DEFAULT_BUFFER a default value, without it
# getting erased. We don't have to reset anyway, because this is
# the first and only time that this CommandLineInterface will run.
try:
with patch_context:
document = cli.run(reset_current_buffer=False)
if document:
return document.text
finally:
eventloop.close()
def reset(self):
"""Resets the prompt and cli to a pristine state on this object."""
self.cli = None
def create_prompt_layout(self, message='', lexer=None, is_password=False,
reserve_space_for_menu=8, get_prompt_tokens=None,
get_bottom_toolbar_tokens=None, display_completions_in_columns=False,
extra_input_processors=None, multiline=False, wrap_lines=True):
"""Create a Container instance for a prompt.
Parameters
----------
message : Text to be used as prompt.
lexer : ~prompt_toolkit.layout.lexers.Lexer to be used for
the highlighting.
is_password : bool or ~prompt_toolkit.filters.CLIFilter.
When True, display input as '*'.
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.
get_prompt_tokens : An optional callable that returns the tokens to be
shown in the menu. (To be used instead of a `message`.)
get_bottom_toolbar_tokens : An optional callable that returns the
tokens for a toolbar at the bottom.
display_completions_in_columns : `bool` or
:class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in
multiple columns.
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.
wrap_lines : `bool` or :class:`~prompt_toolkit.filters.CLIFilter`.
When True (the default), automatically wrap long lines instead of
scrolling horizontally.
Notes
-----
This method was forked from the mainline prompt-toolkit repo.
Copyright (c) 2014, Jonathan Slenders, All rights reserved.
WARNING; This method is due for removal once prompt-toolkit >v0.54
is released.
"""
assert isinstance(message, str)
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 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)]
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 issubclass(lexer, pygments.lexer.Lexer):
lexer = PygmentsLexer(lexer)
except TypeError: # Happens when lexer is `None` or an instance of something else.
pass
# Create highlighters and processors list.
if ConditionalHighlighter is None:
highlighters = None
highlighters_kwargs = {}
else:
highlighters = [
ConditionalHighlighter(
# 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.)
SearchHighlighter(preview_search=True),
HasFocus(SEARCH_BUFFER)),
SelectionHighlighter()]
highlighters_kwargs = {'highlighters': highlighters}
input_processors = [
ConditionalProcessor(AppendAutoSuggestion(), HasFocus(DEFAULT_BUFFER) & ~IsDone()),
ConditionalProcessor(PasswordProcessor(), is_password)]
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), ~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:
return LayoutDimension(min=reserve_space_for_menu)
else:
return LayoutDimension()
# Create and return Container instance.
return HSplit([
ConditionalContainer(
Window(
TokenListControl(get_prompt_tokens_1),
dont_extend_height=True),
filter=multiline,
),
VSplit([
# In multiline mode, the prompt is displayed in a left pane.
ConditionalContainer(
Window(
TokenListControl(get_prompt_tokens_2),
dont_extend_width=True,
),
filter=multiline,
),
# The main input, with completion menus floating on top of it.
FloatContainer(
Window(
BufferControl(
input_processors=input_processors,
lexer=lexer,
wrap_lines=wrap_lines,
# Enable preview_search, we want to have immediate feedback
# in reverse-i-search mode.
preview_search=True,
**highlighters_kwargs),
get_height=get_height,
),
[
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))
]
),
]),
ValidationToolbar(),
SystemToolbar(),
# In multiline mode, we use two toolbars for 'arg' and 'search'.
ConditionalContainer(ArgToolbar(), multiline),
ConditionalContainer(SearchToolbar(), multiline),
] + toolbars)
def create_prompt_application(self,
message='',
multiline=False,
wrap_lines=True,
is_password=False,
vi_mode=False,
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_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,
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.)
message : Text to be shown before the prompt.
mulitiline : Allow multiline input. Pressing enter will insert a
newline. (This requires Meta+Enter to accept the input.)
wrap_lines : `bool` or :class:`~prompt_toolkit.filters.CLIFilter`.
When True (the default), automatically wrap long lines instead of
scrolling horizontally.
is_password : Show asterisks instead of the actual typed characters.
vi_mode : `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. If
True, use Vi key bindings.
complete_while_typing : `bool` or
:class:`~prompt_toolkit.filters.CLIFilter`. Enable autocompletion while
typing.
enable_history_search : `bool` or
:class:`~prompt_toolkit.filters.CLIFilter`. Enable up-arrow parting
string matching.
lexer : :class:`~prompt_toolkit.layout.lexers.Lexer` to be used for
the syntax highlighting.
validator : :class:`~prompt_toolkit.validation.Validator` instance
for input validation.
completer : :class:`~prompt_toolkit.completion.Completer` instance
for input completion.
reserve_space_for_menu : Space to be reserved for displaying the menu.
(0 means that no space needs to be reserved.)
auto_suggest : :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
instance for input suggestions.
style : Pygments style class for the color scheme.
enable_system_bindings : `bool` or
:class:`~prompt_toolkit.filters.CLIFilter`. Pressing Meta+'!' will show
a system prompt.
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.
history : :class:`~prompt_toolkit.history.History` instance.
clipboard : :class:`~prompt_toolkit.clipboard.base.Clipboard` instance.
(e.g. :class:`~prompt_toolkit.clipboard.in_memory.InMemoryClipboard`)
get_bottom_toolbar_tokens : Optional callable which takes a
:class:`~prompt_toolkit.interface.CommandLineInterface` and returns a
list of tokens for the bottom toolbar.
display_completions_in_columns : `bool` or
:class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in
multiple columns.
get_title : Callable that returns the title to be displayed in the
terminal.
mouse_support : `bool` or :class:`~prompt_toolkit.filters.CLIFilter`
to enable mouse support.
default : The default text to be shown in the input buffer. (This can
be edited by the user.)
Notes
-----
This method was forked from the mainline prompt-toolkit repo.
Copyright (c) 2014, Jonathan Slenders, All rights reserved.
WARNING; This method is due for removal once prompt-toolkit >v0.54
is released.
"""
if key_bindings_registry is None:
key_bindings_registry = KeyBindingManager.for_prompt(
enable_vi_mode=vi_mode,
enable_system_bindings=enable_system_bindings,
enable_open_in_editor=enable_open_in_editor).registry
# 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 issubclass(style, pygments.style.Style):
style = PygmentsStyle(style)
except TypeError: # Happens when style is `None` or an instance of something else.
pass
# Create application
return Application(
layout=self.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_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,
on_abort=on_abort,
on_exit=on_exit)