Skip to content

Make plugins optional #301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,41 @@ Python Language Server

A Python 2.7 and 3.4+ implementation of the `Language Server Protocol`_.

Feature Providers
-----------------
* Jedi_ for Completions, Definitions, Hover, References, Signature Help, and Symbols
Installation
------------

The base language server requires Jedi_ to provide Completions, Definitions, Hover, References, Signature Help, and
Symbols:

``pip install python-language-server``

If the respective dependencies are found, the following optional providers will be enabled:

* Rope_ for Completions and renaming
* Pyflakes_ linter to detect various errors
* McCabe_ linter for complexity checking
* pycodestyle_ linter for style checking
* pydocstyle_ linter for docstring style checking
* YAPF_ for code formatting

Optional providers can be installed using the `extras` syntax. To install YAPF_ formatting for example:

``pip install 'python-language-server[yapf]'``

All optional providers can be installed using:

``pip install 'python-language-server[all]'``

3rd Party Plugins
~~~~~~~~~~~~~~~~~
Installing these plugins will add extra functionality to the language server:

* pyls-mypy_ Mypy type checking for Python 3
* pyls-isort_ Isort import sort code formatting

Please see the above repositories for examples on how to write plugins for the Python Language Server. Please file an
issue if you require assistance writing a plugin.

Configuration
-------------

Expand Down Expand Up @@ -78,11 +96,6 @@ Document Formatting:

.. image:: https://raw.githubusercontent.com/palantir/python-language-server/develop/resources/document-format.gif

Installation
------------

``pip install python-language-server``

Development
-----------

Expand Down
17 changes: 16 additions & 1 deletion pyls/config/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Copyright 2017 Palantir Technologies, Inc.
import logging
import pkg_resources

import pluggy

from pyls import _utils, hookspecs, uris, PYLS
Expand Down Expand Up @@ -32,10 +34,23 @@ def __init__(self, root_uri, init_opts):
self._pm.trace.root.setwriter(log.debug)
self._pm.enable_tracing()
self._pm.add_hookspecs(hookspecs)

# Pluggy will skip loading a plugin if it throws a DistributionNotFound exception.
# However I don't want all plugins to have to catch ImportError and re-throw. So here we'll filter
# out any entry points that throw ImportError assuming one or more of their dependencies isn't present.
for entry_point in pkg_resources.iter_entry_points(PYLS):
try:
entry_point.load()
except ImportError as e:
log.warn("Failed to load %s entry point '%s': %s", PYLS, entry_point.name, e)
self._pm.set_blocked(entry_point.name)

# Load the entry points into pluggy, having blocked any failing ones
self._pm.load_setuptools_entrypoints(PYLS)

for name, plugin in self._pm.list_name_plugin():
log.info("Loaded pyls plugin %s from %s", name, plugin)
if plugin is not None:
log.info("Loaded pyls plugin %s from %s", name, plugin)

for plugin_conf in self._pm.hook.pyls_settings(config=self):
self._plugin_settings = _utils.merge_dicts(self._plugin_settings, plugin_conf)
Expand Down
5 changes: 3 additions & 2 deletions pyls/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import pkgutil

import jedi
from rope.base import libutils
from rope.base.project import Project

from . import lsp, uris, _utils

Expand Down Expand Up @@ -86,6 +84,8 @@ def __init__(self, root_uri, rpc_manager):
self.__rope_config = None

def _rope_project_builder(self, rope_config):
from rope.base.project import Project

# TODO: we could keep track of dirty files and validate only those
if self.__rope is None or self.__rope_config != rope_config:
rope_folder = rope_config.get('ropeFolder')
Expand Down Expand Up @@ -169,6 +169,7 @@ def __str__(self):
return str(self.uri)

def _rope_resource(self, rope_config):
from rope.base import libutils
return libutils.path_to_resource(self._rope_project_builder(rope_config), self.path)

@property
Expand Down
20 changes: 14 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@
'futures; python_version<"3.2"',
'jedi>=0.10',
'json-rpc==1.10.8',
'mccabe',
'pycodestyle',
'pydocstyle>=2.0.0',
'pyflakes>=1.6.0',
'rope>=0.10.5',
'yapf',
'pluggy'
],

Expand All @@ -51,6 +45,20 @@
# for example:
# $ pip install -e .[test]
extras_require={
'all': [
'mccabe',
'pycodestyle',
'pydocstyle>=2.0.0',
'pyflakes>=1.6.0',
'rope>-0.10.5',
'yapf',
],
'mccabe': ['mccabe'],
'pycodestyle': ['pycodestyle'],
'pydocstyle': ['pydocstyle>=2.0.0'],
'pyflakes': ['pyflakes>=1.6.0'],
'rope': ['rope>0.10.5'],
'yapf': ['yapf'],
'test': ['tox', 'versioneer', 'pytest', 'mock', 'pytest-cov', 'coverage'],
},

Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ deps =
mock
pytest-cov
pylint
extras = all

[testenv:lint]
commands =
Expand Down