* 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>`_ - `Before <https://i.imgur.com/EMhPdgU.png>`_
- `After <https://i.imgur.com/sJiqgsb.png>`_ - `After <https://i.imgur.com/sJiqgsb.png>`_
* The autovox xontrib now preserves activated environment on cd * 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.) * 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, * Pressing ``Ctrl+Z`` no longer deadlocks the terminal,

View file

@ -44,10 +44,10 @@ For those of you who want the gritty details.
shell shell
base_shell base_shell
readline_shell readline_shell
ptk2/shell ptk_shell/shell
ptk2/history ptk_shell/history
ptk2/completer ptk_shell/completer
ptk2/key_bindings ptk_shell/key_bindings
pretty pretty
replay replay
diff_history diff_history
@ -88,13 +88,3 @@ For those of you who want the gritty details.
mplhooks mplhooks
vox 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: .. _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: :members:
:undoc-members: :undoc-members:
:inherited-members: :inherited-members:

View file

@ -1,10 +1,10 @@
.. _xonsh_ptk2_history: .. _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: :members:
:undoc-members: :undoc-members:
:inherited-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: .. _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: :members:
:undoc-members: :undoc-members:
:inherited-members: :inherited-members:

View file

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

View file

@ -4,16 +4,15 @@ Xonsh currently has the following external dependencies,
*Run Time:* *Run Time:*
#. Python v3.4+ #. Python v3.5+
Pip supports "extra" dependencies in the form of ``xonsh[ptk,linux]``, where Pip supports "extra" dependencies in the form of ``xonsh[ptk,linux]``, where
the list in the brackets identify the optional features the list in the brackets identify the optional features
Xonsh currently has the following extras Xonsh currently has the following extras
#. ``ptk``: prompt-toolkit: #. ``ptk``: prompt-toolkit >= 2.0: *advanced readline library, line-editing*
*advanced readline library, line-editing* #. ``pygments``: pygments >=2.2: *syntax-highlighting*
#. ``pygments>=2.2``: *syntax-highlighting*
#. ``proctitle``: setproctitle: *change the title of terminal to reflect the current subprocess* #. ``proctitle``: setproctitle: *change the title of terminal to reflect the current subprocess*
#. ``linux``: distro: *linux specific platform information* #. ``linux``: distro: *linux specific platform information*
#. ``mac``: gnureadline: *GNU's featureful version of readline* #. ``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 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 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 with the appropriate event, in this case ``on_ptk_create``.
the method of ``handler`` differs depending on the version of ``prompt_toolkit``.
We'll start with a toy example that just inserts the text "hi" into the current line of the prompt:: We'll start with a toy example that just inserts the text "hi" into the current line of the prompt::
@events.on_ptk_create @events.on_ptk_create
def custom_keybindings(bindings, **kw): def custom_keybindings(bindings, **kw):
# prompt_toolkit 1.x
# handler = bindings.registry.add_binding @bindings.add(Keys.ControlW)
# prompt_toolkit 2.x
handler = bindings.add
@handler(Keys.ControlW)
def say_hi(event): def say_hi(event):
event.current_buffer.insert_text('hi') event.current_buffer.insert_text('hi')
Put that in your `xonshrc <xonshrc.html>`_, restart xonsh and then see if Put that in your `xonshrc <xonshrc.html>`_, restart xonsh and then see if
pressing ``Ctrl-w`` does anything (it should!) 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? 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 cloud_sptheme
numpydoc numpydoc
Sphinx==2.4.4 Sphinx==2.4.4
prompt_toolkit prompt_toolkit>=2.0
pygments>=2.2 pygments>=2.2
psutil psutil
pyzmq pyzmq

View file

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

View file

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

View file

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

View file

@ -149,23 +149,25 @@ def test_nested():
def events_fxt(): def events_fxt():
return EventManager() return EventManager()
@pytest.fixture @pytest.fixture
def xonsh_builtins_ls_colors(xonsh_builtins, events_fxt): def xonsh_builtins_ls_colors(xonsh_builtins, events_fxt):
x = xonsh_builtins.__xonsh__ x = xonsh_builtins.__xonsh__
xonsh_builtins.__xonsh__.shell = DummyShell() # because load_command_cache zaps it. 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) lsc = LsColors(LsColors.default_settings)
xonsh_builtins.__xonsh__.env["LS_COLORS"] = lsc # establish LS_COLORS before style. xonsh_builtins.__xonsh__.env["LS_COLORS"] = lsc # establish LS_COLORS before style.
xonsh_builtins.__xonsh__.shell.shell.styler = XonshStyle() # default style xonsh_builtins.__xonsh__.shell.shell.styler = XonshStyle() # default style
events.on_lscolors_change(on_lscolors_change) events.on_lscolors_change(on_lscolors_change)
yield xonsh_builtins yield xonsh_builtins
xonsh_builtins.__xonsh__ = x xonsh_builtins.__xonsh__ = x
@skip_if_on_windows @skip_if_on_windows
def test_path(tmpdir, xonsh_builtins_ls_colors): def test_path(tmpdir, xonsh_builtins_ls_colors):
test_dir = str(tmpdir.mkdir("xonsh-test-highlight-path")) test_dir = str(tmpdir.mkdir("xonsh-test-highlight-path"))
check_token( check_token(
"cd {}".format(test_dir), [(Name.Builtin, "cd"), (Color.BOLD_BLUE, test_dir)] "cd {}".format(test_dir), [(Name.Builtin, "cd"), (Color.BOLD_BLUE, test_dir)]
@ -184,7 +186,7 @@ def test_path(tmpdir, xonsh_builtins_ls_colors):
def test_color_on_lscolors_change(tmpdir, xonsh_builtins_ls_colors): def test_color_on_lscolors_change(tmpdir, xonsh_builtins_ls_colors):
"""Verify colorizer returns Token.Text if file type not defined in LS_COLORS""" """Verify colorizer returns Token.Text if file type not defined in LS_COLORS"""
lsc = xonsh_builtins_ls_colors.__xonsh__.env["LS_COLORS"] lsc = xonsh_builtins_ls_colors.__xonsh__.env["LS_COLORS"]
test_dir = str(tmpdir.mkdir("xonsh-test-highlight-path")) test_dir = str(tmpdir.mkdir("xonsh-test-highlight-path"))
lsc['di'] = ('GREEN',) lsc['di'] = ('GREEN',)
@ -194,11 +196,12 @@ def test_color_on_lscolors_change(tmpdir, xonsh_builtins_ls_colors):
) )
del lsc['di'] del lsc['di']
check_token( check_token(
"cd {}".format(test_dir), [(Name.Builtin, "cd"), (Text, test_dir)] "cd {}".format(test_dir), [(Name.Builtin, "cd"), (Text, test_dir)]
) )
@skip_if_on_windows @skip_if_on_windows
def test_subproc_args(): def test_subproc_args():
check_token("cd 192.168.0.1", [(Text, "192.168.0.1")]) check_token("cd 192.168.0.1", [(Text, "192.168.0.1")])

