lsp: Extend completion documentation

This commit is contained in:
Sonny Piers 2024-06-16 14:18:08 +02:00 committed by Sonny Piers
parent da5b9909fc
commit a075b26769
3 changed files with 42 additions and 9 deletions

View file

@ -17,6 +17,7 @@
# #
# SPDX-License-Identifier: LGPL-3.0-or-later # SPDX-License-Identifier: LGPL-3.0-or-later
import sys
import typing as T import typing as T
from . import gir, language from . import gir, language
@ -30,6 +31,9 @@ from .tokenizer import Token, TokenType
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]] Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
def debug(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
def _complete( def _complete(
lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
) -> T.Iterator[Completion]: ) -> T.Iterator[Completion]:
@ -72,7 +76,9 @@ def complete(
@completer([language.GtkDirective]) @completer([language.GtkDirective])
def using_gtk(lsp, ast_node, match_variables): def using_gtk(lsp, ast_node, match_variables):
yield Completion("using Gtk 4.0;", CompletionItemKind.Keyword) yield Completion(
"using Gtk 4.0", CompletionItemKind.Keyword, snippet="using Gtk 4.0;\n"
)
@completer( @completer(
@ -101,7 +107,7 @@ def object_completer(lsp, ast_node, match_variables):
ns = ast_node.root.gir.namespaces.get(match_variables[0]) ns = ast_node.root.gir.namespaces.get(match_variables[0])
if ns is not None: if ns is not None:
for c in ns.classes.values(): for c in ns.classes.values():
yield Completion(c.name, CompletionItemKind.Class, docs=c.doc) yield Completion(c.name, CompletionItemKind.Class, docs=c.doc, detail=c.detail)
@completer( @completer(
@ -112,7 +118,7 @@ def gtk_object_completer(lsp, ast_node, match_variables):
ns = ast_node.root.gir.namespaces.get("Gtk") ns = ast_node.root.gir.namespaces.get("Gtk")
if ns is not None: if ns is not None:
for c in ns.classes.values(): for c in ns.classes.values():
yield Completion(c.name, CompletionItemKind.Class, docs=c.doc) yield Completion(c.name, CompletionItemKind.Class, docs=c.doc, detail=c.detail)
@completer( @completer(
@ -131,6 +137,8 @@ def property_completer(lsp, ast_node, match_variables):
CompletionItemKind.Property, CompletionItemKind.Property,
sort_text=f"0 {prop_name}", sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: ${{1|true,false|}};", snippet=f"{prop_name}: ${{1|true,false|}};",
docs=prop.doc,
detail=prop.detail,
) )
elif isinstance(prop.type, gir.StringType): elif isinstance(prop.type, gir.StringType):
yield Completion( yield Completion(
@ -138,6 +146,8 @@ def property_completer(lsp, ast_node, match_variables):
CompletionItemKind.Property, CompletionItemKind.Property,
sort_text=f"0 {prop_name}", sort_text=f"0 {prop_name}",
snippet=f'{prop_name}: "$0";', snippet=f'{prop_name}: "$0";',
docs=prop.doc,
detail=prop.detail,
) )
elif ( elif (
isinstance(prop.type, gir.Enumeration) isinstance(prop.type, gir.Enumeration)
@ -150,6 +160,8 @@ def property_completer(lsp, ast_node, match_variables):
CompletionItemKind.Property, CompletionItemKind.Property,
sort_text=f"0 {prop_name}", sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: ${{1|{choices}|}};", snippet=f"{prop_name}: ${{1|{choices}|}};",
docs=prop.doc,
detail=prop.detail,
) )
else: else:
yield Completion( yield Completion(
@ -157,6 +169,8 @@ def property_completer(lsp, ast_node, match_variables):
CompletionItemKind.Property, CompletionItemKind.Property,
sort_text=f"0 {prop_name}", sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: $0;", snippet=f"{prop_name}: $0;",
docs=prop.doc,
detail=prop.detail,
) )
@ -168,7 +182,12 @@ def prop_value_completer(lsp, ast_node, match_variables):
if (vt := ast_node.value_type) is not None: if (vt := ast_node.value_type) is not None:
if isinstance(vt.value_type, gir.Enumeration): if isinstance(vt.value_type, gir.Enumeration):
for name, member in vt.value_type.members.items(): for name, member in vt.value_type.members.items():
yield Completion(name, CompletionItemKind.EnumMember, docs=member.doc) yield Completion(
name,
CompletionItemKind.EnumMember,
docs=member.doc,
detail=member.detail,
)
elif isinstance(vt.value_type, gir.BoolType): elif isinstance(vt.value_type, gir.BoolType):
yield Completion("true", CompletionItemKind.Constant) yield Completion("true", CompletionItemKind.Constant)
@ -181,7 +200,7 @@ def prop_value_completer(lsp, ast_node, match_variables):
) )
def signal_completer(lsp, ast_node, match_variables): def signal_completer(lsp, ast_node, match_variables):
if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType): if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
for signal in ast_node.gir_class.signals: for signal_name, signal in ast_node.gir_class.signals.items():
if not isinstance(ast_node.parent, language.Object): if not isinstance(ast_node.parent, language.Object):
name = "on" name = "on"
else: else:
@ -192,10 +211,12 @@ def signal_completer(lsp, ast_node, match_variables):
.lower() .lower()
) )
yield Completion( yield Completion(
signal, signal_name,
CompletionItemKind.Event, CompletionItemKind.Event,
sort_text=f"1 {signal}", sort_text=f"1 {signal_name}",
snippet=f"{signal} => \$${{1:{name}_{signal.replace('-', '_')}}}()$0;", snippet=f"{signal_name} => \$${{1:{name}_{signal_name.replace('-', '_')}}}()$0;",
docs=signal.doc,
detail=signal.detail,
) )

View file

@ -336,6 +336,15 @@ class GirNode:
def available_in(self) -> str: def available_in(self) -> str:
return self.xml.get("version") return self.xml.get("version")
@cached_property
def detail(self) -> T.Optional[str]:
try:
el = self.xml.get_elements("doc")
if len(el) == 1:
return el[0].cdata.strip().partition("\n")[0]
except:
return None
@cached_property @cached_property
def doc(self) -> T.Optional[str]: def doc(self) -> T.Optional[str]:
sections = [] sections = []

View file

@ -84,6 +84,7 @@ class Completion:
docs: T.Optional[str] = None docs: T.Optional[str] = None
text: T.Optional[str] = None text: T.Optional[str] = None
snippet: T.Optional[str] = None snippet: T.Optional[str] = None
detail: T.Optional[str] = None
def to_json(self, snippets: bool): def to_json(self, snippets: bool):
insert_text = self.text or self.label insert_text = self.text or self.label
@ -96,7 +97,8 @@ class Completion:
"label": self.label, "label": self.label,
"kind": self.kind, "kind": self.kind,
"tags": [CompletionItemTag.Deprecated] if self.deprecated else None, "tags": [CompletionItemTag.Deprecated] if self.deprecated else None,
"detail": self.signature, # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemLabelDetails
"labelDetails": ({"detail": self.signature} if self.signature else None),
"documentation": ( "documentation": (
{ {
"kind": "markdown", "kind": "markdown",
@ -109,6 +111,7 @@ class Completion:
"sortText": self.sort_text, "sortText": self.sort_text,
"insertText": insert_text, "insertText": insert_text,
"insertTextFormat": insert_text_format, "insertTextFormat": insert_text_format,
"detail": self.detail if self.detail else None,
} }
return {k: v for k, v in result.items() if v is not None} return {k: v for k, v in result.items() if v is not None}