* delete package ptk; rename ptk2 to ptk_shell.; leave ptk2 as alias for ptk_shell.
* SHELL_TYPE "prompt_toolkit" only; remove ptk1 specific behavior.
* Doc updates: eliminate reference to prompt-toolkit < 2.0
* update requirements files ptk>=2; test shell_style="none"
* fix ptk2 stub per code review
* Add ptk2 to list of packages to install.
This commit is contained in:
Bob Hyman 2020-04-18 10:44:27 -04:00 committed by GitHub
parent 3a232c6782
commit 53bfe8c99c
Failed to generate hash of commit
46 changed files with 524 additions and 1484 deletions

View file

@ -157,6 +157,7 @@ v0.9.14
- `Before <https://i.imgur.com/EMhPdgU.png>`_
- `After <https://i.imgur.com/sJiqgsb.png>`_
* The autovox xontrib now preserves activated environment on cd
* setup.cfg -- duplicated flake8 config so interactive use and test runs enforce same rules. (Implementation is arguably a regression.)
* Pressing ``Ctrl+Z`` no longer deadlocks the terminal,

View file

@ -44,10 +44,10 @@ For those of you who want the gritty details.
shell
base_shell
readline_shell
ptk2/shell
ptk2/history
ptk2/completer
ptk2/key_bindings
ptk_shell/shell
ptk_shell/history
ptk_shell/completer
ptk_shell/key_bindings
pretty
replay
diff_history
@ -88,13 +88,3 @@ For those of you who want the gritty details.
mplhooks
vox
**Pending Deprecation:**
.. toctree::
:maxdepth: 1
ptk/shell
ptk/history
ptk/completer
ptk/key_bindings

View file

@ -1,7 +0,0 @@
.. _xonsh_ptk_completer:
*************************************************************
Prompt Toolkit Completer (``xonsh.ptk.completer``)
*************************************************************
.. warning:: This module is pending deprecation.

View file

@ -1,7 +0,0 @@
.. _xonsh_ptk_history:
****************************************************************
Prompt Toolkit History Object (``xonsh.ptk.history``)
****************************************************************
.. warning:: This module is pending deprecation.

View file

@ -1,8 +0,0 @@
.. _xonsh_ptk_key_bindings:
**********************************************************
Prompt Toolkit Key Bindings (``xonsh.ptk.key_bindings``)
**********************************************************
.. warning:: This module is pending deprecation.

View file

@ -1,7 +0,0 @@
.. _xonsh_ptk_shell:
******************************************************
Prompt Toolkit Shell (``xonsh.ptk.shell``)
******************************************************
.. warning:: This module is pending deprecation.

View file

@ -1,10 +0,0 @@
.. _xonsh_ptk2_key_bindings:
***********************************************************
Prompt Toolkit 2 Key Bindings (``xonsh.ptk2.key_bindings``)
***********************************************************
.. automodule:: xonsh.ptk2.key_bindings
:members:
:undoc-members:
:inherited-members:

View file

@ -1,10 +1,10 @@
.. _xonsh_ptk2_completer:
*************************************************************
Prompt Toolkit 2 Completer (``xonsh.ptk2.completer``)
Prompt Toolkit 2+ Completer (``xonsh.ptk_shell.completer``)
*************************************************************
.. automodule:: xonsh.ptk2.completer
.. automodule:: xonsh.ptk_shell.completer
:members:
:undoc-members:
:inherited-members:

View file

@ -1,10 +1,10 @@
.. _xonsh_ptk2_history:
****************************************************************
Prompt Toolkit 2 History Object (``xonsh.ptk2.history``)
Prompt Toolkit 2+ History Object (``xonsh.ptk_shell.history``)
****************************************************************
.. automodule:: xonsh.ptk2.history
.. automodule:: xonsh.ptk_shell.history
:members:
:undoc-members:
:inherited-members:

View file

@ -0,0 +1,10 @@
.. _xonsh_ptk_shell_key_bindings:
*****************************************************************
Prompt Toolkit 2+ Key Bindings (``xonsh.ptk_shell.key_bindings``)
*****************************************************************
.. automodule:: xonsh.ptk_shell.key_bindings
:members:
:undoc-members:
:inherited-members:

View file

@ -1,10 +1,10 @@
.. _xonsh_ptk2_shell:
******************************************************
Prompt Toolkit 2 Shell (``xonsh.ptk2.shell``)
Prompt Toolkit 2+ Shell (``xonsh.ptk_shell.shell``)
******************************************************
.. automodule:: xonsh.ptk2.shell
.. automodule:: xonsh.ptk_shell.shell
:members:
:undoc-members:
:inherited-members:

View file

@ -32,12 +32,7 @@ spec = importlib.util.find_spec("prompt_toolkit")
if spec is not None:
# hacky runaround to import PTK-specific events
builtins.__xonsh__.env = Env()
from xonsh.platform import ptk_version_info
if ptk_version_info()[0] < 2:
from xonsh.ptk.shell import events
else:
from xonsh.ptk2.shell import events
from xonsh.ptk_shell.shell import events
else:
from xonsh.events import events

View file

@ -4,16 +4,15 @@ Xonsh currently has the following external dependencies,
*Run Time:*
#. Python v3.4+
#. Python v3.5+
Pip supports "extra" dependencies in the form of ``xonsh[ptk,linux]``, where
the list in the brackets identify the optional features
Xonsh currently has the following extras
#. ``ptk``: prompt-toolkit:
*advanced readline library, line-editing*
#. ``pygments>=2.2``: *syntax-highlighting*
#. ``ptk``: prompt-toolkit >= 2.0: *advanced readline library, line-editing*
#. ``pygments``: pygments >=2.2: *syntax-highlighting*
#. ``proctitle``: setproctitle: *change the title of terminal to reflect the current subprocess*
#. ``linux``: distro: *linux specific platform information*
#. ``mac``: gnureadline: *GNU's featureful version of readline*

View file

@ -72,26 +72,21 @@ Custom keyload function
We need our additional keybindings to load after the shell is initialized, so we
define a function that contains all of the custom keybindings and decorate it
with the appropriate event, in this case ``on_ptk_create``. Please note that
the method of ``handler`` differs depending on the version of ``prompt_toolkit``.
with the appropriate event, in this case ``on_ptk_create``.
We'll start with a toy example that just inserts the text "hi" into the current line of the prompt::
@events.on_ptk_create
def custom_keybindings(bindings, **kw):
# prompt_toolkit 1.x
# handler = bindings.registry.add_binding
# prompt_toolkit 2.x
handler = bindings.add
@handler(Keys.ControlW)
@bindings.add(Keys.ControlW)
def say_hi(event):
event.current_buffer.insert_text('hi')
Put that in your `xonshrc <xonshrc.html>`_, restart xonsh and then see if
pressing ``Ctrl-w`` does anything (it should!)
.. note:: From prompt_toolkit 2.x it is also possible to write ``Keys.ControlW`` like ``c-w``.
.. note:: It is also possible to write ``Keys.ControlW`` like ``c-w``.
What commands can keybindings run?

24
news/add_ptk3.rst Normal file
View file

