diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py index 3642bd6..635ef7c 100644 --- a/blueprintcompiler/gir.py +++ b/blueprintcompiler/gir.py @@ -18,7 +18,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import os -import sys import typing as T from functools import cached_property @@ -29,6 +28,7 @@ from gi.repository import GIRepository # type: ignore from . import typelib, xml_reader from .errors import CompileError, CompilerBugError +from .lsp_utils import CodeAction _namespace_cache: T.Dict[str, "Namespace"] = {} _xml_cache = {} @@ -65,6 +65,27 @@ def get_namespace(namespace: str, version: str) -> "Namespace": return _namespace_cache[filename] +_available_namespaces: list[tuple[str, str]] = [] + + +def get_available_namespaces() -> T.List[T.Tuple[str, str]]: + if len(_available_namespaces): + return _available_namespaces + + search_paths: list[str] = [ + *GIRepository.Repository.get_search_path(), + *_user_search_paths, + ] + + for search_path in search_paths: + for filename in os.listdir(search_path): + if filename.endswith(".typelib"): + namespace, version = filename.removesuffix(".typelib").rsplit("-", 1) + _available_namespaces.append((namespace, version)) + + return _available_namespaces + + def get_xml(namespace: str, version: str): search_paths = [] @@ -1011,9 +1032,11 @@ class GirContext: ns = ns or "Gtk" if ns not in self.namespaces and ns not in self.not_found_namespaces: + all_available = list(set(ns for ns, _version in get_available_namespaces())) + raise CompileError( f"Namespace {ns} was not imported", - did_you_mean=(ns, self.namespaces.keys()), + did_you_mean=(ns, all_available), ) def validate_type(self, name: str, ns: str) -> None: diff --git a/blueprintcompiler/language/types.py b/blueprintcompiler/language/types.py index e7b1867..b3fb586 100644 --- a/blueprintcompiler/language/types.py +++ b/blueprintcompiler/language/types.py @@ -55,7 +55,16 @@ class TypeName(AstNode): @validate("namespace") def gir_ns_exists(self): if not self.tokens["extern"]: - self.root.gir.validate_ns(self.tokens["namespace"]) + try: + self.root.gir.validate_ns(self.tokens["namespace"]) + except CompileError as e: + ns = self.tokens["namespace"] + e.actions = [ + self.root.import_code_action(n, version) + for n, version in gir.get_available_namespaces() + if n == ns + ] + raise e @validate() def deprecated(self) -> None: diff --git a/blueprintcompiler/language/ui.py b/blueprintcompiler/language/ui.py index 3ce23da..34ba193 100644 --- a/blueprintcompiler/language/ui.py +++ b/blueprintcompiler/language/ui.py @@ -99,6 +99,18 @@ 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 + + return CodeAction( + f"Import {ns} {version}", + f"\nusing {ns} {version};", + Range(pos, pos, self.group.text), + ) + @context(ScopeCtx) def scope_ctx(self) -> ScopeCtx: return ScopeCtx(node=self)