Compare commits

...

7 commits

Author SHA1 Message Date
James Westman
72583be267
completions: Improve accessibility properties 2025-01-05 15:21:26 -06:00
James Westman
bcb8e4ea14
completions: Sort completion items 2025-01-05 14:47:44 -06:00
James Westman
3cdc3a159c
completions: Add object value completions
For object properties, add completions for named objects in the
blueprint and for matching classes in imported namespaces. Also add null
and bind.
2025-01-05 14:47:44 -06:00
James Westman
2204eaf92d
completions: Add completer for import statements 2025-01-05 14:47:44 -06:00
James Westman
533a49acd7
completions: Add translation-domain completer 2025-01-05 14:47:44 -06:00
James Westman
21f138fa83
completions: Complete available namespaces
Add completions for namespaces in the typelib path that can be imported.
Accepting the completion automatically adds an import statement.
2025-01-05 14:47:44 -06:00
James Westman
461fe25316
lsp: Fix completions when editing existing item
Many completion snippets insert more than just the name. For example,
the object completer inserts the braces and places your cursor inside
them automatically, to save some typing. However, if you're changing the
class of an existing object, this isn't what you want. Changed so that
if the next token is '{', only the name is inserted.

Made similar changes to the property and signal completers.
2025-01-05 14:47:40 -06:00
16 changed files with 446 additions and 147 deletions

View file

