1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.
import logging
from rope.contrib.codeassist import code_assist, sorted_proposals
from pylsp import _utils, hookimpl, lsp
log = logging.getLogger(__name__)
@hookimpl
def pylsp_settings():
# Default rope_completion to disabled
return {"plugins": {"rope_completion": {"enabled": False, "eager": False}}}
def _resolve_completion(completion, data, markup_kind):
try:
doc = _utils.format_docstring(data.get_doc(), markup_kind=markup_kind)
except Exception as e:
log.debug("Failed to resolve Rope completion: %s", e)
doc = ""
completion["detail"] = "{0} {1}".format(data.scope or "", data.name)
completion["documentation"] = doc
return completion
@hookimpl
def pylsp_completions(config, workspace, document, position):
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
# So we only skip import completions when the cursor is one space after `import`
"line": position["line"],
"character": max(position["character"] - 1, 0),
}
)
if word == "import":
return None
offset = document.offset_at_position(position)
rope_config = config.settings(document_path=document.path).get("rope", {})
rope_project = workspace._rope_project_builder(rope_config)
document_rope = document._rope_resource(rope_config)
completion_capabilities = config.capabilities.get("textDocument", {}).get(
"completion", {}
)
item_capabilities = completion_capabilities.get("completionItem", {})
supported_markup_kinds = item_capabilities.get("documentationFormat", ["markdown"])
preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
try:
definitions = code_assist(
rope_project, document.source, offset, document_rope, maxfixes=3
)
except Exception as e:
log.debug("Failed to run Rope code assist: %s", e)
return []
definitions = sorted_proposals(definitions)
new_definitions = []
for d in definitions:
item = {
"label": d.name,
"kind": _kind(d),
"sortText": _sort_text(d),
"data": {"doc_uri": document.uri},
}
if resolve_eagerly:
item = _resolve_completion(item, d, preferred_markup_kind)
new_definitions.append(item)
# most recently retrieved completion items, used for resolution
document.shared_data["LAST_ROPE_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 pylsp_completion_item_resolve(config, completion_item, document):
"""Resolve formatted completion for given non-resolved completion"""
shared_data = document.shared_data["LAST_ROPE_COMPLETIONS"].get(
completion_item["label"]
)
completion_capabilities = config.capabilities.get("textDocument", {}).get(
"completion", {}
)
item_capabilities = completion_capabilities.get("completionItem", {})
supported_markup_kinds = item_capabilities.get("documentationFormat", ["markdown"])
preferred_markup_kind = _utils.choose_markup_kind(supported_markup_kinds)
if shared_data:
completion, data = shared_data
return _resolve_completion(completion, data, preferred_markup_kind)
return completion_item
def _sort_text(definition):
"""Ensure builtins appear at the bottom.
Description is of format <type>: <module>.<item>
"""
if definition.name.startswith("_"):
# It's a 'hidden' func, put it next last
return "z" + definition.name
if definition.scope == "builtin":
return "y" + definition.name
# Else put it at the front
return "a" + definition.name
def _kind(d):
"""Return the LSP type"""
MAP = {
"none": lsp.CompletionItemKind.Value,
"type": lsp.CompletionItemKind.Class,
"tuple": lsp.CompletionItemKind.Class,
"dict": lsp.CompletionItemKind.Class,
"dictionary": lsp.CompletionItemKind.Class,
"function": lsp.CompletionItemKind.Function,
"lambda": lsp.CompletionItemKind.Function,
"generator": lsp.CompletionItemKind.Function,
"class": lsp.CompletionItemKind.Class,
"instance": lsp.CompletionItemKind.Reference,
"method": lsp.CompletionItemKind.Method,
"builtin": lsp.CompletionItemKind.Class,
"builtinfunction": lsp.CompletionItemKind.Function,
"module": lsp.CompletionItemKind.Module,
"file": lsp.CompletionItemKind.File,
"xrange": lsp.CompletionItemKind.Class,
"slice": lsp.CompletionItemKind.Class,
"traceback": lsp.CompletionItemKind.Class,
"frame": lsp.CompletionItemKind.Class,
"buffer": lsp.CompletionItemKind.Class,
"dictproxy": lsp.CompletionItemKind.Class,
"funcdef": lsp.CompletionItemKind.Function,
"property": lsp.CompletionItemKind.Property,
"import": lsp.CompletionItemKind.Module,
"keyword": lsp.CompletionItemKind.Keyword,
"constant": lsp.CompletionItemKind.Variable,
"variable": lsp.CompletionItemKind.Variable,
"value": lsp.CompletionItemKind.Value,
"param": lsp.CompletionItemKind.Variable,
"statement": lsp.CompletionItemKind.Keyword,
}
return MAP.get(d.type)
|