Skip to content
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

Implement completionItem/resolve #905

Closed
Closed
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
5 changes: 5 additions & 0 deletions pyls/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def pyls_completions(config, workspace, document, position):
pass


@hookspec(firstresult=True)
def pyls_completion_item_resolve(config, workspace, completion_item):
pass


@hookspec
def pyls_definitions(config, workspace, document, position):
pass
Expand Down
36 changes: 32 additions & 4 deletions pyls/plugins/jedi_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,19 @@
# Types of parso node for errors
_ERRORS = ('error_node', )

# most recently retrieved completion items, used for resolution
_LAST_COMPLETIONS = {}


@hookimpl
def pyls_completions(config, document, position):
"""Get formatted completions for current code position"""
# pylint: disable=too-many-locals
# pylint: disable=global-statement
global _LAST_COMPLETIONS

settings = config.plugin_settings('jedi_completion', document_path=document.path)
resolve_eagerly = settings.get('eager', False)
code_position = _utils.position_to_jedi_linecolumn(document, position)

code_position["fuzzy"] = settings.get("fuzzy", False)
Expand All @@ -79,14 +87,27 @@ def pyls_completions(config, document, position):
if include_class_objects:
for c in completions:
if c.type == 'class':
completion_dict = _format_completion(c, False)
completion_dict = _format_completion(c, False, resolve=resolve_eagerly)
completion_dict['kind'] = lsp.CompletionItemKind.TypeParameter
completion_dict['label'] += ' object'
ready_completions.append(completion_dict)

_LAST_COMPLETIONS = {
# label is the only required property; here it is assumed to be unique
completion['label']: (completion, data)
for completion, data in zip(ready_completions, completions)
}

return ready_completions or None


@hookimpl
def pyls_completion_item_resolve(completion_item):
"""Resolve formatted completion for given non-resolved completion"""
completion, data = _LAST_COMPLETIONS.get(completion_item['label'])
return _resolve_completion(completion, data)


def is_exception_class(name):
"""
Determine if a class name is an instance of an Exception.
Expand Down Expand Up @@ -137,16 +158,23 @@ def use_snippets(document, position):
not (expr_type in _ERRORS and 'import' in code))


def _format_completion(d, include_params=True):
def _resolve_completion(completion, d):
completion['detail'] = _detail(d)
completion['documentation'] = _utils.format_docstring(d.docstring())
return completion


def _format_completion(d, include_params=True, resolve=False):
completion = {
'label': _label(d),
'kind': _TYPE_MAP.get(d.type),
'detail': _detail(d),
'documentation': _utils.format_docstring(d.docstring()),
'sortText': _sort_text(d),
'insertText': d.name
}

if resolve:
completion = _resolve_completion(completion, d)

if d.type == 'path':
path = osp.normpath(d.name)
path = path.replace('\\', '\\\\')
Expand Down
51 changes: 41 additions & 10 deletions pyls/plugins/rope_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,35 @@

log = logging.getLogger(__name__)

# most recently retrieved completion items, used for resolution
_LAST_COMPLETIONS = {}


@hookimpl
def pyls_settings():
# Default rope_completion to disabled
return {'plugins': {'rope_completion': {'enabled': False}}}
return {'plugins': {'rope_completion': {'enabled': False, 'eager': False}}}


def _resolve_completion(completion, data):
try:
doc = data.get_doc()
except AttributeError:
doc = ""
completion['detail'] = '{0} {1}'.format(data.scope or "", data.name)
completion['documentation'] = doc
return completion


@hookimpl
def pyls_completions(config, workspace, document, position):
# pylint: disable=too-many-locals
# pylint: disable=global-statement
global _LAST_COMPLETIONS

settings = config.plugin_settings('rope_completion', document_path=document.path)
resolve_eagerly = settings.get('eager', False)

# Rope is a bit rubbish at completing module imports, so we'll return None
word = document.word_at_position({
# The -1 should really be trying to look at the previous word, but that might be quite expensive
Expand All @@ -39,22 +59,33 @@ def pyls_completions(config, workspace, document, position):
definitions = sorted_proposals(definitions)
new_definitions = []
for d in definitions:
try:
doc = d.get_doc()
except AttributeError:
doc = None
new_definitions.append({
item = {
'label': d.name,
'kind': _kind(d),
'detail': '{0} {1}'.format(d.scope or "", d.name),
'documentation': doc or "",
'sortText': _sort_text(d)
})
}
if resolve_eagerly:
item = _resolve_completion(item, d)
new_definitions.append(item)

_LAST_COMPLETIONS = {
# label is the only required property; here it is assumed to be unique
completion['label']: (completion, data)
for completion, data in zip(new_definitions, definitions)
}

definitions = new_definitions

return definitions or None


@hookimpl
def pyls_completion_item_resolve(completion_item):
"""Resolve formatted completion for given non-resolved completion"""
completion, data = _LAST_COMPLETIONS.get(completion_item['label'])
return _resolve_completion(completion, data)


def _sort_text(definition):
""" Ensure builtins appear at the bottom.
Description is of format <type>: <module>.<item>
Expand All @@ -70,7 +101,7 @@ def _sort_text(definition):