@ -23,7 +23,7 @@ from . import annotations, gir, language
from .ast_utils import AstNode
from .completions_utils import *
from .language.types import ClassName
from .lsp_utils import Completion, CompletionItemKind
from .lsp_utils import Completion, CompletionItemKind, TextEdit, get_docs_section
from .parser import SKIP_TOKENS
from .tokenizer import Token, TokenType
@ -31,13 +31,18 @@ Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
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,
next_token: Token,
) -> T.Iterator[Completion]:
for child in ast_node.children:
if child.group.start <= idx and (
idx < child.group.end or (idx == child.group.end and child.incomplete)
):
yield from _complete(lsp, child, tokens, idx, token_idx)
yield from _complete(lsp, child, tokens, idx, token_idx, next_token)
return
prev_tokens: T.List[Token] = []
@ -50,7 +55,7 @@ def _complete(
token_idx -= 1
for completer in ast_node.completers:
yield from completer(prev_tokens, ast_node, lsp)
yield from completer(prev_tokens, next_token, ast_node, lsp, idx)
def complete(
@ -62,35 +67,117 @@ def complete(
if token.start < idx <= token.end:
token_idx = i
if tokens[token_idx].type == TokenType.EOF:
next_token = tokens[token_idx]
else:
next_token_idx = token_idx + 1
while tokens[next_token_idx].type == TokenType.WHITESPACE:
next_token_idx += 1
next_token = tokens[next_token_idx]
# if the current token is an identifier or whitespace, move to the token before it
while tokens[token_idx].type in [TokenType.IDENT, TokenType.WHITESPACE]:
idx = tokens[token_idx].start
token_idx -= 1
yield from _complete(lsp, ast_node, tokens, idx, token_idx)
yield from _complete(lsp, ast_node, tokens, idx, token_idx, next_token)
@completer([language.GtkDirective])
def using_gtk(lsp, ast_node, match_variables):
def using_gtk(_ctx: CompletionContext):
yield Completion(
"using Gtk 4.0", CompletionItemKind.Keyword, snippet="using Gtk 4.0;\n"
)
@completer([language.UI])
def using(ctx: CompletionContext):
imported_namespaces = set(
[import_.namespace for import_ in ctx.ast_node.root.using]
)
# Import statements must be before any content
for i in ctx.ast_node.root.children:
if not isinstance(i, language.GtkDirective) and not isinstance(
i, language.Import
):
if ctx.index >= i.range.end:
return
for ns, version in gir.get_available_namespaces():
if ns not in imported_namespaces and ns != "Gtk":
yield Completion(
f"using {ns} {version}",
CompletionItemKind.Module,
text=f"using {ns} {version};",
sort_text=get_sort_key(CompletionPriority.NAMESPACE, ns),
)
@completer([language.UI])
def translation_domain(ctx: CompletionContext):
if ctx.ast_node.root.translation_domain is not None:
return
# Translation domain must be after the import statements but before any content
for i in ctx.ast_node.root.children:
if isinstance(i, language.Import):
if ctx.index <= i.range.start:
return
elif not isinstance(i, language.GtkDirective):
if ctx.index >= i.range.end:
return
yield Completion(
"translation-domain",
CompletionItemKind.Keyword,
sort_text=get_sort_key(CompletionPriority.KEYWORD, "translation-domain"),
snippet='translation-domain "$0";',
docs=get_docs_section("Syntax TranslationDomain"),
)
def _available_namespace_completions(ctx: CompletionContext):
imported_namespaces = set(
[import_.namespace for import_ in ctx.ast_node.root.using]
)
for ns, version in gir.get_available_namespaces():
if ns not in imported_namespaces and ns != "Gtk":
yield Completion(
ns,
CompletionItemKind.Module,
text=ns + ".",
sort_text=get_sort_key(CompletionPriority.IMPORT_NAMESPACE, ns),
signature=f" using {ns} {version}",
additional_text_edits=[
TextEdit(
ctx.ast_node.root.import_range(ns), f"\nusing {ns} {version};"
)
],
)
@completer(
applies_in=[language.UI, language.ObjectContent, language.Template],
matches=new_statement_patterns,
)
def namespace(lsp, ast_node, match_variables):
def namespace(ctx: CompletionContext):
yield Completion("Gtk", CompletionItemKind.Module, text="Gtk.")
for ns in ast_node.root.children[language.Import]:
for ns in ctx.ast_node.root.children[language.Import]:
if ns.gir_namespace is not None:
yield Completion(
ns.gir_namespace.name,
CompletionItemKind.Module,
text=ns.gir_namespace.name + ".",
sort_text=get_sort_key(
CompletionPriority.NAMESPACE, ns.gir_namespace.name
),
)
yield from _available_namespace_completions(ctx)
@completer(
applies_in=[language.UI, language.ObjectContent, language.Template],
@ -99,14 +186,19 @@ def namespace(lsp, ast_node, match_variables):
[(TokenType.IDENT, None), (TokenType.OP, ".")],
],
)
def object_completer(lsp, ast_node, match_variables):
ns = ast_node.root.gir.namespaces.get(match_variables[0])
def object_completer(ctx: CompletionContext):
ns = ctx.ast_node.root.gir.namespaces.get(ctx.match_variables[0])
if ns is not None:
for c in ns.classes.values():
snippet = c.name
if str(ctx.next_token) != "{":
snippet += " {\n $0\n}"
yield Completion(
c.name,
CompletionItemKind.Class,
snippet=f"{c.name} {{\n $0\n}}",
sort_text=get_sort_key(CompletionPriority.CLASS, c.name),
snippet=snippet,
docs=c.doc,
detail=c.detail,
)
@ -116,14 +208,19 @@ def object_completer(lsp, ast_node, match_variables):
applies_in=[language.UI, language.ObjectContent, language.Template],
matches=new_statement_patterns,
)
def gtk_object_completer(lsp, ast_node, match_variables):
ns = ast_node.root.gir.namespaces.get("Gtk")
def gtk_object_completer(ctx: CompletionContext):
ns = ctx.ast_node.root.gir.namespaces.get("Gtk")
if ns is not None:
for c in ns.classes.values():
snippet = c.name
if str(ctx.next_token) != "{":
snippet += " {\n $0\n}"
yield Completion(
c.name,
CompletionItemKind.Class,
snippet=f"{c.name} {{\n $0\n}}",
sort_text=get_sort_key(CompletionPriority.CLASS, c.name),
snippet=snippet,
docs=c.doc,
detail=c.detail,
)
@ -133,76 +230,38 @@ def gtk_object_completer(lsp, ast_node, match_variables):
applies_in=[language.ObjectContent],
matches=new_statement_patterns,
)
def property_completer(lsp, ast_node, match_variables):
if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
for prop_name, prop in ast_node.gir_class.properties.items():
if (
isinstance(prop.type, gir.BoolType)
and lsp.client_supports_completion_choice
):
yield Completion(
prop_name,
CompletionItemKind.Property,
sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: ${{1|true,false|}};",
docs=prop.doc,
detail=prop.detail,
)
elif isinstance(prop.type, gir.StringType):
snippet = (
f'{prop_name}: _("$0");'
if annotations.is_property_translated(prop)
else f'{prop_name}: "$0";'
)
yield Completion(
prop_name,
CompletionItemKind.Property,
sort_text=f"0 {prop_name}",
snippet=snippet,
docs=prop.doc,
detail=prop.detail,
)
elif (
isinstance(prop.type, gir.Enumeration)
and len(prop.type.members) <= 10
and lsp.client_supports_completion_choice
):
choices = ",".join(prop.type.members.keys())
yield Completion(
prop_name,
CompletionItemKind.Property,
sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: ${{1|{choices}|}};",
docs=prop.doc,
detail=prop.detail,
)
elif prop.type.full_name == "Gtk.Expression":
yield Completion(
prop_name,
CompletionItemKind.Property,
sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: expr $0;",
docs=prop.doc,
detail=prop.detail,
)
else:
yield Completion(
prop_name,
CompletionItemKind.Property,
sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: $0;",
docs=prop.doc,
detail=prop.detail,
)
def property_completer(ctx: CompletionContext):
assert isinstance(ctx.ast_node, language.ObjectContent)
if ctx.ast_node.gir_class and hasattr(ctx.ast_node.gir_class, "properties"):
for prop_name, prop in ctx.ast_node.gir_class.properties.items():
yield get_property_completion(
prop_name,
prop,
ctx,
annotations.is_property_translated(prop),
prop.doc,
)
@completer(
applies_in=[language.Property, language.A11yProperty],
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
)
def prop_value_completer(lsp, ast_node, match_variables):
if (vt := ast_node.value_type) is not None:
def prop_value_completer(ctx: CompletionContext):
if isinstance(ctx.ast_node, language.Property):
yield Completion(
"bind",
CompletionItemKind.Keyword,
snippet="bind $0",
docs=get_docs_section("Syntax Binding"),
sort_text=get_sort_key(CompletionPriority.KEYWORD, "bind"),
)
assert isinstance(ctx.ast_node, language.Property) or isinstance(
ctx.ast_node, language.A11yProperty
)
if (vt := ctx.ast_node.value_type) is not None:
if isinstance(vt.value_type, gir.Enumeration):
for name, member in vt.value_type.members.items():
yield Completion(
@ -210,41 +269,99 @@ def prop_value_completer(lsp, ast_node, match_variables):
CompletionItemKind.EnumMember,
docs=member.doc,
detail=member.detail,
sort_text=get_sort_key(CompletionPriority.ENUM_MEMBER, name),
)
elif isinstance(vt.value_type, gir.BoolType):
yield Completion("true", CompletionItemKind.Constant)
yield Completion("false", CompletionItemKind.Constant)
yield Completion(
"true",
CompletionItemKind.Constant,
sort_text=get_sort_key(CompletionPriority.ENUM_MEMBER, "true"),
)
yield Completion(
"false",
CompletionItemKind.Constant,
sort_text=get_sort_key(CompletionPriority.ENUM_MEMBER, "false"),
)
elif isinstance(vt.value_type, gir.Class) or isinstance(
vt.value_type, gir.Interface
):
yield Completion(
"null",
CompletionItemKind.Constant,
sort_text=get_sort_key(CompletionPriority.KEYWORD, "null"),
)
for id, obj in ctx.ast_node.root.context[language.ScopeCtx].objects.items():
if obj.gir_class is not None and obj.gir_class.assignable_to(
vt.value_type
):
yield Completion(
id,
CompletionItemKind.Variable,
signature=" " + obj.signature,
sort_text=get_sort_key(CompletionPriority.NAMED_OBJECT, id),
)
if isinstance(ctx.ast_node, language.Property):
yield from _available_namespace_completions(ctx)
for ns in ctx.ast_node.root.gir.namespaces.values():
for c in ns.classes.values():
if not c.abstract and c.assignable_to(vt.value_type):
name = (
c.name if ns.name == "Gtk" else ns.name + "." + c.name
)
snippet = name
if str(ctx.next_token) != "{":
snippet += " {\n $0\n}"
yield Completion(
name,
CompletionItemKind.Class,
sort_text=get_sort_key(CompletionPriority.CLASS, name),
snippet=snippet,
detail=c.detail,
docs=c.doc,
)
@completer(
applies_in=[language.ObjectContent],
matches=new_statement_patterns,
)
def signal_completer(lsp, ast_node, match_variables):
if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
for signal_name, signal in ast_node.gir_class.signals.items():
if not isinstance(ast_node.parent, language.Object):
name = "on"
def signal_completer(ctx: CompletionContext):
assert isinstance(ctx.ast_node, language.ObjectContent)
if ctx.ast_node.gir_class and hasattr(ctx.ast_node.gir_class, "signals"):
for signal_name, signal in ctx.ast_node.gir_class.signals.items():
if str(ctx.next_token) == "=>":
snippet = signal_name
else:
name = "on_" + (
ast_node.parent.children[ClassName][0].tokens["id"]
or ast_node.parent.children[ClassName][0]
.tokens["class_name"]
.lower()
)
if not isinstance(ctx.ast_node.parent, language.Object):
name = "on"
else:
name = "on_" + (
ctx.ast_node.parent.children[ClassName][0].tokens["id"]
or ctx.ast_node.parent.children[ClassName][0]
.tokens["class_name"]
.lower()
)
snippet = f"{signal_name} => \\$${{1:${name}_{signal_name.replace('-', '_')}}}()$0;"
yield Completion(
signal_name,
CompletionItemKind.Event,
sort_text=f"1 {signal_name}",
snippet=f"{signal_name} => \\$${{1:${name}_{signal_name.replace('-', '_')}}}()$0;",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, signal_name),
snippet=snippet,
docs=signal.doc,
detail=signal.detail,
)
@completer(applies_in=[language.UI], matches=new_statement_patterns)
def template_completer(lsp, ast_node, match_variables):
def template_completer(_ctx: CompletionContext):
yield Completion(
"template",
CompletionItemKind.Snippet,

View file

@ -19,10 +19,39 @@
import typing as T
from dataclasses import dataclass
from enum import Enum
from .lsp_utils import Completion
from . import gir
from .ast_utils import AstNode
from .lsp_utils import Completion, CompletionItemKind
from .tokenizer import Token, TokenType
class CompletionPriority(Enum):
ENUM_MEMBER = "00"
NAMED_OBJECT = "01"
OBJECT_MEMBER = "02"
CLASS = "03"
NAMESPACE = "04"
KEYWORD = "05"
# An available namespace that hasn't been imported yet
IMPORT_NAMESPACE = "99"
def get_sort_key(priority: CompletionPriority, name: str):
return f"{priority.value} {name}"
@dataclass
class CompletionContext:
client_supports_completion_choice: bool
ast_node: AstNode
match_variables: T.List[str]
next_token: Token
index: int
new_statement_patterns = [
[(TokenType.PUNCTUATION, "{")],
[(TokenType.PUNCTUATION, "}")],
@ -32,8 +61,10 @@ new_statement_patterns = [
def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
def decorator(func):
def inner(prev_tokens: T.List[Token], ast_node, lsp):
def decorator(func: T.Callable[[CompletionContext], T.Generator[Completion]]):
def inner(
prev_tokens: T.List[Token], next_token: Token, ast_node, lsp, idx: int
):
# For completers that apply in ObjectContent nodes, we can further
# check that the object is the right class
if applies_in_subclass is not None:
@ -66,10 +97,51 @@ def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None
if not any_match:
return
yield from func(lsp, ast_node, match_variables)
context = CompletionContext(
client_supports_completion_choice=lsp.client_supports_completion_choice,
ast_node=ast_node,
match_variables=match_variables,
next_token=next_token,
index=idx,
)
yield from func(context)
for c in applies_in:
c.completers.append(inner)
return inner
return decorator
def get_property_completion(
name: str,
type: gir.GirType,
ctx: CompletionContext,
translated: bool,
doc: str,
) -> Completion:
if str(ctx.next_token) == ":":
snippet = name
elif isinstance(type, gir.BoolType) and ctx.client_supports_completion_choice:
snippet = f"{name}: ${{1|true,false|}};"
elif isinstance(type, gir.StringType):
snippet = f'{name}: _("$0");' if translated else f'{name}: "$0";'
elif (
isinstance(type, gir.Enumeration)
and len(type.members) <= 10
and ctx.client_supports_completion_choice
):
choices = ",".join(type.members.keys())
snippet = f"{name}: ${{1|{choices}|}};"
elif type.full_name == "Gtk.Expression":
snippet = f"{name}: expr $0;"
else:
snippet = f"{name}: $0;"
return Completion(
name,
CompletionItemKind.Property,
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, name),
snippet=snippet,
docs=doc,
)

View file

@ -143,7 +143,7 @@ class ExtAdwResponseDialog(AstNode):
applies_in_subclass=("Adw", "MessageDialog"),
matches=new_statement_patterns,
)
def complete_adw_message_dialog(lsp, ast_node, match_variables):
def complete_adw_message_dialog(_ctx: CompletionContext):
yield Completion(
"responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
)
@ -154,9 +154,12 @@ def complete_adw_message_dialog(lsp, ast_node, match_variables):
applies_in_subclass=("Adw", "AlertDialog"),
matches=new_statement_patterns,
)
def complete_adw_alert_dialog(lsp, ast_node, match_variables):
def complete_adw_alert_dialog(_ctx: CompletionContext):
yield Completion(
"responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
"responses",
CompletionItemKind.Keyword,
snippet="responses [\n\t$0\n]",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "responses"),
)