@ -0,0 +1,24 @@
**Added:**
* Support package prompt-toolkit V3 as well as V2 in prompt_toolkit shell.
**Changed:**
* $SHELL_TYPE "prompt_toolkit" with any suffix creates the "prompt_toolkit" shell, requires package prompt-toolkit >= 2.0
* Moved code from package xonsh.ptk2 to xonsh.ptk_shell (because it's the only one now); package xonsh.ptk2 redirects thence.
**Deprecated:**
* prompt-toolkit versions before 2.0
**Removed:**
* package xonsh.ptk
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -1,7 +1,7 @@
cloud_sptheme
numpydoc
Sphinx==2.4.4
prompt_toolkit
prompt_toolkit>=2.0
pygments>=2.2
psutil
pyzmq

View file

@ -4,7 +4,7 @@ flake8
pytest-flake8
pytest-cov
pytest-timeout
prompt-toolkit
prompt-toolkit>=2.0
pygments>=2.2
codecov
coverage

View file

@ -4,7 +4,7 @@ flake8
pytest-flake8
pytest-cov
pytest-timeout
prompt-toolkit
prompt-toolkit>=2.0
pygments>=2.2
codecov
coverage

View file

@ -357,7 +357,7 @@ def main():
packages=[
"xonsh",
"xonsh.ply.ply",
"xonsh.ptk",
"xonsh.ptk_shell",
"xonsh.ptk2",
"xonsh.parsers",
"xonsh.xoreutils",
@ -408,7 +408,7 @@ def main():
}
skw["cmdclass"]["develop"] = xdevelop
skw["extras_require"] = {
"ptk": ["prompt-toolkit"],
"ptk": ["prompt-toolkit>=2.0"],
"pygments": ["pygments>=2.2"],
"mac": ["gnureadline"],
"linux": ["distro"],

View file

@ -149,11 +149,12 @@ def test_nested():
def events_fxt():
return EventManager()
@pytest.fixture
def xonsh_builtins_ls_colors(xonsh_builtins, events_fxt):
x = xonsh_builtins.__xonsh__
xonsh_builtins.__xonsh__.shell = DummyShell() # because load_command_cache zaps it.
xonsh_builtins.__xonsh__.shell.shell_type = "prompt_toolkit2"
xonsh_builtins.__xonsh__.shell.shell_type = "prompt_toolkit"
lsc = LsColors(LsColors.default_settings)
xonsh_builtins.__xonsh__.env["LS_COLORS"] = lsc # establish LS_COLORS before style.
xonsh_builtins.__xonsh__.shell.shell.styler = XonshStyle() # default style
@ -163,6 +164,7 @@ def xonsh_builtins_ls_colors(xonsh_builtins, events_fxt):
yield xonsh_builtins
xonsh_builtins.__xonsh__ = x
@skip_if_on_windows
def test_path(tmpdir, xonsh_builtins_ls_colors):
@ -199,6 +201,7 @@ def test_color_on_lscolors_change(tmpdir, xonsh_builtins_ls_colors):
"cd {}".format(test_dir), [(Name.Builtin, "cd"), (Text, test_dir)]
)
@skip_if_on_windows
def test_subproc_args():
check_token("cd 192.168.0.1", [(Text, "192.168.0.1")])

View file

@ -5,21 +5,54 @@ try:
except ImportError:
pytest.mark.skip(msg="prompt_toolkit is not available")
from xonsh.ptk2.history import PromptToolkitHistory
from tools import skip_if_lt_ptk2
@pytest.fixture
def history_obj():
"""Instantiate `PromptToolkitHistory` and append a line string"""
from xonsh.ptk_shell.history import PromptToolkitHistory
hist = PromptToolkitHistory(load_prev=False)
hist.append_string("line10")
return hist
@skip_if_lt_ptk2
def test_obj(history_obj):
assert ["line10"] == history_obj.get_strings()
assert len(history_obj) == 1
assert ["line10"] == [x for x in history_obj]
def test_ptk2_backcompat():
"""
Test that legacy code (ahem, xontribs) can still reference xonsh.ptk2 (for a while)
"""
import xonsh.ptk_shell.shell as imports_new
import xonsh.ptk2.shell as imports_legacy
# defining the ptk2 package this way leaves out the internal global names (which all start with '_')
s_new = set(dir(imports_new))
s_legacy = set(dir(imports_legacy))
extra_names = s_new - s_legacy
for name in extra_names:
assert name.startswith('_')
assert s_legacy.issubset(s_new)
# prove that legacy API is usable
@pytest.fixture
def history_obj_legacy():
"""Instantiate `PromptToolkitHistory` via legacy alias and append a line string"""
from xonsh.ptk2.history import PromptToolkitHistory
hist = PromptToolkitHistory(load_prev=False)
hist.append_string("line10")
return hist
def test_obj_legacy(history_obj_legacy):
history_obj = history_obj_legacy
assert ["line10"] == history_obj.get_strings()
assert len(history_obj) == 1
assert ["line10"] == [x for x in history_obj]

View file

@ -12,9 +12,7 @@ from prompt_toolkit.buffer import Buffer
from xonsh.tools import ON_WINDOWS
from xonsh.built_ins import XonshSession
from tools import DummyEnv, skip_if_lt_ptk2
from tools import DummyEnv
Context = namedtuple("Context", ["indent", "buffer", "accept", "cli", "cr"])
@ -24,7 +22,7 @@ def ctx():
builtins.__xonsh__ = XonshSession()
builtins.__xonsh__.env = DummyEnv()
builtins.__xonsh__.env["INDENT"] = " "
from xonsh.ptk2.key_bindings import carriage_return
from xonsh.ptk_shell.key_bindings import carriage_return
ptk_buffer = Buffer()
ptk_buffer.accept_action = MagicMock(name="accept")
@ -40,7 +38,6 @@ def ctx():
del builtins.__xonsh__
@skip_if_lt_ptk2
def test_colon_indent(ctx):
document = Document("for i in range(5):")
ctx.buffer.set_document(document)
@ -48,7 +45,6 @@ def test_colon_indent(ctx):
assert ctx.buffer.document.current_line == ctx.indent
@skip_if_lt_ptk2
def test_dedent(ctx):
document = Document("\n" + ctx.indent + "pass")
ctx.buffer.set_document(document)
@ -61,25 +57,23 @@ def test_dedent(ctx):
assert ctx.buffer.document.current_line == ctx.indent
@skip_if_lt_ptk2
def test_nodedent(ctx):
"""don't dedent if first line of ctx.buffer"""
mock = MagicMock(return_value=True)
with patch("xonsh.ptk2.key_bindings.can_compile", mock):
with patch("xonsh.ptk_shell.key_bindings.can_compile", mock):
document = Document("pass")
ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli)
assert ctx.accept.mock_calls is not None
mock = MagicMock(return_value=True)
with patch("xonsh.ptk2.key_bindings.can_compile", mock):
with patch("xonsh.ptk_shell.key_bindings.can_compile", mock):
document = Document(ctx.indent + "pass")
ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli)
assert ctx.accept.mock_calls is not None
@skip_if_lt_ptk2
def test_continuation_line(ctx):
document = Document("\nsecond line")
ctx.buffer.set_document(document)
@ -87,10 +81,9 @@ def test_continuation_line(ctx):
assert ctx.buffer.document.current_line == ""
@skip_if_lt_ptk2
def test_trailing_slash(ctx):
mock = MagicMock(return_value=True)
with patch("xonsh.ptk2.key_bindings.can_compile", mock):
with patch("xonsh.ptk_shell.key_bindings.can_compile", mock):
document = Document("this line will \\")
ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli)
@ -100,20 +93,18 @@ def test_trailing_slash(ctx):
assert ctx.accept.mock_calls is not None
@skip_if_lt_ptk2
def test_cant_compile_newline(ctx):
mock = MagicMock(return_value=False)
with patch("xonsh.ptk2.key_bindings.can_compile", mock):
with patch("xonsh.ptk_shell.key_bindings.can_compile", mock):
document = Document("for i in (1, 2, ")
ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli)
assert ctx.buffer.document.current_line == ""
@skip_if_lt_ptk2
def test_can_compile_and_executes(ctx):
mock = MagicMock(return_value=True)
with patch("xonsh.ptk2.key_bindings.can_compile", mock):
with patch("xonsh.ptk_shell.key_bindings.can_compile", mock):
document = Document("ls")
ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli)

70
tests/test_ptk_shell.py Normal file
View file

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
"""Test initialization of prompt_toolkit shell"""
import os
import gc
import builtins
import pytest
from tools import skip_if_on_windows
from xonsh.platform import ON_WINDOWS, minimum_required_ptk_version
from xonsh.ptk_shell.shell import PromptToolkitShell
# verify error if ptk not installed or below min
import warnings
import xonsh.platform
from xonsh.shell import Shell
@pytest.mark.parametrize(
"ptk_ver, ini_shell_type, exp_shell_type, warn_snip",
[
(None, "prompt_toolkit", "readline", "prompt_toolkit is not available"),
((0, 5, 7), "prompt_toolkit", "readline", "is not supported"),
((1, 0, 0), "prompt_toolkit", "readline", "is not supported"),
((2, 0, 0), "prompt_toolkit", "prompt_toolkit", None),
((2, 0, 0), "best", "prompt_toolkit", None),
((2, 0, 0), "readline", "readline", None),
((3, 0, 0), "prompt_toolkit", "prompt_toolkit", None),
((3, 0, 0), "best", "prompt_toolkit", None),
((3, 0, 0), "readline", "readline", None),
((4, 0, 0), "prompt_toolkit", "prompt_toolkit", None),
],
)
def test_prompt_toolkit_version_checks(ptk_ver, ini_shell_type, exp_shell_type, warn_snip, monkeypatch, xonsh_builtins):
mocked_warn = ""
def mock_warning(msg):
nonlocal mocked_warn
mocked_warn = msg
return
def mock_ptk_above_min_supported():
nonlocal ptk_ver
return ptk_ver and (ptk_ver[:2] >= minimum_required_ptk_version)
def mock_has_prompt_toolkit():
nonlocal ptk_ver
return ptk_ver is not None
monkeypatch.setattr("xonsh.shell.warnings.warn", mock_warning) # hardwon: patch the caller!
monkeypatch.setattr("xonsh.shell.ptk_above_min_supported", mock_ptk_above_min_supported) # have to patch both callers
monkeypatch.setattr("xonsh.platform.ptk_above_min_supported", mock_ptk_above_min_supported)
monkeypatch.setattr("xonsh.shell.has_prompt_toolkit", mock_has_prompt_toolkit)
monkeypatch.setattr("xonsh.platform.has_prompt_toolkit", mock_has_prompt_toolkit)
act_shell_type = Shell.choose_shell_type(ini_shell_type, {})
assert act_shell_type == exp_shell_type
if warn_snip:
assert warn_snip in mocked_warn
pass
# someday: initialize PromptToolkitShell and have it actually do something.

View file

@ -26,7 +26,7 @@ def xonsh_builtins_LS_COLORS(xonsh_builtins):
e = xonsh_builtins.__xonsh__.env
lsc = LsColors(LsColors.default_settings)
xonsh_builtins.__xonsh__.env["LS_COLORS"] = lsc
xonsh_builtins.__xonsh__.shell.shell_type = "prompt_toolkit2"
xonsh_builtins.__xonsh__.shell.shell_type = "prompt_toolkit"
# styler = XonshStyle() # default style
# xonsh_builtins.__xonsh__.shell.shell.styler = styler
# can't really instantiate XonshStyle separate from a shell??

View file

@ -77,3 +77,4 @@ hello = 'world'
ctx = xontrib_context("script")
assert ctx == {"hello": "world"}

View file

@ -15,7 +15,6 @@ import pytest
from xonsh.environ import Env
from xonsh.base_shell import BaseShell
from xonsh.platform import ptk_version_info
VER_3_5 = (3, 5)
@ -59,10 +58,6 @@ skip_if_on_darwin = pytest.mark.skipif(ON_DARWIN, reason="not Mac friendly")
skip_if_on_travis = pytest.mark.skipif(ON_TRAVIS, reason="not Travis CI friendly")
skip_if_lt_ptk2 = pytest.mark.skipif(
ptk_version_info()[0] < 2, reason="prompt-toolkit <2"
)
def sp(cmd):
return subprocess.check_output(cmd, universal_newlines=True)

View file