def _kind(d):
""" Return the VSCode type """
""" Return the LSP type """
MAP = {
'none': lsp.CompletionItemKind.Value,
'type': lsp.CompletionItemKind.Class,
Expand Down
10 changes: 8 additions & 2 deletions pyls/python_ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ def capabilities(self):
'resolveProvider': False, # We may need to make this configurable
},
'completionProvider': {
'resolveProvider': False, # We know everything ahead of time
'triggerCharacters': ['.']
'resolveProvider': True, # We could know everything ahead of time, but this takes time to transfer
'triggerCharacters': ['.'],
},
'documentFormattingProvider': True,
'documentHighlightProvider': True,
Expand Down Expand Up @@ -243,6 +243,9 @@ def completions(self, doc_uri, position):
'items': flatten(completions)
}

def completion_item_resolve(self, completion_item):
return self._hook('pyls_completion_item_resolve', completion_item=completion_item)

def definitions(self, doc_uri, position):
return flatten(self._hook('pyls_definitions', doc_uri, position=position))

Expand Down Expand Up @@ -289,6 +292,9 @@ def signature_help(self, doc_uri, position):
def folding(self, doc_uri):
return flatten(self._hook('pyls_folding_range', doc_uri))

def m_completion_item__resolve(self, **completionItem):
return self.completion_item_resolve(completionItem)

def m_text_document__did_close(self, textDocument=None, **_kwargs):
workspace = self._match_uri_to_workspace(textDocument['uri'])
workspace.rm_document(textDocument['uri'])
Expand Down
28 changes: 27 additions & 1 deletion test/plugins/test_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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.jedi_completion import pyls_completion_item_resolve as pyls_jedi_completion_item_resolve
from pyls.plugins.rope_completion import pyls_completions as pyls_rope_completions


Expand Down Expand Up @@ -38,6 +39,10 @@ def everyone(self, a, b, c=None, d=2):
print Hello().world

print Hello().every

def documented_hello():
\"\"\"Sends a polite greeting\"\"\"
pass
"""


Expand All @@ -62,6 +67,25 @@ def test_jedi_completion(config, workspace):
pyls_jedi_completions(config, doc, {'line': 1, 'character': 1000})


def test_jedi_completion_item_resolve(config, workspace):
# Over the blank line
com_position = {'line': 8, 'character': 0}
doc = Document(DOC_URI, workspace, DOC)
completions = pyls_jedi_completions(config, doc, com_position)

items = {c['label']: c for c in completions}

documented_hello_item = items['documented_hello()']

assert 'documentation' not in documented_hello_item
assert 'detail' not in documented_hello_item

resolved_documented_hello = pyls_jedi_completion_item_resolve(
completion_item=documented_hello_item
)
assert 'Sends a polite greeting' in resolved_documented_hello['documentation']


def test_jedi_completion_with_fuzzy_enabled(config, workspace):
# Over 'i' in os.path.isabs(...)
config.update({'plugins': {'jedi_completion': {'fuzzy': True}}})
Expand Down Expand Up @@ -333,7 +357,9 @@ def test_jedi_completion_environment(workspace):
# After 'import logh' with new environment
completions = pyls_jedi_completions(doc._config, doc, com_position)
assert completions[0]['label'] == 'loghub'
assert 'changelog generator' in completions[0]['documentation'].lower()

resolved = pyls_jedi_completion_item_resolve(completions[0])
assert 'changelog generator' in resolved['documentation'].lower()


def test_document_path_completions(tmpdir, workspace_other_root_path):
Expand Down
10 changes: 10 additions & 0 deletions vscode-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
"default": false,
"description": "Enable fuzzy when requesting autocomplete."
},
"pyls.plugins.jedi_completion.eager": {
"type": "boolean",
"default": false,
"description": "Resolve documentation and detail eagerly."
},
"pyls.plugins.jedi_definition.enabled": {
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -274,6 +279,11 @@
"default": true,
"description": "Enable or disable the plugin."
},
"pyls.plugins.rope_completion.eager": {
"type": "boolean",
"default": false,
"description": "Resolve documentation and detail eagerly."
},
"pyls.plugins.yapf.enabled": {
"type": "boolean",
"default": true,
Expand Down