View file

@ -26,7 +26,11 @@ from .values import ArrayValue, ExprValue, ObjectValue, Value
class Property(AstNode):
grammar = Statement(
UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
UseIdent("name"),
":",
AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue).expected(
"property value"
),
)
@property

View file

@ -25,7 +25,7 @@ from .gobject_object import ObjectContent, validate_parent_type
from .values import Value
def get_property_types(gir):
def get_property_types(gir: gir.GirContext) -> T.Dict[str, T.Optional[GirType]]:
# from <https://docs.gtk.org/gtk4/enum.AccessibleProperty.html>
return {
"autocomplete": gir.get_type("AccessibleAutocomplete", "Gtk"),
@ -50,7 +50,7 @@ def get_property_types(gir):
}
def get_relation_types(gir):
def get_relation_types(gir: gir.GirContext) -> T.Dict[str, T.Optional[GirType]]:
# from <https://docs.gtk.org/gtk4/enum.AccessibleRelation.html>
widget = gir.get_type("Widget", "Gtk")
return {
@ -75,7 +75,7 @@ def get_relation_types(gir):
}
def get_state_types(gir):
def get_state_types(gir: gir.GirContext) -> T.Dict[str, T.Optional[GirType]]:
# from <https://docs.gtk.org/gtk4/enum.AccessibleState.html>
return {
"busy": BoolType(),
@ -89,6 +89,20 @@ def get_state_types(gir):
}
TRANSLATED = set(
[
"description",
"help-text",
"label",
"placeholder",
"role-description",
"value-text",
"col-index-text",
"row-index-text",
]
)
def get_types(gir):
return {
**get_property_types(gir),
@ -121,7 +135,9 @@ class A11yProperty(AstNode):
grammar = Statement(
UseIdent("name"),
":",
AnyOf(Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]),
AnyOf(
Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]
).expected("value"),
)
@property
@ -232,9 +248,12 @@ class ExtAccessibility(AstNode):
applies_in=[ObjectContent],
matches=new_statement_patterns,
)
def a11y_completer(lsp, ast_node, match_variables):
def a11y_completer(_ctx: CompletionContext):
yield Completion(
"accessibility", CompletionItemKind.Snippet, snippet="accessibility {\n $0\n}"
"accessibility",
CompletionItemKind.Snippet,
snippet="accessibility {\n $0\n}",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "accessibility"),
)
@ -242,12 +261,14 @@ def a11y_completer(lsp, ast_node, match_variables):
applies_in=[ExtAccessibility],
matches=new_statement_patterns,
)
def a11y_name_completer(lsp, ast_node, match_variables):
for name, type in get_types(ast_node.root.gir).items():
yield Completion(
def a11y_property_completer(ctx: CompletionContext):
for name, type in get_types(ctx.ast_node.root.gir).items():
yield get_property_completion(
name,
CompletionItemKind.Property,
docs=_get_docs(ast_node.root.gir, type.name),
type,
ctx,
name in TRANSLATED,
_get_docs(ctx.ast_node.root.gir, name),
)

View file

@ -94,8 +94,13 @@ class ExtComboBoxItems(AstNode):
applies_in_subclass=("Gtk", "ComboBoxText"),
matches=new_statement_patterns,
)
def items_completer(lsp, ast_node, match_variables):
yield Completion("items", CompletionItemKind.Snippet, snippet="items [$0]")
def items_completer(_ctx: CompletionContext):
yield Completion(
"items",
CompletionItemKind.Snippet,
snippet="items [$0]",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "items"),
)
@decompiler("items", parent_type="Gtk.ComboBoxText")

