diff --git a/pyls/lsp.py b/pyls/lsp.py index a315eabe..36a8d842 100644 --- a/pyls/lsp.py +++ b/pyls/lsp.py @@ -39,6 +39,11 @@ class DiagnosticSeverity(object): Hint = 4 +class InsertTextFormat(object): + PlainText = 1 + Snippet = 2 + + class MessageType(object): Error = 1 Warning = 2 diff --git a/pyls/plugins/jedi_completion.py b/pyls/plugins/jedi_completion.py index e7f06966..c6d25913 100644 --- a/pyls/plugins/jedi_completion.py +++ b/pyls/plugins/jedi_completion.py @@ -6,16 +6,39 @@ @hookimpl -def pyls_completions(document, position): +def pyls_completions(config, document, position): definitions = document.jedi_script(position).completions() - return [{ - 'label': _label(d), - 'kind': _kind(d), - 'detail': _detail(d), - 'documentation': _utils.format_docstring(d.docstring()), - 'sortText': _sort_text(d), - 'insertText': d.name - } for d in definitions] or None + if not definitions: + return None + + settings = config.plugin_settings('jedi_completion', document_path=document.path) + include_params = settings.get('include_params', True) + + completions = [] + for d in definitions: + completion = { + 'label': _label(d), + 'kind': _kind(d), + 'detail': _detail(d), + 'documentation': _utils.format_docstring(d.docstring()), + 'sortText': _sort_text(d), + 'insertText': d.name + } + + if include_params and hasattr(d, 'params') and d.params: + # For completions with params, we can generate a snippet instead + completion['insertTextFormat'] = lsp.InsertTextFormat.Snippet + snippet = d.name + '(' + for i, param in enumerate(d.params): + snippet += '${%s:%s}' % (i + 1, param.name) + if i < len(d.params) - 1: + snippet += ', ' + snippet += ')$0' + completion['insertText'] = snippet + + completions.append(completion) + + return completions or None def _label(definition): diff --git a/test/plugins/test_completion.py b/test/plugins/test_completion.py index bdfbeb4c..e93ef0f3 100644 --- a/test/plugins/test_completion.py +++ b/test/plugins/test_completion.py @@ -1,7 +1,7 @@ # Copyright 2017 Palantir Technologies, Inc. import os -from pyls import uris +from pyls import uris, lsp from pyls.workspace import Document from pyls.plugins.jedi_completion import pyls_completions as pyls_jedi_completions from pyls.plugins.rope_completion import pyls_completions as pyls_rope_completions @@ -26,7 +26,12 @@ class Hello(): def world(self): return None + def everyone(self, a, b, c=None, d=2): + pass + print Hello().world + +print Hello().every """ @@ -37,17 +42,17 @@ def test_rope_import_completion(config, workspace): assert items is None -def test_jedi_completion(): +def test_jedi_completion(config): # Over 'i' in os.path.isabs(...) com_position = {'line': 1, 'character': 15} doc = Document(DOC_URI, DOC) - items = pyls_jedi_completions(doc, com_position) + items = pyls_jedi_completions(config, doc, com_position) assert items assert items[0]['label'] == 'isabs(s)' # Test we don't throw with big character - pyls_jedi_completions(doc, {'line': 1, 'character': 1000}) + pyls_jedi_completions(config, doc, {'line': 1, 'character': 1000}) def test_rope_completion(config, workspace): @@ -61,11 +66,11 @@ def test_rope_completion(config, workspace): assert items[0]['label'] == 'isabs' -def test_jedi_completion_ordering(): +def test_jedi_completion_ordering(config): # Over the blank line com_position = {'line': 8, 'character': 0} doc = Document(DOC_URI, DOC) - completions = pyls_jedi_completions(doc, com_position) + completions = pyls_jedi_completions(config, doc, com_position) items = {c['label']: c['sortText'] for c in completions} @@ -73,13 +78,34 @@ def test_jedi_completion_ordering(): assert items['hello()'] < items['_a_hello()'] -def test_jedi_property_completion(): +def test_jedi_property_completion(config): # Over the 'w' in 'print Hello().world' - com_position = {'line': 15, 'character': 15} + com_position = {'line': 18, 'character': 15} doc = Document(DOC_URI, DOC) - completions = pyls_jedi_completions(doc, com_position) + completions = pyls_jedi_completions(config, doc, com_position) items = {c['label']: c['sortText'] for c in completions} # Ensure we can complete the 'world' property assert 'world' in items + + +def test_jedi_method_completion(config): + # Over the 'y' in 'print Hello().every' + com_position = {'line': 20, 'character': 19} + doc = Document(DOC_URI, DOC) + + completions = pyls_jedi_completions(config, doc, com_position) + everyone_method = [completion for completion in completions if completion['label'] == 'everyone(a, b, c, d)'][0] + + assert everyone_method['insertTextFormat'] == lsp.InsertTextFormat.Snippet + assert everyone_method['insertText'] == 'everyone(${1:a}, ${2:b}, ${3:c}, ${4:d})$0' + + # Disable param snippets + config.update({'plugins': {'jedi_completion': {'include_params': False}}}) + + completions = pyls_jedi_completions(config, doc, com_position) + everyone_method = [completion for completion in completions if completion['label'] == 'everyone(a, b, c, d)'][0] + + assert 'insertTextFormat' not in everyone_method + assert everyone_method['insertText'] == 'everyone' diff --git a/vscode-client/package.json b/vscode-client/package.json index b2fa4e96..2f43ed1e 100644 --- a/vscode-client/package.json +++ b/vscode-client/package.json @@ -35,6 +35,11 @@ "default": true, "description": "Enable or disable the plugin." }, + "pyls.plugins.jedi_completion.include_params": { + "type": "boolean", + "default": true, + "description": "Auto-completes methods and classes with tabstops for each parameter." + }, "pyls.plugins.jedi_definition.enabled": { "type": "boolean", "default": true,