Skip to content

Commit

Permalink
Merge pull request #56 from krassowski/feature/completionItem/resolve
Browse files Browse the repository at this point in the history
Implement completionItem/resolve
  • Loading branch information
pappasam authored Feb 1, 2021
2 parents e6bb038 + b91e43a commit 4effbb8
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 35 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jedi-language-server aims to support all of Jedi's capabilities and expose them

### Language Features

- [completionItem/resolve](https://microsoft.github.io/language-server-protocol/specification#completionItem_resolve)
- [textDocument/codeAction](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction) (refactor.inline, refactor.extract)
- [textDocument/completion](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion)
- [textDocument/definition](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition)
Expand Down Expand Up @@ -91,7 +92,8 @@ If you are configuring manually, jedi-language-server supports the following [in
"autoImportModules": []
},
"completion": {
"disableSnippets": false
"disableSnippets": false,
"resolveEagerly": false
},
"diagnostics": {
"enable": true,
Expand Down
11 changes: 11 additions & 0 deletions jedi_language_server/initialize_params_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ def initializationOptions_completion_disableSnippets(self) -> bool:
default = False
return bool(rgetattr(self._initialize_params, path, default))

@cached_property
def initializationOptions_completion_resolveEagerly(self):
_path = (
"initializationOptions",
"completion",
"resolveEagerly",
)
path = ".".join(_path)
default = False
return bool(rgetattr(self._initialize_params, path, default))

@cached_property
def initializationOptions_diagnostics_enable(self) -> bool:
_path = (
Expand Down
48 changes: 37 additions & 11 deletions jedi_language_server/jedi_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,46 +324,72 @@ def is_import(script_: Script, line: int, column: int) -> bool:
CompletionItemKind.Function,
}

_MOST_RECENT_COMPLETIONS: Dict[str, Completion] = {}


def clear_completions_cache():
"""Clears the cache of completions used for completionItem/resolve."""
_MOST_RECENT_COMPLETIONS.clear()


def lsp_completion_item(
name: Completion,
completion: Completion,
char_before_cursor: str,
enable_snippets: bool,
resolve_eagerly: bool,
markup_kind: MarkupKind,
) -> CompletionItem:
"""Using a Jedi completion, obtain a jedi completion item."""
name_name = name.name
name_clean = clean_completion_name(name_name, char_before_cursor)
lsp_type = get_lsp_completion_type(name.type)
completion_name = completion.name
name_clean = clean_completion_name(completion_name, char_before_cursor)
lsp_type = get_lsp_completion_type(completion.type)
completion_item = CompletionItem(
label=name_name,
filter_text=name_name,
label=completion_name,
filter_text=completion_name,
kind=lsp_type,
detail=name.description,
documentation=MarkupContent(kind=markup_kind, value=name.docstring()),
sort_text=complete_sort_name(name),
sort_text=complete_sort_name(completion),
insert_text=name_clean,
insert_text_format=InsertTextFormat.PlainText,
)
if resolve_eagerly:
completion_item = lsp_completion_item_resolve(
completion_item, markup_kind=markup_kind
)
else:
_MOST_RECENT_COMPLETIONS[completion_name] = completion

if not enable_snippets:
return completion_item
if lsp_type not in _LSP_TYPE_FOR_SNIPPET:
return completion_item

signatures = name.get_signatures()
signatures = completion.get_signatures()
if not signatures:
return completion_item

try:
snippet_signature = get_snippet_signature(signatures[0])
except Exception: # pylint: disable=broad-except
return completion_item
new_text = name_name + snippet_signature
new_text = completion_name + snippet_signature
completion_item.insertText = new_text
completion_item.insertTextFormat = InsertTextFormat.Snippet
return completion_item


def lsp_completion_item_resolve(
item: CompletionItem,
markup_kind: MarkupKind,
) -> CompletionItem:
"""Resolve completion item using cached jedi completion data."""
completion = _MOST_RECENT_COMPLETIONS[item.label]
item.detail = completion.description
item.documentation = MarkupContent(
kind=markup_kind, value=completion.docstring()
)
return item


def random_var(
beginning: str,
random_length: int = 8,
Expand Down
79 changes: 56 additions & 23 deletions jedi_language_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from pygls.features import (
CODE_ACTION,
COMPLETION,
COMPLETION_ITEM_RESOLVE,
DEFINITION,
DOCUMENT_HIGHLIGHT,
DOCUMENT_SYMBOL,
Expand All @@ -32,6 +33,7 @@
CodeAction,
CodeActionKind,
CodeActionParams,
CompletionItem,
CompletionList,
CompletionParams,
DidChangeTextDocumentParams,
Expand All @@ -45,6 +47,7 @@
InitializeResult,
Location,
MarkupContent,
MarkupKind,
RenameParams,
SymbolInformation,
TextDocumentPositionParams,
Expand Down Expand Up @@ -115,6 +118,36 @@ def __init__(self, *args, **kwargs):
# Server capabilities


@SERVER.feature(COMPLETION_ITEM_RESOLVE)
def completion_item_resolve(
server: JediLanguageServer, params: CompletionItem
) -> CompletionItem:
"""Resolves documentation and detail of given completion item."""
markup_kind = choose_markup(server)
# note: params is not a CompletionItem
# but a namedtuple complying with CompletionItem protocol
item = CompletionItem(
label=params.label,
kind=params.kind,
detail=params.detail,
documentation=params.documentation,
deprecated=params.deprecated,
preselect=params.preselect,
sort_text=params.sortText,
filter_text=params.filterText,
insert_text=params.insertText,
insert_text_format=params.insertTextFormat,
text_edit=params.textEdit,
additional_text_edits=params.additionalTextEdits,
commit_characters=params.commitCharacters,
command=params.command,
data=params.data,
)
return jedi_utils.lsp_completion_item_resolve(
item, markup_kind=markup_kind
)


@SERVER.feature(COMPLETION, trigger_characters=[".", "'", '"'])
def completion(
server: JediLanguageServer, params: CompletionParams
Expand All @@ -124,23 +157,16 @@ def completion(
jedi_script = jedi_utils.script(server.project, document)
jedi_lines = jedi_utils.line_column(jedi_script, params.position)
completions_jedi = jedi_script.complete(**jedi_lines)
markup_preferred = (
server.initialize_params.initializationOptions_markupKindPreferred
)
markup_supported = (
server.initialize_params.capabilities_textDocument_completion_completionItem_documentationFormat
)
markup_kind = (
markup_preferred
if markup_preferred in markup_supported
else markup_supported[0]
)
snippet_support = (
server.initialize_params.capabilities_textDocument_completion_completionItem_snippetSupport
)
snippet_disable = (
server.initialize_params.initializationOptions_completion_disableSnippets
)
resolve_eagerly = (
server.initialize_params.initializationOptions_completion_resolveEagerly
)
markup_kind = choose_markup(server)
is_import_context = jedi_utils.is_import(
script_=jedi_script,
line=jedi_lines["line"],
Expand All @@ -153,11 +179,13 @@ def completion(
document=server.workspace.get_document(params.textDocument.uri),
position=params.position,
)
jedi_utils.clear_completions_cache()
completion_items = [
jedi_utils.lsp_completion_item(
name=completion,
completion=completion,
char_before_cursor=char_before_cursor,
enable_snippets=enable_snippets,
resolve_eagerly=resolve_eagerly,
markup_kind=markup_kind,
)
for completion in completions_jedi
Expand Down Expand Up @@ -255,17 +283,7 @@ def hover(
docstring = name.docstring()
if not docstring:
continue
markup_preferred = (
server.initialize_params.initializationOptions_markupKindPreferred
)
markup_supported = (
server.initialize_params.capabilities_textDocument_hover_contentFormat
)
markup_kind = (
markup_preferred
if markup_preferred in markup_supported
else markup_supported[0]
)
markup_kind = choose_markup(server)
contents = MarkupContent(kind=markup_kind, value=docstring)
document = server.workspace.get_document(params.textDocument.uri)
_range = pygls_utils.current_word_range(document, params.position)
Expand Down Expand Up @@ -498,3 +516,18 @@ def did_change(
def did_open(server: JediLanguageServer, params: DidOpenTextDocumentParams):
"""Actions run on textDocument/didOpen."""
_publish_diagnostics(server, params.textDocument.uri)


def choose_markup(server: JediLanguageServer) -> MarkupKind:
"""Returns the preferred or first of supported markup kinds."""
markup_preferred = (
server.initialize_params.initializationOptions_markupKindPreferred
)
markup_supported = (
server.initialize_params.capabilities_textDocument_completion_completionItem_documentationFormat
)
return (
markup_preferred
if markup_preferred in markup_supported
else markup_supported[0]
)

0 comments on commit 4effbb8

Please sign in to comment.