View file

@ -101,12 +101,25 @@ ext_file_filter_suffixes = create_node("suffixes", "suffix")
applies_in_subclass=("Gtk", "FileFilter"),
matches=new_statement_patterns,
)
def file_filter_completer(lsp, ast_node, match_variables):
def file_filter_completer(_ctx: CompletionContext):
yield Completion(
"mime-types", CompletionItemKind.Snippet, snippet='mime-types ["$0"]'
"mime-types",
CompletionItemKind.Snippet,
snippet='mime-types ["$0"]',
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "mime-types"),
)
yield Completion(
"patterns",
CompletionItemKind.Snippet,
snippet='patterns ["$0"]',
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "patterns"),
)
yield Completion(
"suffixes",
CompletionItemKind.Snippet,
snippet='suffixes ["$0"]',
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "suffixes"),
)
yield Completion("patterns", CompletionItemKind.Snippet, snippet='patterns ["$0"]')
yield Completion("suffixes", CompletionItemKind.Snippet, snippet='suffixes ["$0"]')
@decompiler("mime-types")

View file

@ -93,8 +93,13 @@ class ExtLayout(AstNode):
applies_in_subclass=("Gtk", "Widget"),
matches=new_statement_patterns,
)
def layout_completer(lsp, ast_node, match_variables):
yield Completion("layout", CompletionItemKind.Snippet, snippet="layout {\n $0\n}")
def layout_completer(_ctx: CompletionContext):
yield Completion(
"layout",
CompletionItemKind.Snippet,
snippet="layout {\n $0\n}",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "layout"),
)
@decompiler("layout")

