From 21f138fa83e35996804f6ac5cbbc9715aa354cae Mon Sep 17 00:00:00 2001 From: James Westman Date: Sat, 4 Jan 2025 16:05:27 -0600 Subject: [PATCH] completions: Complete available namespaces Add completions for namespaces in the typelib path that can be imported. Accepting the completion automatically adds an import statement. --- blueprintcompiler/completions.py | 20 +++++++++++++++++++- blueprintcompiler/language/ui.py | 18 ++++++++++++------ blueprintcompiler/lsp_utils.py | 6 ++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/blueprintcompiler/completions.py b/blueprintcompiler/completions.py index f1c17b6..60cadad 100644 --- a/blueprintcompiler/completions.py +++ b/blueprintcompiler/completions.py @@ -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 from .parser import SKIP_TOKENS from .tokenizer import Token, TokenType @@ -96,14 +96,32 @@ def using_gtk(_ctx: CompletionContext): ) def namespace(ctx: CompletionContext): yield Completion("Gtk", CompletionItemKind.Module, text="Gtk.") + + imported_namespaces = set(["Gtk"]) + for ns in ctx.ast_node.root.children[language.Import]: if ns.gir_namespace is not None: + imported_namespaces.add(ns.gir_namespace.name) yield Completion( ns.gir_namespace.name, CompletionItemKind.Module, text=ns.gir_namespace.name + ".", ) + for ns, version in gir.get_available_namespaces(): + if ns not in imported_namespaces: + yield Completion( + ns, + CompletionItemKind.Module, + text=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], diff --git a/blueprintcompiler/language/ui.py b/blueprintcompiler/language/ui.py index d55a22a..896c0f7 100644 --- a/blueprintcompiler/language/ui.py +++ b/blueprintcompiler/language/ui.py @@ -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 diff --git a/blueprintcompiler/lsp_utils.py b/blueprintcompiler/lsp_utils.py index b938181..2a4380a 100644 --- a/blueprintcompiler/lsp_utils.py +++ b/blueprintcompiler/lsp_utils.py @@ -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}