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.
This commit is contained in:
James Westman 2025-01-04 13:45:47 -06:00
parent 2e42dc6848
commit bf4d8579b6
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
13 changed files with 122 additions and 97 deletions

View file

@ -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)
def complete(
@ -62,16 +67,24 @@ 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"
)
@ -81,9 +94,9 @@ def using_gtk(lsp, ast_node, match_variables):
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,
@ -99,14 +112,18 @@ 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}}",
snippet=snippet,
docs=c.doc,
detail=c.detail,
)
@ -116,14 +133,18 @@ 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}}",
snippet=snippet,
docs=c.doc,
detail=c.detail,
)
@ -133,76 +154,55 @@ 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 (
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():
if str(ctx.next_token) == ":":
snippet = prop_name
elif (
isinstance(prop.type, gir.BoolType)
and lsp.client_supports_completion_choice
and ctx.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,
)
snippet = f"{prop_name}: ${{1|true,false|}};"
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
and ctx.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,
)
snippet = f"{prop_name}: ${{1|{choices}|}};"
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,
)
snippet = f"{prop_name}: expr $0;"
else:
yield Completion(
prop_name,
CompletionItemKind.Property,
sort_text=f"0 {prop_name}",
snippet=f"{prop_name}: $0;",
docs=prop.doc,
detail=prop.detail,
)
snippet = f"{prop_name}: $0;"
yield Completion(
prop_name,
CompletionItemKind.Property,
sort_text=f"0 {prop_name}",
snippet=snippet,
docs=prop.doc,
detail=prop.detail,
)
@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):
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(
@ -221,30 +221,38 @@ def prop_value_completer(lsp, ast_node, match_variables):
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;",
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,