View file

@ -243,7 +243,7 @@ from .ui import UI
applies_in=[UI],
matches=new_statement_patterns,
)
def menu_completer(lsp, ast_node, match_variables):
def menu_completer(_ctx: CompletionContext):
yield Completion("menu", CompletionItemKind.Snippet, snippet="menu {\n $0\n}")
@ -251,23 +251,50 @@ def menu_completer(lsp, ast_node, match_variables):
applies_in=[Menu],
matches=new_statement_patterns,
)
def menu_content_completer(lsp, ast_node, match_variables):
def menu_content_completer(_ctx: CompletionContext):
yield Completion(
"submenu", CompletionItemKind.Snippet, snippet="submenu {\n $0\n}"
"submenu",
CompletionItemKind.Snippet,
snippet="submenu {\n $0\n}",
sort_text=get_sort_key(CompletionPriority.CLASS, "1 submenu"),
)
yield Completion(
"section", CompletionItemKind.Snippet, snippet="section {\n $0\n}"
"section",
CompletionItemKind.Snippet,
snippet="section {\n $0\n}",
sort_text=get_sort_key(CompletionPriority.CLASS, "1 section"),
)
yield Completion(
"item",
CompletionItemKind.Snippet,
snippet="item {\n $0\n}",
sort_text=get_sort_key(CompletionPriority.CLASS, "1 item"),
)
yield Completion("item", CompletionItemKind.Snippet, snippet="item {\n $0\n}")
yield Completion(
"item (shorthand)",
CompletionItemKind.Snippet,
snippet='item (_("${1:Label}"), "${2:action-name}", "${3:icon-name}")',
sort_text=get_sort_key(CompletionPriority.CLASS, "0 item (shorthand)"),
)
yield Completion("label", CompletionItemKind.Snippet, snippet="label: $0;")
yield Completion("action", CompletionItemKind.Snippet, snippet='action: "$0";')
yield Completion("icon", CompletionItemKind.Snippet, snippet='icon: "$0";')
yield Completion(
"label",
CompletionItemKind.Snippet,
snippet="label: $0;",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "label"),
)
yield Completion(
"action",
CompletionItemKind.Snippet,
snippet='action: "$0";',
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "action"),
)
yield Completion(
"icon",
CompletionItemKind.Snippet,
snippet='icon: "$0";',
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "icon"),
)
@decompiler("menu")