View file

@ -5,21 +5,54 @@ try:
except ImportError: except ImportError:
pytest.mark.skip(msg="prompt_toolkit is not available") pytest.mark.skip(msg="prompt_toolkit is not available")
from xonsh.ptk2.history import PromptToolkitHistory
from tools import skip_if_lt_ptk2
@pytest.fixture @pytest.fixture
def history_obj(): def history_obj():
"""Instantiate `PromptToolkitHistory` and append a line string""" """Instantiate `PromptToolkitHistory` and append a line string"""
from xonsh.ptk_shell.history import PromptToolkitHistory
hist = PromptToolkitHistory(load_prev=False) hist = PromptToolkitHistory(load_prev=False)
hist.append_string("line10") hist.append_string("line10")
return hist return hist
@skip_if_lt_ptk2
def test_obj(history_obj): def test_obj(history_obj):
assert ["line10"] == history_obj.get_strings() assert ["line10"] == history_obj.get_strings()
assert len(history_obj) == 1 assert len(history_obj) == 1
assert ["line10"] == [x for x in history_obj] 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.tools import ON_WINDOWS
from xonsh.built_ins import XonshSession 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"]) Context = namedtuple("Context", ["indent", "buffer", "accept", "cli", "cr"])
@ -24,7 +22,7 @@ def ctx():
builtins.__xonsh__ = XonshSession() builtins.__xonsh__ = XonshSession()
builtins.__xonsh__.env = DummyEnv() builtins.__xonsh__.env = DummyEnv()
builtins.__xonsh__.env["INDENT"] = " " 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 = Buffer()
ptk_buffer.accept_action = MagicMock(name="accept") ptk_buffer.accept_action = MagicMock(name="accept")
@ -40,7 +38,6 @@ def ctx():
del builtins.__xonsh__ del builtins.__xonsh__
@skip_if_lt_ptk2
def test_colon_indent(ctx): def test_colon_indent(ctx):
document = Document("for i in range(5):") document = Document("for i in range(5):")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
@ -48,7 +45,6 @@ def test_colon_indent(ctx):
assert ctx.buffer.document.current_line == ctx.indent assert ctx.buffer.document.current_line == ctx.indent
@skip_if_lt_ptk2
def test_dedent(ctx): def test_dedent(ctx):
document = Document("\n" + ctx.indent + "pass") document = Document("\n" + ctx.indent + "pass")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
@ -61,25 +57,23 @@ def test_dedent(ctx):
assert ctx.buffer.document.current_line == ctx.indent assert ctx.buffer.document.current_line == ctx.indent
@skip_if_lt_ptk2
def test_nodedent(ctx): def test_nodedent(ctx):
"""don't dedent if first line of ctx.buffer""" """don't dedent if first line of ctx.buffer"""
mock = MagicMock(return_value=True) 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") document = Document("pass")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli) ctx.cr(ctx.buffer, ctx.cli)
assert ctx.accept.mock_calls is not None assert ctx.accept.mock_calls is not None
mock = MagicMock(return_value=True) 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") document = Document(ctx.indent + "pass")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli) ctx.cr(ctx.buffer, ctx.cli)
assert ctx.accept.mock_calls is not None assert ctx.accept.mock_calls is not None
@skip_if_lt_ptk2
def test_continuation_line(ctx): def test_continuation_line(ctx):
document = Document("\nsecond line") document = Document("\nsecond line")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
@ -87,10 +81,9 @@ def test_continuation_line(ctx):
assert ctx.buffer.document.current_line == "" assert ctx.buffer.document.current_line == ""
@skip_if_lt_ptk2
def test_trailing_slash(ctx): def test_trailing_slash(ctx):
mock = MagicMock(return_value=True) 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 \\") document = Document("this line will \\")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli) ctx.cr(ctx.buffer, ctx.cli)
@ -100,20 +93,18 @@ def test_trailing_slash(ctx):
assert ctx.accept.mock_calls is not None assert ctx.accept.mock_calls is not None
@skip_if_lt_ptk2
def test_cant_compile_newline(ctx): def test_cant_compile_newline(ctx):
mock = MagicMock(return_value=False) 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, ") document = Document("for i in (1, 2, ")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli) ctx.cr(ctx.buffer, ctx.cli)
assert ctx.buffer.document.current_line == "" assert ctx.buffer.document.current_line == ""
@skip_if_lt_ptk2
def test_can_compile_and_executes(ctx): def test_can_compile_and_executes(ctx):
mock = MagicMock(return_value=True) 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") document = Document("ls")
ctx.buffer.set_document(document) ctx.buffer.set_document(document)
ctx.cr(ctx.buffer, ctx.cli) 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 e = xonsh_builtins.__xonsh__.env
lsc = LsColors(LsColors.default_settings) lsc = LsColors(LsColors.default_settings)
xonsh_builtins.__xonsh__.env["LS_COLORS"] = lsc 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 # styler = XonshStyle() # default style
# xonsh_builtins.__xonsh__.shell.shell.styler = styler # xonsh_builtins.__xonsh__.shell.shell.styler = styler
# can't really instantiate XonshStyle separate from a shell?? # can't really instantiate XonshStyle separate from a shell??

View file

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

View file

@ -15,7 +15,6 @@ import pytest
from xonsh.environ import Env from xonsh.environ import Env
from xonsh.base_shell import BaseShell from xonsh.base_shell import BaseShell
from xonsh.platform import ptk_version_info
VER_3_5 = (3, 5) 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_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): def sp(cmd):
return subprocess.check_output(cmd, universal_newlines=True) 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"), "LANG": VarDocs("Fallback locale setting for systems where it matters"),
"LS_COLORS": VarDocs( "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:" 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:" "*.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:" "*.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. History objects should be created via a subclass of History.
Indexing
--------
History acts like a sequence that can be indexed to return History acts like a sequence that can be indexed to return
``HistoryEntry`` objects. ``HistoryEntry`` objects.

View file

@ -257,7 +257,7 @@ def _pprint_displayhook(value):
printed_val = repr(value) printed_val = repr(value)
if HAS_PYGMENTS and env.get("COLOR_RESULTS"): if HAS_PYGMENTS and env.get("COLOR_RESULTS"):
tokens = list(pygments.lex(printed_val, lexer=pyghooks.XonshLexer())) 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) print_color(tokens, end=end)
else: else:
print(printed_val) # black & white case print(printed_val) # black & white case

