diff --git a/pyls/_utils.py b/pyls/_utils.py index 81414312..fb353c3c 100644 --- a/pyls/_utils.py +++ b/pyls/_utils.py @@ -89,33 +89,6 @@ def _merge_dicts_(a, b): return dict(_merge_dicts_(dict_a, dict_b)) -def race_hooks(hook_caller, pool, **kwargs): - """Given a pluggy hook spec, execute impls in parallel returning the first non-None result. - - Note this does not support a lot of pluggy functionality, e.g. hook wrappers. - """ - impls = hook_caller._nonwrappers + hook_caller._wrappers - log.debug("Racing hook impls for hook %s: %s", hook_caller, impls) - - if not impls: - return None - - def _apply(impl): - try: - return impl, impl.function(**kwargs) - except Exception: - log.exception("Failed to run hook %s", impl.plugin_name) - raise - - # imap unordered gives us an iterator over the items in the order they finish. - # We have to be careful to set chunksize to 1 to ensure hooks each get their own thread. - # Unfortunately, there's no way to interrupt these threads, so we just have to leave them be. - for impl, result in pool.imap_unordered(_apply, impls, chunksize=1): - if result is not None: - log.debug("Hook from plugin %s returned: %s", impl.plugin_name, result) - return result - - def format_docstring(contents): """Python doc strings come in a number of formats, but LSP wants markdown. diff --git a/pyls/plugins/completion/__init__.py b/pyls/plugins/completion/__init__.py new file mode 100644 index 00000000..b1fce092 --- /dev/null +++ b/pyls/plugins/completion/__init__.py @@ -0,0 +1,18 @@ +from pyls import hookimpl +from .jedi_completion import pyls_jedi_completions +from .rope_completion import pyls_rope_completions + + +@hookimpl +def pyls_settings(): + return {'plugins': {'completion': {'provider': 'jedi'}}} + + +@hookimpl +def pyls_completions(config, document, position): + provider = config.plugin_settings('completion').get('provider', 'jedi') + if provider == 'jedi': + return pyls_jedi_completions(document, position) + elif provider == 'rope': + return pyls_rope_completions(document, position) + return [] diff --git a/pyls/plugins/jedi_completion.py b/pyls/plugins/completion/jedi_completion.py similarity index 97% rename from pyls/plugins/jedi_completion.py rename to pyls/plugins/completion/jedi_completion.py index e70cb5d4..882b18fa 100644 --- a/pyls/plugins/jedi_completion.py +++ b/pyls/plugins/completion/jedi_completion.py @@ -1,13 +1,12 @@ # Copyright 2017 Palantir Technologies, Inc. import logging from pyls.lsp import CompletionItemKind -from pyls import hookimpl, _utils +from pyls import _utils log = logging.getLogger(__name__) -@hookimpl -def pyls_completions(document, position): +def pyls_jedi_completions(document, position): log.debug('Launching Jedi') definitions = document.jedi_script(position).completions() definitions = [{ diff --git a/pyls/plugins/rope_completion.py b/pyls/plugins/completion/rope_completion.py similarity index 97% rename from pyls/plugins/rope_completion.py rename to pyls/plugins/completion/rope_completion.py index e3b17dcf..b9d234c4 100644 --- a/pyls/plugins/rope_completion.py +++ b/pyls/plugins/completion/rope_completion.py @@ -2,14 +2,13 @@ import logging from rope.contrib.codeassist import code_assist, sorted_proposals -from pyls import hookimpl, lsp +from pyls import lsp log = logging.getLogger(__name__) -@hookimpl -def pyls_completions(document, position): +def pyls_rope_completions(document, position): log.debug('Launching Rope') # Rope is a bit rubbish at completing module imports, so we'll return None diff --git a/pyls/python_ls.py b/pyls/python_ls.py index 8bceb6e5..45f26a23 100644 --- a/pyls/python_ls.py +++ b/pyls/python_ls.py @@ -1,6 +1,5 @@ # Copyright 2017 Palantir Technologies, Inc. import logging -from multiprocessing import dummy as multiprocessing from . import lsp, _utils from .config import config from .language_server import LanguageServer @@ -8,7 +7,6 @@ log = logging.getLogger(__name__) -PLUGGY_RACE_POOL_SIZE = 5 LINT_DEBOUNCE_S = 0.5 # 500 ms @@ -18,14 +16,10 @@ class PythonLanguageServer(LanguageServer): workspace = None config = None - _pool = multiprocessing.Pool(PLUGGY_RACE_POOL_SIZE) - - def _hook_caller(self, hook_name): - return self.config.plugin_manager.subset_hook_caller(hook_name, self.config.disabled_plugins) - def _hook(self, hook_name, doc_uri=None, **kwargs): doc = self.workspace.get_document(doc_uri) if doc_uri else None - return self._hook_caller(hook_name)(config=self.config, workspace=self.workspace, document=doc, **kwargs) + hook = self.config.plugin_manager.subset_hook_caller(hook_name, self.config.disabled_plugins) + return hook(config=self.config, workspace=self.workspace, document=doc, **kwargs) def capabilities(self): return { @@ -65,14 +59,10 @@ def code_lens(self, doc_uri): return flatten(self._hook('pyls_code_lens', doc_uri)) def completions(self, doc_uri, position): - completions = _utils.race_hooks( - self._hook_caller('pyls_completions'), self._pool, - document=self.workspace.get_document(doc_uri) if doc_uri else None, - position=position - ) + completions = self._hook('pyls_completions', doc_uri, position=position) return { 'isIncomplete': False, - 'items': completions or [] + 'items': flatten(completions) } def definitions(self, doc_uri, position): diff --git a/setup.py b/setup.py index 665e3bcc..55a821a6 100755 --- a/setup.py +++ b/setup.py @@ -61,8 +61,7 @@ 'pyls = pyls.__main__:main', ], 'pyls': [ - 'rope_completion = pyls.plugins.rope_completion', - 'jedi_completion = pyls.plugins.jedi_completion', + 'completion = pyls.plugins.completion', 'jedi_definition = pyls.plugins.definition', 'jedi_hover = pyls.plugins.hover', 'jedi_references = pyls.plugins.references', diff --git a/test/plugins/test_completion.py b/test/plugins/test_completion.py index 7d9b280c..020df482 100644 --- a/test/plugins/test_completion.py +++ b/test/plugins/test_completion.py @@ -4,8 +4,8 @@ from pyls import uris from pyls.workspace import Document, get_preferred_submodules -from pyls.plugins.jedi_completion import pyls_completions as pyls_jedi_completions -from pyls.plugins.rope_completion import pyls_completions as pyls_rope_completions +from pyls.plugins.completion.jedi_completion import pyls_jedi_completions +from pyls.plugins.completion.rope_completion import pyls_rope_completions LOCATION = os.path.realpath( os.path.join(os.getcwd(), os.path.dirname(__file__)) diff --git a/vscode-client/package.json b/vscode-client/package.json index a36da57e..587fe760 100644 --- a/vscode-client/package.json +++ b/vscode-client/package.json @@ -30,10 +30,16 @@ }, "uniqueItems": true }, - "pyls.plugins.jedi_completion.enabled": { + "pyls.plugins.completion.enabled": { "type": "boolean", "default": true, - "description": "Enable or disable the plugin." + "description": "Enable or disable completions." + }, + "pyls.plugins.completion.provider": { + "type": "string", + "default": "jedi", + "enum": ["jedi", "rope"], + "description": "Select a completion provider." }, "pyls.plugins.jedi_definition.enabled": { "type": "boolean", @@ -136,11 +142,6 @@ "default": true, "description": "Enable or disable the plugin." }, - "pyls.plugins.rope_completion.enabled": { - "type": "boolean", - "default": true, - "description": "Enable or disable the plugin." - }, "pyls.plugins.yapf.enabled": { "type": "boolean", "default": true,