View file

@ -137,14 +137,19 @@ class ExtScaleMarks(AstNode):
applies_in_subclass=("Gtk", "Scale"),
matches=new_statement_patterns,
)
def complete_marks(lsp, ast_node, match_variables):
yield Completion("marks", CompletionItemKind.Keyword, snippet="marks [\n\t$0\n]")
def complete_marks(_ctx: CompletionContext):
yield Completion(
"marks",
CompletionItemKind.Keyword,
snippet="marks [\n\t$0\n]",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "marks"),
)
@completer(
applies_in=[ExtScaleMarks],
)
def complete_mark(lsp, ast_node, match_variables):
def complete_mark(_ctx: CompletionContext):
yield Completion("mark", CompletionItemKind.Keyword, snippet="mark ($0),")

View file

@ -104,8 +104,13 @@ class ExtSizeGroupWidgets(AstNode):
applies_in_subclass=("Gtk", "SizeGroup"),
matches=new_statement_patterns,
)
def size_group_completer(lsp, ast_node, match_variables):
yield Completion("widgets", CompletionItemKind.Snippet, snippet="widgets [$0]")
def size_group_completer(_ctx: CompletionContext):
yield Completion(
"widgets",
CompletionItemKind.Snippet,
snippet="widgets [$0]",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "widgets"),
)
@decompiler("widgets")