@ -1021,7 +1021,8 @@ def DEFAULT_DOCS():
),
"LANG": VarDocs("Fallback locale setting for systems where it matters"),
"LS_COLORS": VarDocs(
"Color settings for ``ls`` command line utility",
"Color settings for ``ls`` command line utility and, "
"with ``$SHELL_TYPE='prompt_toolkit'``, file arguments in subprocess mode.",
default="``*.7z=1;0;31:*.Z=1;0;31:*.aac=0;36:*.ace=1;0;31:"
"*.alz=1;0;31:*.arc=1;0;31:*.arj=1;0;31:*.asf=1;0;35:*.au=0;36:"
"*.avi=1;0;35:*.bmp=1;0;35:*.bz=1;0;31:*.bz2=1;0;31:*.cab=1;0;31:"

View file

@ -27,8 +27,6 @@ class History:
History objects should be created via a subclass of History.
Indexing
--------
History acts like a sequence that can be indexed to return
``HistoryEntry`` objects.

View file

@ -257,7 +257,7 @@ def _pprint_displayhook(value):
printed_val = repr(value)
if HAS_PYGMENTS and env.get("COLOR_RESULTS"):
tokens = list(pygments.lex(printed_val, lexer=pyghooks.XonshLexer()))
end = "" if env.get("SHELL_TYPE") == "prompt_toolkit2" else "\n"
end = "" if env.get("SHELL_TYPE") == "prompt_toolkit" else "\n"
print_color(tokens, end=end)
else:
print(printed_val) # black & white case

View file

@ -169,19 +169,13 @@ def ptk_version_info():
return None
minimum_required_ptk_version = (2, 0)
"""Minimum version of prompt-toolkit supported by Xonsh"""
@functools.lru_cache(1)
def ptk_above_min_supported():
minimum_required_ptk_version = (1, 0)
return ptk_version_info()[:2] >= minimum_required_ptk_version
@functools.lru_cache(1)
def ptk_shell_type():
"""Returns the prompt_toolkit shell type based on the installed version."""
if ptk_version_info()[:2] < (2, 0):
return "prompt_toolkit1"
else:
return "prompt_toolkit2"
return ptk_version_info() and ptk_version_info()[:2] >= minimum_required_ptk_version
@functools.lru_cache(1)
@ -198,7 +192,7 @@ def win_ansi_support():
@functools.lru_cache(1)
def ptk_below_max_supported():
ptk_max_version_cutoff = (2, 0)
ptk_max_version_cutoff = (99999, 0) # currently, no limit.
return ptk_version_info()[:2] < ptk_max_version_cutoff

View file

@ -1,80 +0,0 @@
# -*- coding: utf-8 -*-
"""History object for use with prompt_toolkit."""
import builtins
from threading import Thread
import prompt_toolkit.history
class PromptToolkitHistory(prompt_toolkit.history.History):
"""History class that implements the prompt-toolkit history interface
with the xonsh backend.
"""
def __init__(self, load_prev=True, wait_for_gc=True, *args, **kwargs):
"""Initialize history object."""
super().__init__()
self.strings = []
if load_prev:
PromptToolkitHistoryAdder(self, wait_for_gc=wait_for_gc)
def append(self, entry):
"""Append new entry to the history."""
self.strings.append(entry)
def __getitem__(self, index):
return self.strings[index]
def __len__(self):
return len(self.strings)
def __iter__(self):
return iter(self.strings)
class PromptToolkitHistoryAdder(Thread):
def __init__(self, ptkhist, wait_for_gc=True, *args, **kwargs):
"""Thread responsible for adding inputs from history to the current
prompt-toolkit history instance. May wait for the history garbage
collector to finish.
"""
super(PromptToolkitHistoryAdder, self).__init__(*args, **kwargs)
self.daemon = True
self.ptkhist = ptkhist
self.wait_for_gc = wait_for_gc
self.start()
def run(self):
hist = builtins.__xonsh__.history
if hist is None:
return
buf = None
ptkhist = self.ptkhist
for cmd in hist.all_items():
line = cmd["inp"].rstrip()
if len(ptkhist) == 0 or line != ptkhist[-1]:
ptkhist.append(line)
if buf is None:
buf = self._buf()
if buf is None:
continue
buf.reset(initial_document=buf.document)
def _buf(self):
# Thread-safe version of
# buf = builtins.__xonsh__.shell.shell.prompter.cli.application.buffer
path = [
"__xonsh__",
"shell",
"shell",
"prompter",
"cli",
"application",
"buffer",
]
buf = builtins
for a in path:
buf = getattr(buf, a, None)
if buf is None:
break
return buf

View file

@ -1,131 +0,0 @@
"""A prompt-toolkit inspired shortcut collection."""
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
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
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.
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:
if builtins.__xonsh__.env.get("VI_MODE"):
editing_mode = EditingMode.VI
else:
editing_mode = EditingMode.EMACS
kwargs["editing_mode"] = editing_mode
cli = CommandLineInterface(
application=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
except Exception:
xt.print_exception()
# return something to prevent xonsh crash when any
# exceptions raise
return ""
finally:
eventloop.close()
def reset(self):
"""Resets the prompt and cli to a pristine state on this object."""
self.cli = None

View file

@ -0,0 +1 @@
from xonsh.ptk_shell import * # noqa: F403 F401

View file

@ -1,105 +1 @@
# -*- coding: utf-8 -*-
"""Completer implementation to use with prompt_toolkit."""
import os
import builtins
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.application.current import get_app
class PromptToolkitCompleter(Completer):
"""Simple prompt_toolkit Completer object.
It just redirects requests to normal Xonsh completer.
"""
def __init__(self, completer, ctx, shell):
"""Takes instance of xonsh.completer.Completer, the xonsh execution
context, and the shell instance itself.
"""
self.completer = completer
self.ctx = ctx
self.shell = shell
self.hist_suggester = AutoSuggestFromHistory()
def get_completions(self, document, complete_event):
"""Returns a generator for list of completions."""
env = builtins.__xonsh__.env
should_complete = complete_event.completion_requested or env.get(
"UPDATE_COMPLETIONS_ON_KEYPRESS"
)
# Only generate completions when the user hits tab.
if not should_complete or self.completer is None:
return
# generate actual completions
line = document.current_line.lstrip()
line_ex = builtins.aliases.expand_alias(line)
endidx = document.cursor_position_col
begidx = line[:endidx].rfind(" ") + 1 if line[:endidx].rfind(" ") >= 0 else 0
prefix = line[begidx:endidx]
expand_offset = len(line_ex) - len(line)
# get normal completions
completions, l = self.completer.complete(
prefix, line_ex, begidx + expand_offset, endidx + expand_offset, self.ctx
)
# completions from auto suggest
sug_comp = None
if env.get("AUTO_SUGGEST") and env.get("AUTO_SUGGEST_IN_COMPLETIONS"):
sug_comp = self.suggestion_completion(document, line)
if sug_comp is None:
pass
elif len(completions) == 0:
completions = (sug_comp,)
else:
completions = set(completions)
completions.discard(sug_comp)
completions = (sug_comp,) + tuple(sorted(completions))
# reserve space, if needed.
if len(completions) <= 1:
pass
elif len(os.path.commonprefix(completions)) <= len(prefix):
self.reserve_space()
# Find common prefix (strip quoting)
c_prefix = os.path.commonprefix([a.strip("'\"") for a in completions])
# Find last split symbol, do not trim the last part
while c_prefix:
if c_prefix[-1] in r"/\.:@,":
break
c_prefix = c_prefix[:-1]
# yield completions
if sug_comp is None:
pre = min(document.cursor_position_col - begidx, len(c_prefix))
else:
pre = len(c_prefix)
for comp in completions:
# do not display quote
disp = comp[pre:].strip("'\"")
yield Completion(comp, -l, display=disp)
def suggestion_completion(self, document, line):
"""Provides a completion based on the current auto-suggestion."""
app = self.shell.prompter.app
sug = self.hist_suggester.get_suggestion(app.current_buffer, document)
if sug is None:
return None
comp, _, _ = sug.text.partition(" ")
_, _, prev = line.rpartition(" ")
return prev + comp
def reserve_space(self):
"""Adjust the height for showing autocompletion menu."""
app = get_app()
render = app.renderer
window = app.layout.container.children[0].content.children[1].content
if window and window.render_info:
h = window.render_info.content_height
r = builtins.__xonsh__.env.get("COMPLETIONS_MENU_ROWS")
size = h + r
last_h = render._last_screen.height if render._last_screen else 0
last_h = max(render._min_available_height, last_h)
if last_h < size:
if render._last_screen:
render._last_screen.height = size
from xonsh.ptk_shell.completer import * # noqa: F403 F401

View file

@ -1,53 +1 @@
# -*- coding: utf-8 -*-
"""History object for use with prompt_toolkit."""
import builtins
import prompt_toolkit.history
class PromptToolkitHistory(prompt_toolkit.history.History):
"""History class that implements the prompt-toolkit history interface
with the xonsh backend.
"""
def __init__(self, load_prev=True, *args, **kwargs):
"""Initialize history object."""
super().__init__()
self.load_prev = load_prev
def store_string(self, entry):
pass
def load_history_strings(self):
"""Loads synchronous history strings"""
if not self.load_prev:
return
hist = builtins.__xonsh__.history
if hist is None:
return
for cmd in hist.all_items(newest_first=True):
line = cmd["inp"].rstrip()
strs = self.get_strings()
if len(strs) == 0 or line != strs[-1]:
yield line
def __getitem__(self, index):
return self.get_strings()[index]
def __len__(self):
return len(self.get_strings())
def __iter__(self):
return iter(self.get_strings())
def _cust_history_matches(self, i):
"""Custom history search method for prompt_toolkit that matches previous
commands anywhere on a line, not just at the start.
This gets monkeypatched into the prompt_toolkit prompter if
``XONSH_HISTORY_MATCH_ANYWHERE=True``"""
return (
self.history_search_text is None
or self.history_search_text in self._working_lines[i]
)
from xonsh.ptk_shell.history import * # noqa: F403 F401

View file

@ -1,359 +1 @@
# -*- coding: utf-8 -*-
"""Key bindings for prompt_toolkit xonsh shell."""
import builtins
from prompt_toolkit import search
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import (
Condition,
IsMultiline,
HasSelection,
EmacsInsertMode,
ViInsertMode,
IsSearching,
)
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
from xonsh.shell import transform_command
DEDENT_TOKENS = frozenset(["raise", "return", "pass", "break", "continue"])
def carriage_return(b, cli, *, autoindent=True):
"""Preliminary parser to determine if 'Enter' key should send command to the
xonsh parser for execution or should insert a newline for continued input.
Current 'triggers' for inserting a newline are:
- Not on first line of buffer and line is non-empty
- Previous character is a colon (covers if, for, etc...)
- User is in an open paren-block
- Line ends with backslash
- Any text exists below cursor position (relevant when editing previous
multiline blocks)
"""
doc = b.document
at_end_of_line = _is_blank(doc.current_line_after_cursor)
current_line_blank = _is_blank(doc.current_line)
env = builtins.__xonsh__.env
indent = env.get("INDENT") if autoindent else ""
partial_string_info = check_for_partial_string(doc.text)
in_partial_string = (
partial_string_info[0] is not None and partial_string_info[1] is None
)
# indent after a colon
if doc.current_line_before_cursor.strip().endswith(":") and at_end_of_line:
b.newline(copy_margin=autoindent)
b.insert_text(indent, fire_event=False)
# if current line isn't blank, check dedent tokens
elif (
not current_line_blank
and doc.current_line.split(maxsplit=1)[0] in DEDENT_TOKENS
and doc.line_count > 1
):
b.newline(copy_margin=autoindent)
b.delete_before_cursor(count=len(indent))
elif not doc.on_first_line and not current_line_blank:
b.newline(copy_margin=autoindent)
elif doc.current_line.endswith(get_line_continuation()):
b.newline(copy_margin=autoindent)
elif doc.find_next_word_beginning() is not None and (
any(not _is_blank(i) for i in doc.lines_from_current[1:])
):
b.newline(copy_margin=autoindent)
elif not current_line_blank and not can_compile(doc.text):
b.newline(copy_margin=autoindent)
elif current_line_blank and in_partial_string:
b.newline(copy_margin=autoindent)
else:
b.validate_and_handle()
def _is_blank(l):
return len(l.strip()) == 0
def can_compile(src):
"""Returns whether the code can be compiled, i.e. it is valid xonsh."""
src = src if src.endswith("\n") else src + "\n"
src = transform_command(src, show_diff=False)
src = src.lstrip()
try:
builtins.__xonsh__.execer.compile(
src, mode="single", glbs=None, locs=builtins.__xonsh__.ctx
)
rtn = True
except SyntaxError:
rtn = False
except Exception:
rtn = True
return rtn
@Condition
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 = get_app().current_buffer.document.current_line_before_cursor
return bool(before_cursor.isspace())
@Condition
def beginning_of_line():
"""Check if cursor is at beginning of a line other than the first line in a
multiline document
"""
app = get_app()
before_cursor = app.current_buffer.document.current_line_before_cursor
return bool(
len(before_cursor) == 0 and not app.current_buffer.document.on_first_line
)
@Condition
def end_of_line():
"""Check if cursor is at the end of a line other than the last line in a
multiline 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
return bool(at_end and not last_line)
@Condition
def should_confirm_completion():
"""Check if completion needs confirmation"""
return (
builtins.__xonsh__.env.get("COMPLETIONS_CONFIRM")
and get_app().current_buffer.complete_state
)
# Copied from prompt-toolkit's key_binding/bindings/basic.py
@Condition
def ctrl_d_condition():
"""Ctrl-D binding is only active when the default buffer is selected and
empty.
"""
if builtins.__xonsh__.env.get("IGNOREEOF"):
return False
else:
app = get_app()
buffer_name = app.current_buffer.name
return buffer_name == DEFAULT_BUFFER and not app.current_buffer.text
@Condition
def autopair_condition():
"""Check if XONSH_AUTOPAIR is set"""
return builtins.__xonsh__.env.get("XONSH_AUTOPAIR", False)
@Condition
def whitespace_or_bracket_before():
"""Check if there is whitespace or an opening
bracket to the left of the cursor"""
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():
"""Check if there is whitespace or a closing
bracket to the right of the cursor"""
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):
"""
Load custom key bindings.
"""
handle = key_bindings.add
has_selection = HasSelection()
insert_mode = ViInsertMode() | EmacsInsertMode()
@handle(Keys.Tab, filter=tab_insert_indent)
def insert_indent(event):
"""
If there are only whitespaces before current cursor position insert
indent instead of autocompleting.
"""
env = builtins.__xonsh__.env
event.cli.current_buffer.insert_text(env.get("INDENT"))
@handle(Keys.ControlX, Keys.ControlE, filter=~has_selection)
def open_editor(event):
""" Open current buffer in editor """
event.current_buffer.open_in_editor(event.cli)
@handle(Keys.BackTab, filter=insert_mode)
def insert_literal_tab(event):
""" Insert literal tab on Shift+Tab instead of autocompleting """
b = event.current_buffer
if b.complete_state:
b.complete_previous()
else:
env = builtins.__xonsh__.env
event.cli.current_buffer.insert_text(env.get("INDENT"))
@handle("(", filter=autopair_condition & whitespace_or_bracket_after)
def insert_right_parens(event):
event.cli.current_buffer.insert_text("(")
event.cli.current_buffer.insert_text(")", move_cursor=False)
@handle(")", filter=autopair_condition)
def overwrite_right_parens(event):
buffer = event.cli.current_buffer
if buffer.document.current_char == ")":
buffer.cursor_position += 1
else:
buffer.insert_text(")")
@handle("[", filter=autopair_condition & whitespace_or_bracket_after)
def insert_right_bracket(event):
event.cli.current_buffer.insert_text("[")
event.cli.current_buffer.insert_text("]", move_cursor=False)
@handle("]", filter=autopair_condition)
def overwrite_right_bracket(event):
buffer = event.cli.current_buffer
if buffer.document.current_char == "]":
buffer.cursor_position += 1
else:
buffer.insert_text("]")
@handle("{", filter=autopair_condition & whitespace_or_bracket_after)
def insert_right_brace(event):
event.cli.current_buffer.insert_text("{")
event.cli.current_buffer.insert_text("}", move_cursor=False)
@handle("}", filter=autopair_condition)
def overwrite_right_brace(event):
buffer = event.cli.current_buffer
if buffer.document.current_char == "}":
buffer.cursor_position += 1
else:
buffer.insert_text("}")
@handle("'", filter=autopair_condition)
def insert_right_quote(event):
buffer = event.cli.current_buffer
if buffer.document.current_char == "'":
buffer.cursor_position += 1
elif whitespace_or_bracket_before() and whitespace_or_bracket_after():
buffer.insert_text("'")
buffer.insert_text("'", move_cursor=False)
else:
buffer.insert_text("'")
@handle('"', filter=autopair_condition)
def insert_right_double_quote(event):
buffer = event.cli.current_buffer
if buffer.document.current_char == '"':
buffer.cursor_position += 1
elif whitespace_or_bracket_before() and whitespace_or_bracket_after():
buffer.insert_text('"')
buffer.insert_text('"', move_cursor=False)
else:
buffer.insert_text('"')
@handle(Keys.Backspace, filter=autopair_condition)
def delete_brackets_or_quotes(event):
"""Delete empty pair of brackets or quotes"""
buffer = event.cli.current_buffer
before = buffer.document.char_before_cursor
after = buffer.document.current_char
if any(
[before == b and after == a for (b, a) in ["()", "[]", "{}", "''", '""']]
):
buffer.delete(1)
buffer.delete_before_cursor(1)
@handle(Keys.ControlD, filter=ctrl_d_condition)
def call_exit_alias(event):
"""Use xonsh exit function"""
b = event.cli.current_buffer
b.validate_and_handle()
xonsh_exit([])
@handle(Keys.ControlJ, filter=IsMultiline() & insert_mode)
@handle(Keys.ControlM, filter=IsMultiline() & insert_mode)
def multiline_carriage_return(event):
""" Wrapper around carriage_return multiline parser """
b = event.cli.current_buffer
carriage_return(b, event.cli)
@handle(Keys.ControlJ, filter=should_confirm_completion)
@handle(Keys.ControlM, filter=should_confirm_completion)
def enter_confirm_completion(event):
"""Ignore <enter> (confirm completion)"""
event.current_buffer.complete_state = None
@handle(Keys.Escape, filter=should_confirm_completion)
def esc_cancel_completion(event):
"""Use <ESC> to cancel completion"""
event.cli.current_buffer.cancel_completion()
@handle(Keys.Escape, Keys.ControlJ)
def execute_block_now(event):
"""Execute a block of text irrespective of cursor position"""
b = event.cli.current_buffer
b.validate_and_handle()
@handle(Keys.Left, filter=beginning_of_line)
def wrap_cursor_back(event):
"""Move cursor to end of previous line unless at beginning of
document
"""
b = event.cli.current_buffer
b.cursor_up(count=1)
relative_end_index = b.document.get_end_of_line_position()
b.cursor_right(count=relative_end_index)
@handle(Keys.Right, filter=end_of_line)
def wrap_cursor_forward(event):
"""Move cursor to beginning of next line unless at end of document"""
b = event.cli.current_buffer
relative_begin_index = b.document.get_start_of_line_position()
b.cursor_left(count=abs(relative_begin_index))
b.cursor_down(count=1)
@handle(Keys.ControlM, filter=IsSearching())
@handle(Keys.ControlJ, filter=IsSearching())
def accept_search(event):
search.accept_search()
@handle(Keys.ControlZ)
def skip_control_z(event):
"""Prevents the writing of ^Z to the prompt, if Ctrl+Z was pressed
during the previous command.
"""
pass
from xonsh.ptk_shell.key_bindings import * # noqa: F403 F401

View file

@ -1,375 +1 @@
# -*- coding: utf-8 -*-
"""The prompt_toolkit based xonsh shell."""
import os
import sys
import builtins
from types import MethodType
from xonsh.events import events
from xonsh.base_shell import BaseShell
from xonsh.shell import transform_command
from xonsh.tools import print_exception, carriage_return
from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS, ON_POSIX
from xonsh.style_tools import partial_color_tokenize, _TokenType, DEFAULT_STYLE_DICT
from xonsh.lazyimps import pygments, pyghooks, winutils
from xonsh.pygments_cache import get_all_styles
from xonsh.ptk2.history import PromptToolkitHistory, _cust_history_matches
from xonsh.ptk2.completer import PromptToolkitCompleter
from xonsh.ptk2.key_bindings import load_xonsh_bindings
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.history import ThreadedHistory
from prompt_toolkit.shortcuts import print_formatted_text as ptk_print
from prompt_toolkit.shortcuts import CompleteStyle
from prompt_toolkit.shortcuts.prompt import PromptSession
from prompt_toolkit.formatted_text import PygmentsTokens
from prompt_toolkit.styles import merge_styles, Style
from prompt_toolkit.styles.pygments import (
style_from_pygments_cls,
style_from_pygments_dict,
)
Token = _TokenType()
events.transmogrify("on_ptk_create", "LoadEvent")
events.doc(
"on_ptk_create",
"""
on_ptk_create(prompter: PromptSession, history: PromptToolkitHistory, completer: PromptToolkitCompleter, bindings: KeyBindings) ->
Fired after prompt toolkit has been initialized
""",
)
class PromptToolkit2Shell(BaseShell):
"""The xonsh shell for prompt_toolkit v2."""
completion_displays_to_styles = {
"multi": CompleteStyle.MULTI_COLUMN,
"single": CompleteStyle.COLUMN,
"readline": CompleteStyle.READLINE_LIKE,
"none": None,
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
if ON_WINDOWS:
winutils.enable_virtual_terminal_processing()
self._first_prompt = True
self.history = ThreadedHistory(PromptToolkitHistory())
self.prompter = PromptSession(history=self.history)
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
self.key_bindings = KeyBindings()
load_xonsh_bindings(self.key_bindings)
# Store original `_history_matches` in case we need to restore it
self._history_matches_orig = self.prompter.default_buffer._history_matches
# This assumes that PromptToolkit2Shell 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, 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")
auto_suggest = auto_suggest if env.get("AUTO_SUGGEST") else None
refresh_interval = env.get("PROMPT_REFRESH_INTERVAL")
refresh_interval = refresh_interval if refresh_interval > 0 else None
complete_in_thread = env.get("COMPLETION_IN_THREAD")
completions_display = env.get("COMPLETIONS_DISPLAY")
complete_style = self.completion_displays_to_styles[completions_display]
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
get_bottom_toolbar_tokens = self.bottom_toolbar_tokens
if env.get("UPDATE_PROMPT_ON_KEYPRESS"):
get_prompt_tokens = self.prompt_tokens
get_rprompt_tokens = self.rprompt_tokens
else:
get_prompt_tokens = self.prompt_tokens()
get_rprompt_tokens = self.rprompt_tokens()
if get_bottom_toolbar_tokens:
get_bottom_toolbar_tokens = get_bottom_toolbar_tokens()
if env.get("VI_MODE"):
editing_mode = EditingMode.VI
else:
editing_mode = EditingMode.EMACS
if env.get("XONSH_HISTORY_MATCH_ANYWHERE"):
self.prompter.default_buffer._history_matches = MethodType(
_cust_history_matches, self.prompter.default_buffer
)
elif (
self.prompter.default_buffer._history_matches
is not self._history_matches_orig
):
self.prompter.default_buffer._history_matches = self._history_matches_orig
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,
"editing_mode": editing_mode,
"prompt_continuation": self.continuation_tokens,
"enable_history_search": enable_history_search,
"reserve_space_for_menu": 0,
"key_bindings": self.key_bindings,
"complete_style": complete_style,
"complete_while_typing": complete_while_typing,
"include_default_pygments_style": False,
"refresh_interval": refresh_interval,
"complete_in_thread": complete_in_thread,
}
if builtins.__xonsh__.env.get("COLOR_INPUT"):
if HAS_PYGMENTS:
prompt_args["lexer"] = PygmentsLexer(pyghooks.XonshLexer)
style = style_from_pygments_cls(pyghooks.xonsh_style_proxy(self.styler))
else:
style = style_from_pygments_dict(DEFAULT_STYLE_DICT)
prompt_args["style"] = style
style_overrides_env = env.get("PTK_STYLE_OVERRIDES")
if style_overrides_env:
try:
style_overrides = Style.from_dict(style_overrides_env)
prompt_args["style"] = merge_styles([style, style_overrides])
except (AttributeError, TypeError, ValueError):
print_exception()
line = self.prompter.prompt(**prompt_args)
events.on_post_prompt.fire()
return line
def _push(self, line):
"""Pushes a line onto the buffer and compiles the code in a way that
enables multiline input.
"""
code = None
self.buffer.append(line)
if self.need_more_lines:
return None, code
src = "".join(self.buffer)
src = transform_command(src)
try:
code = self.execer.compile(src, mode="single", glbs=self.ctx, locs=None)
self.reset_buffer()
except Exception: # pylint: disable=broad-except
self.reset_buffer()
print_exception()
return src, None
return src, code
def cmdloop(self, intro=None):
"""Enters a loop that reads and execute input from user."""
if intro:
print(intro)
auto_suggest = AutoSuggestFromHistory()
self.push = self._push
while not builtins.__xonsh__.exit:
try:
line = self.singleline(auto_suggest=auto_suggest)
if not line:
self.emptyline()
else:
line = self.precmd(line)
self.default(line)
except (KeyboardInterrupt, SystemExit):
self.reset_buffer()
except EOFError:
if builtins.__xonsh__.env.get("IGNOREEOF"):
print('Use "exit" to leave the shell.', file=sys.stderr)
else:
break
def prompt_tokens(self):
"""Returns a list of (token, str) tuples for the current prompt."""
p = builtins.__xonsh__.env.get("PROMPT")
try:
p = self.prompt_formatter(p)
except Exception: # pylint: disable=broad-except
print_exception()
toks = partial_color_tokenize(p)
if self._first_prompt:
carriage_return()
self._first_prompt = False
self.settitle()
return PygmentsTokens(toks)
def rprompt_tokens(self):
"""Returns a list of (token, str) tuples for the current right
prompt.
"""
p = builtins.__xonsh__.env.get("RIGHT_PROMPT")
# self.prompt_formatter does handle empty strings properly,
# but this avoids descending into it in the common case of
# $RIGHT_PROMPT == ''.
if isinstance(p, str) and len(p) == 0:
return []
try:
p = self.prompt_formatter(p)
except Exception: # pylint: disable=broad-except
print_exception()
toks = partial_color_tokenize(p)
return PygmentsTokens(toks)
def _bottom_toolbar_tokens(self):
"""Returns a list of (token, str) tuples for the current bottom
toolbar.
"""
p = builtins.__xonsh__.env.get("BOTTOM_TOOLBAR")
if not p:
return
try:
p = self.prompt_formatter(p)
except Exception: # pylint: disable=broad-except
print_exception()
toks = partial_color_tokenize(p)
return PygmentsTokens(toks)
@property
def bottom_toolbar_tokens(self):
"""Returns self._bottom_toolbar_tokens if it would yield a result
"""
if builtins.__xonsh__.env.get("BOTTOM_TOOLBAR"):
return self._bottom_toolbar_tokens
def continuation_tokens(self, width, line_number, is_soft_wrap=False):
"""Displays dots in multiline prompt"""
if is_soft_wrap:
return ""
width = width - 1
dots = builtins.__xonsh__.env.get("MULTILINE_PROMPT")
dots = dots() if callable(dots) else dots
if not dots:
return ""
basetoks = self.format_color(dots)
baselen = sum(len(t[1]) for t in basetoks)
if baselen == 0:
return [(Token, " " * (width + 1))]
toks = basetoks * (width // baselen)
n = width % baselen
count = 0
for tok in basetoks:
slen = len(tok[1])
newcount = slen + count
if slen == 0:
continue
elif newcount <= n:
toks.append(tok)
else:
toks.append((tok[0], tok[1][: n - count]))
count = newcount
if n <= count:
break
toks.append((Token, " ")) # final space
return PygmentsTokens(toks)
def format_color(self, string, hide=False, force_string=False, **kwargs):
"""Formats a color string using Pygments. This, therefore, returns
a list of (Token, str) tuples. If force_string is set to true, though,
this will return a color formatted string.
"""
tokens = partial_color_tokenize(string)
if force_string and HAS_PYGMENTS:
env = builtins.__xonsh__.env
self.styler.style_name = env.get("XONSH_COLOR_STYLE")
proxy_style = pyghooks.xonsh_style_proxy(self.styler)
formatter = pyghooks.XonshTerminal256Formatter(style=proxy_style)
s = pygments.format(tokens, formatter)
return s
elif force_string:
print("To force colorization of string, install Pygments")
return tokens
else:
return tokens
def print_color(self, string, end="\n", **kwargs):
"""Prints a color string using prompt-toolkit color management."""
if isinstance(string, str):
tokens = partial_color_tokenize(string)
else:
# assume this is a list of (Token, str) tuples and just print
tokens = string
tokens = PygmentsTokens(tokens)
if HAS_PYGMENTS:
env = builtins.__xonsh__.env
self.styler.style_name = env.get("XONSH_COLOR_STYLE")
proxy_style = style_from_pygments_cls(
pyghooks.xonsh_style_proxy(self.styler)
)
else:
proxy_style = style_from_pygments_dict(DEFAULT_STYLE_DICT)
ptk_print(
tokens, style=proxy_style, end=end, include_default_pygments_style=False
)
def color_style_names(self):
"""Returns an iterable of all available style names."""
if not HAS_PYGMENTS:
return ["For other xonsh styles, please install pygments"]
return get_all_styles()
def color_style(self):
"""Returns the current color map."""
if not HAS_PYGMENTS:
return DEFAULT_STYLE_DICT
env = builtins.__xonsh__.env
self.styler.style_name = env.get("XONSH_COLOR_STYLE")
return self.styler.styles
def restore_tty_sanity(self):
"""An interface for resetting the TTY stdin mode. This is highly
dependent on the shell backend. Also it is mostly optional since
it only affects ^Z backgrounding behaviour.
"""
# PTK does not seem to need any specialization here. However,
# if it does for some reason in the future...
# The following writes an ANSI escape sequence that sends the cursor
# to the end of the line. This has the effect of restoring ECHO mode.
# See http://unix.stackexchange.com/a/108014/129048 for more details.
# This line can also be replaced by os.system("stty sane"), as per
# http://stackoverflow.com/questions/19777129/interactive-python-interpreter-run-in-background#comment29421919_19778355
# However, it is important to note that not termios-based solution
# seems to work. My guess is that this is because termios restoration
# needs to be performed by the subprocess itself. This fix is important
# when subprocesses don't properly restore the terminal attributes,
# like Python in interactive mode. Also note that the sequences "\033M"
# and "\033E" seem to work too, but these are technically VT100 codes.
# I used the more primitive ANSI sequence to maximize compatibility.
# -scopatz 2017-01-28
# if not ON_POSIX:
# return
# sys.stdout.write('\033[9999999C\n')
if not ON_POSIX:
return
stty, _ = builtins.__xonsh__.commands_cache.lazyget("stty", (None, None))
if stty is None:
return
os.system(stty + " sane")
from xonsh.ptk_shell.shell import * # noqa: F403 F401

View file

@ -3,9 +3,9 @@
import os
import builtins
from prompt_toolkit.layout.dimension import LayoutDimension
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.application.current import get_app
class PromptToolkitCompleter(Completer):
@ -80,8 +80,8 @@ class PromptToolkitCompleter(Completer):
def suggestion_completion(self, document, line):
"""Provides a completion based on the current auto-suggestion."""
cli = self.shell.prompter.cli
sug = self.hist_suggester.get_suggestion(cli, cli.current_buffer, document)
app = self.shell.prompter.app
sug = self.hist_suggester.get_suggestion(app.current_buffer, document)
if sug is None:
return None
comp, _, _ = sug.text.partition(" ")
@ -89,20 +89,17 @@ class PromptToolkitCompleter(Completer):
return prev + comp
def reserve_space(self):
cli = builtins.__xonsh__.shell.shell.prompter.cli
window = cli.application.layout.children[0].content.children[1]
"""Adjust the height for showing autocompletion menu."""
app = get_app()
render = app.renderer
window = app.layout.container.children[0].content.children[1].content
if window and window.render_info:
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
last_h = render._last_screen.height if render._last_screen else 0
last_h = max(render._min_available_height, last_h)
if last_h < size:
if render._last_screen:
render._last_screen.height = size

View file

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""History object for use with prompt_toolkit."""
import builtins
import prompt_toolkit.history
class PromptToolkitHistory(prompt_toolkit.history.History):
"""History class that implements the prompt-toolkit history interface
with the xonsh backend.
"""
def __init__(self, load_prev=True, *args, **kwargs):
"""Initialize history object."""
super().__init__()
self.load_prev = load_prev
def store_string(self, entry):
pass
def load_history_strings(self):
"""Loads synchronous history strings"""
if not self.load_prev:
return
hist = builtins.__xonsh__.history
if hist is None:
return
for cmd in hist.all_items(newest_first=True):
line = cmd["inp"].rstrip()
strs = self.get_strings()
if len(strs) == 0 or line != strs[-1]:
yield line
def __getitem__(self, index):
return self.get_strings()[index]
def __len__(self):
return len(self.get_strings())
def __iter__(self):
return iter(self.get_strings())
def _cust_history_matches(self, i):
"""Custom history search method for prompt_toolkit that matches previous
commands anywhere on a line, not just at the start.
This gets monkeypatched into the prompt_toolkit prompter if
``XONSH_HISTORY_MATCH_ANYWHERE=True``"""
return (
self.history_search_text is None
or self.history_search_text in self._working_lines[i]
)

View file

@ -2,6 +2,7 @@
"""Key bindings for prompt_toolkit xonsh shell."""
import builtins
from prompt_toolkit import search
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import (
Condition,
@ -9,14 +10,15 @@ from prompt_toolkit.filters import (
HasSelection,
EmacsInsertMode,
ViInsertMode,
IsSearching,
)
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
from xonsh.shell import transform_command
env = builtins.__xonsh__.env
DEDENT_TOKENS = frozenset(["raise", "return", "pass", "break", "continue"])
@ -36,6 +38,7 @@ def carriage_return(b, cli, *, autoindent=True):
at_end_of_line = _is_blank(doc.current_line_after_cursor)
current_line_blank = _is_blank(doc.current_line)
env = builtins.__xonsh__.env
indent = env.get("INDENT") if autoindent else ""
partial_string_info = check_for_partial_string(doc.text)
@ -68,7 +71,7 @@ def carriage_return(b, cli, *, autoindent=True):
elif current_line_blank and in_partial_string:
b.newline(copy_margin=autoindent)
else:
b.accept_action.validate_and_handle(cli, b)
b.validate_and_handle()
def _is_blank(l):
@ -93,35 +96,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
len(before_cursor) == 0 and 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
@ -129,37 +133,40 @@ 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
and 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
return False
else:
return cli.current_buffer_name == DEFAULT_BUFFER and not cli.current_buffer.text
app = get_app()
buffer_name = app.current_buffer.name
return 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()
@ -168,10 +175,10 @@ def whitespace_or_bracket_before(cli):
@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()
@ -179,11 +186,11 @@ def whitespace_or_bracket_after(cli):
)
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()
@ -193,6 +200,7 @@ def load_xonsh_bindings(key_bindings_manager):
If there are only whitespaces before current cursor position insert
indent instead of autocompleting.
"""
env = builtins.__xonsh__.env
event.cli.current_buffer.insert_text(env.get("INDENT"))
@handle(Keys.ControlX, Keys.ControlE, filter=~has_selection)
@ -207,6 +215,7 @@ def load_xonsh_bindings(key_bindings_manager):
if b.complete_state:
b.complete_previous()
else:
env = builtins.__xonsh__.env
event.cli.current_buffer.insert_text(env.get("INDENT"))
@handle("(", filter=autopair_condition & whitespace_or_bracket_after)
@ -256,9 +265,7 @@ def load_xonsh_bindings(key_bindings_manager):
if buffer.document.current_char == "'":
buffer.cursor_position += 1
elif whitespace_or_bracket_before(event.cli) and whitespace_or_bracket_after(
event.cli
):
elif whitespace_or_bracket_before() and whitespace_or_bracket_after():
buffer.insert_text("'")
buffer.insert_text("'", move_cursor=False)
else:
@ -270,9 +277,7 @@ def load_xonsh_bindings(key_bindings_manager):
if buffer.document.current_char == '"':
buffer.cursor_position += 1
elif whitespace_or_bracket_before(event.cli) and whitespace_or_bracket_after(
event.cli
):
elif whitespace_or_bracket_before() and whitespace_or_bracket_after():
buffer.insert_text('"')
buffer.insert_text('"', move_cursor=False)
else:
@ -296,16 +301,18 @@ def load_xonsh_bindings(key_bindings_manager):
def call_exit_alias(event):
"""Use xonsh exit function"""
b = event.cli.current_buffer
b.accept_action.validate_and_handle(event.cli, b)
b.validate_and_handle()
xonsh_exit([])
@handle(Keys.ControlJ, filter=IsMultiline())
@handle(Keys.ControlJ, filter=IsMultiline() & insert_mode)
@handle(Keys.ControlM, filter=IsMultiline() & insert_mode)
def multiline_carriage_return(event):
""" Wrapper around carriage_return multiline parser """
b = event.cli.current_buffer
carriage_return(b, event.cli)
@handle(Keys.ControlJ, filter=should_confirm_completion)
@handle(Keys.ControlM, filter=should_confirm_completion)
def enter_confirm_completion(event):
"""Ignore <enter> (confirm completion)"""
event.current_buffer.complete_state = None
@ -319,7 +326,7 @@ def load_xonsh_bindings(key_bindings_manager):
def execute_block_now(event):
"""Execute a block of text irrespective of cursor position"""
b = event.cli.current_buffer
b.accept_action.validate_and_handle(event.cli, b)
b.validate_and_handle()
@handle(Keys.Left, filter=beginning_of_line)
def wrap_cursor_back(event):
@ -339,28 +346,14 @@ def load_xonsh_bindings(key_bindings_manager):
b.cursor_left(count=abs(relative_begin_index))
b.cursor_down(count=1)
@handle(Keys.ControlI, filter=insert_mode)
def generate_completions(event):
@handle(Keys.ControlM, filter=IsSearching())
@handle(Keys.ControlJ, filter=IsSearching())
def accept_search(event):
search.accept_search()
@handle(Keys.ControlZ)
def skip_control_z(event):
"""Prevents the writing of ^Z to the prompt, if Ctrl+Z was pressed
during the previous command.
"""
Tab-completion: where the first tab completes the common suffix and the
second tab lists all the completions.
Notes
-----
This method was forked from the mainline prompt-toolkit repo.
Copyright (c) 2014, Jonathan Slenders, All rights reserved.
"""
b = event.current_buffer
def second_tab():
if b.complete_state:
b.complete_next()
else:
event.cli.start_completion(select_first=False)
# On the second tab-press, or when already navigating through
# completions.
if event.is_repeat or b.complete_state:
second_tab()
else:
event.cli.start_completion(insert_common_part=True, select_first=False)
pass

View file

@ -1,31 +1,36 @@
# -*- coding: utf-8 -*-
"""The prompt_toolkit based xonsh shell."""
import os
import sys
import builtins
from types import MethodType
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.base_shell import BaseShell
from xonsh.tools import print_exception, carriage_return, ansicolors_to_ptk1_names
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.events import events
from xonsh.base_shell import BaseShell
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 as _DEFAULT_STYLE_DICT,
)
from xonsh.tools import print_exception, carriage_return
from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS, ON_POSIX
from xonsh.style_tools import partial_color_tokenize, _TokenType, DEFAULT_STYLE_DICT
from xonsh.lazyimps import pygments, pyghooks, winutils
from xonsh.pygments_cache import get_all_styles
from xonsh.lazyasd import LazyObject
from xonsh.ptk_shell.history import PromptToolkitHistory, _cust_history_matches
from xonsh.ptk_shell.completer import PromptToolkitCompleter
from xonsh.ptk_shell.key_bindings import load_xonsh_bindings
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.history import ThreadedHistory
from prompt_toolkit.shortcuts import print_formatted_text as ptk_print
from prompt_toolkit.shortcuts import CompleteStyle
from prompt_toolkit.shortcuts.prompt import PromptSession
from prompt_toolkit.formatted_text import PygmentsTokens
from prompt_toolkit.styles import merge_styles, Style
from prompt_toolkit.styles.pygments import (
style_from_pygments_cls,
style_from_pygments_dict,
)
Token = _TokenType()
@ -34,54 +39,45 @@ events.transmogrify("on_ptk_create", "LoadEvent")
events.doc(
"on_ptk_create",
"""
on_ptk_create(prompter: Prompter, history: PromptToolkitHistory, completer: PromptToolkitCompleter, bindings: KeyBindingManager) ->
on_ptk_create(prompter: PromptSession, history: PromptToolkitHistory, completer: PromptToolkitCompleter, bindings: KeyBindings) ->
Fired after prompt toolkit has been initialized
""",
)
# Convert new ansicolor names to names
# understood by PTK1
DEFAULT_STYLE_DICT = LazyObject(
lambda: ansicolors_to_ptk1_names(_DEFAULT_STYLE_DICT),
globals(),
"DEFAULT_STYLE_DICT",
)
class PromptToolkitShell(BaseShell):
"""The xonsh shell."""
"""The xonsh shell for prompt_toolkit v2 and later."""
completion_displays_to_styles = {
"multi": CompleteStyle.MULTI_COLUMN,
"single": CompleteStyle.COLUMN,
"readline": CompleteStyle.READLINE_LIKE,
"none": None,
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
if ON_WINDOWS:
winutils.enable_virtual_terminal_processing()
self._first_prompt = True
self.prompter = Prompter()
self.history = PromptToolkitHistory()
self.history = ThreadedHistory(PromptToolkitHistory())
self.prompter = PromptSession(history=self.history)
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
key_bindings_manager_args = {
"enable_auto_suggest_bindings": True,
"enable_search": True,
"enable_abort_and_exit_bindings": True,
}
self.key_bindings_manager = KeyBindingManager(**key_bindings_manager_args)
load_xonsh_bindings(self.key_bindings_manager)
self.key_bindings = KeyBindings()
load_xonsh_bindings(self.key_bindings)
# Store original `_history_matches` in case we need to restore it
self._history_matches_orig = self.prompter.default_buffer._history_matches
# 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_manager,
bindings=self.key_bindings,
)
def singleline(
self,
store_in_history=True,
auto_suggest=None,
enable_history_search=True,
multiline=True,
**kwargs
self, 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
@ -90,14 +86,13 @@ class PromptToolkitShell(BaseShell):
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
refresh_interval = env.get("PROMPT_REFRESH_INTERVAL")
refresh_interval = refresh_interval if refresh_interval > 0 else None
complete_in_thread = env.get("COMPLETION_IN_THREAD")
completions_display = env.get("COMPLETIONS_DISPLAY")
multicolumn = completions_display == "multi"
complete_style = self.completion_displays_to_styles[completions_display]
complete_while_typing = env.get("UPDATE_COMPLETIONS_ON_KEYPRESS")
if complete_while_typing:
# PTK requires history search to be none when completing while typing
@ -105,45 +100,71 @@ class PromptToolkitShell(BaseShell):
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"):
prompt_tokens_cached = self.prompt_tokens(None)
get_prompt_tokens = lambda cli: prompt_tokens_cached
rprompt_tokens_cached = self.rprompt_tokens(None)
get_rprompt_tokens = lambda cli: rprompt_tokens_cached
bottom_toolbar_tokens_cached = self.bottom_toolbar_tokens(None)
get_bottom_toolbar_tokens = lambda cli: bottom_toolbar_tokens_cached
else:
get_bottom_toolbar_tokens = self.bottom_toolbar_tokens
if env.get("UPDATE_PROMPT_ON_KEYPRESS"):
get_prompt_tokens = self.prompt_tokens
get_rprompt_tokens = self.rprompt_tokens
get_bottom_toolbar_tokens = self.bottom_toolbar_tokens
else:
get_prompt_tokens = self.prompt_tokens()
get_rprompt_tokens = self.rprompt_tokens()
if get_bottom_toolbar_tokens:
get_bottom_toolbar_tokens = get_bottom_toolbar_tokens()
with self.prompter:
prompt_args = {
"mouse_support": mouse_support,
"auto_suggest": auto_suggest,
"get_prompt_tokens": get_prompt_tokens,
"get_rprompt_tokens": get_rprompt_tokens,
"get_bottom_toolbar_tokens": get_bottom_toolbar_tokens,
"completer": completer,
"multiline": multiline,
"get_continuation_tokens": self.continuation_tokens,
"history": history,
"enable_history_search": enable_history_search,
"reserve_space_for_menu": 0,
"key_bindings_registry": self.key_bindings_manager.registry,
"display_completions_in_columns": multicolumn,
"complete_while_typing": complete_while_typing,
}
if builtins.__xonsh__.env.get("COLOR_INPUT"):
if HAS_PYGMENTS:
prompt_args["lexer"] = PygmentsLexer(pyghooks.XonshLexer)
prompt_args["style"] = PygmentsStyle(
pyghooks.xonsh_style_proxy(self.styler)
)
else:
prompt_args["style"] = style_from_dict(DEFAULT_STYLE_DICT)
line = self.prompter.prompt(**prompt_args)
events.on_post_prompt.fire()
if env.get("VI_MODE"):
editing_mode = EditingMode.VI
else:
editing_mode = EditingMode.EMACS
if env.get("XONSH_HISTORY_MATCH_ANYWHERE"):
self.prompter.default_buffer._history_matches = MethodType(
_cust_history_matches, self.prompter.default_buffer
)
elif (
self.prompter.default_buffer._history_matches
is not self._history_matches_orig
):
self.prompter.default_buffer._history_matches = self._history_matches_orig
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,
"editing_mode": editing_mode,
"prompt_continuation": self.continuation_tokens,
"enable_history_search": enable_history_search,
"reserve_space_for_menu": 0,
"key_bindings": self.key_bindings,
"complete_style": complete_style,
"complete_while_typing": complete_while_typing,
"include_default_pygments_style": False,
"refresh_interval": refresh_interval,
"complete_in_thread": complete_in_thread,
}
if builtins.__xonsh__.env.get("COLOR_INPUT"):
if HAS_PYGMENTS:
prompt_args["lexer"] = PygmentsLexer(pyghooks.XonshLexer)
style = style_from_pygments_cls(pyghooks.xonsh_style_proxy(self.styler))
else:
style = style_from_pygments_dict(DEFAULT_STYLE_DICT)
prompt_args["style"] = style
style_overrides_env = env.get("PTK_STYLE_OVERRIDES")
if style_overrides_env:
try:
style_overrides = Style.from_dict(style_overrides_env)
prompt_args["style"] = merge_styles([style, style_overrides])
except (AttributeError, TypeError, ValueError):
print_exception()
line = self.prompter.prompt(**prompt_args)
events.on_post_prompt.fire()
return line
def _push(self, line):
@ -187,7 +208,7 @@ class PromptToolkitShell(BaseShell):
else:
break
def prompt_tokens(self, cli):
def prompt_tokens(self):
"""Returns a list of (token, str) tuples for the current prompt."""
p = builtins.__xonsh__.env.get("PROMPT")
try:
@ -199,9 +220,9 @@ class PromptToolkitShell(BaseShell):
carriage_return()
self._first_prompt = False
self.settitle()
return toks
return PygmentsTokens(toks)
def rprompt_tokens(self, cli):
def rprompt_tokens(self):
"""Returns a list of (token, str) tuples for the current right
prompt.
"""
@ -216,32 +237,38 @@ class PromptToolkitShell(BaseShell):
except Exception: # pylint: disable=broad-except
print_exception()
toks = partial_color_tokenize(p)
return toks
return PygmentsTokens(toks)
def bottom_toolbar_tokens(self, cli):
def _bottom_toolbar_tokens(self):
"""Returns a list of (token, str) tuples for the current bottom
toolbar.
"""
p = builtins.__xonsh__.env.get("BOTTOM_TOOLBAR")
# self.prompt_formatter does handle empty strings properly,
# but this avoids descending into it in the common case of
# $TOOLBAR == ''.
if isinstance(p, str) and len(p) == 0:
return []
if not p:
return
try:
p = self.prompt_formatter(p)
except Exception: # pylint: disable=broad-except
print_exception()
toks = partial_color_tokenize(p)
return toks
return PygmentsTokens(toks)
def continuation_tokens(self, cli, width):
@property
def bottom_toolbar_tokens(self):
"""Returns self._bottom_toolbar_tokens if it would yield a result
"""
if builtins.__xonsh__.env.get("BOTTOM_TOOLBAR"):
return self._bottom_toolbar_tokens
def continuation_tokens(self, width, line_number, is_soft_wrap=False):
"""Displays dots in multiline prompt"""
if is_soft_wrap:
return ""
width = width - 1
dots = builtins.__xonsh__.env.get("MULTILINE_PROMPT")
dots = dots() if callable(dots) else dots
if dots is None:
return [(Token, " " * (width + 1))]
if not dots:
return ""
basetoks = self.format_color(dots)
baselen = sum(len(t[1]) for t in basetoks)
if baselen == 0:
@ -262,7 +289,7 @@ class PromptToolkitShell(BaseShell):
if n <= count:
break
toks.append((Token, " ")) # final space
return toks
return PygmentsTokens(toks)
def format_color(self, string, hide=False, force_string=False, **kwargs):
"""Formats a color string using Pygments. This, therefore, returns
@ -286,17 +313,22 @@ class PromptToolkitShell(BaseShell):
def print_color(self, string, end="\n", **kwargs):
"""Prints a color string using prompt-toolkit color management."""
if isinstance(string, str):
tokens = partial_color_tokenize(string + end)
tokens = partial_color_tokenize(string)
else:
# assume this is a list of (Token, str) tuples and just print
tokens = string
tokens = PygmentsTokens(tokens)
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_cls(
pyghooks.xonsh_style_proxy(self.styler)
)
else:
proxy_style = style_from_dict(DEFAULT_STYLE_DICT)
print_tokens(tokens, style=proxy_style)
proxy_style = style_from_pygments_dict(DEFAULT_STYLE_DICT)
ptk_print(
tokens, style=proxy_style, end=end, include_default_pygments_style=False
)
def color_style_names(self):
"""Returns an iterable of all available style names."""
@ -335,3 +367,9 @@ class PromptToolkitShell(BaseShell):
# if not ON_POSIX:
# return
# sys.stdout.write('\033[9999999C\n')
if not ON_POSIX:
return
stty, _ = builtins.__xonsh__.commands_cache.lazyget("stty", (None, None))
if stty is None:
return
os.system(stty + " sane")

View file

@ -33,7 +33,6 @@ from xonsh.lazyasd import LazyObject, LazyDict, lazyobject
from xonsh.tools import (
ON_WINDOWS,
intensify_colors_for_cmd_exe,
ansicolors_to_ptk1_names,
ANSICOLOR_NAMES_MAP,
PTK_NEW_OLD_COLOR_MAP,
hardcode_colors_for_win10,
@ -385,16 +384,6 @@ class XonshStyle(Style):
color_token = color_token_by_name(xonsh_color, self.styles)
file_color_tokens[file_type] = color_token
# Convert new ansicolor names to old PTK1 names
# Can be remvoed when PTK1 support is dropped.
if (
builtins.__xonsh__.shell.shell_type != "prompt_toolkit2"
and pygments_version_info()
and pygments_version_info() < (2, 4, 0)
):
for smap in [self.trap, cmap, PTK_STYLE, self._smap]:
smap.update(ansicolors_to_ptk1_names(smap))
if ON_WINDOWS and "prompt_toolkit" in builtins.__xonsh__.shell.shell_type:
self.enhance_colors_for_cmd_exe()
@ -1369,20 +1358,24 @@ events.on_lscolors_change(on_lscolors_change)
def color_file(file_path: str, mode: int) -> (Color, str):
"""Determine color to use for file as ls -c would, given stat() results and its name.
Parameters
----------
file_path : string
relative path of file (as user typed it).
mode : int
stat() results for file_path.
Returns
-------
color token, color_key
Bugs
----
Notes
-----
* doesn't handle CA (capability)
* doesn't handle LS TARGET mapping.
* doesn't handle LS TARGET mapping
"""
lsc = builtins.__xonsh__.env["LS_COLORS"]

View file

@ -11,7 +11,7 @@ from xonsh.platform import (
best_shell_type,
has_prompt_toolkit,
ptk_above_min_supported,
ptk_shell_type,
minimum_required_ptk_version,
)
from xonsh.tools import XonshError, print_exception
from xonsh.events import events
@ -121,19 +121,66 @@ class Shell(object):
"best": "best",
"d": "dumb",
"dumb": "dumb",
"ptk": "prompt_toolkit",
"ptk1": "prompt_toolkit1",
"ptk2": "prompt_toolkit2",
"ptk": "prompt_toolkit", # there's only 1 prompt_toolkit shell (now)
"ptk1": "prompt_toolkit", # allow any old config reference to use it
"ptk2": "prompt_toolkit", # so long as user actually has ptk2+ installed.
"prompt-toolkit": "prompt_toolkit",
"prompt_toolkit": "prompt_toolkit",
"prompt-toolkit1": "prompt_toolkit1",
"prompt-toolkit2": "prompt_toolkit2",
"prompt-toolkit1": "prompt_toolkit",
"prompt-toolkit2": "prompt_toolkit",
"prompt-toolkit3": "prompt_toolkit",
"prompt_toolkit3": "prompt_toolkit",
"ptk3": "prompt_toolkit",
"rand": "random",
"random": "random",
"rl": "readline",
"readline": "readline",
}
@staticmethod
def choose_shell_type(init_shell_type=None, env=None):
# pick a valid shell -- if no shell is specified by the user,
# shell type is pulled from env
# extracted for testability
shell_type = init_shell_type
if shell_type is None and env:
shell_type = env.get("SHELL_TYPE")
if shell_type == "none":
# This bricks interactive xonsh
# Can happen from the use of .xinitrc, .xsession, etc
# odd logic. We don't override if shell.__init__( shell_type="none"),
# only if it come from environment?
shell_type = "best"
shell_type = Shell.shell_type_aliases.get(shell_type, shell_type)
if shell_type == "best" or shell_type is None:
shell_type = best_shell_type()
elif env and env.get("TERM", "") == "dumb":
shell_type = "dumb"
elif shell_type == "random":
shell_type = random.choice(("readline", "prompt_toolkit"))
if shell_type == "prompt_toolkit":
if not has_prompt_toolkit():
warnings.warn(
"prompt_toolkit is not available, using " "readline instead."
)
shell_type = "readline"
elif not ptk_above_min_supported():
warnings.warn(
"prompt-toolkit version < v{}.{}.0 is not ".format(
*minimum_required_ptk_version
)
+ "supported. Please update prompt-toolkit. Using "
+ "readline instead."
)
shell_type = "readline"
if init_shell_type in ("ptk1", "prompt_toolkit1"):
warnings.warn(
"$SHELL_TYPE='{}' now deprecated, please update your run control file'".format(
init_shell_type
)
)
return shell_type
def __init__(self, execer, ctx=None, shell_type=None, **kwargs):
"""
Parameters
@ -157,44 +204,15 @@ class Shell(object):
env=env.detype(), ts=[time.time(), None], locked=True
)
# pick a valid shell -- if no shell is specified by the user,
# shell type is pulled from env
if shell_type is None:
shell_type = env.get("SHELL_TYPE")
if shell_type == "none":
# This bricks interactive xonsh
# Can happen from the use of .xinitrc, .xsession, etc
shell_type = "best"
shell_type = self.shell_type_aliases.get(shell_type, shell_type)
if shell_type == "best" or shell_type is None:
shell_type = best_shell_type()
elif env.get("TERM", "") == "dumb":
shell_type = "dumb"
elif shell_type == "random":
shell_type = random.choice(("readline", "prompt_toolkit"))
if shell_type == "prompt_toolkit":
if not has_prompt_toolkit():
warnings.warn(
"prompt_toolkit is not available, using " "readline instead."
)
shell_type = "readline"
elif not ptk_above_min_supported():
warnings.warn(
"prompt-toolkit version < v1.0.0 is not "
"supported. Please update prompt-toolkit. Using "
"readline instead."
)
shell_type = "readline"
else:
shell_type = ptk_shell_type()
shell_type = self.choose_shell_type(shell_type, env)
self.shell_type = env["SHELL_TYPE"] = shell_type
# actually make the shell
if shell_type == "none":
from xonsh.base_shell import BaseShell as shell_class
elif shell_type == "prompt_toolkit2":
from xonsh.ptk2.shell import PromptToolkit2Shell as shell_class
elif shell_type == "prompt_toolkit1":
from xonsh.ptk.shell import PromptToolkitShell as shell_class
elif shell_type == "prompt_toolkit":
from xonsh.ptk_shell.shell import PromptToolkitShell as shell_class
elif shell_type == "readline":
from xonsh.readline_shell import ReadlineShell as shell_class
elif shell_type == "jupyter":

View file

@ -8,7 +8,6 @@ import builtins
from prompt_toolkit.filters import completion_is_selected, IsMultiline
from prompt_toolkit.keys import Keys
from xonsh.built_ins import DynamicAccessProxy
from xonsh.platform import ptk_shell_type
from xonsh.tools import check_for_partial_string
__all__ = ()
@ -36,18 +35,11 @@ def expand_abbrev(buffer):
@events.on_ptk_create
def custom_keybindings(bindings, **kw):
if ptk_shell_type() == "prompt_toolkit2":
from xonsh.ptk2.key_bindings import carriage_return
from prompt_toolkit.filters import EmacsInsertMode, ViInsertMode
from xonsh.ptk_shell.key_bindings import carriage_return
from prompt_toolkit.filters import EmacsInsertMode, ViInsertMode
handler = bindings.add
insert_mode = ViInsertMode() | EmacsInsertMode()
else:
from xonsh.ptk.key_bindings import carriage_return
from prompt_toolkit.filters import to_filter
handler = bindings.registry.add_binding
insert_mode = to_filter(True)
handler = bindings.add
insert_mode = ViInsertMode() | EmacsInsertMode()
@handler(" ", filter=IsMultiline() & insert_mode)
def handle_space(event):

View file

@ -4,8 +4,6 @@ Alt+Left/Right remains unmodified to jump over smaller word segments.
"""
from prompt_toolkit.keys import Keys
from xonsh.platform import ptk_shell_type
__all__ = ()
@ -17,19 +15,14 @@ def custom_keybindings(bindings, **kw):
# Alt+Left and Alt+Right still jump over smaller word segments.
# See https://github.com/xonsh/xonsh/issues/2403
if ptk_shell_type() == "prompt_toolkit2":
handler = bindings.add
else:
handler = bindings.registry.add_binding
@handler(Keys.ControlLeft)
@bindings.add(Keys.ControlLeft)
def ctrl_left(event):
buff = event.current_buffer
pos = buff.document.find_previous_word_beginning(count=event.arg, WORD=True)
if pos:
buff.cursor_position += pos
@handler(Keys.ControlRight)
@bindings.add(Keys.ControlRight)
def ctrl_right(event):
buff = event.current_buffer
pos = buff.document.find_next_word_ending(count=event.arg, WORD=True)