View file

@ -169,19 +169,13 @@ def ptk_version_info():
return None return None
minimum_required_ptk_version = (2, 0)
"""Minimum version of prompt-toolkit supported by Xonsh"""
@functools.lru_cache(1) @functools.lru_cache(1)
def ptk_above_min_supported(): def ptk_above_min_supported():
minimum_required_ptk_version = (1, 0) return ptk_version_info() and ptk_version_info()[:2] >= minimum_required_ptk_version
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"
@functools.lru_cache(1) @functools.lru_cache(1)
@ -198,7 +192,7 @@ def win_ansi_support():
@functools.lru_cache(1) @functools.lru_cache(1)
def ptk_below_max_supported(): 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 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 -*- from xonsh.ptk_shell.completer import * # noqa: F403 F401
"""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

View file

@ -1,53 +1 @@
# -*- coding: utf-8 -*- from xonsh.ptk_shell.history import * # noqa: F403 F401
"""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

@ -1,359 +1 @@
# -*- coding: utf-8 -*- from xonsh.ptk_shell.key_bindings import * # noqa: F403 F401
"""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

View file

@ -1,375 +1 @@
# -*- coding: utf-8 -*- from xonsh.ptk_shell.shell import * # noqa: F403 F401
"""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")

View file

@ -3,9 +3,9 @@
import os import os
import builtins import builtins
from prompt_toolkit.layout.dimension import LayoutDimension
from prompt_toolkit.completion import Completer, Completion from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.application.current import get_app
class PromptToolkitCompleter(Completer): class PromptToolkitCompleter(Completer):
@ -80,8 +80,8 @@ class PromptToolkitCompleter(Completer):
def suggestion_completion(self, document, line): def suggestion_completion(self, document, line):
"""Provides a completion based on the current auto-suggestion.""" """Provides a completion based on the current auto-suggestion."""
cli = self.shell.prompter.cli app = self.shell.prompter.app
sug = self.hist_suggester.get_suggestion(cli, cli.current_buffer, document) sug = self.hist_suggester.get_suggestion(app.current_buffer, document)
if sug is None: if sug is None:
return None return None
comp, _, _ = sug.text.partition(" ") comp, _, _ = sug.text.partition(" ")
@ -89,20 +89,17 @@ class PromptToolkitCompleter(Completer):
return prev + comp return prev + comp
def reserve_space(self): def reserve_space(self):
cli = builtins.__xonsh__.shell.shell.prompter.cli """Adjust the height for showing autocompletion menu."""
window = cli.application.layout.children[0].content.children[1] app = get_app()
render = app.renderer
window = app.layout.container.children[0].content.children[1].content
if window and window.render_info: if window and window.render_info:
h = window.render_info.content_height h = window.render_info.content_height
r = builtins.__xonsh__.env.get("COMPLETIONS_MENU_ROWS") r = builtins.__xonsh__.env.get("COMPLETIONS_MENU_ROWS")
size = h + r size = h + r
last_h = render._last_screen.height if render._last_screen else 0
def comp_height(cli): last_h = max(render._min_available_height, last_h)
# If there is an autocompletion menu to be shown, make sure that o if last_h < size:
# layout has at least a minimal height in order to display it. if render._last_screen:
if not cli.is_done: render._last_screen.height = size
return LayoutDimension(min=size)
else:
return LayoutDimension()
window._height = comp_height

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.""" """Key bindings for prompt_toolkit xonsh shell."""
import builtins import builtins
from prompt_toolkit import search
from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import ( from prompt_toolkit.filters import (
Condition, Condition,
@ -9,14 +10,15 @@ from prompt_toolkit.filters import (
HasSelection, HasSelection,
EmacsInsertMode, EmacsInsertMode,
ViInsertMode, ViInsertMode,
IsSearching,
) )
from prompt_toolkit.keys import Keys from prompt_toolkit.keys import Keys
from prompt_toolkit.application.current import get_app
from xonsh.aliases import xonsh_exit from xonsh.aliases import xonsh_exit
from xonsh.tools import check_for_partial_string, get_line_continuation from xonsh.tools import check_for_partial_string, get_line_continuation
from xonsh.shell import transform_command from xonsh.shell import transform_command
env = builtins.__xonsh__.env
DEDENT_TOKENS = frozenset(["raise", "return", "pass", "break", "continue"]) 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) at_end_of_line = _is_blank(doc.current_line_after_cursor)
current_line_blank = _is_blank(doc.current_line) current_line_blank = _is_blank(doc.current_line)
env = builtins.__xonsh__.env
indent = env.get("INDENT") if autoindent else "" indent = env.get("INDENT") if autoindent else ""
partial_string_info = check_for_partial_string(doc.text) 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: elif current_line_blank and in_partial_string:
b.newline(copy_margin=autoindent) b.newline(copy_margin=autoindent)
else: else:
b.accept_action.validate_and_handle(cli, b) b.validate_and_handle()
def _is_blank(l): def _is_blank(l):
@ -93,35 +96,36 @@ def can_compile(src):
@Condition @Condition
def tab_insert_indent(cli): def tab_insert_indent():
"""Check if <Tab> should insert indent instead of starting autocompletion. """Check if <Tab> should insert indent instead of starting autocompletion.
Checks if there are only whitespaces before the cursor - if so indent Checks if there are only whitespaces before the cursor - if so indent
should be inserted, otherwise autocompletion. should be inserted, otherwise autocompletion.
""" """
before_cursor = cli.current_buffer.document.current_line_before_cursor before_cursor = get_app().current_buffer.document.current_line_before_cursor
return bool(before_cursor.isspace()) return bool(before_cursor.isspace())
@Condition @Condition
def beginning_of_line(cli): def beginning_of_line():
"""Check if cursor is at beginning of a line other than the first line in a """Check if cursor is at beginning of a line other than the first line in a
multiline document multiline document
""" """
before_cursor = cli.current_buffer.document.current_line_before_cursor app = get_app()
before_cursor = app.current_buffer.document.current_line_before_cursor
return bool( 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 @Condition
def end_of_line(cli): def end_of_line():
"""Check if cursor is at the end of a line other than the last line in a """Check if cursor is at the end of a line other than the last line in a
multiline document multiline document
""" """
d = cli.current_buffer.document d = get_app().current_buffer.document
at_end = d.is_cursor_at_the_end_of_line at_end = d.is_cursor_at_the_end_of_line
last_line = d.is_cursor_at_the_end last_line = d.is_cursor_at_the_end
@ -129,37 +133,40 @@ def end_of_line(cli):
@Condition @Condition
def should_confirm_completion(cli): def should_confirm_completion():
"""Check if completion needs confirmation""" """Check if completion needs confirmation"""
return ( return (
builtins.__xonsh__.env.get("COMPLETIONS_CONFIRM") 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 # Copied from prompt-toolkit's key_binding/bindings/basic.py
@Condition @Condition
def ctrl_d_condition(cli): def ctrl_d_condition():
"""Ctrl-D binding is only active when the default buffer is selected and """Ctrl-D binding is only active when the default buffer is selected and
empty. empty.
""" """
if builtins.__xonsh__.env.get("IGNOREEOF"): if builtins.__xonsh__.env.get("IGNOREEOF"):
raise EOFError return False
else: 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 @Condition
def autopair_condition(cli): def autopair_condition():
"""Check if XONSH_AUTOPAIR is set""" """Check if XONSH_AUTOPAIR is set"""
return builtins.__xonsh__.env.get("XONSH_AUTOPAIR", False) return builtins.__xonsh__.env.get("XONSH_AUTOPAIR", False)
@Condition @Condition
def whitespace_or_bracket_before(cli): def whitespace_or_bracket_before():
"""Check if there is whitespace or an opening """Check if there is whitespace or an opening
bracket to the left of the cursor""" bracket to the left of the cursor"""
d = cli.current_buffer.document d = get_app().current_buffer.document
return bool( return bool(
d.cursor_position == 0 d.cursor_position == 0
or d.char_before_cursor.isspace() or d.char_before_cursor.isspace()
@ -168,10 +175,10 @@ def whitespace_or_bracket_before(cli):
@Condition @Condition
def whitespace_or_bracket_after(cli): def whitespace_or_bracket_after():
"""Check if there is whitespace or a closing """Check if there is whitespace or a closing
bracket to the right of the cursor""" bracket to the right of the cursor"""
d = cli.current_buffer.document d = get_app().current_buffer.document
return bool( return bool(
d.is_cursor_at_the_end_of_line d.is_cursor_at_the_end_of_line
or d.current_char.isspace() 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. Load custom key bindings.
""" """
handle = key_bindings_manager.registry.add_binding handle = key_bindings.add
has_selection = HasSelection() has_selection = HasSelection()
insert_mode = ViInsertMode() | EmacsInsertMode() insert_mode = ViInsertMode() | EmacsInsertMode()
@ -193,6 +200,7 @@ def load_xonsh_bindings(key_bindings_manager):
If there are only whitespaces before current cursor position insert If there are only whitespaces before current cursor position insert
indent instead of autocompleting. indent instead of autocompleting.
""" """
env = builtins.__xonsh__.env
event.cli.current_buffer.insert_text(env.get("INDENT")) event.cli.current_buffer.insert_text(env.get("INDENT"))
@handle(Keys.ControlX, Keys.ControlE, filter=~has_selection) @handle(Keys.ControlX, Keys.ControlE, filter=~has_selection)
@ -207,6 +215,7 @@ def load_xonsh_bindings(key_bindings_manager):
if b.complete_state: if b.complete_state:
b.complete_previous() b.complete_previous()
else: else:
env = builtins.__xonsh__.env
event.cli.current_buffer.insert_text(env.get("INDENT")) event.cli.current_buffer.insert_text(env.get("INDENT"))
@handle("(", filter=autopair_condition & whitespace_or_bracket_after) @handle("(", filter=autopair_condition & whitespace_or_bracket_after)
@ -256,9 +265,7 @@ def load_xonsh_bindings(key_bindings_manager):
if buffer.document.current_char == "'": if buffer.document.current_char == "'":
buffer.cursor_position += 1 buffer.cursor_position += 1
elif whitespace_or_bracket_before(event.cli) and whitespace_or_bracket_after( elif whitespace_or_bracket_before() and whitespace_or_bracket_after():
event.cli
):
buffer.insert_text("'") buffer.insert_text("'")
buffer.insert_text("'", move_cursor=False) buffer.insert_text("'", move_cursor=False)
else: else:
@ -270,9 +277,7 @@ def load_xonsh_bindings(key_bindings_manager):
if buffer.document.current_char == '"': if buffer.document.current_char == '"':
buffer.cursor_position += 1 buffer.cursor_position += 1
elif whitespace_or_bracket_before(event.cli) and whitespace_or_bracket_after( elif whitespace_or_bracket_before() and whitespace_or_bracket_after():
event.cli
):
buffer.insert_text('"') buffer.insert_text('"')
buffer.insert_text('"', move_cursor=False) buffer.insert_text('"', move_cursor=False)
else: else:
@ -296,16 +301,18 @@ def load_xonsh_bindings(key_bindings_manager):
def call_exit_alias(event): def call_exit_alias(event):
"""Use xonsh exit function""" """Use xonsh exit function"""
b = event.cli.current_buffer b = event.cli.current_buffer
b.accept_action.validate_and_handle(event.cli, b) b.validate_and_handle()
xonsh_exit([]) 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): def multiline_carriage_return(event):
""" Wrapper around carriage_return multiline parser """ """ Wrapper around carriage_return multiline parser """
b = event.cli.current_buffer b = event.cli.current_buffer
carriage_return(b, event.cli) carriage_return(b, event.cli)
@handle(Keys.ControlJ, filter=should_confirm_completion) @handle(Keys.ControlJ, filter=should_confirm_completion)
@handle(Keys.ControlM, filter=should_confirm_completion)
def enter_confirm_completion(event): def enter_confirm_completion(event):
"""Ignore <enter> (confirm completion)""" """Ignore <enter> (confirm completion)"""
event.current_buffer.complete_state = None event.current_buffer.complete_state = None
@ -319,7 +326,7 @@ def load_xonsh_bindings(key_bindings_manager):
def execute_block_now(event): def execute_block_now(event):
"""Execute a block of text irrespective of cursor position""" """Execute a block of text irrespective of cursor position"""
b = event.cli.current_buffer 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) @handle(Keys.Left, filter=beginning_of_line)
def wrap_cursor_back(event): 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_left(count=abs(relative_begin_index))
b.cursor_down(count=1) b.cursor_down(count=1)
@handle(Keys.ControlI, filter=insert_mode) @handle(Keys.ControlM, filter=IsSearching())
def generate_completions(event): @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 pass
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)

View file

@ -1,31 +1,36 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""The prompt_toolkit based xonsh shell.""" """The prompt_toolkit based xonsh shell."""
import os
import sys import sys
import builtins 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.events import events
from xonsh.base_shell import BaseShell
from xonsh.shell import transform_command from xonsh.shell import transform_command
from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS from xonsh.tools import print_exception, carriage_return
from xonsh.style_tools import ( from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS, ON_POSIX
partial_color_tokenize, from xonsh.style_tools import partial_color_tokenize, _TokenType, DEFAULT_STYLE_DICT
_TokenType,
DEFAULT_STYLE_DICT as _DEFAULT_STYLE_DICT,
)
from xonsh.lazyimps import pygments, pyghooks, winutils from xonsh.lazyimps import pygments, pyghooks, winutils
from xonsh.pygments_cache import get_all_styles 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() Token = _TokenType()
@ -34,54 +39,45 @@ events.transmogrify("on_ptk_create", "LoadEvent")
events.doc( events.doc(
"on_ptk_create", "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 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): 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): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
if ON_WINDOWS: if ON_WINDOWS:
winutils.enable_virtual_terminal_processing() winutils.enable_virtual_terminal_processing()
self._first_prompt = True self._first_prompt = True
self.prompter = Prompter() self.history = ThreadedHistory(PromptToolkitHistory())
self.history = PromptToolkitHistory() self.prompter = PromptSession(history=self.history)
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self) self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
key_bindings_manager_args = { self.key_bindings = KeyBindings()
"enable_auto_suggest_bindings": True, load_xonsh_bindings(self.key_bindings)
"enable_search": True, # Store original `_history_matches` in case we need to restore it
"enable_abort_and_exit_bindings": True, self._history_matches_orig = self.prompter.default_buffer._history_matches
}
self.key_bindings_manager = KeyBindingManager(**key_bindings_manager_args)
load_xonsh_bindings(self.key_bindings_manager)
# This assumes that PromptToolkitShell is a singleton # This assumes that PromptToolkitShell is a singleton
events.on_ptk_create.fire( events.on_ptk_create.fire(
prompter=self.prompter, prompter=self.prompter,
history=self.history, history=self.history,
completer=self.pt_completer, completer=self.pt_completer,
bindings=self.key_bindings_manager, bindings=self.key_bindings,
) )
def singleline( def singleline(
self, self, auto_suggest=None, enable_history_search=True, multiline=True, **kwargs
store_in_history=True,
auto_suggest=None,
enable_history_search=True,
multiline=True,
**kwargs
): ):
"""Reads a single line of input from the shell. The store_in_history """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 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() events.on_pre_prompt.fire()
env = builtins.__xonsh__.env env = builtins.__xonsh__.env
mouse_support = env.get("MOUSE_SUPPORT") 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 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") 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") complete_while_typing = env.get("UPDATE_COMPLETIONS_ON_KEYPRESS")
if complete_while_typing: if complete_while_typing:
# PTK requires history search to be none when completing while typing # PTK requires history search to be none when completing while typing
@ -105,45 +100,71 @@ class PromptToolkitShell(BaseShell):
if HAS_PYGMENTS: if HAS_PYGMENTS:
self.styler.style_name = env.get("XONSH_COLOR_STYLE") self.styler.style_name = env.get("XONSH_COLOR_STYLE")
completer = None if completions_display == "none" else self.pt_completer 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_bottom_toolbar_tokens = self.bottom_toolbar_tokens
get_prompt_tokens = lambda cli: prompt_tokens_cached
rprompt_tokens_cached = self.rprompt_tokens(None) if env.get("UPDATE_PROMPT_ON_KEYPRESS"):
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_prompt_tokens = self.prompt_tokens get_prompt_tokens = self.prompt_tokens
get_rprompt_tokens = self.rprompt_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: if env.get("VI_MODE"):
prompt_args = { editing_mode = EditingMode.VI
"mouse_support": mouse_support, else:
"auto_suggest": auto_suggest, editing_mode = EditingMode.EMACS
"get_prompt_tokens": get_prompt_tokens,
"get_rprompt_tokens": get_rprompt_tokens, if env.get("XONSH_HISTORY_MATCH_ANYWHERE"):
"get_bottom_toolbar_tokens": get_bottom_toolbar_tokens, self.prompter.default_buffer._history_matches = MethodType(
"completer": completer, _cust_history_matches, self.prompter.default_buffer
"multiline": multiline, )
"get_continuation_tokens": self.continuation_tokens, elif (
"history": history, self.prompter.default_buffer._history_matches
"enable_history_search": enable_history_search, is not self._history_matches_orig
"reserve_space_for_menu": 0, ):
"key_bindings_registry": self.key_bindings_manager.registry, self.prompter.default_buffer._history_matches = self._history_matches_orig
"display_completions_in_columns": multicolumn,
"complete_while_typing": complete_while_typing, prompt_args = {
} "mouse_support": mouse_support,
if builtins.__xonsh__.env.get("COLOR_INPUT"): "auto_suggest": auto_suggest,
if HAS_PYGMENTS: "message": get_prompt_tokens,
prompt_args["lexer"] = PygmentsLexer(pyghooks.XonshLexer) "rprompt": get_rprompt_tokens,
prompt_args["style"] = PygmentsStyle( "bottom_toolbar": get_bottom_toolbar_tokens,
pyghooks.xonsh_style_proxy(self.styler) "completer": completer,
) "multiline": multiline,
else: "editing_mode": editing_mode,
prompt_args["style"] = style_from_dict(DEFAULT_STYLE_DICT) "prompt_continuation": self.continuation_tokens,
line = self.prompter.prompt(**prompt_args) "enable_history_search": enable_history_search,
events.on_post_prompt.fire() "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 return line
def _push(self, line): def _push(self, line):
@ -187,7 +208,7 @@ class PromptToolkitShell(BaseShell):
else: else:
break break
def prompt_tokens(self, cli): def prompt_tokens(self):
"""Returns a list of (token, str) tuples for the current prompt.""" """Returns a list of (token, str) tuples for the current prompt."""
p = builtins.__xonsh__.env.get("PROMPT") p = builtins.__xonsh__.env.get("PROMPT")
try: try:
@ -199,9 +220,9 @@ class PromptToolkitShell(BaseShell):
carriage_return() carriage_return()
self._first_prompt = False self._first_prompt = False
self.settitle() 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 """Returns a list of (token, str) tuples for the current right
prompt. prompt.
""" """
@ -216,32 +237,38 @@ class PromptToolkitShell(BaseShell):
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
print_exception() print_exception()
toks = partial_color_tokenize(p) 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 """Returns a list of (token, str) tuples for the current bottom
toolbar. toolbar.
""" """
p = builtins.__xonsh__.env.get("BOTTOM_TOOLBAR") p = builtins.__xonsh__.env.get("BOTTOM_TOOLBAR")
# self.prompt_formatter does handle empty strings properly, if not p:
# but this avoids descending into it in the common case of return
# $TOOLBAR == ''.
if isinstance(p, str) and len(p) == 0:
return []
try: try:
p = self.prompt_formatter(p) p = self.prompt_formatter(p)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
print_exception() print_exception()
toks = partial_color_tokenize(p) 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""" """Displays dots in multiline prompt"""
if is_soft_wrap:
return ""
width = width - 1 width = width - 1
dots = builtins.__xonsh__.env.get("MULTILINE_PROMPT") dots = builtins.__xonsh__.env.get("MULTILINE_PROMPT")
dots = dots() if callable(dots) else dots dots = dots() if callable(dots) else dots
if dots is None: if not dots:
return [(Token, " " * (width + 1))] return ""
basetoks = self.format_color(dots) basetoks = self.format_color(dots)
baselen = sum(len(t[1]) for t in basetoks) baselen = sum(len(t[1]) for t in basetoks)
if baselen == 0: if baselen == 0:
@ -262,7 +289,7 @@ class PromptToolkitShell(BaseShell):
if n <= count: if n <= count:
break break
toks.append((Token, " ")) # final space toks.append((Token, " ")) # final space
return toks return PygmentsTokens(toks)
def format_color(self, string, hide=False, force_string=False, **kwargs): def format_color(self, string, hide=False, force_string=False, **kwargs):
"""Formats a color string using Pygments. This, therefore, returns """Formats a color string using Pygments. This, therefore, returns
@ -286,17 +313,22 @@ class PromptToolkitShell(BaseShell):
def print_color(self, string, end="\n", **kwargs): def print_color(self, string, end="\n", **kwargs):
"""Prints a color string using prompt-toolkit color management.""" """Prints a color string using prompt-toolkit color management."""
if isinstance(string, str): if isinstance(string, str):
tokens = partial_color_tokenize(string + end) tokens = partial_color_tokenize(string)
else: else:
# assume this is a list of (Token, str) tuples and just print # assume this is a list of (Token, str) tuples and just print
tokens = string tokens = string
tokens = PygmentsTokens(tokens)
if HAS_PYGMENTS: if HAS_PYGMENTS:
env = builtins.__xonsh__.env env = builtins.__xonsh__.env
self.styler.style_name = env.get("XONSH_COLOR_STYLE") self.styler.style_name = env.get("XONSH_COLOR_STYLE")
proxy_style = PygmentsStyle(pyghooks.xonsh_style_proxy(self.styler)) proxy_style = style_from_pygments_cls(
pyghooks.xonsh_style_proxy(self.styler)
)
else: else:
proxy_style = style_from_dict(DEFAULT_STYLE_DICT) proxy_style = style_from_pygments_dict(DEFAULT_STYLE_DICT)
print_tokens(tokens, style=proxy_style) ptk_print(
tokens, style=proxy_style, end=end, include_default_pygments_style=False
)
def color_style_names(self): def color_style_names(self):
"""Returns an iterable of all available style names.""" """Returns an iterable of all available style names."""
@ -335,3 +367,9 @@ class PromptToolkitShell(BaseShell):
# if not ON_POSIX: # if not ON_POSIX:
# return # return
# sys.stdout.write('\033[9999999C\n') # sys.stdout.write('\033[9999999C\n')
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 ( from xonsh.tools import (
ON_WINDOWS, ON_WINDOWS,
intensify_colors_for_cmd_exe, intensify_colors_for_cmd_exe,
ansicolors_to_ptk1_names,
ANSICOLOR_NAMES_MAP, ANSICOLOR_NAMES_MAP,
PTK_NEW_OLD_COLOR_MAP, PTK_NEW_OLD_COLOR_MAP,
hardcode_colors_for_win10, hardcode_colors_for_win10,
@ -385,16 +384,6 @@ class XonshStyle(Style):
color_token = color_token_by_name(xonsh_color, self.styles) color_token = color_token_by_name(xonsh_color, self.styles)
file_color_tokens[file_type] = color_token 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: if ON_WINDOWS and "prompt_toolkit" in builtins.__xonsh__.shell.shell_type:
self.enhance_colors_for_cmd_exe() 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): 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. """Determine color to use for file as ls -c would, given stat() results and its name.
Parameters Parameters
---------- ----------
file_path : string file_path : string
relative path of file (as user typed it). relative path of file (as user typed it).
mode : int mode : int
stat() results for file_path. stat() results for file_path.
Returns Returns
------- -------
color token, color_key color token, color_key
Bugs Notes
---- -----
* doesn't handle CA (capability) * doesn't handle CA (capability)
* doesn't handle LS TARGET mapping. * doesn't handle LS TARGET mapping
""" """
lsc = builtins.__xonsh__.env["LS_COLORS"] lsc = builtins.__xonsh__.env["LS_COLORS"]

View file

@ -11,7 +11,7 @@ from xonsh.platform import (
best_shell_type, best_shell_type,
has_prompt_toolkit, has_prompt_toolkit,
ptk_above_min_supported, ptk_above_min_supported,
ptk_shell_type, minimum_required_ptk_version,
) )
from xonsh.tools import XonshError, print_exception from xonsh.tools import XonshError, print_exception
from xonsh.events import events from xonsh.events import events
@ -121,19 +121,66 @@ class Shell(object):
"best": "best", "best": "best",
"d": "dumb", "d": "dumb",
"dumb": "dumb", "dumb": "dumb",
"ptk": "prompt_toolkit", "ptk": "prompt_toolkit", # there's only 1 prompt_toolkit shell (now)
"ptk1": "prompt_toolkit1", "ptk1": "prompt_toolkit", # allow any old config reference to use it
"ptk2": "prompt_toolkit2", "ptk2": "prompt_toolkit", # so long as user actually has ptk2+ installed.
"prompt-toolkit": "prompt_toolkit", "prompt-toolkit": "prompt_toolkit",
"prompt_toolkit": "prompt_toolkit", "prompt_toolkit": "prompt_toolkit",
"prompt-toolkit1": "prompt_toolkit1", "prompt-toolkit1": "prompt_toolkit",
"prompt-toolkit2": "prompt_toolkit2", "prompt-toolkit2": "prompt_toolkit",
"prompt-toolkit3": "prompt_toolkit",
"prompt_toolkit3": "prompt_toolkit",
"ptk3": "prompt_toolkit",
"rand": "random", "rand": "random",
"random": "random", "random": "random",
"rl": "readline", "rl": "readline",
"readline": "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): def __init__(self, execer, ctx=None, shell_type=None, **kwargs):
""" """
Parameters Parameters
@ -157,44 +204,15 @@ class Shell(object):
env=env.detype(), ts=[time.time(), None], locked=True env=env.detype(), ts=[time.time(), None], locked=True
) )
# pick a valid shell -- if no shell is specified by the user, shell_type = self.choose_shell_type(shell_type, env)
# 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()
self.shell_type = env["SHELL_TYPE"] = shell_type self.shell_type = env["SHELL_TYPE"] = shell_type
# actually make the shell # actually make the shell
if shell_type == "none": if shell_type == "none":
from xonsh.base_shell import BaseShell as shell_class from xonsh.base_shell import BaseShell as shell_class
elif shell_type == "prompt_toolkit2": elif shell_type == "prompt_toolkit":
from xonsh.ptk2.shell import PromptToolkit2Shell as shell_class from xonsh.ptk_shell.shell import PromptToolkitShell as shell_class
elif shell_type == "prompt_toolkit1":
from xonsh.ptk.shell import PromptToolkitShell as shell_class
elif shell_type == "readline": elif shell_type == "readline":
from xonsh.readline_shell import ReadlineShell as shell_class from xonsh.readline_shell import ReadlineShell as shell_class
elif shell_type == "jupyter": elif shell_type == "jupyter":

