diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6d373cc..1ec071e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ stages:
- pages
build:
- image: registry.gitlab.gnome.org/gnome/blueprint-compiler
+ image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
stage: build
script:
- black --check --diff ./ tests
@@ -33,7 +33,7 @@ build:
path: coverage.xml
fuzz:
- image: registry.gitlab.gnome.org/gnome/blueprint-compiler
+ image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
stage: build
script:
- meson _build
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 3ab4fa2..220c117 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -8,7 +8,7 @@ in the NEWS file.
3. Make a new commit with just these two changes. Use `Release v{version}` as the commit message. Tag the commit as `v{version}` and push the tag.
4. Create a "Post-release version bump" commit.
5. Go to the Releases page in GitLab and create a new release from the tag.
-6. Announce the release through relevant channels (Mastodon, TWIG, etc.)
+6. Announce the release through relevant channels (Twitter, TWIG, etc.)
## Related projects
diff --git a/NEWS.md b/NEWS.md
index a12dab0..389f82c 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,35 +1,3 @@
-# v0.16.0
-
-## Added
-- Added more "go to reference" implementations in the language server
-- Added semantic token support for flag members in the language server
-- Added property documentation to the hover tooltip for notify signals
-- The language server now shows relevant sections of the reference documentation when hovering over keywords and symbols
-- Added `not-swapped` flag to signal handlers, which may be needed for signal handlers that specify an object
-- Added expression literals, which allow you to specify a Gtk.Expression property (as opposed to the existing expression support, which is for property bindings)
-
-## Changed
-- The formatter adds trailing commas to lists (Alexey Yerin)
-- The formatter removes trailing whitespace from comments (Alexey Yerin)
-- Autocompleting a commonly translated property automatically adds the `_("")` syntax
-- Marking a single-quoted string as translatable now generates a warning, since gettext does not recognize it when using the configuration recommended in the blueprint documentation
-
-## Fixed
-- Added support for libgirepository-2.0 so that blueprint doesn't crash due to import conflicts on newer versions of PyGObject (Jordan Petridis)
-- Fixed a bug when decompiling/porting files with enum values
-- Fixed several issues where tests would fail with versions of GTK that added new deprecations
-- Addressed a problem with the language server protocol in some editors (Luoyayu)
-- Fixed an issue where the compiler would crash instead of reporting compiler errors
-- Fixed a crash in the language server that occurred when a detailed signal (e.g. `notify::*`) was not complete
-- The language server now properly implements the shutdown command, fixing support for some editors and improving robustness when restarting (Alexey Yerin)
-- Marking a string in an array as translatable now generates an error, since it doesn't work
--
-
-## Documentation
-- Added mention of `null` in the Literal Values section
-- Add apps to Built with Blueprint section (Benedek Dévényi, Vladimir Vaskov)
-- Corrected and updated many parts of the documentation
-
# v0.14.0
## Added
diff --git a/blueprint-compiler.doap b/blueprint-compiler.doap
deleted file mode 100644
index f3e4000..0000000
--- a/blueprint-compiler.doap
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
- Blueprint
- A modern language for creating GTK interfaces
- Blueprint is a language and associated tooling for building user interfaces for GTK.
-
- Python
-
-
-
-
-
-
-
- James Westman
-
- jwestman
-
-
-
diff --git a/blueprintcompiler/annotations.py b/blueprintcompiler/annotations.py
deleted file mode 100644
index c40de13..0000000
--- a/blueprintcompiler/annotations.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# annotations.py
-#
-# Copyright 2024 James Westman
-#
-# This file is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation; either version 3 of the
-# License, or (at your option) any later version.
-#
-# This file is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program. If not, see .
-#
-# SPDX-License-Identifier: LGPL-3.0-or-later
-
-# Extra information about types in common libraries that's used for things like completions.
-
-import typing as T
-from dataclasses import dataclass
-
-from . import gir
-
-
-@dataclass
-class Annotation:
- translatable_properties: T.List[str]
-
-
-def is_property_translated(property: gir.Property):
- ns = property.get_containing(gir.Namespace)
- ns_name = ns.name + "-" + ns.version
- if annotation := _ANNOTATIONS.get(ns_name):
- assert property.container is not None
- return (
- property.container.name + ":" + property.name
- in annotation.translatable_properties
- )
- else:
- return False
-
-
-_ANNOTATIONS = {
- "Gtk-4.0": Annotation(
- translatable_properties=[
- "AboutDialog:comments",
- "AboutDialog:translator-credits",
- "AboutDialog:website-label",
- "AlertDialog:detail",
- "AlertDialog:message",
- "AppChooserButton:heading",
- "AppChooserDialog:heading",
- "AppChooserWidget:default-text",
- "AssistantPage:title",
- "Button:label",
- "CellRendererText:markup",
- "CellRendererText:placeholder-text",
- "CellRendererText:text",
- "CheckButton:label",
- "ColorButton:title",
- "ColorDialog:title",
- "ColumnViewColumn:title",
- "ColumnViewRow:accessible-description",
- "ColumnViewRow:accessible-label",
- "Entry:placeholder-text",
- "Entry:primary-icon-tooltip-markup",
- "Entry:primary-icon-tooltip-text",
- "Entry:secondary-icon-tooltip-markup",
- "Entry:secondary-icon-tooltip-text",
- "EntryBuffer:text",
- "Expander:label",
- "FileChooserNative:accept-label",
- "FileChooserNative:cancel-label",
- "FileChooserWidget:subtitle",
- "FileDialog:accept-label",
- "FileDialog:title",
- "FileDialog:initial-name",
- "FileFilter:name",
- "FontButton:title",
- "FontDialog:title",
- "Frame:label",
- "Inscription:markup",
- "Inscription:text",
- "Label:label",
- "ListItem:accessible-description",
- "ListItem:accessible-label",
- "LockButton:text-lock",
- "LockButton:text-unlock",
- "LockButton:tooltip-lock",
- "LockButton:tooltip-not-authorized",
- "LockButton:tooltip-unlock",
- "MenuButton:label",
- "MessageDialog:secondary-text",
- "MessageDialog:text",
- "NativeDialog:title",
- "NotebookPage:menu-label",
- "NotebookPage:tab-label",
- "PasswordEntry:placeholder-text",
- "Picture:alternative-text",
- "PrintDialog:accept-label",
- "PrintDialog:title",
- "Printer:name",
- "PrintJob:title",
- "PrintOperation:custom-tab-label",
- "PrintOperation:export-filename",
- "PrintOperation:job-name",
- "ProgressBar:text",
- "SearchEntry:placeholder-text",
- "ShortcutLabel:disabled-text",
- "ShortcutsGroup:title",
- "ShortcutsSection:title",
- "ShortcutsShortcut:title",
- "ShortcutsShortcut:subtitle",
- "StackPage:title",
- "Text:placeholder-text",
- "TextBuffer:text",
- "TreeViewColumn:title",
- "Widget:tooltip-markup",
- "Widget:tooltip-text",
- "Window:title",
- "Editable:text",
- "FontChooser:preview-text",
- ]
- ),
- "Adw-1": Annotation(
- translatable_properties=[
- "AboutDialog:comments",
- "AboutDialog:translator-credits",
- "AboutWindow:comments",
- "AboutWindow:translator-credits",
- "ActionRow:subtitle",
- "ActionRow:title",
- "AlertDialog:body",
- "AlertDialog:heading",
- "Avatar:text",
- "Banner:button-label",
- "Banner:title",
- "ButtonContent:label",
- "Dialog:title",
- "ExpanderRow:subtitle",
- "MessageDialog:body",
- "MessageDialog:heading",
- "NavigationPage:title",
- "PreferencesGroup:description",
- "PreferencesGroup:title",
- "PreferencesPage:description",
- "PreferencesPage:title",
- "PreferencesRow:title",
- "SplitButton:dropdown-tooltip",
- "SplitButton:label",
- "StatusPage:description",
- "StatusPage:title",
- "TabPage:indicator-tooltip",
- "TabPage:keyword",
- "TabPage:title",
- "Toast:button-label",
- "Toast:title",
- "ViewStackPage:title",
- "ViewSwitcherTitle:subtitle",
- "ViewSwitcherTitle:title",
- "WindowTitle:subtitle",
- "WindowTitle:title",
- ]
- ),
- "Shumate-1.0": Annotation(
- translatable_properties=[
- "License:extra-text",
- "MapSource:license",
- "MapSource:name",
- ]
- ),
- "GtkSource-5": Annotation(
- translatable_properties=[
- "CompletionCell:markup",
- "CompletionCell:text",
- "CompletionSnippets:title",
- "CompletionWords:title",
- "GutterRendererText:markup",
- "GutterRendererText:text",
- "SearchSettings:search-text",
- "Snippet:description",
- "Snippet:name",
- "SnippetChunk:tooltip-text",
- "StyleScheme:description",
- "StyleScheme:name",
- ]
- ),
-}
diff --git a/blueprintcompiler/ast_utils.py b/blueprintcompiler/ast_utils.py
index 8f742e0..c874358 100644
--- a/blueprintcompiler/ast_utils.py
+++ b/blueprintcompiler/ast_utils.py
@@ -160,11 +160,6 @@ class AstNode:
yield e
if e.fatal:
return
- except MultipleErrors as e:
- for error in e.errors:
- yield error
- if error.fatal:
- return
for child in self.children:
yield from child._get_errors()
@@ -184,16 +179,14 @@ class AstNode:
token = self.group.tokens.get(attr.token_name)
if token and token.start <= idx < token.end:
return getattr(self, name)
+ else:
+ return getattr(self, name)
for child in self.children:
if idx in child.range:
if docs := child.get_docs(idx):
return docs
- for name, attr in self._attrs_by_type(Docs):
- if not attr.token_name:
- return getattr(self, name)
-
return None
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
@@ -254,7 +247,14 @@ def validate(
if skip_incomplete and self.incomplete:
return
- def fill_error(e: CompileError):
+ try:
+ func(self)
+ except CompileError as e:
+ # If the node is only partially complete, then an error must
+ # have already been reported at the parsing stage
+ if self.incomplete:
+ return
+
if e.range is None:
e.range = (
Range.join(
@@ -264,26 +264,8 @@ def validate(
or self.range
)
- try:
- func(self)
- except CompileError as e:
- # If the node is only partially complete, then an error must
- # have already been reported at the parsing stage
- if self.incomplete:
- return
-
- fill_error(e)
-
# Re-raise the exception
raise e
- except MultipleErrors as e:
- if self.incomplete:
- return
-
- for error in e.errors:
- fill_error(error)
-
- raise e
inner._validator = True
return inner
diff --git a/blueprintcompiler/completions.py b/blueprintcompiler/completions.py
index b10ec3e..e05d6ee 100644
--- a/blueprintcompiler/completions.py
+++ b/blueprintcompiler/completions.py
@@ -17,9 +17,10 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
+import sys
import typing as T
-from . import annotations, gir, language
+from . import gir, language
from .ast_utils import AstNode
from .completions_utils import *
from .language.types import ClassName
@@ -30,6 +31,10 @@ from .tokenizer import Token, TokenType
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
+def debug(*args, **kwargs):
+ print(*args, file=sys.stderr, **kwargs)
+
+
def _complete(
lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
) -> T.Iterator[Completion]:
@@ -134,7 +139,7 @@ def gtk_object_completer(lsp, ast_node, match_variables):
matches=new_statement_patterns,
)
def property_completer(lsp, ast_node, match_variables):
- if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
+ if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
for prop_name, prop in ast_node.gir_class.properties.items():
if (
isinstance(prop.type, gir.BoolType)
@@ -149,17 +154,11 @@ def property_completer(lsp, ast_node, match_variables):
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,
+ snippet=f'{prop_name}: "$0";',
docs=prop.doc,
detail=prop.detail,
)
@@ -177,15 +176,6 @@ def property_completer(lsp, ast_node, match_variables):
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,
@@ -198,7 +188,7 @@ def property_completer(lsp, ast_node, match_variables):
@completer(
- applies_in=[language.Property, language.A11yProperty],
+ applies_in=[language.Property, language.BaseAttribute],
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
)
def prop_value_completer(lsp, ast_node, match_variables):
@@ -222,7 +212,7 @@ def prop_value_completer(lsp, ast_node, match_variables):
matches=new_statement_patterns,
)
def signal_completer(lsp, ast_node, match_variables):
- if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
+ if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
for signal_name, signal in ast_node.gir_class.signals.items():
if not isinstance(ast_node.parent, language.Object):
name = "on"
diff --git a/blueprintcompiler/completions_utils.py b/blueprintcompiler/completions_utils.py
index eccf125..03bec0f 100644
--- a/blueprintcompiler/completions_utils.py
+++ b/blueprintcompiler/completions_utils.py
@@ -31,6 +31,17 @@ new_statement_patterns = [
]
+def applies_to(*ast_types):
+ """Decorator describing which AST nodes the completer should apply in."""
+
+ def decorator(func):
+ for c in ast_types:
+ c.completers.append(func)
+ return func
+
+ return decorator
+
+
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):
diff --git a/blueprintcompiler/decompiler.py b/blueprintcompiler/decompiler.py
index 850b6d8..de6c06f 100644
--- a/blueprintcompiler/decompiler.py
+++ b/blueprintcompiler/decompiler.py
@@ -255,11 +255,7 @@ def decompile_element(
ctx._node_stack.append(xml)
ctx.start_block()
-
- try:
- gir = decompiler(*args, **kwargs)
- except TypeError as e:
- raise UnsupportedError(tag=xml.tag)
+ gir = decompiler(*args, **kwargs)
if not decompiler._skip_children:
for child in xml.children:
@@ -270,6 +266,8 @@ def decompile_element(
except UnsupportedError as e:
raise e
+ except TypeError as e:
+ raise UnsupportedError(tag=xml.tag)
def decompile(data: str) -> str:
diff --git a/blueprintcompiler/errors.py b/blueprintcompiler/errors.py
index df1c2e1..1e7297c 100644
--- a/blueprintcompiler/errors.py
+++ b/blueprintcompiler/errors.py
@@ -219,7 +219,7 @@ def report_bug(): # pragma: no cover
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
The blueprint-compiler program has crashed. Please report the above stacktrace,
along with the input file(s) if possible, on GitLab:
-{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue
+{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue
{Colors.CLEAR}"""
)
diff --git a/blueprintcompiler/formatter.py b/blueprintcompiler/formatter.py
index f438675..c003d45 100644
--- a/blueprintcompiler/formatter.py
+++ b/blueprintcompiler/formatter.py
@@ -20,8 +20,7 @@
import re
from enum import Enum
-from . import tokenizer
-from .errors import CompilerBugError
+from . import tokenizer, utils
from .tokenizer import TokenType
OPENING_TOKENS = ("{", "[")
@@ -146,10 +145,8 @@ def format(data, tab_size=2, insert_space=True):
is_child_type = False
elif str_item in CLOSING_TOKENS:
- if str_item == "]" and str(last_not_whitespace) != "[":
+ if str_item == "]" and last_not_whitespace != ",":
current_line = current_line[:-1]
- if str(last_not_whitespace) != ",":
- current_line += ","
commit_current_line()
current_line = "]"
elif str(last_not_whitespace) in OPENING_TOKENS:
@@ -193,13 +190,10 @@ def format(data, tab_size=2, insert_space=True):
elif prev_line_type in require_extra_newline:
newlines = 2
- current_line = "\n".join(
- [line.rstrip() for line in current_line.split("\n")]
- )
commit_current_line(LineType.COMMENT, newlines_before=newlines)
- else: # pragma: no cover
- raise CompilerBugError()
+ else:
+ commit_current_line()
elif str_item == "(" and (
re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py
index 333f4ac..30a5eaa 100644
--- a/blueprintcompiler/gir.py
+++ b/blueprintcompiler/gir.py
@@ -24,20 +24,8 @@ from functools import cached_property
import gi # type: ignore
-try:
- gi.require_version("GIRepository", "3.0")
- from gi.repository import GIRepository # type: ignore
-
- _repo = GIRepository.Repository()
-except ValueError:
- # We can remove this once we can bump the minimum dependencies
- # to glib 2.80 and pygobject 3.52
- # dependency('glib-2.0', version: '>= 2.80.0')
- # dependency('girepository-2.0', version: '>= 2.80.0')
- gi.require_version("GIRepository", "2.0")
- from gi.repository import GIRepository # type: ignore
-
- _repo = GIRepository.Repository
+gi.require_version("GIRepository", "2.0")
+from gi.repository import GIRepository # type: ignore
from . import typelib, xml_reader
from .errors import CompileError, CompilerBugError
@@ -54,7 +42,7 @@ def add_typelib_search_path(path: str):
def get_namespace(namespace: str, version: str) -> "Namespace":
- search_paths = [*_repo.get_search_path(), *_user_search_paths]
+ search_paths = [*GIRepository.Repository.get_search_path(), *_user_search_paths]
filename = f"{namespace}-{version}.typelib"
@@ -86,7 +74,7 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
return _available_namespaces
search_paths: list[str] = [
- *_repo.get_search_path(),
+ *GIRepository.Repository.get_search_path(),
*_user_search_paths,
]
@@ -467,13 +455,10 @@ class Signature(GirNode):
return result
@cached_property
- def return_type(self) -> T.Optional[GirType]:
- if self.tl.SIGNATURE_RETURN_TYPE == 0:
- return None
- else:
- return self.get_containing(Repository)._resolve_type_id(
- self.tl.SIGNATURE_RETURN_TYPE
- )
+ def return_type(self) -> GirType:
+ return self.get_containing(Repository)._resolve_type_id(
+ self.tl.SIGNATURE_RETURN_TYPE
+ )
class Signal(GirNode):
@@ -493,10 +478,7 @@ class Signal(GirNode):
args = ", ".join(
[f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
)
- result = f"signal {self.container.full_name}::{self.name} ({args})"
- if self.gir_signature.return_type is not None:
- result += f" -> {self.gir_signature.return_type.full_name}"
- return result
+ return f"signal {self.container.full_name}::{self.name} ({args})"
@property
def online_docs(self) -> T.Optional[str]:
@@ -908,6 +890,14 @@ class Namespace(GirNode):
if isinstance(entry, Class)
}
+ @cached_property
+ def interfaces(self) -> T.Mapping[str, Interface]:
+ return {
+ name: entry
+ for name, entry in self.entries.items()
+ if isinstance(entry, Interface)
+ }
+
def get_type(self, name) -> T.Optional[GirType]:
"""Gets a type (class, interface, enum, etc.) from this namespace."""
return self.entries.get(name)
@@ -931,8 +921,13 @@ class Namespace(GirNode):
"""Looks up a type in the scope of this namespace (including in the
namespace's dependencies)."""
- ns, name = type_name.split(".", 1)
- return self.get_containing(Repository).get_type(name, ns)
+ if type_name in _BASIC_TYPES:
+ return _BASIC_TYPES[type_name]()
+ elif "." in type_name:
+ ns, name = type_name.split(".", 1)
+ return self.get_containing(Repository).get_type(name, ns)
+ else:
+ return self.get_type(type_name)
@property
def online_docs(self) -> T.Optional[str]:
@@ -951,7 +946,7 @@ class Repository(GirNode):
self.includes = {
name: get_namespace(name, version) for name, version in deps
}
- except: # pragma: no cover
+ except:
raise CompilerBugError(f"Failed to load dependencies.")
else:
self.includes = {}
@@ -959,6 +954,12 @@ class Repository(GirNode):
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
return self.lookup_namespace(ns).get_type(name)
+ def get_type_by_cname(self, name: str) -> T.Optional[GirType]:
+ for ns in [self.namespace, *self.includes.values()]:
+ if type := ns.get_type_by_cname(name):
+ return type
+ return None
+
def lookup_namespace(self, ns: str):
"""Finds a namespace among this namespace's dependencies."""
if ns == self.namespace.name:
diff --git a/blueprintcompiler/interactive_port.py b/blueprintcompiler/interactive_port.py
index 12dd485..0c37885 100644
--- a/blueprintcompiler/interactive_port.py
+++ b/blueprintcompiler/interactive_port.py
@@ -71,7 +71,7 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
print(
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
-{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
+{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
)
return CouldNotPort("does not compile")
@@ -136,7 +136,7 @@ def step1():
wrap.write(
f"""[wrap-git]
directory = blueprint-compiler
-url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
+url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
revision = {VERSION}
depth = 1
diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py
index 5eb2b60..b302686 100644
--- a/blueprintcompiler/language/__init__.py
+++ b/blueprintcompiler/language/__init__.py
@@ -4,6 +4,7 @@ from .adw_breakpoint import (
AdwBreakpointSetters,
)
from .adw_response_dialog import ExtAdwResponseDialog
+from .attributes import BaseAttribute
from .binding import Binding
from .common import *
from .contexts import ScopeCtx, ValueTypeCtx
@@ -19,7 +20,7 @@ from .expression import (
from .gobject_object import Object, ObjectContent
from .gobject_property import Property
from .gobject_signal import Signal
-from .gtk_a11y import A11yProperty, ExtAccessibility
+from .gtk_a11y import ExtAccessibility
from .gtk_combo_box_text import ExtComboBoxItems
from .gtk_file_filter import (
Filters,
@@ -41,7 +42,6 @@ from .types import ClassName
from .ui import UI
from .values import (
ArrayValue,
- ExprValue,
Flag,
Flags,
IdentLiteral,
diff --git a/blueprintcompiler/language/adw_breakpoint.py b/blueprintcompiler/language/adw_breakpoint.py
index 3d2c10d..eec7c7e 100644
--- a/blueprintcompiler/language/adw_breakpoint.py
+++ b/blueprintcompiler/language/adw_breakpoint.py
@@ -81,8 +81,8 @@ class AdwBreakpointSetter(AstNode):
return self.tokens["property"]
@property
- def value(self) -> T.Optional[Value]:
- return self.children[Value][0] if len(self.children[Value]) > 0 else None
+ def value(self) -> Value:
+ return self.children[Value][0]
@property
def gir_class(self) -> T.Optional[GirType]:
@@ -106,10 +106,7 @@ class AdwBreakpointSetter(AstNode):
return None
@property
- def document_symbol(self) -> T.Optional[DocumentSymbol]:
- if self.value is None:
- return None
-
+ def document_symbol(self) -> DocumentSymbol:
return DocumentSymbol(
f"{self.object_id}.{self.property_name}",
SymbolKind.Property,
@@ -118,17 +115,6 @@ class AdwBreakpointSetter(AstNode):
self.value.range.text,
)
- def get_reference(self, idx: int) -> T.Optional[LocationLink]:
- if idx in self.group.tokens["object"].range:
- if self.object is not None:
- return LocationLink(
- self.group.tokens["object"].range,
- self.object.range,
- self.object.ranges["id"],
- )
-
- return None
-
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
if self.gir_property is not None:
@@ -210,10 +196,6 @@ class AdwBreakpointSetters(AstNode):
def unique(self):
self.validate_unique_in_parent("Duplicate setters block")
- @docs("setters")
- def ref_docs(self):
- return get_docs_section("Syntax ExtAdwBreakpoint")
-
@decompiler("condition", cdata=True)
def decompile_condition(ctx: DecompileCtx, gir, cdata):
diff --git a/blueprintcompiler/language/adw_response_dialog.py b/blueprintcompiler/language/adw_response_dialog.py
index d2680fd..46172f2 100644
--- a/blueprintcompiler/language/adw_response_dialog.py
+++ b/blueprintcompiler/language/adw_response_dialog.py
@@ -20,6 +20,7 @@
from ..decompiler import decompile_translatable, truthy
from .common import *
+from .contexts import ValueTypeCtx
from .gobject_object import ObjectContent, validate_parent_type
from .values import StringValue
@@ -93,6 +94,10 @@ class ExtAdwResponseDialogResponse(AstNode):
self.value.range.text,
)
+ @context(ValueTypeCtx)
+ def value_type(self) -> ValueTypeCtx:
+ return ValueTypeCtx(StringType())
+
@validate("id")
def unique_in_parent(self):
self.validate_unique_in_parent(
@@ -133,10 +138,6 @@ class ExtAdwResponseDialog(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate responses block")
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax ExtAdwMessageDialog")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/attributes.py b/blueprintcompiler/language/attributes.py
new file mode 100644
index 0000000..8ff1f0b
--- /dev/null
+++ b/blueprintcompiler/language/attributes.py
@@ -0,0 +1,32 @@
+# attributes.py
+#
+# Copyright 2022 James Westman
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the
+# License, or (at your option) any later version.
+#
+# This file is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see .
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+
+from .common import *
+
+
+class BaseAttribute(AstNode):
+ """A helper class for attribute syntax of the form `name: literal_value;`"""
+
+ tag_name: str = ""
+ attr_name: str = "name"
+
+ @property
+ def name(self):
+ return self.tokens["name"]
diff --git a/blueprintcompiler/language/binding.py b/blueprintcompiler/language/binding.py
index 07572a9..3b9af97 100644
--- a/blueprintcompiler/language/binding.py
+++ b/blueprintcompiler/language/binding.py
@@ -58,10 +58,6 @@ class BindingFlag(AstNode):
"Only bindings with a single lookup can have flags",
)
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Binding")
-
class Binding(AstNode):
grammar = [
@@ -103,10 +99,6 @@ class Binding(AstNode):
actions=[CodeAction("use 'bind'", "bind")],
)
- @docs("bind")
- def ref_docs(self):
- return get_docs_section("Syntax Binding")
-
@dataclass
class SimpleBinding:
diff --git a/blueprintcompiler/language/common.py b/blueprintcompiler/language/common.py
index 1cc1b3b..29df47d 100644
--- a/blueprintcompiler/language/common.py
+++ b/blueprintcompiler/language/common.py
@@ -55,7 +55,6 @@ from ..lsp_utils import (
SemanticToken,
SemanticTokenType,
SymbolKind,
- get_docs_section,
)
from ..parse_tree import *
diff --git a/blueprintcompiler/language/contexts.py b/blueprintcompiler/language/contexts.py
index 6e26048..c5e97b3 100644
--- a/blueprintcompiler/language/contexts.py
+++ b/blueprintcompiler/language/contexts.py
@@ -79,9 +79,3 @@ class ScopeCtx:
for child in node.children:
if child.context[ScopeCtx] is self:
yield from self._iter_recursive(child)
-
-
-@dataclass
-class ExprValueCtx:
- """Indicates that the context is an expression literal, where the
- "item" keyword may be used."""
diff --git a/blueprintcompiler/language/expression.py b/blueprintcompiler/language/expression.py
index de6fbf1..37e73e8 100644
--- a/blueprintcompiler/language/expression.py
+++ b/blueprintcompiler/language/expression.py
@@ -38,6 +38,10 @@ class ExprBase(AstNode):
def type(self) -> T.Optional[GirType]:
raise NotImplementedError()
+ @property
+ def type_complete(self) -> bool:
+ return True
+
@property
def rhs(self) -> T.Optional["ExprBase"]:
if isinstance(self.parent, Expression):
@@ -61,6 +65,10 @@ class Expression(ExprBase):
def type(self) -> T.Optional[GirType]:
return self.last.type
+ @property
+ def type_complete(self) -> bool:
+ return self.last.type_complete
+
class InfixExpr(ExprBase):
@property
@@ -81,16 +89,6 @@ class LiteralExpr(ExprBase):
or self.root.is_legacy_template(self.literal.value.ident)
)
- @property
- def is_this(self) -> bool:
- from .values import IdentLiteral
-
- return (
- not self.is_object
- and isinstance(self.literal.value, IdentLiteral)
- and self.literal.value.ident == "item"
- )
-
@property
def literal(self):
from .values import Literal
@@ -101,14 +99,14 @@ class LiteralExpr(ExprBase):
def type(self) -> T.Optional[GirType]:
return self.literal.value.type
- @validate()
- def item_validations(self):
- if self.is_this:
- if not isinstance(self.rhs, CastExpr):
- raise CompileError('"item" must be cast to its object type')
+ @property
+ def type_complete(self) -> bool:
+ from .values import IdentLiteral
- if not isinstance(self.rhs.rhs, LookupOp):
- raise CompileError('"item" can only be used for looking up properties')
+ if isinstance(self.literal.value, IdentLiteral):
+ if object := self.context[ScopeCtx].objects.get(self.literal.value.ident):
+ return not object.gir_class.incomplete
+ return True
class LookupOp(InfixExpr):
@@ -173,28 +171,10 @@ class LookupOp(InfixExpr):
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
)
- @validate("property")
- def property_deprecated(self):
- if self.lhs.type is None or not (
- isinstance(self.lhs.type, gir.Class)
- or isinstance(self.lhs.type, gir.Interface)
- ):
- return
-
- if property := self.lhs.type.properties.get(self.property_name):
- if property.deprecated:
- hints = []
- if property.deprecated_doc:
- hints.append(property.deprecated_doc)
- raise DeprecatedWarning(
- f"{property.signature} is deprecated",
- hints=hints,
- )
-
class CastExpr(InfixExpr):
grammar = [
- Keyword("as"),
+ "as",
AnyOf(
["<", TypeName, Match(">").expected()],
[
@@ -213,6 +193,10 @@ class CastExpr(InfixExpr):
def type(self) -> T.Optional[GirType]:
return self.children[TypeName][0].gir_type
+ @property
+ def type_complete(self) -> bool:
+ return True
+
@validate()
def cast_makes_sense(self):
if self.type is None or self.lhs.type is None:
@@ -236,10 +220,6 @@ class CastExpr(InfixExpr):
],
)
- @docs("as")
- def ref_docs(self):
- return get_docs_section("Syntax CastExpression")
-
class ClosureArg(AstNode):
grammar = Expression
@@ -289,10 +269,6 @@ class ClosureExpr(ExprBase):
if not self.tokens["extern"]:
raise CompileError(f"{self.closure_name} is not a builtin function")
- @docs("name")
- def ref_docs(self):
- return get_docs_section("Syntax ClosureExpression")
-
expr.children = [
AnyOf(ClosureExpr, LiteralExpr, ["(", Expression, ")"]),
@@ -302,18 +278,9 @@ expr.children = [
@decompiler("lookup", skip_children=True, cdata=True)
def decompile_lookup(
- ctx: DecompileCtx,
- gir: gir.GirContext,
- cdata: str,
- name: str,
- type: T.Optional[str] = None,
+ ctx: DecompileCtx, gir: gir.GirContext, cdata: str, name: str, type: str
):
- if ctx.parent_node is not None and ctx.parent_node.tag == "property":
- ctx.print("expr ")
-
- if type is None:
- type = ""
- elif t := ctx.type_by_cname(type):
+ if t := ctx.type_by_cname(type):
type = decompile.full_name(t)
else:
type = "$" + type
@@ -332,8 +299,6 @@ def decompile_lookup(
if constant is not None:
if constant == ctx.template_class:
ctx.print("template." + name)
- elif constant == "":
- ctx.print(f"item as <{type}>.{name}")
else:
ctx.print(constant + "." + name)
return
@@ -348,9 +313,6 @@ def decompile_lookup(
def decompile_constant(
ctx: DecompileCtx, gir: gir.GirContext, cdata: str, type: T.Optional[str] = None
):
- if ctx.parent_node is not None and ctx.parent_node.tag == "property":
- ctx.print("expr ")
-
if type is None:
if cdata == ctx.template_class:
ctx.print("template")
@@ -363,9 +325,6 @@ def decompile_constant(
@decompiler("closure", skip_children=True)
def decompile_closure(ctx: DecompileCtx, gir: gir.GirContext, function: str, type: str):
- if ctx.parent_node is not None and ctx.parent_node.tag == "property":
- ctx.print("expr ")
-
if t := ctx.type_by_cname(type):
type = decompile.full_name(t)
else:
diff --git a/blueprintcompiler/language/gobject_object.py b/blueprintcompiler/language/gobject_object.py
index 1def15b..54cb297 100644
--- a/blueprintcompiler/language/gobject_object.py
+++ b/blueprintcompiler/language/gobject_object.py
@@ -28,18 +28,7 @@ from .common import *
from .response_id import ExtResponse
from .types import ClassName, ConcreteClassName
-RESERVED_IDS = {
- "this",
- "self",
- "template",
- "true",
- "false",
- "null",
- "none",
- "item",
- "expr",
- "typeof",
-}
+RESERVED_IDS = {"this", "self", "template", "true", "false", "null", "none"}
class ObjectContent(AstNode):
diff --git a/blueprintcompiler/language/gobject_property.py b/blueprintcompiler/language/gobject_property.py
index 50a7512..5d0c867 100644
--- a/blueprintcompiler/language/gobject_property.py
+++ b/blueprintcompiler/language/gobject_property.py
@@ -21,12 +21,13 @@
from .binding import Binding
from .common import *
from .contexts import ValueTypeCtx
-from .values import ArrayValue, ExprValue, ObjectValue, Value
+from .gtkbuilder_template import Template
+from .values import ArrayValue, ObjectValue, Value
class Property(AstNode):
grammar = Statement(
- UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
+ UseIdent("name"), ":", AnyOf(Binding, ObjectValue, Value, ArrayValue)
)
@property
@@ -34,7 +35,7 @@ class Property(AstNode):
return self.tokens["name"]
@property
- def value(self) -> T.Union[Binding, ExprValue, ObjectValue, Value, ArrayValue]:
+ def value(self) -> T.Union[Binding, ObjectValue, Value, ArrayValue]:
return self.children[0]
@property
@@ -50,7 +51,7 @@ class Property(AstNode):
@property
def document_symbol(self) -> DocumentSymbol:
- if isinstance(self.value, ObjectValue) or self.value is None:
+ if isinstance(self.value, ObjectValue):
detail = None
else:
detail = self.value.range.text
diff --git a/blueprintcompiler/language/gobject_signal.py b/blueprintcompiler/language/gobject_signal.py
index 3b4235f..9348321 100644
--- a/blueprintcompiler/language/gobject_signal.py
+++ b/blueprintcompiler/language/gobject_signal.py
@@ -27,7 +27,6 @@ from .gtkbuilder_template import Template
class SignalFlag(AstNode):
grammar = AnyOf(
UseExact("flag", "swapped"),
- UseExact("flag", "not-swapped"),
UseExact("flag", "after"),
)
@@ -41,31 +40,6 @@ class SignalFlag(AstNode):
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
)
- @validate()
- def swapped_exclusive(self):
- if self.flag in ["swapped", "not-swapped"]:
- self.validate_unique_in_parent(
- "'swapped' and 'not-swapped' flags cannot be used together",
- lambda x: x.flag in ["swapped", "not-swapped"],
- )
-
- @validate()
- def swapped_unnecessary(self):
- if self.flag == "not-swapped" and self.parent.object_id is None:
- raise CompileWarning(
- "'not-swapped' is the default for handlers that do not specify an object",
- actions=[CodeAction("Remove 'not-swapped' flag", "")],
- )
- elif self.flag == "swapped" and self.parent.object_id is not None:
- raise CompileWarning(
- "'swapped' is the default for handlers that specify an object",
- actions=[CodeAction("Remove 'swapped' flag", "")],
- )
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Signal")
-
class Signal(AstNode):
grammar = Statement(
@@ -76,7 +50,7 @@ class Signal(AstNode):
UseIdent("detail_name").expected("a signal detail name"),
]
),
- Keyword("=>"),
+ "=>",
Mark("detail_start"),
Optional(["$", UseLiteral("extern", True)]),
UseIdent("handler").expected("the name of a function to handle the signal"),
@@ -114,17 +88,9 @@ class Signal(AstNode):
def flags(self) -> T.List[SignalFlag]:
return self.children[SignalFlag]
- # Returns True if the "swapped" flag is present, False if "not-swapped" is present, and None if neither are present.
- # GtkBuilder's default if swapped is not specified is to not swap the arguments if no object is specified, and to
- # swap them if an object is specified.
@property
- def is_swapped(self) -> T.Optional[bool]:
- for flag in self.flags:
- if flag.flag == "swapped":
- return True
- elif flag.flag == "not-swapped":
- return False
- return None
+ def is_swapped(self) -> bool:
+ return any(x.flag == "swapped" for x in self.flags)
@property
def is_after(self) -> bool:
@@ -143,25 +109,14 @@ class Signal(AstNode):
@property
def document_symbol(self) -> DocumentSymbol:
- detail = self.ranges["detail_start", "detail_end"]
return DocumentSymbol(
self.full_name,
SymbolKind.Event,
self.range,
self.group.tokens["name"].range,
- detail.text if detail is not None else None,
+ self.ranges["detail_start", "detail_end"].text,
)
- def get_reference(self, idx: int) -> T.Optional[LocationLink]:
- if self.object_id is not None and idx in self.group.tokens["object"].range:
- obj = self.context[ScopeCtx].objects.get(self.object_id)
- if obj is not None:
- return LocationLink(
- self.group.tokens["object"].range, obj.range, obj.ranges["id"]
- )
-
- return None
-
@validate("handler")
def old_extern(self):
if not self.tokens["extern"]:
@@ -209,38 +164,17 @@ class Signal(AstNode):
if self.gir_signal is not None:
return self.gir_signal.doc
- @docs("detail_name")
- def detail_docs(self):
- if self.name == "notify":
- if self.gir_class is not None and not isinstance(
- self.gir_class, ExternType
- ):
- prop = self.gir_class.properties.get(self.tokens["detail_name"])
- if prop is not None:
- return prop.doc
-
- @docs("=>")
- def ref_docs(self):
- return get_docs_section("Syntax Signal")
-
@decompiler("signal")
def decompile_signal(
- ctx: DecompileCtx, gir, name, handler, swapped=None, after="false", object=None
+ ctx, gir, name, handler, swapped="false", after="false", object=None
):
object_name = object or ""
-
- if object_name == ctx.template_class:
- object_name = "template"
-
name = name.replace("_", "-")
line = f"{name} => ${handler}({object_name})"
if decompile.truthy(swapped):
line += " swapped"
- elif swapped is not None:
- line += " not-swapped"
-
if decompile.truthy(after):
line += " after"
diff --git a/blueprintcompiler/language/gtk_a11y.py b/blueprintcompiler/language/gtk_a11y.py
index 0cc3cb3..8870a74 100644
--- a/blueprintcompiler/language/gtk_a11y.py
+++ b/blueprintcompiler/language/gtk_a11y.py
@@ -19,6 +19,8 @@
import typing as T
+from ..decompiler import escape_quote
+from .attributes import BaseAttribute
from .common import *
from .contexts import ValueTypeCtx
from .gobject_object import ObjectContent, validate_parent_type
@@ -117,7 +119,7 @@ def _get_docs(gir, name):
return gir_type.doc
-class A11yProperty(AstNode):
+class A11yProperty(BaseAttribute):
grammar = Statement(
UseIdent("name"),
":",
@@ -223,10 +225,6 @@ class ExtAccessibility(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate accessibility block")
- @docs("accessibility")
- def ref_docs(self):
- return get_docs_section("Syntax ExtAccessibility")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/gtk_combo_box_text.py b/blueprintcompiler/language/gtk_combo_box_text.py
index 32b3486..3a0d7c7 100644
--- a/blueprintcompiler/language/gtk_combo_box_text.py
+++ b/blueprintcompiler/language/gtk_combo_box_text.py
@@ -19,6 +19,7 @@
from .common import *
+from .contexts import ValueTypeCtx
from .gobject_object import ObjectContent, validate_parent_type
from .values import StringValue
@@ -54,10 +55,6 @@ class Item(AstNode):
f"Duplicate item '{self.name}'", lambda x: x.name == self.name
)
- @docs("name")
- def ref_docs(self):
- return get_docs_section("Syntax ExtComboBoxItems")
-
class ExtComboBoxItems(AstNode):
grammar = [
@@ -84,10 +81,6 @@ class ExtComboBoxItems(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate items block")
- @docs("items")
- def ref_docs(self):
- return get_docs_section("Syntax ExtComboBoxItems")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/gtk_file_filter.py b/blueprintcompiler/language/gtk_file_filter.py
index e84afc7..482d6e1 100644
--- a/blueprintcompiler/language/gtk_file_filter.py
+++ b/blueprintcompiler/language/gtk_file_filter.py
@@ -29,23 +29,25 @@ class Filters(AstNode):
self.tokens["tag_name"],
SymbolKind.Array,
self.range,
- self.group.tokens["tag_name"].range,
+ self.group.tokens[self.tokens["tag_name"]].range,
)
@validate()
def container_is_file_filter(self):
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
- @validate("tag_name")
+ @validate()
def unique_in_parent(self):
- self.validate_unique_in_parent(
- f"Duplicate {self.tokens['tag_name']} block",
- check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
- )
+ # The token argument to validate() needs to be calculated based on
+ # the instance, hence wrapping it like this.
+ @validate(self.tokens["tag_name"])
+ def wrapped_validator(self):
+ self.validate_unique_in_parent(
+ f"Duplicate {self.tokens['tag_name']} block",
+ check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
+ )
- @docs("tag_name")
- def ref_docs(self):
- return get_docs_section("Syntax ExtFileFilter")
+ wrapped_validator(self)
class FilterString(AstNode):
@@ -74,7 +76,8 @@ def create_node(tag_name: str, singular: str):
return Group(
Filters,
[
- UseExact("tag_name", tag_name),
+ Keyword(tag_name),
+ UseLiteral("tag_name", tag_name),
"[",
Delimited(
Group(
diff --git a/blueprintcompiler/language/gtk_layout.py b/blueprintcompiler/language/gtk_layout.py
index 8d3e37a..508609d 100644
--- a/blueprintcompiler/language/gtk_layout.py
+++ b/blueprintcompiler/language/gtk_layout.py
@@ -83,10 +83,6 @@ class ExtLayout(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate layout block")
- @docs("layout")
- def ref_docs(self):
- return get_docs_section("Syntax ExtLayout")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/gtk_list_item_factory.py b/blueprintcompiler/language/gtk_list_item_factory.py
index c9e1399..c54547f 100644
--- a/blueprintcompiler/language/gtk_list_item_factory.py
+++ b/blueprintcompiler/language/gtk_list_item_factory.py
@@ -50,7 +50,7 @@ class ExtListItemFactory(AstNode):
else:
return self.root.gir.get_type("ListItem", "Gtk")
- @validate("id")
+ @validate("template")
def container_is_builder_list(self):
validate_parent_type(
self,
@@ -59,7 +59,7 @@ class ExtListItemFactory(AstNode):
"sub-templates",
)
- @validate("id")
+ @validate("template")
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate template block")
@@ -76,7 +76,7 @@ class ExtListItemFactory(AstNode):
f"Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here"
)
- @validate("id")
+ @validate("template")
def type_name_upgrade(self):
if self.type_name is None:
raise UpgradeWarning(
@@ -103,9 +103,8 @@ class ExtListItemFactory(AstNode):
@property
def action_widgets(self):
- # The sub-template shouldn't have its own actions, this is just here to satisfy XmlOutput._emit_object_or_template
+ """
+ The sub-template shouldn't have it`s own actions this is
+ just hear to satisfy XmlOutput._emit_object_or_template
+ """
return None
-
- @docs("id")
- def ref_docs(self):
- return get_docs_section("Syntax ExtListItemFactory")
diff --git a/blueprintcompiler/language/gtk_menu.py b/blueprintcompiler/language/gtk_menu.py
index c7ef5f2..a77484a 100644
--- a/blueprintcompiler/language/gtk_menu.py
+++ b/blueprintcompiler/language/gtk_menu.py
@@ -70,25 +70,6 @@ class Menu(AstNode):
if self.id in RESERVED_IDS:
raise CompileWarning(f"{self.id} may be a confusing object ID")
- @docs("menu")
- def ref_docs_menu(self):
- return get_docs_section("Syntax Menu")
-
- @docs("section")
- def ref_docs_section(self):
- return get_docs_section("Syntax Menu")
-
- @docs("submenu")
- def ref_docs_submenu(self):
- return get_docs_section("Syntax Menu")
-
- @docs("item")
- def ref_docs_item(self):
- if self.tokens["shorthand"]:
- return get_docs_section("Syntax MenuItemShorthand")
- else:
- return get_docs_section("Syntax Menu")
-
class MenuAttribute(AstNode):
tag_name = "attribute"
@@ -175,7 +156,6 @@ menu_item_shorthand = Group(
[
Keyword("item"),
UseLiteral("tag", "item"),
- UseLiteral("shorthand", True),
"(",
Group(
MenuAttribute,
diff --git a/blueprintcompiler/language/gtk_scale.py b/blueprintcompiler/language/gtk_scale.py
index 1fd5ac3..ac4b77c 100644
--- a/blueprintcompiler/language/gtk_scale.py
+++ b/blueprintcompiler/language/gtk_scale.py
@@ -94,10 +94,6 @@ class ExtScaleMark(AstNode):
did_you_mean=(self.position, positions.keys()),
)
- @docs("mark")
- def ref_docs(self):
- return get_docs_section("Syntax ExtScaleMarks")
-
class ExtScaleMarks(AstNode):
grammar = [
@@ -127,10 +123,6 @@ class ExtScaleMarks(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate 'marks' block")
- @docs("marks")
- def ref_docs(self):
- return get_docs_section("Syntax ExtScaleMarks")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/gtk_size_group.py b/blueprintcompiler/language/gtk_size_group.py
index 54d85e5..0945e69 100644
--- a/blueprintcompiler/language/gtk_size_group.py
+++ b/blueprintcompiler/language/gtk_size_group.py
@@ -39,12 +39,6 @@ class Widget(AstNode):
self.group.tokens["name"].range,
)
- def get_reference(self, _idx: int) -> T.Optional[LocationLink]:
- if obj := self.context[ScopeCtx].objects.get(self.name):
- return LocationLink(self.range, obj.range, obj.ranges["id"])
- else:
- return None
-
@validate("name")
def obj_widget(self):
object = self.context[ScopeCtx].objects.get(self.tokens["name"])
@@ -94,10 +88,6 @@ class ExtSizeGroupWidgets(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate widgets block")
- @docs("widgets")
- def ref_docs(self):
- return get_docs_section("Syntax ExtSizeGroupWidgets")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/gtk_string_list.py b/blueprintcompiler/language/gtk_string_list.py
index a146f35..36a01f6 100644
--- a/blueprintcompiler/language/gtk_string_list.py
+++ b/blueprintcompiler/language/gtk_string_list.py
@@ -57,7 +57,7 @@ class ExtStringListStrings(AstNode):
self.group.tokens["strings"].range,
)
- @validate("strings")
+ @validate("items")
def container_is_string_list(self):
validate_parent_type(self, "Gtk", "StringList", "StringList items")
@@ -65,10 +65,6 @@ class ExtStringListStrings(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate strings block")
- @docs("strings")
- def ref_docs(self):
- return get_docs_section("Syntax ExtStringListStrings")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/gtk_styles.py b/blueprintcompiler/language/gtk_styles.py
index 8617522..236dde0 100644
--- a/blueprintcompiler/language/gtk_styles.py
+++ b/blueprintcompiler/language/gtk_styles.py
@@ -70,10 +70,6 @@ class ExtStyles(AstNode):
def unique_in_parent(self):
self.validate_unique_in_parent("Duplicate styles block")
- @docs("styles")
- def ref_docs(self):
- return get_docs_section("Syntax ExtStyles")
-
@completer(
applies_in=[ObjectContent],
diff --git a/blueprintcompiler/language/gtkbuilder_child.py b/blueprintcompiler/language/gtkbuilder_child.py
index bee551c..b0563ba 100644
--- a/blueprintcompiler/language/gtkbuilder_child.py
+++ b/blueprintcompiler/language/gtkbuilder_child.py
@@ -53,10 +53,6 @@ class ChildExtension(AstNode):
def child(self) -> ExtResponse:
return self.children[0]
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax ChildExtension")
-
class ChildAnnotation(AstNode):
grammar = ["[", AnyOf(ChildInternal, ChildExtension, ChildType), "]"]
diff --git a/blueprintcompiler/language/gtkbuilder_template.py b/blueprintcompiler/language/gtkbuilder_template.py
index 96383eb..29f7b37 100644
--- a/blueprintcompiler/language/gtkbuilder_template.py
+++ b/blueprintcompiler/language/gtkbuilder_template.py
@@ -88,10 +88,6 @@ class Template(Object):
f"Only one template may be defined per file, but this file contains {len(self.parent.children[Template])}",
)
- @docs("id")
- def ref_docs(self):
- return get_docs_section("Syntax Template")
-
@decompiler("template")
def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
diff --git a/blueprintcompiler/language/imports.py b/blueprintcompiler/language/imports.py
index 2d4bcf6..bf5dddd 100644
--- a/blueprintcompiler/language/imports.py
+++ b/blueprintcompiler/language/imports.py
@@ -59,12 +59,14 @@ class GtkDirective(AstNode):
@property
def gir_namespace(self):
- # For better error handling, just assume it's 4.0
- return gir.get_namespace("Gtk", "4.0")
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax GtkDecl")
+ # validate the GTK version first to make sure the more specific error
+ # message is emitted
+ self.gtk_version()
+ if self.tokens["version"] is not None:
+ return gir.get_namespace("Gtk", self.tokens["version"])
+ else:
+ # For better error handling, just assume it's 4.0
+ return gir.get_namespace("Gtk", "4.0")
class Import(AstNode):
@@ -84,7 +86,7 @@ class Import(AstNode):
@validate("namespace", "version")
def namespace_exists(self):
- gir.get_namespace(self.namespace, self.version)
+ gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
@validate()
def unused(self):
@@ -100,10 +102,6 @@ class Import(AstNode):
@property
def gir_namespace(self):
try:
- return gir.get_namespace(self.namespace, self.version)
+ return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
except CompileError:
return None
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Using")
diff --git a/blueprintcompiler/language/response_id.py b/blueprintcompiler/language/response_id.py
index 939f71f..8c0c807 100644
--- a/blueprintcompiler/language/response_id.py
+++ b/blueprintcompiler/language/response_id.py
@@ -124,16 +124,6 @@ class ExtResponse(AstNode):
object = self.parent_by_type(Child).object
return object.id
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax ExtResponse")
-
- @docs("response_id")
- def response_id_docs(self):
- if enum := self.root.gir.get_type("ResponseType", "Gtk"):
- if member := enum.members.get(self.response_id, None):
- return member.doc
-
def decompile_response_type(parent_element, child_element):
obj_id = None
diff --git a/blueprintcompiler/language/translation_domain.py b/blueprintcompiler/language/translation_domain.py
index 0f60af9..ff20ead 100644
--- a/blueprintcompiler/language/translation_domain.py
+++ b/blueprintcompiler/language/translation_domain.py
@@ -29,7 +29,3 @@ class TranslationDomain(AstNode):
@property
def domain(self):
return self.tokens["domain"]
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax TranslationDomain")
diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py
index 833a4a3..9b29d94 100644
--- a/blueprintcompiler/language/values.py
+++ b/blueprintcompiler/language/values.py
@@ -20,11 +20,9 @@
import typing as T
from blueprintcompiler.gir import ArrayType
-from blueprintcompiler.lsp_utils import SemanticToken
from .common import *
-from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
-from .expression import Expression
+from .contexts import ScopeCtx, ValueTypeCtx
from .gobject_object import Object
from .types import TypeName
@@ -58,23 +56,6 @@ class Translated(AstNode):
f"Cannot convert translated string to {expected_type.full_name}"
)
- @validate("context")
- def context_double_quoted(self):
- if self.translate_context is None:
- return
-
- if not str(self.group.tokens["context"]).startswith('"'):
- raise CompileWarning("gettext may not recognize single-quoted strings")
-
- @validate("string")
- def string_double_quoted(self):
- if not str(self.group.tokens["string"]).startswith('"'):
- raise CompileWarning("gettext may not recognize single-quoted strings")
-
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Translated")
-
class TypeLiteral(AstNode):
grammar = [
@@ -120,10 +101,6 @@ class TypeLiteral(AstNode):
],
)
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax TypeLiteral")
-
class QuotedLiteral(AstNode):
grammar = UseQuoted("value")
@@ -225,22 +202,15 @@ class Flag(AstNode):
return self.tokens["value"]
@property
- def value(self) -> T.Optional[str]:
+ def value(self) -> T.Optional[int]:
type = self.context[ValueTypeCtx].value_type
if not isinstance(type, Enumeration):
return None
elif member := type.members.get(self.name):
- return member.nick
+ return member.value
else:
return None
- def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
- yield SemanticToken(
- self.group.tokens["value"].start,
- self.group.tokens["value"].end,
- SemanticTokenType.EnumMember,
- )
-
@docs()
def docs(self):
type = self.context[ValueTypeCtx].value_type
@@ -281,10 +251,6 @@ class Flags(AstNode):
if expected_type is not None and not isinstance(expected_type, gir.Bitfield):
raise CompileError(f"{expected_type.full_name} is not a bitfield type")
- @docs()
- def ref_docs(self):
- return get_docs_section("Syntax Flags")
-
class IdentLiteral(AstNode):
grammar = UseIdent("value")
@@ -333,12 +299,7 @@ class IdentLiteral(AstNode):
if self.ident == "null":
if not self.context[ValueTypeCtx].allow_null:
raise CompileError("null is not permitted here")
- elif self.ident == "item":
- if not self.context[ExprValueCtx]:
- raise CompileError(
- '"item" can only be used in an expression literal'
- )
- elif self.ident not in ["true", "false"]:
+ else:
raise CompileError(
f"Could not find object with ID {self.ident}",
did_you_mean=(
@@ -426,35 +387,6 @@ class ObjectValue(AstNode):
)
-class ExprValue(AstNode):
- grammar = [Keyword("expr"), Expression]
-
- @property
- def expression(self) -> Expression:
- return self.children[Expression][0]
-
- @validate("expr")
- def validate_for_type(self) -> None:
- expected_type = self.parent.context[ValueTypeCtx].value_type
- expr_type = self.root.gir.get_type("Expression", "Gtk")
- if expected_type is not None and not expected_type.assignable_to(expr_type):
- raise CompileError(
- f"Cannot convert Gtk.Expression to {expected_type.full_name}"
- )
-
- @docs("expr")
- def ref_docs(self):
- return get_docs_section("Syntax ExprValue")
-
- @context(ExprValueCtx)
- def expr_literal(self):
- return ExprValueCtx()
-
- @context(ValueTypeCtx)
- def value_type(self):
- return ValueTypeCtx(None, must_infer_type=True)
-
-
class Value(AstNode):
grammar = AnyOf(Translated, Flags, Literal)
@@ -500,14 +432,6 @@ class ArrayValue(AstNode):
range=quoted_literal.range,
)
)
- elif isinstance(value.child, Translated):
- errors.append(
- CompileError(
- "Arrays can't contain translated strings",
- range=value.child.range,
- )
- )
-
if len(errors) > 0:
raise MultipleErrors(errors)
diff --git a/blueprintcompiler/lsp.py b/blueprintcompiler/lsp.py
index c4076b4..25b289f 100644
--- a/blueprintcompiler/lsp.py
+++ b/blueprintcompiler/lsp.py
@@ -118,7 +118,6 @@ class LanguageServer:
self.client_capabilities = {}
self.client_supports_completion_choice = False
self._open_files: T.Dict[str, OpenFile] = {}
- self._exited = False
def run(self):
# Read tags from gir files. During normal compilation these are
@@ -126,7 +125,7 @@ class LanguageServer:
xml_reader.PARSE_GIR.add("doc")
try:
- while not self._exited:
+ while True:
line = ""
content_len = -1
while content_len == -1 or (line != "\n" and line != "\r\n"):
@@ -150,7 +149,7 @@ class LanguageServer:
def _send(self, data):
data["jsonrpc"] = "2.0"
- line = json.dumps(data, separators=(",", ":"))
+ line = json.dumps(data, separators=(",", ":")) + "\r\n"
printerr("output: " + line)
sys.stdout.write(
f"Content-Length: {len(line.encode())}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{line}"
@@ -222,14 +221,6 @@ class LanguageServer:
},
)
- @command("shutdown")
- def shutdown(self, id, params):
- self._send_response(id, None)
-
- @command("exit")
- def exit(self, id, params):
- self._exited = True
-
@command("textDocument/didOpen")
def didOpen(self, id, params):
doc = params.get("textDocument")
diff --git a/blueprintcompiler/lsp_utils.py b/blueprintcompiler/lsp_utils.py
index b938181..9362e8c 100644
--- a/blueprintcompiler/lsp_utils.py
+++ b/blueprintcompiler/lsp_utils.py
@@ -19,8 +19,6 @@
import enum
-import json
-import os
import typing as T
from dataclasses import dataclass, field
@@ -202,27 +200,3 @@ class TextEdit:
def to_json(self):
return {"range": self.range.to_json(), "newText": self.newText}
-
-
-_docs_sections: T.Optional[dict[str, T.Any]] = None
-
-
-def get_docs_section(section_name: str) -> T.Optional[str]:
- global _docs_sections
-
- if _docs_sections is None:
- try:
- with open(
- os.path.join(os.path.dirname(__file__), "reference_docs.json")
- ) as f:
- _docs_sections = json.load(f)
- except FileNotFoundError:
- _docs_sections = {}
-
- if section := _docs_sections.get(section_name):
- content = section["content"]
- link = section["link"]
- content += f"\n\n---\n\n[Online documentation]({link})"
- return content
- else:
- return None
diff --git a/blueprintcompiler/outputs/xml/__init__.py b/blueprintcompiler/outputs/xml/__init__.py
index 15850f7..5e43834 100644
--- a/blueprintcompiler/outputs/xml/__init__.py
+++ b/blueprintcompiler/outputs/xml/__init__.py
@@ -134,11 +134,6 @@ class XmlOutput(OutputFormat):
self._emit_expression(value.expression, xml)
xml.end_tag()
- elif isinstance(value, ExprValue):
- xml.start_tag("property", **props)
- self._emit_expression(value.expression, xml)
- xml.end_tag()
-
elif isinstance(value, ObjectValue):
xml.start_tag("property", **props)
self._emit_object(value.object, xml)
@@ -174,7 +169,7 @@ class XmlOutput(OutputFormat):
"signal",
name=name,
handler=signal.handler,
- swapped=signal.is_swapped,
+ swapped=signal.is_swapped or None,
after=signal.is_after or None,
object=(
self._object_id(signal, signal.object_id) if signal.object_id else None
@@ -223,6 +218,12 @@ class XmlOutput(OutputFormat):
xml.put_text(
"|".join([str(flag.value or flag.name) for flag in value.child.flags])
)
+ elif isinstance(value.child, Translated):
+ raise CompilerBugError("translated values must be handled in the parent")
+ elif isinstance(value.child, TypeLiteral):
+ xml.put_text(value.child.type_name.glib_type_name)
+ elif isinstance(value.child, ObjectValue):
+ self._emit_object(value.child.object, xml)
else:
raise CompilerBugError()
@@ -244,9 +245,6 @@ class XmlOutput(OutputFormat):
raise CompilerBugError()
def _emit_literal_expr(self, expr: LiteralExpr, xml: XmlEmitter):
- if expr.is_this:
- return
-
if expr.is_object:
xml.start_tag("constant")
else:
@@ -308,9 +306,6 @@ class XmlOutput(OutputFormat):
elif isinstance(extension, AdwBreakpointSetters):
for setter in extension.setters:
- if setter.value is None:
- continue
-
attrs = {}
if isinstance(setter.value.child, Translated):
@@ -371,13 +366,12 @@ class XmlOutput(OutputFormat):
elif isinstance(extension, ExtScaleMarks):
xml.start_tag("marks")
- for mark in extension.marks:
- label = mark.label.child if mark.label is not None else None
+ for mark in extension.children:
xml.start_tag(
"mark",
value=mark.value,
position=mark.position,
- **self._translated_string_attrs(label),
+ **self._translated_string_attrs(mark.label and mark.label.child),
)
if mark.label is not None:
xml.put_text(mark.label.string)
diff --git a/blueprintcompiler/outputs/xml/xml_emitter.py b/blueprintcompiler/outputs/xml/xml_emitter.py
index d34eff4..ca87a49 100644
--- a/blueprintcompiler/outputs/xml/xml_emitter.py
+++ b/blueprintcompiler/outputs/xml/xml_emitter.py
@@ -40,9 +40,7 @@ class XmlEmitter:
self._tag_stack = []
self._needs_newline = False
- def start_tag(
- self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None, float]
- ):
+ def start_tag(self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None]):
self._indent()
self.result += f"<{tag}"
for key, val in attrs.items():
@@ -73,7 +71,6 @@ class XmlEmitter:
self._needs_newline = False
def put_cdata(self, text: str):
- text = text.replace("]]>", "]]]]>")
self.result += f""
self._needs_newline = False
diff --git a/blueprintcompiler/parse_tree.py b/blueprintcompiler/parse_tree.py
index e590539..fff6e4a 100644
--- a/blueprintcompiler/parse_tree.py
+++ b/blueprintcompiler/parse_tree.py
@@ -17,7 +17,7 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
-"""Utilities for parsing an AST from a token stream."""
+""" Utilities for parsing an AST from a token stream. """
import typing as T
from enum import Enum
@@ -95,11 +95,19 @@ class ParseGroup:
try:
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
- except TypeError: # pragma: no cover
+ except TypeError as e:
raise CompilerBugError(
f"Failed to construct ast.{self.ast_type.__name__} from ParseGroup. See the previous stacktrace."
)
+ def __str__(self):
+ result = str(self.ast_type.__name__)
+ result += "".join([f"\n{key}: {val}" for key, val in self.keys.items()]) + "\n"
+ result += "\n".join(
+ [str(child) for children in self.children.values() for child in children]
+ )
+ return result.replace("\n", "\n ")
+
class ParseContext:
"""Contains the state of the parser."""
@@ -257,6 +265,10 @@ class ParseNode:
"""Convenience method for err()."""
return self.err("Expected " + expect)
+ def warn(self, message) -> "Warning":
+ """Causes this ParseNode to emit a warning if it parses successfully."""
+ return Warning(self, message)
+
class Err(ParseNode):
"""ParseNode that emits a compile error if it fails to parse."""
@@ -278,6 +290,27 @@ class Err(ParseNode):
return True
+class Warning(ParseNode):
+ """ParseNode that emits a compile warning if it parses successfully."""
+
+ def __init__(self, child, message: str):
+ self.child = to_parse_node(child)
+ self.message = message
+
+ def _parse(self, ctx: ParseContext):
+ ctx.skip()
+ start_idx = ctx.index
+ if self.child.parse(ctx).succeeded():
+ start_token = ctx.tokens[start_idx]
+ end_token = ctx.tokens[ctx.index]
+ ctx.warnings.append(
+ CompileWarning(self.message, start_token.start, end_token.end)
+ )
+ return True
+ else:
+ return False
+
+
class Fail(ParseNode):
"""ParseNode that emits a compile error if it parses successfully."""
diff --git a/blueprintcompiler/parser.py b/blueprintcompiler/parser.py
index 1f87647..89e1533 100644
--- a/blueprintcompiler/parser.py
+++ b/blueprintcompiler/parser.py
@@ -33,9 +33,7 @@ def parse(
original_text = tokens[0].string if len(tokens) else ""
ctx = ParseContext(tokens, original_text)
AnyOf(UI).parse(ctx)
-
- assert ctx.last_group is not None
- ast_node = ctx.last_group.to_ast()
+ ast_node = ctx.last_group.to_ast() if ctx.last_group else None
errors = [*ctx.errors, *ast_node.errors]
warnings = [*ctx.warnings, *ast_node.warnings]
diff --git a/blueprintcompiler/typelib.py b/blueprintcompiler/typelib.py
index be22eb1..145bf57 100644
--- a/blueprintcompiler/typelib.py
+++ b/blueprintcompiler/typelib.py
@@ -148,8 +148,8 @@ class Typelib:
SIGNATURE_ARGUMENTS = Field(0x8, "offset")
ATTR_OFFSET = Field(0x0, "u32")
- ATTR_NAME = Field(0x4, "string")
- ATTR_VALUE = Field(0x8, "string")
+ ATTR_NAME = Field(0x0, "string")
+ ATTR_VALUE = Field(0x0, "string")
TYPE_BLOB_TAG = Field(0x0, "u8", 3, 5)
TYPE_BLOB_INTERFACE = Field(0x2, "dir_entry")
diff --git a/build-aux/install_deps.sh b/build-aux/install_deps.sh
index 342778d..381a57a 100755
--- a/build-aux/install_deps.sh
+++ b/build-aux/install_deps.sh
@@ -7,8 +7,8 @@ git clone --depth=1 https://gitlab.gnome.org/GNOME/gtk.git
cd gtk
meson setup builddir \
--prefix=/usr \
- -Ddocumentation=true \
- -Dbuild-demos=false \
+ -Dgtk_doc=true \
+ -Ddemos=false \
-Dbuild-examples=false \
-Dbuild-tests=false \
-Dbuild-testsuite=false
diff --git a/docs/collect-sections.py b/docs/collect-sections.py
deleted file mode 100755
index a2dd004..0000000
--- a/docs/collect-sections.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/usr/bin/env python3
-
-import json
-import os
-import re
-import sys
-from dataclasses import dataclass
-from pathlib import Path
-
-__all__ = ["get_docs_section"]
-
-DOCS_ROOT = "https://gnome.pages.gitlab.gnome.org/blueprint-compiler"
-
-
-sections: dict[str, "Section"] = {}
-
-
-@dataclass
-class Section:
- link: str
- lines: str
-
- def to_json(self):
- return {
- "content": rst_to_md(self.lines),
- "link": self.link,
- }
-
-
-def load_reference_docs():
- for filename in Path(os.path.dirname(__file__), "reference").glob("*.rst"):
- with open(filename) as f:
- section_name = None
- lines = []
-
- def close_section():
- if section_name:
- html_file = re.sub(r"\.rst$", ".html", filename.name)
- anchor = re.sub(r"[^a-z0-9]+", "-", section_name.lower())
- link = f"{DOCS_ROOT}/reference/{html_file}#{anchor}"
- sections[section_name] = Section(link, lines)
-
- for line in f:
- if m := re.match(r"\.\.\s+_(.*):", line):
- close_section()
- section_name = m.group(1)
- lines = []
- else:
- lines.append(line)
-
- close_section()
-
-
-# This isn't a comprehensive rST to markdown converter, it just needs to handle the
-# small subset of rST used in the reference docs.
-def rst_to_md(lines: list[str]) -> str:
- result = ""
-
- def rst_to_md_inline(line):
- line = re.sub(r"``(.*?)``", r"`\1`", line)
- line = re.sub(
- r":ref:`(.*?)<(.*?)>`",
- lambda m: f"[{m.group(1)}]({sections[m.group(2)].link})",
- line,
- )
- line = re.sub(r"`([^`]*?) <([^`>]*?)>`_", r"[\1](\2)", line)
- return line
-
- i = 0
- n = len(lines)
- heading_levels = {}
-
- def print_block(lang: str = "", code: bool = True, strip_links: bool = False):
- nonlocal result, i
- block = ""
- while i < n:
- line = lines[i].rstrip()
- if line.startswith(" "):
- line = line[3:]
- elif line != "":
- break
-
- if strip_links:
- line = re.sub(r":ref:`(.*?)<(.*?)>`", r"\1", line)
-
- if not code:
- line = rst_to_md_inline(line)
-
- block += line + "\n"
- i += 1
-
- if code:
- result += f"```{lang}\n{block.strip()}\n```\n\n"
- else:
- result += block
-
- while i < n:
- line = lines[i].rstrip()
- i += 1
- if line == ".. rst-class:: grammar-block":
- print_block("text", strip_links=True)
- elif line == ".. code-block:: blueprint":
- print_block("blueprint")
- elif line == ".. note::":
- result += "#### Note\n"
- print_block(code=False)
- elif m := re.match(r"\.\. image:: (.*)", line):
- result += f"})\n"
- elif i < n and re.match(r"^((-+)|(~+)|(\++))$", lines[i]):
- level_char = lines[i][0]
- if level_char not in heading_levels:
- heading_levels[level_char] = max(heading_levels.values(), default=1) + 1
- result += (
- "#" * heading_levels[level_char] + " " + rst_to_md_inline(line) + "\n"
- )
- i += 1
- else:
- result += rst_to_md_inline(line) + "\n"
-
- return result
-
-
-if __name__ == "__main__":
- if len(sys.argv) != 2:
- print("Usage: collect_sections.py ")
- sys.exit(1)
-
- outfile = sys.argv[1]
-
- load_reference_docs()
-
- # print the sections to a json file
- with open(outfile, "w") as f:
- json.dump(
- {name: section.to_json() for name, section in sections.items()},
- f,
- indent=2,
- sort_keys=True,
- )
diff --git a/docs/flatpak.rst b/docs/flatpak.rst
index 8081c8d..0071d2f 100644
--- a/docs/flatpak.rst
+++ b/docs/flatpak.rst
@@ -16,8 +16,8 @@ a module in your flatpak manifest:
"sources": [
{
"type": "git",
- "url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
- "tag": "v0.16.0"
+ "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
+ "tag": "v0.14.0"
}
]
}
diff --git a/docs/index.rst b/docs/index.rst
index 6cd130f..e5601ea 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -26,7 +26,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
using Gtk 4.0;
- template $MyAppWindow: ApplicationWindow {
+ template MyAppWindow : ApplicationWindow {
default-width: 600;
default-height: 300;
title: _("Hello, Blueprint!");
@@ -35,7 +35,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
HeaderBar {}
Label {
- label: bind template.main_text;
+ label: bind MyAppWindow.main_text;
}
}
@@ -59,7 +59,7 @@ Features
Links
-----
-- `Source code `_
+- `Source code `_
- `Workbench `_ lets you try, preview and export Blueprint
- `GNOME Builder `_ provides builtin support
- `Vim syntax highlighting plugin by thetek42 `_
@@ -82,12 +82,10 @@ Built with Blueprint
- `Blurble `_
- `Bottles `_
- `Cartridges `_
-- `Cassette `_
- `Cavalier `_
- `Chance `_
- `Commit `_
- `Confy `_
-- `Cozy `_
- `Daikhan `_
- `Damask `_
- `Denaro `_
diff --git a/docs/meson.build b/docs/meson.build
index d9ad736..95e545d 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -9,11 +9,3 @@ custom_target('docs',
)
endif
-
-custom_target('reference_docs.json',
- output: 'reference_docs.json',
- command: [meson.current_source_dir() / 'collect-sections.py', '@OUTPUT@'],
- build_always_stale: true,
- install: true,
- install_dir: py.get_install_dir() / 'blueprintcompiler',
-)
\ No newline at end of file
diff --git a/docs/reference/diagnostics.rst b/docs/reference/diagnostics.rst
index c8774d6..2c0af59 100644
--- a/docs/reference/diagnostics.rst
+++ b/docs/reference/diagnostics.rst
@@ -21,7 +21,7 @@ The tokenizer encountered an unexpected sequence of characters that aren't part
child_not_accepted
------------------
-The parent class does not have child objects (it does not implement `Gtk.Buildable `_ and is not a subclass of `Gio.ListStore `_). Some classes use properties instead of children to add widgets. Check the parent class's documentation.
+The parent class does not have child widgets (it does not implement `Gtk.Buildable `_ and is not a subclass of `Gio.ListStore `_). Some classes use properties instead of children to add widgets. Check the parent class's documentation.
.. _Diagnostic conversion_error:
diff --git a/docs/reference/expressions.rst b/docs/reference/expressions.rst
index 3d523d1..1ba50ee 100644
--- a/docs/reference/expressions.rst
+++ b/docs/reference/expressions.rst
@@ -8,10 +8,10 @@ automatically.
.. code-block:: blueprint
- label: bind template.account.username;
- /* ^ ^ ^
- | creates lookup expressions that are re-evaluated when
- | the account's username *or* the account itself changes
+ label: bind MyAppWindow.account.username;
+ /* ^ ^ ^
+ | creates lookup expressions that are re-evaluated when
+ | the account's username *or* the account itself changes
|
binds the `label` property to the expression's output
*/
@@ -42,22 +42,22 @@ Expressions are composed of property lookups and/or closures. Property lookups a
.. _Syntax LookupExpression:
-Lookups
--------
+Lookup Expressions
+------------------
.. rst-class:: grammar-block
LookupExpression = '.' `>
-Lookup expressions perform a GObject property lookup on the preceding expression. They are recalculated whenever the property changes, using the `notify signal `_.
+Lookup expressions perform a GObject property lookup on the preceding expression. They are recalculated whenever the property changes, using the `notify signal `_
The type of a property expression is the type of the property it refers to.
.. _Syntax ClosureExpression:
-Closures
---------
+Closure Expressions
+-------------------
.. rst-class:: grammar-block
@@ -65,48 +65,23 @@ Closures
Closure expressions allow you to perform additional calculations that aren't supported in blueprint by writing those calculations as application code. These application-defined functions are created in the same way as :ref:`signal handlers`.
-Expressions are only reevaluated when their inputs change. Because blueprint doesn't manage a closure's application code, it can't tell what changes might affect the result. Therefore, closures must be *pure*, or deterministic. They may only calculate the result based on their immediate inputs, not properties of their inputs or outside variables.
+Expressions are only reevaluated when their inputs change. Because blueprint doesn't manage a closure's application code, it can't tell what changes might affect the result. Therefore, closures must be *pure*, or deterministic. They may only calculate the result based on their immediate inputs, properties of their inputs or outside variables.
Blueprint doesn't know the closure's return type, so closure expressions must be cast to the correct return type using a :ref:`cast expression`.
.. _Syntax CastExpression:
-Casts
------
+Cast Expressions
+----------------
.. rst-class:: grammar-block
CastExpression = 'as' '<' :ref:`TypeName` '>'
-Cast expressions allow Blueprint to know the type of an expression when it can't otherwise determine it. This is necessary for closures and for properties of application-defined types.
-
-Example
-~~~~~~~
+Cast expressions allow Blueprint to know the type of an expression when it can't otherwise determine it.
.. code-block:: blueprint
// Cast the result of the closure so blueprint knows it's a string
- label: bind $format_bytes(template.file-size) as
-
-.. _Syntax ExprValue:
-
-Expression Values
------------------
-
-.. rst-class:: grammar-block
-
- ExprValue = 'expr' :ref:`Expression`
-
-Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter `_ has an ``expression`` property of type `Gtk.Expression `_. This expression is evaluated for every item in a list model to determine whether the item should be filtered.
-
-To define an expression for such a property, use ``expr`` instead of ``bind``. Inside the expression, you can use the ``item`` keyword to refer to the item being evaluated. You must cast the item to the correct type using the ``as`` keyword, and you can only use ``item`` in a property lookup--you may not pass it to a closure.
-
-Example
-~~~~~~~
-
-.. code-block:: blueprint
-
- BoolFilter {
- expression: expr item as <$UserAccount>.active;
- }
+ label: bind $my_closure() as
\ No newline at end of file
diff --git a/docs/reference/extensions.rst b/docs/reference/extensions.rst
index 2fd5dbb..93ed0fc 100644
--- a/docs/reference/extensions.rst
+++ b/docs/reference/extensions.rst
@@ -10,7 +10,7 @@ Properties are the main way to set values on objects, but they are limited by th
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() `_ for internal details.
- Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue `_.
+ Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue `_.
.. rst-class:: grammar-block
@@ -227,7 +227,7 @@ Valid in `Gtk.BuilderListItemFactory `_, `Gtk.ColumnViewRow `_, or `Gtk.ColumnViewCell `_. The template object can be referenced with the ``template`` keyword.
+The template type must be `Gtk.ListItem `_, `Gtk.ColumnViewRow `_, or `Gtk.ColumnViewCell `_ The template object can be referenced with the ``template`` keyword.
.. code-block:: blueprint
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
index d49feb9..7670ba5 100644
--- a/docs/reference/index.rst
+++ b/docs/reference/index.rst
@@ -31,7 +31,7 @@ Tokens
IDENT
~~~~~
-An identifier starts with an ASCII underscore ``_`` or letter ``[A-Za-z]`` and consists of ASCII underscores, letters, digits ``[0-9]``, and dashes ``-``. Dashes are included for historical reasons, since GObject properties and signals are traditionally kebab-case.
+An identifier starts with an ASCII underscore ``_`` or letter ``[A-Za-z]`` and consists of ASCII underscores, letters, digits ``[0-9]``, and dashes ``-``. Dashes are included for historical reasons, since GObject properties are traditionally kebab-case.
.. _Syntax NUMBER:
diff --git a/docs/reference/objects.rst b/docs/reference/objects.rst
index 6f76da6..699db49 100644
--- a/docs/reference/objects.rst
+++ b/docs/reference/objects.rst
@@ -58,7 +58,7 @@ Properties
.. rst-class:: grammar-block
- Property = `> ':' ( :ref:`Binding` | :ref:`ExprValue` | :ref:`ObjectValue` | :ref:`Value` ) ';'
+ Property = `> ':' ( :ref:`Binding` | :ref:`ObjectValue` | :ref:`Value` ) ';'
Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container.
@@ -91,7 +91,7 @@ Signal Handlers
.. rst-class:: grammar-block
Signal = `> ('::' `>)? '=>' '$' `> '(' `>? ')' (SignalFlag)* ';'
- SignalFlag = 'after' | 'swapped' | 'not-swapped'
+ SignalFlag = 'after' | 'swapped'
Signals are one way to respond to user input (another is `actions `_, which use the `action-name property `_).
@@ -99,8 +99,6 @@ Signals provide a handle for your code to listen to events in the UI. The handle
Optionally, you can provide an object ID to use when connecting the signal.
-The ``swapped`` flag is used to swap the order of the object and userdata arguments in C applications. If an object argument is specified, then this is the default behavior, so the ``not-swapped`` flag can be used to prevent the swap.
-
Example
~~~~~~~
@@ -110,6 +108,7 @@ Example
clicked => $on_button_clicked();
}
+
.. _Syntax Child:
Children
diff --git a/docs/reference/templates.rst b/docs/reference/templates.rst
index 74e4225..a1a9968 100644
--- a/docs/reference/templates.rst
+++ b/docs/reference/templates.rst
@@ -90,7 +90,6 @@ To reference the template object in a binding or expression, use the ``template`
Language Implementations
------------------------
-- **C** ``gtk_widget_class_set_template ()``: https://docs.gtk.org/gtk4/class.Widget.html#building-composite-widgets-from-template-xml
-- **gtk-rs** ``#[template]``: https://gtk-rs.org/gtk4-rs/stable/latest/book/composite_templates.html
-- **GJS** ``GObject.registerClass()``: https://gjs.guide/guides/gtk/3/14-templates.html
-- **PyGObject** ``@Gtk.Template``: https://pygobject.gnome.org/guide/gtk_template.html
+- ``gtk_widget_class_set_template ()`` in C: https://docs.gtk.org/gtk4/class.Widget.html#building-composite-widgets-from-template-xml
+- ``#[template]`` in gtk-rs: https://gtk-rs.org/gtk4-rs/stable/latest/book/composite_templates.html
+- ``GObject.registerClass()`` in GJS: https://gjs.guide/guides/gtk/3/14-templates.html
\ No newline at end of file
diff --git a/docs/reference/values.rst b/docs/reference/values.rst
index fd414a8..95f6bc8 100644
--- a/docs/reference/values.rst
+++ b/docs/reference/values.rst
@@ -25,7 +25,8 @@ Literals
NumberLiteral = ( '-' | '+' )? `>
IdentLiteral = `>
-Literals are used to specify values for properties. They can be strings, numbers, references to objects, ``null``, types, boolean values, or enum members.
+Literals are used to specify values for properties. They can be strings, numbers, references to objects, types, boolean values, or enum members.
+
.. _Syntax TypeLiteral:
@@ -109,7 +110,7 @@ Bindings
.. rst-class:: grammar-block
Binding = 'bind' :ref:`Expression` (BindingFlag)*
- BindingFlag = 'inverted' | 'bidirectional' | 'no-sync-create'
+ BindingFlag = 'inverted' | 'bidirectional' | 'sync-create'
Bindings keep a property updated as other properties change. They can be used to keep the UI in sync with application data, or to connect two parts of the UI.
@@ -120,8 +121,8 @@ Simple Bindings
A binding that consists of a source object and a single lookup is called a "simple binding". These are implemented using `GObject property bindings `_ and support a few flags:
-- ``inverted``: For boolean properties, the target is set to the inverse of the source property.
- ``bidirectional``: The binding is two-way, so changes to the target property will also update the source property.
+- ``inverted``: For boolean properties, the target is set to the inverse of the source property.
- ``no-sync-create``: Normally, when a binding is created, the target property is immediately updated with the current value of the source property. This flag disables that behavior, and the bound property will be updated the next time the source property changes.
Complex Bindings
@@ -137,11 +138,11 @@ Example
/* Use bindings to show a label when a switch
* is active, without any application code */
- Switch show_label {}
+ Switch advanced_feature {}
- Label {
- visible: bind show_label.active;
- label: _("I'm a label that's only visible when the switch is enabled!");
+ Label warning {
+ visible: bind advanced_feature.active;
+ label: _("This is an advanced feature. Use with caution!");
}
.. _Syntax ObjectValue:
diff --git a/docs/setup.rst b/docs/setup.rst
index 914c753..839f8f6 100644
--- a/docs/setup.rst
+++ b/docs/setup.rst
@@ -8,7 +8,7 @@ Setting up Blueprint on a new or existing project
Using the porting tool
~~~~~~~~~~~~~~~~~~~~~~
-Clone `blueprint-compiler `_
+Clone `blueprint-compiler `_
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
or you can leave it uninstalled.
@@ -29,7 +29,7 @@ blueprint-compiler works as a meson subproject.
[wrap-git]
directory = blueprint-compiler
- url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
+ url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
revision = main
depth = 1
diff --git a/docs/translations.rst b/docs/translations.rst
index 7af2099..7ebf929 100644
--- a/docs/translations.rst
+++ b/docs/translations.rst
@@ -24,8 +24,6 @@ If you're using Meson's `i18n module $on_icon_name_changed(label) swapped;
styles [
- "destructive",
+ "destructive"
]
}
diff --git a/tests/sample_errors/deprecations.blp b/tests/sample_errors/deprecations.blp
new file mode 100644
index 0000000..f67f002
--- /dev/null
+++ b/tests/sample_errors/deprecations.blp
@@ -0,0 +1,9 @@
+using Gtk 4.0;
+
+Dialog {
+ use-header-bar: 1;
+}
+
+Window {
+ keys-changed => $on_window_keys_changed();
+}
diff --git a/tests/sample_errors/deprecations.err b/tests/sample_errors/deprecations.err
new file mode 100644
index 0000000..e3abd61
--- /dev/null
+++ b/tests/sample_errors/deprecations.err
@@ -0,0 +1 @@
+3,1,6,Gtk.Dialog is deprecated
\ No newline at end of file
diff --git a/tests/sample_errors/expr_item_not_cast.blp b/tests/sample_errors/expr_item_not_cast.blp
deleted file mode 100644
index 76a1d89..0000000
--- a/tests/sample_errors/expr_item_not_cast.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-BoolFilter {
- expression: expr item.visible;
-}
diff --git a/tests/sample_errors/expr_item_not_cast.err b/tests/sample_errors/expr_item_not_cast.err
deleted file mode 100644
index f6cf7d4..0000000
--- a/tests/sample_errors/expr_item_not_cast.err
+++ /dev/null
@@ -1 +0,0 @@
-4,20,4,"item" must be cast to its object type
\ No newline at end of file
diff --git a/tests/sample_errors/expr_value_assignment.blp b/tests/sample_errors/expr_value_assignment.blp
deleted file mode 100644
index 51d778f..0000000
--- a/tests/sample_errors/expr_value_assignment.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-Label {
- label: expr 1;
-}
diff --git a/tests/sample_errors/expr_value_assignment.err b/tests/sample_errors/expr_value_assignment.err
deleted file mode 100644
index 1c7092a..0000000
--- a/tests/sample_errors/expr_value_assignment.err
+++ /dev/null
@@ -1 +0,0 @@
-4,10,4,Cannot convert Gtk.Expression to string
\ No newline at end of file
diff --git a/tests/sample_errors/expr_value_closure_arg.blp b/tests/sample_errors/expr_value_closure_arg.blp
deleted file mode 100644
index 7f828c4..0000000
--- a/tests/sample_errors/expr_value_closure_arg.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-BoolFilter {
- expression: expr $closure(item as ) as ;
-}
diff --git a/tests/sample_errors/expr_value_closure_arg.err b/tests/sample_errors/expr_value_closure_arg.err
deleted file mode 100644
index b9e19f8..0000000
--- a/tests/sample_errors/expr_value_closure_arg.err
+++ /dev/null
@@ -1 +0,0 @@
-4,29,4,"item" can only be used for looking up properties
\ No newline at end of file
diff --git a/tests/sample_errors/expr_value_item.blp b/tests/sample_errors/expr_value_item.blp
deleted file mode 100644
index 141c806..0000000
--- a/tests/sample_errors/expr_value_item.blp
+++ /dev/null
@@ -1,5 +0,0 @@
-using Gtk 4.0;
-
-BoolFilter {
- expression: expr item as