View file

@ -75,8 +75,13 @@ class ExtStringListStrings(AstNode):
applies_in_subclass=("Gtk", "StringList"),
matches=new_statement_patterns,
)
def strings_completer(lsp, ast_node, match_variables):
yield Completion("strings", CompletionItemKind.Snippet, snippet="strings [$0]")
def strings_completer(_ctx: CompletionContext):
yield Completion(
"strings",
CompletionItemKind.Snippet,
snippet="strings [$0]",
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "strings"),
)
@decompiler("items", parent_type="Gtk.StringList")

View file

@ -80,8 +80,13 @@ class ExtStyles(AstNode):
applies_in_subclass=("Gtk", "Widget"),
matches=new_statement_patterns,
)
def style_completer(lsp, ast_node, match_variables):
yield Completion("styles", CompletionItemKind.Keyword, snippet='styles ["$0"]')
def style_completer(_ctx: CompletionContext):
yield Completion(
"styles",
CompletionItemKind.Keyword,
snippet='styles ["$0"]',
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "styles"),
)
@decompiler("style")

View file

@ -110,16 +110,22 @@ class UI(AstNode):
and self.template.class_name.glib_type_name == id
)
def import_code_action(self, ns: str, version: str) -> CodeAction:
if len(self.children[Import]):
pos = self.children[Import][-1].range.end
else:
pos = self.children[GtkDirective][0].range.end
def import_range(self, ns: str):
"""Returns a range to insert a new import statement"""
pos = self.children[GtkDirective][0].range.end
# try to insert alphabetically
for import_ in self.children[Import]:
if ns.lower() > import_.namespace.lower():
pos = import_.range.end
return Range(pos, pos, self.group.text)
def import_code_action(self, ns: str, version: str) -> CodeAction:
return CodeAction(
f"Import {ns} {version}",
f"\nusing {ns} {version};",
Range(pos, pos, self.group.text),
self.import_range(ns),
)
@cached_property

View file

@ -87,6 +87,7 @@ class Completion:
text: T.Optional[str] = None
snippet: T.Optional[str] = None
detail: T.Optional[str] = None
additional_text_edits: T.Optional[T.List["TextEdit"]] = None
def to_json(self, snippets: bool):
insert_text = self.text or self.label
@ -114,6 +115,11 @@ class Completion:
"insertText": insert_text,
"insertTextFormat": insert_text_format,
"detail": self.detail if self.detail else None,
"additionalTextEdits": (
[edit.to_json() for edit in self.additional_text_edits]
if self.additional_text_edits
else None
),
}
return {k: v for k, v in result.items() if v is not None}

View file

@ -64,11 +64,11 @@ class TestSamples(unittest.TestCase):
def assert_ast_doesnt_crash(self, text, tokens, ast: AstNode):
lsp = LanguageServer()
for i in range(len(text)):
for i in range(len(text) + 1):
ast.get_docs(i)
for i in range(len(text)):
for i in range(len(text) + 1):
list(complete(lsp, ast, tokens, i))
for i in range(len(text)):
for i in range(len(text) + 1):
ast.get_reference(i)
ast.get_document_symbols()