View file

@ -8,7 +8,6 @@ import builtins
from prompt_toolkit.filters import completion_is_selected, IsMultiline from prompt_toolkit.filters import completion_is_selected, IsMultiline
from prompt_toolkit.keys import Keys from prompt_toolkit.keys import Keys
from xonsh.built_ins import DynamicAccessProxy from xonsh.built_ins import DynamicAccessProxy
from xonsh.platform import ptk_shell_type
from xonsh.tools import check_for_partial_string from xonsh.tools import check_for_partial_string
__all__ = () __all__ = ()
@ -36,18 +35,11 @@ def expand_abbrev(buffer):
@events.on_ptk_create @events.on_ptk_create
def custom_keybindings(bindings, **kw): def custom_keybindings(bindings, **kw):
if ptk_shell_type() == "prompt_toolkit2": from xonsh.ptk_shell.key_bindings import carriage_return
from xonsh.ptk2.key_bindings import carriage_return from prompt_toolkit.filters import EmacsInsertMode, ViInsertMode
from prompt_toolkit.filters import EmacsInsertMode, ViInsertMode
handler = bindings.add handler = bindings.add
insert_mode = ViInsertMode() | EmacsInsertMode() 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(" ", filter=IsMultiline() & insert_mode) @handler(" ", filter=IsMultiline() & insert_mode)
def handle_space(event): 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 prompt_toolkit.keys import Keys
from xonsh.platform import ptk_shell_type
__all__ = () __all__ = ()
@ -17,19 +15,14 @@ def custom_keybindings(bindings, **kw):
# Alt+Left and Alt+Right still jump over smaller word segments. # Alt+Left and Alt+Right still jump over smaller word segments.
# See https://github.com/xonsh/xonsh/issues/2403 # See https://github.com/xonsh/xonsh/issues/2403
if ptk_shell_type() == "prompt_toolkit2": @bindings.add(Keys.ControlLeft)
handler = bindings.add
else:
handler = bindings.registry.add_binding
@handler(Keys.ControlLeft)
def ctrl_left(event): def ctrl_left(event):
buff = event.current_buffer buff = event.current_buffer
pos = buff.document.find_previous_word_beginning(count=event.arg, WORD=True) pos = buff.document.find_previous_word_beginning(count=event.arg, WORD=True)
if pos: if pos:
buff.cursor_position += pos buff.cursor_position += pos
@handler(Keys.ControlRight) @bindings.add(Keys.ControlRight)
def ctrl_right(event): def ctrl_right(event):
buff = event.current_buffer buff = event.current_buffer
pos = buff.document.find_next_word_ending(count=event.arg, WORD=True) pos = buff.document.find_next_word_ending(count=event.arg, WORD=True)