From 3d79f9560c82b5060f9303b1b1893c0b115b18c7 Mon Sep 17 00:00:00 2001 From: James Westman Date: Sat, 15 Jul 2023 17:34:04 -0500 Subject: [PATCH 1/7] ci: Fix Dockerfile --- build-aux/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-aux/Dockerfile b/build-aux/Dockerfile index aa26f43..d7093ff 100644 --- a/build-aux/Dockerfile +++ b/build-aux/Dockerfile @@ -2,7 +2,7 @@ FROM fedora:latest RUN dnf install -y meson gcc g++ python3-pip gobject-introspection-devel \ python3-devel python3-gobject git diffutils xorg-x11-server-Xvfb \ - appstream-devel "dnf-command(builddep)" + appstream-devel dbus-x11 "dnf-command(builddep)" RUN dnf build-dep -y gtk4 libadwaita RUN pip3 install furo mypy sphinx coverage black isort From 4eaf735732561e4fbcc0739669d47a62e77a6fed Mon Sep 17 00:00:00 2001 From: James Westman Date: Thu, 6 Jul 2023 20:13:49 -0500 Subject: [PATCH 2/7] gir: Fix signatures for properties and signals Add arguments to signal signatures and fix property signatures --- blueprintcompiler/gir.py | 49 ++++++++++++++++++++++++++++-------- blueprintcompiler/typelib.py | 13 ++++++++++ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py index 2986447..6e37626 100644 --- a/blueprintcompiler/gir.py +++ b/blueprintcompiler/gir.py @@ -318,7 +318,7 @@ class Property(GirNode): @cached_property def signature(self): - return f"{self.full_name} {self.container.name}.{self.name}" + return f"{self.type.full_name} {self.container.name}:{self.name}" @property def writable(self) -> bool: @@ -329,27 +329,56 @@ class Property(GirNode): return self.tl.PROP_CONSTRUCT_ONLY == 1 -class Parameter(GirNode): +class Argument(GirNode): def __init__(self, container: GirNode, tl: typelib.Typelib) -> None: super().__init__(container, tl) + @cached_property + def name(self) -> str: + return self.tl.ARG_NAME + + @cached_property + def type(self) -> GirType: + return self.get_containing(Repository)._resolve_type_id(self.tl.ARG_TYPE) + + +class Signature(GirNode): + def __init__(self, container: GirNode, tl: typelib.Typelib) -> None: + super().__init__(container, tl) + + @cached_property + def args(self) -> T.List[Argument]: + n_arguments = self.tl.SIGNATURE_N_ARGUMENTS + blob_size = self.tl.header.HEADER_ARG_BLOB_SIZE + result = [] + for i in range(n_arguments): + entry = self.tl.SIGNATURE_ARGUMENTS[i * blob_size] + result.append(Argument(self, entry)) + return result + + @cached_property + def return_type(self) -> GirType: + return self.get_containing(Repository)._resolve_type_id( + self.tl.SIGNATURE_RETURN_TYPE + ) + class Signal(GirNode): def __init__( self, klass: T.Union["Class", "Interface"], tl: typelib.Typelib ) -> None: super().__init__(klass, tl) - # if parameters := xml.get_elements('parameters'): - # self.params = [Parameter(self, child) for child in parameters[0].get_elements('parameter')] - # else: - # self.params = [] + + @cached_property + def gir_signature(self) -> Signature: + return Signature(self, self.tl.SIGNAL_SIGNATURE) @property def signature(self): - # TODO: fix - # args = ", ".join([f"{p.type_name} {p.name}" for p in self.params]) - args = "" - return f"signal {self.container.name}.{self.name} ({args})" + args = ", ".join( + [f"{a.type.full_name} {a.name}" for a in self.gir_signature.args] + ) + return f"signal {self.container.full_name}::{self.name} ({args})" class Interface(GirNode, GirType): diff --git a/blueprintcompiler/typelib.py b/blueprintcompiler/typelib.py index 8de06c5..2ce3e32 100644 --- a/blueprintcompiler/typelib.py +++ b/blueprintcompiler/typelib.py @@ -118,6 +118,7 @@ class Typelib: HEADER_FUNCTION_BLOB_SIZE = Field(0x3E, "u16") HEADER_CALLBACK_BLOB_SIZE = Field(0x40, "u16") HEADER_SIGNAL_BLOB_SIZE = Field(0x42, "u16") + HEADER_ARG_BLOB_SIZE = Field(0x46, "u16") HEADER_PROPERTY_BLOB_SIZE = Field(0x48, "u16") HEADER_FIELD_BLOB_SIZE = Field(0x4A, "u16") HEADER_VALUE_BLOB_SIZE = Field(0x4C, "u16") @@ -132,6 +133,13 @@ class Typelib: DIR_ENTRY_OFFSET = Field(0x8, "pointer") DIR_ENTRY_NAMESPACE = Field(0x8, "string") + ARG_NAME = Field(0x0, "string") + ARG_TYPE = Field(0xC, "u32") + + SIGNATURE_RETURN_TYPE = Field(0x0, "u32") + SIGNATURE_N_ARGUMENTS = Field(0x6, "u16") + SIGNATURE_ARGUMENTS = Field(0x8, "offset") + ATTR_OFFSET = Field(0x0, "u32") ATTR_NAME = Field(0x0, "string") ATTR_VALUE = Field(0x0, "string") @@ -180,6 +188,11 @@ class Typelib: PROP_CONSTRUCT_ONLY = Field(0x4, "u32", 4, 1) PROP_TYPE = Field(0xC, "u32") + SIGNAL_DEPRECATED = Field(0x0, "u16", 0, 1) + SIGNAL_DETAILED = Field(0x0, "u16", 5, 1) + SIGNAL_NAME = Field(0x4, "string") + SIGNAL_SIGNATURE = Field(0xC, "pointer") + VALUE_NAME = Field(0x4, "string") VALUE_VALUE = Field(0x8, "i32") From e1b7410e51926882d87e0abc53ec3450bc74d916 Mon Sep 17 00:00:00 2001 From: James Westman Date: Thu, 6 Jul 2023 20:40:56 -0500 Subject: [PATCH 3/7] docs: Add link to online documentation --- blueprintcompiler/gir.py | 74 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py index 6e37626..816b58b 100644 --- a/blueprintcompiler/gir.py +++ b/blueprintcompiler/gir.py @@ -92,6 +92,23 @@ def get_xml(namespace: str, version: str): return _xml_cache[filename] +ONLINE_DOCS = { + "Adw-1": "https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/", + "Gdk-4.0": "https://docs.gtk.org/gdk4/", + "GdkPixbuf-2.0": "https://docs.gtk.org/gdk-pixbuf/", + "Gio-2.0": "https://docs.gtk.org/gio/", + "GLib-2.0": "https://docs.gtk.org/glib/", + "GModule-2.0": "https://docs.gtk.org/gmodule/", + "GObject-2.0": "https://docs.gtk.org/gobject/", + "Gsk-4.0": "https://docs.gtk.org/gsk4/", + "Gtk-4.0": "https://docs.gtk.org/gtk4/", + "GtkSource-5": "https://gnome.pages.gitlab.gnome.org/gtksourceview/gtksourceview5", + "Pango-1.0": "https://docs.gtk.org/Pango/", + "Shumate-1.0": "https://gnome.pages.gitlab.gnome.org/libshumate/", + "WebKit2-4.1": "https://webkitgtk.org/reference/webkit2gtk/stable/", +} + + class GirType: @property def doc(self) -> T.Optional[str]: @@ -291,10 +308,17 @@ class GirNode: except: # Not a huge deal, but if you want docs in the language server you # should ensure .gir files are installed - pass + sections.append("Documentation is not installed") + + if self.online_docs: + sections.append(f"[Online documentation]({self.online_docs})") return "\n\n---\n\n".join(sections) + @property + def online_docs(self) -> T.Optional[str]: + return None + @property def signature(self) -> T.Optional[str]: return None @@ -328,6 +352,14 @@ class Property(GirNode): def construct_only(self) -> bool: return self.tl.PROP_CONSTRUCT_ONLY == 1 + @property + def online_docs(self) -> T.Optional[str]: + if ns := self.get_containing(Namespace).online_docs: + assert self.container is not None + return f"{ns}property.{self.container.name}.{self.name}.html" + else: + return None + class Argument(GirNode): def __init__(self, container: GirNode, tl: typelib.Typelib) -> None: @@ -380,6 +412,14 @@ class Signal(GirNode): ) return f"signal {self.container.full_name}::{self.name} ({args})" + @property + def online_docs(self) -> T.Optional[str]: + if ns := self.get_containing(Namespace).online_docs: + assert self.container is not None + return f"{ns}signal.{self.container.name}.{self.name}.html" + else: + return None + class Interface(GirNode, GirType): def __init__(self, ns: "Namespace", tl: typelib.Typelib): @@ -432,6 +472,13 @@ class Interface(GirNode, GirType): return True return False + @property + def online_docs(self) -> T.Optional[str]: + if ns := self.get_containing(Namespace).online_docs: + return f"{ns}interface.{self.name}.html" + else: + return None + class Class(GirNode, GirType): def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: @@ -544,6 +591,13 @@ class Class(GirNode, GirType): for impl in self.implements: yield from impl.signals.values() + @property + def online_docs(self) -> T.Optional[str]: + if ns := self.get_containing(Namespace).online_docs: + return f"{ns}class.{self.name}.html" + else: + return None + class TemplateType(GirType): def __init__(self, name: str, parent: T.Optional[GirType]): @@ -646,6 +700,13 @@ class Enumeration(GirNode, GirType): def assignable_to(self, type: GirType) -> bool: return type == self + @property + def online_docs(self) -> T.Optional[str]: + if ns := self.get_containing(Namespace).online_docs: + return f"{ns}enum.{self.name}.html" + else: + return None + class Boxed(GirNode, GirType): def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: @@ -658,6 +719,13 @@ class Boxed(GirNode, GirType): def assignable_to(self, type) -> bool: return type == self + @property + def online_docs(self) -> T.Optional[str]: + if ns := self.get_containing(Namespace).online_docs: + return f"{ns}boxed.{self.name}.html" + else: + return None + class Bitfield(Enumeration): def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: @@ -753,6 +821,10 @@ class Namespace(GirNode): else: return self.get_type(type_name) + @property + def online_docs(self) -> T.Optional[str]: + return ONLINE_DOCS.get(f"{self.name}-{self.version}") + class Repository(GirNode): def __init__(self, tl: typelib.Typelib) -> None: From c4fc4f3de870aca63698ddec040f3fb87d7ee674 Mon Sep 17 00:00:00 2001 From: James Westman Date: Thu, 6 Jul 2023 20:53:30 -0500 Subject: [PATCH 4/7] docs: Fix bug with colliding names Often a vfunc has the same name as a signal, and the wrong docs would be shown. --- blueprintcompiler/gir.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py index 816b58b..6dab652 100644 --- a/blueprintcompiler/gir.py +++ b/blueprintcompiler/gir.py @@ -253,6 +253,8 @@ TNode = T.TypeVar("TNode", bound="GirNode") class GirNode: + xml_tag: str + def __init__(self, container: T.Optional["GirNode"], tl: typelib.Typelib) -> None: self.container = container self.tl = tl @@ -269,7 +271,8 @@ class GirNode: def xml(self): for el in self.container.xml.children: if el.attrs.get("name") == self.name: - return el + if el.tag == self.xml_tag: + return el @cached_property def glib_type_name(self) -> str: @@ -329,6 +332,8 @@ class GirNode: class Property(GirNode): + xml_tag = "property" + def __init__(self, klass: T.Union["Class", "Interface"], tl: typelib.Typelib): super().__init__(klass, tl) @@ -396,6 +401,8 @@ class Signature(GirNode): class Signal(GirNode): + xml_tag = "glib:signal" + def __init__( self, klass: T.Union["Class", "Interface"], tl: typelib.Typelib ) -> None: @@ -422,6 +429,8 @@ class Signal(GirNode): class Interface(GirNode, GirType): + xml_tag = "interface" + def __init__(self, ns: "Namespace", tl: typelib.Typelib): super().__init__(ns, tl) @@ -481,6 +490,8 @@ class Interface(GirNode, GirType): class Class(GirNode, GirType): + xml_tag = "class" + def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) @@ -654,6 +665,8 @@ class TemplateType(GirType): class EnumMember(GirNode): + xml_tag = "member" + def __init__(self, enum: "Enumeration", tl: typelib.Typelib) -> None: super().__init__(enum, tl) @@ -679,6 +692,8 @@ class EnumMember(GirNode): class Enumeration(GirNode, GirType): + xml_tag = "enumeration" + def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) @@ -709,6 +724,8 @@ class Enumeration(GirNode, GirType): class Boxed(GirNode, GirType): + xml_tag = "glib:boxed" + def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) @@ -728,6 +745,8 @@ class Boxed(GirNode, GirType): class Bitfield(Enumeration): + xml_tag = "bitfield" + def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) From 9ff76b65cc381ee5f97c243a43d461a7fae086e0 Mon Sep 17 00:00:00 2001 From: James Westman Date: Thu, 6 Jul 2023 20:54:14 -0500 Subject: [PATCH 5/7] docs: Fix docs for accessibility properties --- blueprintcompiler/language/gtk_a11y.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blueprintcompiler/language/gtk_a11y.py b/blueprintcompiler/language/gtk_a11y.py index f7f89d6..6a346ec 100644 --- a/blueprintcompiler/language/gtk_a11y.py +++ b/blueprintcompiler/language/gtk_a11y.py @@ -98,6 +98,7 @@ def get_types(gir): def _get_docs(gir, name): + name = name.replace("-", "_") if gir_type := ( gir.get_type("AccessibleProperty", "Gtk").members.get(name) or gir.get_type("AccessibleRelation", "Gtk").members.get(name) @@ -197,7 +198,9 @@ def a11y_completer(ast_node, match_variables): def a11y_name_completer(ast_node, match_variables): for name, type in get_types(ast_node.root.gir).items(): yield Completion( - name, CompletionItemKind.Property, docs=_get_docs(ast_node.root.gir, type) + name, + CompletionItemKind.Property, + docs=_get_docs(ast_node.root.gir, type.name), ) From cb1eb9ba4438a3e338ed01fd60908d19e6b35261 Mon Sep 17 00:00:00 2001 From: James Westman Date: Thu, 6 Jul 2023 21:21:24 -0500 Subject: [PATCH 6/7] lsp: Show better info on IdentLiteral hover Instead of showing the documentation for the expected type, show the signature of the referenced object. --- blueprintcompiler/language/gobject_object.py | 7 +++++++ .../language/gtk_list_item_factory.py | 8 ++++++++ blueprintcompiler/language/gtk_menu.py | 7 +++++++ .../language/gtkbuilder_template.py | 7 +++++++ blueprintcompiler/language/values.py | 18 ++++++++++++------ 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/blueprintcompiler/language/gobject_object.py b/blueprintcompiler/language/gobject_object.py index d247c61..1a42c0a 100644 --- a/blueprintcompiler/language/gobject_object.py +++ b/blueprintcompiler/language/gobject_object.py @@ -55,6 +55,13 @@ class Object(AstNode): def content(self) -> ObjectContent: return self.children[ObjectContent][0] + @property + def signature(self) -> str: + if self.id: + return f"{self.class_name.gir_type.full_name} {self.id}" + else: + return f"{self.class_name.gir_type.full_name}" + @property def gir_class(self) -> GirType: if self.class_name is None: diff --git a/blueprintcompiler/language/gtk_list_item_factory.py b/blueprintcompiler/language/gtk_list_item_factory.py index f2f9d63..ba2a27f 100644 --- a/blueprintcompiler/language/gtk_list_item_factory.py +++ b/blueprintcompiler/language/gtk_list_item_factory.py @@ -9,6 +9,14 @@ from .types import TypeName class ExtListItemFactory(AstNode): grammar = [UseExact("id", "template"), Optional(TypeName), ObjectContent] + @property + def id(self) -> str: + return "template" + + @property + def signature(self) -> str: + return f"template {self.gir_class.full_name}" + @property def type_name(self) -> T.Optional[TypeName]: if len(self.children[TypeName]) == 1: diff --git a/blueprintcompiler/language/gtk_menu.py b/blueprintcompiler/language/gtk_menu.py index 5698e12..824ec5c 100644 --- a/blueprintcompiler/language/gtk_menu.py +++ b/blueprintcompiler/language/gtk_menu.py @@ -35,6 +35,13 @@ class Menu(AstNode): def id(self) -> str: return self.tokens["id"] + @property + def signature(self) -> str: + if self.id: + return f"Gio.Menu {self.id}" + else: + return "Gio.Menu" + @property def tag(self) -> str: return self.tokens["tag"] diff --git a/blueprintcompiler/language/gtkbuilder_template.py b/blueprintcompiler/language/gtkbuilder_template.py index 9de4d66..7af5cc8 100644 --- a/blueprintcompiler/language/gtkbuilder_template.py +++ b/blueprintcompiler/language/gtkbuilder_template.py @@ -44,6 +44,13 @@ class Template(Object): def id(self) -> str: return "template" + @property + def signature(self) -> str: + if self.parent_type: + return f"template {self.gir_class.full_name} : {self.parent_type.gir_type.full_name}" + else: + return f"template {self.gir_class.full_name}" + @property def gir_class(self) -> GirType: if isinstance(self.class_name.gir_type, ExternType): diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py index 6981141..fd1765b 100644 --- a/blueprintcompiler/language/values.py +++ b/blueprintcompiler/language/values.py @@ -312,14 +312,20 @@ class IdentLiteral(AstNode): @docs() def docs(self) -> T.Optional[str]: - type = self.context[ValueTypeCtx].value_type - if isinstance(type, gir.Enumeration): - if member := type.members.get(self.ident): + expected_type = self.context[ValueTypeCtx].value_type + if isinstance(expected_type, gir.BoolType): + return None + elif isinstance(expected_type, gir.Enumeration): + if member := expected_type.members.get(self.ident): return member.doc else: - return type.doc - elif isinstance(type, gir.GirNode): - return type.doc + return expected_type.doc + elif self.ident == "null" and self.context[ValueTypeCtx].allow_null: + return None + elif object := self.context[ScopeCtx].objects.get(self.ident): + return f"```\n{object.signature}\n```" + elif self.root.is_legacy_template(self.ident): + return f"```\n{self.root.template.signature}\n```" else: return None From abc4e5de65160b63ce15a803be2f177c0650e780 Mon Sep 17 00:00:00 2001 From: James Westman Date: Thu, 6 Jul 2023 21:29:13 -0500 Subject: [PATCH 7/7] lsp: Add docs for Adw.Breakpoint --- blueprintcompiler/language/adw_breakpoint.py | 41 ++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/blueprintcompiler/language/adw_breakpoint.py b/blueprintcompiler/language/adw_breakpoint.py index addbd8a..e71b29a 100644 --- a/blueprintcompiler/language/adw_breakpoint.py +++ b/blueprintcompiler/language/adw_breakpoint.py @@ -24,12 +24,26 @@ from .values import Value class AdwBreakpointCondition(AstNode): - grammar = ["condition", "(", UseQuoted("condition"), Match(")").expected()] + grammar = [ + UseExact("kw", "condition"), + "(", + UseQuoted("condition"), + Match(")").expected(), + ] @property def condition(self) -> str: return self.tokens["condition"] + @docs("kw") + def keyword_docs(self): + klass = self.root.gir.get_type("Breakpoint", "Adw") + if klass is None: + return None + prop = klass.properties.get("condition") + assert isinstance(prop, gir.Property) + return prop.doc + @validate() def unique(self): self.validate_unique_in_parent("Duplicate condition statement") @@ -68,9 +82,16 @@ class AdwBreakpointSetter(AstNode): return None @property - def gir_property(self): - if self.gir_class is not None and not isinstance(self.gir_class, ExternType): + def gir_property(self) -> T.Optional[gir.Property]: + if ( + self.gir_class is not None + and not isinstance(self.gir_class, ExternType) + and self.property_name is not None + ): + assert isinstance(self.gir_class, gir.Class) return self.gir_class.properties.get(self.property_name) + else: + return None @context(ValueTypeCtx) def value_type(self) -> ValueTypeCtx: @@ -81,6 +102,20 @@ class AdwBreakpointSetter(AstNode): return ValueTypeCtx(type, allow_null=True) + @docs("object") + def object_docs(self): + if self.object is not None: + return f"```\n{self.object.signature}\n```" + else: + return None + + @docs("property") + def property_docs(self): + if self.gir_property is not None: + return self.gir_property.doc + else: + return None + @validate("object") def object_exists(self): if self.object is None: