diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1ec071e..6d373cc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ stages:
- pages
build:
- image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
+ image: registry.gitlab.gnome.org/gnome/blueprint-compiler
stage: build
script:
- black --check --diff ./ tests
@@ -33,7 +33,7 @@ build:
path: coverage.xml
fuzz:
- image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
+ image: registry.gitlab.gnome.org/gnome/blueprint-compiler
stage: build
script:
- meson _build
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 220c117..3ab4fa2 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 (Twitter, TWIG, etc.)
+6. Announce the release through relevant channels (Mastodon, TWIG, etc.)
## Related projects
diff --git a/NEWS.md b/NEWS.md
index 389f82c..a12dab0 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,35 @@
+# 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
new file mode 100644
index 0000000..f3e4000
--- /dev/null
+++ b/blueprint-compiler.doap
@@ -0,0 +1,27 @@
+
+
+ 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/ast_utils.py b/blueprintcompiler/ast_utils.py
index 8f742e0..b0c5357 100644
--- a/blueprintcompiler/ast_utils.py
+++ b/blueprintcompiler/ast_utils.py
@@ -196,6 +196,13 @@ class AstNode:
return None
+ def get_child_at(self, idx: int) -> "AstNode":
+ for child in self.children:
+ if idx in child.range:
+ return child.get_child_at(idx)
+
+ return self
+
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
for child in self.children:
yield from child.get_semantic_tokens()
diff --git a/blueprintcompiler/completions.py b/blueprintcompiler/completions.py
index b10ec3e..81dd03d 100644
--- a/blueprintcompiler/completions.py
+++ b/blueprintcompiler/completions.py
@@ -21,9 +21,20 @@ import typing as T
from . import annotations, gir, language
from .ast_utils import AstNode
-from .completions_utils import *
+from .completions_utils import (
+ CompletionContext,
+ CompletionItemKind,
+ CompletionPriority,
+ completer,
+ completers,
+ get_object_id_completions,
+ get_property_completion,
+ get_sort_key,
+ new_statement_patterns,
+)
+from .language.contexts import ValueTypeCtx
from .language.types import ClassName
-from .lsp_utils import Completion, CompletionItemKind
+from .lsp_utils import Completion, CompletionItemKind, TextEdit, get_docs_section
from .parser import SKIP_TOKENS
from .tokenizer import Token, TokenType
@@ -31,15 +42,13 @@ Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
def _complete(
- lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
+ lsp,
+ ast_node: AstNode,
+ tokens: T.List[Token],
+ idx: int,
+ token_idx: int,
+ next_token: Token,
) -> T.Iterator[Completion]:
- for child in ast_node.children:
- if child.group.start <= idx and (
- idx < child.group.end or (idx == child.group.end and child.incomplete)
- ):
- yield from _complete(lsp, child, tokens, idx, token_idx)
- return
-
prev_tokens: T.List[Token] = []
# collect the 5 previous non-skipped tokens
@@ -49,8 +58,8 @@ def _complete(
prev_tokens.insert(0, token)
token_idx -= 1
- for completer in ast_node.completers:
- yield from completer(prev_tokens, ast_node, lsp)
+ for completer in completers:
+ yield from completer(prev_tokens, next_token, ast_node, lsp, idx)
def complete(
@@ -62,147 +71,258 @@ def complete(
if token.start < idx <= token.end:
token_idx = i
+ if tokens[token_idx].type == TokenType.EOF:
+ next_token = tokens[token_idx]
+ else:
+ next_token_idx = token_idx + 1
+ while tokens[next_token_idx].type == TokenType.WHITESPACE:
+ next_token_idx += 1
+ next_token = tokens[next_token_idx]
+
# if the current token is an identifier or whitespace, move to the token before it
- while tokens[token_idx].type in [TokenType.IDENT, TokenType.WHITESPACE]:
+ if tokens[token_idx].type == TokenType.IDENT:
idx = tokens[token_idx].start
token_idx -= 1
- yield from _complete(lsp, ast_node, tokens, idx, token_idx)
+ while tokens[token_idx].type == TokenType.WHITESPACE:
+ idx = tokens[token_idx].start
+ token_idx -= 1
+
+ child_node = ast_node.get_child_at(idx)
+ # If the cursor is at the end of a node, completions should be for the next child of the parent, unless the node
+ # is incomplete.
+ while (
+ child_node.range.end == idx
+ and not child_node.incomplete
+ and child_node.parent is not None
+ ):
+ child_node = child_node.parent
+
+ yield from _complete(lsp, child_node, tokens, idx, token_idx, next_token)
@completer([language.GtkDirective])
-def using_gtk(lsp, ast_node, match_variables):
+def using_gtk(_ctx: CompletionContext):
yield Completion(
"using Gtk 4.0", CompletionItemKind.Keyword, snippet="using Gtk 4.0;\n"
)
+@completer([language.UI])
+def using(ctx: CompletionContext):
+ imported_namespaces = set(
+ [import_.namespace for import_ in ctx.ast_node.root.using]
+ )
+
+ # Import statements must be before any content
+ for i in ctx.ast_node.root.children:
+ if not isinstance(i, language.GtkDirective) and not isinstance(
+ i, language.Import
+ ):
+ if ctx.index >= i.range.end:
+ return
+
+ for ns, version in gir.get_available_namespaces():
+ if ns not in imported_namespaces and ns != "Gtk":
+ yield Completion(
+ f"using {ns} {version}",
+ CompletionItemKind.Module,
+ text=f"using {ns} {version};",
+ sort_text=get_sort_key(CompletionPriority.NAMESPACE, ns),
+ )
+
+
+@completer([language.UI])
+def translation_domain(ctx: CompletionContext):
+ if ctx.ast_node.root.translation_domain is not None:
+ return
+
+ # Translation domain must be after the import statements but before any content
+ for i in ctx.ast_node.root.children:
+ if isinstance(i, language.Import):
+ if ctx.index <= i.range.start:
+ return
+ elif not isinstance(i, language.GtkDirective):
+ if ctx.index >= i.range.end:
+ return
+
+ yield Completion(
+ "translation-domain",
+ CompletionItemKind.Keyword,
+ sort_text=get_sort_key(CompletionPriority.KEYWORD, "translation-domain"),
+ snippet='translation-domain "$0";',
+ docs=get_docs_section("Syntax TranslationDomain"),
+ )
+
+
+def _available_namespace_completions(ctx: CompletionContext):
+ imported_namespaces = set(
+ [import_.namespace for import_ in ctx.ast_node.root.using]
+ )
+
+ for ns, version in gir.get_available_namespaces():
+ if ns not in imported_namespaces and ns != "Gtk":
+ yield Completion(
+ ns,
+ CompletionItemKind.Module,
+ text=ns + ".",
+ sort_text=get_sort_key(CompletionPriority.IMPORT_NAMESPACE, ns),
+ signature=f" using {ns} {version}",
+ additional_text_edits=[
+ TextEdit(
+ ctx.ast_node.root.import_range(ns), f"\nusing {ns} {version};"
+ )
+ ],
+ )
+
+
@completer(
- applies_in=[language.UI, language.ObjectContent, language.Template],
+ applies_in=[
+ language.UI,
+ language.ObjectContent,
+ language.Template,
+ language.TypeName,
+ language.BracketedTypeName,
+ ],
matches=new_statement_patterns,
)
-def namespace(lsp, ast_node, match_variables):
+def namespace(ctx: CompletionContext):
yield Completion("Gtk", CompletionItemKind.Module, text="Gtk.")
- for ns in ast_node.root.children[language.Import]:
+
+ for ns in ctx.ast_node.root.children[language.Import]:
if ns.gir_namespace is not None:
yield Completion(
ns.gir_namespace.name,
CompletionItemKind.Module,
text=ns.gir_namespace.name + ".",
+ sort_text=get_sort_key(
+ CompletionPriority.NAMESPACE, ns.gir_namespace.name
+ ),
)
+ yield from _available_namespace_completions(ctx)
+
@completer(
- applies_in=[language.UI, language.ObjectContent, language.Template],
+ applies_in=[
+ language.UI,
+ language.ObjectContent,
+ language.Template,
+ language.TypeName,
+ language.BracketedTypeName,
+ ],
matches=[
[(TokenType.IDENT, None), (TokenType.OP, "."), (TokenType.IDENT, None)],
[(TokenType.IDENT, None), (TokenType.OP, ".")],
],
)
-def object_completer(lsp, ast_node, match_variables):
- ns = ast_node.root.gir.namespaces.get(match_variables[0])
+def object_completer(ctx: CompletionContext):
+ ns = ctx.ast_node.root.gir.namespaces.get(ctx.match_variables[0])
if ns is not None:
for c in ns.classes.values():
+ snippet = c.name
+ if (
+ str(ctx.next_token) != "{"
+ and not isinstance(ctx.ast_node, language.TypeName)
+ and not isinstance(ctx.ast_node, language.BracketedTypeName)
+ ):
+ snippet += " {\n $0\n}"
+
yield Completion(
c.name,
CompletionItemKind.Class,
- snippet=f"{c.name} {{\n $0\n}}",
+ sort_text=get_sort_key(CompletionPriority.CLASS, c.name),
+ snippet=snippet,
docs=c.doc,
detail=c.detail,
)
@completer(
- applies_in=[language.UI, language.ObjectContent, language.Template],
+ applies_in=[
+ language.UI,
+ language.ObjectContent,
+ language.Template,
+ language.TypeName,
+ language.BracketedTypeName,
+ ],
matches=new_statement_patterns,
)
-def gtk_object_completer(lsp, ast_node, match_variables):
- ns = ast_node.root.gir.namespaces.get("Gtk")
+def gtk_object_completer(ctx: CompletionContext):
+ ns = ctx.ast_node.root.gir.namespaces.get("Gtk")
if ns is not None:
for c in ns.classes.values():
+ snippet = c.name
+ if (
+ str(ctx.next_token) != "{"
+ and not isinstance(ctx.ast_node, language.TypeName)
+ and not isinstance(ctx.ast_node, language.BracketedTypeName)
+ ):
+ snippet += " {\n $0\n}"
+
yield Completion(
c.name,
CompletionItemKind.Class,
- snippet=f"{c.name} {{\n $0\n}}",
+ sort_text=get_sort_key(CompletionPriority.CLASS, c.name),
+ snippet=snippet,
docs=c.doc,
detail=c.detail,
)
+ if isinstance(ctx.ast_node, language.BracketedTypeName) or (
+ isinstance(ctx.ast_node, language.TypeName)
+ and not isinstance(ctx.ast_node, language.ClassName)
+ ):
+ for basic_type in gir.BASIC_TYPES:
+ yield Completion(
+ basic_type,
+ CompletionItemKind.Class,
+ sort_text=get_sort_key(CompletionPriority.CLASS, basic_type),
+ )
+
@completer(
applies_in=[language.ObjectContent],
matches=new_statement_patterns,
)
-def property_completer(lsp, ast_node, match_variables):
- if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
- for prop_name, prop in ast_node.gir_class.properties.items():
- if (
- isinstance(prop.type, gir.BoolType)
- and lsp.client_supports_completion_choice
- ):
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=f"{prop_name}: ${{1|true,false|}};",
- docs=prop.doc,
- detail=prop.detail,
- )
- elif isinstance(prop.type, gir.StringType):
- snippet = (
- f'{prop_name}: _("$0");'
- if annotations.is_property_translated(prop)
- else f'{prop_name}: "$0";'
- )
-
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=snippet,
- docs=prop.doc,
- detail=prop.detail,
- )
- elif (
- isinstance(prop.type, gir.Enumeration)
- and len(prop.type.members) <= 10
- and lsp.client_supports_completion_choice
- ):
- choices = ",".join(prop.type.members.keys())
- yield Completion(
- prop_name,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=f"{prop_name}: ${{1|{choices}|}};",
- docs=prop.doc,
- detail=prop.detail,
- )
- 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,
- CompletionItemKind.Property,
- sort_text=f"0 {prop_name}",
- snippet=f"{prop_name}: $0;",
- docs=prop.doc,
- detail=prop.detail,
- )
+def property_completer(ctx: CompletionContext):
+ assert isinstance(ctx.ast_node, language.ObjectContent)
+ if ctx.ast_node.gir_class and hasattr(ctx.ast_node.gir_class, "properties"):
+ for prop_name, prop in ctx.ast_node.gir_class.properties.items():
+ yield get_property_completion(
+ prop_name,
+ prop.type,
+ ctx,
+ annotations.is_property_translated(prop),
+ prop.doc,
+ )
@completer(
applies_in=[language.Property, language.A11yProperty],
- matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
+ matches=[
+ [(TokenType.IDENT, None), (TokenType.OP, ":")],
+ [(TokenType.PUNCTUATION, ",")],
+ [(TokenType.PUNCTUATION, "[")],
+ ],
)
-def prop_value_completer(lsp, ast_node, match_variables):
- if (vt := ast_node.value_type) is not None:
+def prop_value_completer(ctx: CompletionContext):
+ if isinstance(ctx.ast_node, language.Property):
+ yield Completion(
+ "bind",
+ CompletionItemKind.Keyword,
+ snippet="bind $0",
+ docs=get_docs_section("Syntax Binding"),
+ sort_text=get_sort_key(CompletionPriority.KEYWORD, "bind"),
+ )
+
+ assert isinstance(ctx.ast_node, language.Property) or isinstance(
+ ctx.ast_node, language.A11yProperty
+ )
+
+ if (vt := ctx.ast_node.value_type) is not None:
+ assert isinstance(vt, ValueTypeCtx)
+
if isinstance(vt.value_type, gir.Enumeration):
for name, member in vt.value_type.members.items():
yield Completion(
@@ -210,43 +330,148 @@ def prop_value_completer(lsp, ast_node, match_variables):
CompletionItemKind.EnumMember,
docs=member.doc,
detail=member.detail,
+ sort_text=get_sort_key(CompletionPriority.ENUM_MEMBER, name),
)
elif isinstance(vt.value_type, gir.BoolType):
- yield Completion("true", CompletionItemKind.Constant)
- yield Completion("false", CompletionItemKind.Constant)
+ yield Completion(
+ "true",
+ CompletionItemKind.Constant,
+ sort_text=get_sort_key(CompletionPriority.ENUM_MEMBER, "true"),
+ )
+ yield Completion(
+ "false",
+ CompletionItemKind.Constant,
+ sort_text=get_sort_key(CompletionPriority.ENUM_MEMBER, "false"),
+ )
+
+ elif isinstance(vt.value_type, gir.Class) or isinstance(
+ vt.value_type, gir.Interface
+ ):
+ if vt.allow_null:
+ yield Completion(
+ "null",
+ CompletionItemKind.Constant,
+ sort_text=get_sort_key(CompletionPriority.KEYWORD, "null"),
+ )
+
+ yield from get_object_id_completions(ctx, vt.value_type)
+
+ if isinstance(ctx.ast_node, language.Property):
+ yield from _available_namespace_completions(ctx)
+
+ for ns in ctx.ast_node.root.gir.namespaces.values():
+ for c in ns.classes.values():
+ if not c.abstract and c.assignable_to(vt.value_type):
+ name = (
+ c.name if ns.name == "Gtk" else ns.name + "." + c.name
+ )
+ snippet = name
+ if str(ctx.next_token) != "{":
+ snippet += " {\n $0\n}"
+ yield Completion(
+ name,
+ CompletionItemKind.Class,
+ sort_text=get_sort_key(CompletionPriority.CLASS, name),
+ snippet=snippet,
+ detail=c.detail,
+ docs=c.doc,
+ )
@completer(
applies_in=[language.ObjectContent],
matches=new_statement_patterns,
)
-def signal_completer(lsp, ast_node, match_variables):
- if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
- for signal_name, signal in ast_node.gir_class.signals.items():
- if not isinstance(ast_node.parent, language.Object):
- name = "on"
+def signal_completer(ctx: CompletionContext):
+ assert isinstance(ctx.ast_node, language.ObjectContent)
+
+ if ctx.ast_node.gir_class and hasattr(ctx.ast_node.gir_class, "signals"):
+ for signal_name, signal in ctx.ast_node.gir_class.signals.items():
+ if str(ctx.next_token) == "=>":
+ snippet = signal_name
else:
- name = "on_" + (
- ast_node.parent.children[ClassName][0].tokens["id"]
- or ast_node.parent.children[ClassName][0]
- .tokens["class_name"]
- .lower()
- )
+ if not isinstance(ctx.ast_node.parent, language.Object):
+ name = "on"
+ else:
+ name = "on_" + (
+ ctx.ast_node.parent.children[ClassName][0].tokens["id"]
+ or ctx.ast_node.parent.children[ClassName][0]
+ .tokens["class_name"]
+ .lower()
+ )
+
+ snippet = f"{signal_name} => \\$${{1:{name}_{signal_name.replace('-', '_')}}}()$0;"
+
yield Completion(
signal_name,
CompletionItemKind.Event,
- sort_text=f"1 {signal_name}",
- snippet=f"{signal_name} => \\$${{1:${name}_{signal_name.replace('-', '_')}}}()$0;",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, signal_name),
+ snippet=snippet,
docs=signal.doc,
detail=signal.detail,
)
@completer(applies_in=[language.UI], matches=new_statement_patterns)
-def template_completer(lsp, ast_node, match_variables):
+def template_completer(_ctx: CompletionContext):
yield Completion(
"template",
CompletionItemKind.Snippet,
snippet="template ${1:ClassName} : ${2:ParentClass} {\n $0\n}",
)
+
+
+@completer(
+ applies_in=[language.ObjectContent, language.ChildType],
+ matches=[[(TokenType.PUNCTUATION, "[")]],
+ applies_in_subclass=[("Gtk", "Dialog"), ("Gtk", "InfoBar")],
+)
+def response_id_completer(ctx: CompletionContext):
+ yield Completion(
+ "action",
+ CompletionItemKind.Snippet,
+ sort_text=get_sort_key(CompletionPriority.KEYWORD, "action"),
+ snippet="action response=$0",
+ )
+
+
+@completer(
+ [language.ChildAnnotation, language.ExtResponse],
+ [[(TokenType.IDENT, "action"), (TokenType.IDENT, "response"), (TokenType.OP, "=")]],
+)
+def complete_response_id(ctx: CompletionContext):
+ gir = ctx.ast_node.root.gir
+ response_type = gir.get_type("ResponseType", "Gtk")
+ yield from [
+ Completion(
+ name,
+ kind=CompletionItemKind.EnumMember,
+ docs=member.doc,
+ )
+ for name, member in response_type.members.items()
+ ]
+
+
+@completer(
+ [language.ChildAnnotation, language.ExtResponse],
+ [
+ [
+ (TokenType.IDENT, "action"),
+ (TokenType.IDENT, "response"),
+ (TokenType.OP, "="),
+ (TokenType.IDENT, None),
+ ],
+ [
+ (TokenType.IDENT, "action"),
+ (TokenType.IDENT, "response"),
+ (TokenType.OP, "="),
+ (TokenType.NUMBER, None),
+ ],
+ ],
+)
+def complete_response_default(ctx: CompletionContext):
+ yield Completion(
+ "default",
+ kind=CompletionItemKind.Keyword,
+ )
diff --git a/blueprintcompiler/completions_utils.py b/blueprintcompiler/completions_utils.py
index eccf125..970d429 100644
--- a/blueprintcompiler/completions_utils.py
+++ b/blueprintcompiler/completions_utils.py
@@ -19,28 +19,78 @@
import typing as T
+from dataclasses import dataclass
+from enum import Enum
-from .lsp_utils import Completion
+from . import gir, language
+from .ast_utils import AstNode
+from .lsp_utils import Completion, CompletionItemKind
from .tokenizer import Token, TokenType
+
+class CompletionPriority(Enum):
+ ENUM_MEMBER = "00"
+ NAMED_OBJECT = "01"
+ OBJECT_MEMBER = "02"
+ CLASS = "03"
+ NAMESPACE = "04"
+ KEYWORD = "05"
+ # An available namespace that hasn't been imported yet
+ IMPORT_NAMESPACE = "99"
+
+
+def get_sort_key(priority: CompletionPriority, name: str):
+ return f"{priority.value} {name}"
+
+
+@dataclass
+class CompletionContext:
+ client_supports_completion_choice: bool
+ ast_node: AstNode
+ match_variables: T.List[str]
+ next_token: Token
+ index: int
+
+
new_statement_patterns = [
[(TokenType.PUNCTUATION, "{")],
[(TokenType.PUNCTUATION, "}")],
[(TokenType.PUNCTUATION, "]")],
[(TokenType.PUNCTUATION, ";")],
+ [(TokenType.OP, "<")],
]
+completers = []
+
+
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):
+ def decorator(func: T.Callable[[CompletionContext], T.Generator[Completion]]):
+ def inner(
+ prev_tokens: T.List[Token], next_token: Token, ast_node, lsp, idx: int
+ ):
+ if not any(isinstance(ast_node, rule) for rule in applies_in):
+ return
+
# For completers that apply in ObjectContent nodes, we can further
# check that the object is the right class
if applies_in_subclass is not None:
- type = ast_node.root.gir.get_type(
- applies_in_subclass[1], applies_in_subclass[0]
- )
- if not ast_node.gir_class or not ast_node.gir_class.assignable_to(type):
+ parent_obj = ast_node
+ while parent_obj is not None and not hasattr(parent_obj, "gir_class"):
+ parent_obj = parent_obj.parent
+
+ if (
+ parent_obj is None
+ or not parent_obj.gir_class
+ or not any(
+ [
+ parent_obj.gir_class.assignable_to(
+ parent_obj.root.gir.get_type(c[1], c[0])
+ )
+ for c in applies_in_subclass
+ ]
+ )
+ ):
return
any_match = len(matches) == 0
@@ -66,10 +116,65 @@ def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None
if not any_match:
return
- yield from func(lsp, ast_node, match_variables)
+ context = CompletionContext(
+ client_supports_completion_choice=lsp.client_supports_completion_choice,
+ ast_node=ast_node,
+ match_variables=match_variables,
+ next_token=next_token,
+ index=idx,
+ )
+ yield from func(context)
- for c in applies_in:
- c.completers.append(inner)
+ completers.append(inner)
return inner
return decorator
+
+
+def get_property_completion(
+ name: str,
+ type: gir.GirType,
+ ctx: CompletionContext,
+ translated: bool,
+ doc: str,
+) -> Completion:
+ if str(ctx.next_token) == ":":
+ snippet = name
+ elif isinstance(type, gir.BoolType) and ctx.client_supports_completion_choice:
+ snippet = f"{name}: ${{1|true,false|}};"
+ elif isinstance(type, gir.StringType):
+ snippet = f'{name}: _("$0");' if translated else f'{name}: "$0";'
+ elif (
+ isinstance(type, gir.Enumeration)
+ and len(type.members) <= 10
+ and ctx.client_supports_completion_choice
+ ):
+ choices = ",".join(type.members.keys())
+ snippet = f"{name}: ${{1|{choices}|}};"
+ elif type.full_name == "Gtk.Expression":
+ snippet = f"{name}: expr $0;"
+ else:
+ snippet = f"{name}: $0;"
+
+ return Completion(
+ name,
+ CompletionItemKind.Property,
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, name),
+ snippet=snippet,
+ docs=doc,
+ )
+
+
+def get_object_id_completions(
+ ctx: CompletionContext, value_type: T.Optional[gir.GirType] = None
+):
+ for id, obj in ctx.ast_node.root.context[language.ScopeCtx].objects.items():
+ if value_type is None or (
+ obj.gir_class is not None and obj.gir_class.assignable_to(value_type)
+ ):
+ yield Completion(
+ id,
+ CompletionItemKind.Variable,
+ signature=" " + obj.signature,
+ sort_text=get_sort_key(CompletionPriority.NAMED_OBJECT, id),
+ )
diff --git a/blueprintcompiler/decompiler.py b/blueprintcompiler/decompiler.py
index de6c06f..850b6d8 100644
--- a/blueprintcompiler/decompiler.py
+++ b/blueprintcompiler/decompiler.py
@@ -255,7 +255,11 @@ def decompile_element(
ctx._node_stack.append(xml)
ctx.start_block()
- gir = decompiler(*args, **kwargs)
+
+ try:
+ gir = decompiler(*args, **kwargs)
+ except TypeError as e:
+ raise UnsupportedError(tag=xml.tag)
if not decompiler._skip_children:
for child in xml.children:
@@ -266,8 +270,6 @@ 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 1e7297c..f5d2e06 100644
--- a/blueprintcompiler/errors.py
+++ b/blueprintcompiler/errors.py
@@ -92,29 +92,38 @@ class CompileError(PrintableError):
def pretty_print(self, filename: str, code: str, stream=sys.stdout) -> None:
assert self.range is not None
- line_num, col_num = utils.idx_to_pos(self.range.start + 1, code)
- end_line_num, end_col_num = utils.idx_to_pos(self.range.end + 1, code)
- line = code.splitlines(True)[line_num] if code != "" else ""
+ def format_line(range: Range):
+ line_num, col_num = utils.idx_to_pos(range.start, code)
+ end_line_num, end_col_num = utils.idx_to_pos(range.end, code)
+ line = code.splitlines(True)[line_num] if code != "" else ""
- # Display 1-based line numbers
- line_num += 1
- end_line_num += 1
+ # Display 1-based line numbers
+ line_num += 1
+ end_line_num += 1
+ col_num += 1
+ end_col_num += 1
- n_spaces = col_num - 1
- n_carets = (
- (end_col_num - col_num)
- if line_num == end_line_num
- else (len(line) - n_spaces - 1)
- )
+ n_spaces = col_num - 1
+ n_carets = (
+ (end_col_num - col_num)
+ if line_num == end_line_num
+ else (len(line) - n_spaces - 1)
+ )
- n_spaces += line.count("\t", 0, col_num)
- n_carets += line.count("\t", col_num, col_num + n_carets)
- line = line.replace("\t", " ")
+ n_spaces += line.count("\t", 0, col_num)
+ n_carets += line.count("\t", col_num, col_num + n_carets)
+ line = line.replace("\t", " ")
+
+ n_carets = max(n_carets, 1)
+
+ return line_num, col_num, line.rstrip(), (" " * n_spaces) + ("^" * n_carets)
+
+ line_num, col_num, line, carets = format_line(self.range)
stream.write(
f"""{self.color}{Colors.BOLD}{self.category}: {self.message}{Colors.CLEAR}
at {filename} line {line_num} column {col_num}:
-{Colors.FAINT}{line_num :>4} |{Colors.CLEAR}{line.rstrip()}\n {Colors.FAINT}|{" "*n_spaces}{"^"*n_carets}{Colors.CLEAR}\n"""
+{Colors.FAINT}{line_num :>4} |{Colors.CLEAR}{line}\n {Colors.FAINT}|{carets}{Colors.CLEAR}\n"""
)
for hint in self.hints:
@@ -139,14 +148,12 @@ at {filename} line {line_num} column {col_num}:
)
for ref in self.references:
- line_num, col_num = utils.idx_to_pos(ref.range.start + 1, code)
- line = code.splitlines(True)[line_num]
- line_num += 1
+ line_num, col_num, line, carets = format_line(ref.range)
stream.write(
f"""{Colors.FAINT}note: {ref.message}:
at {filename} line {line_num} column {col_num}:
-{Colors.FAINT}{line_num :>4} |{line.rstrip()}\n {Colors.FAINT}|{" "*(col_num-1)}^{Colors.CLEAR}\n"""
+{Colors.FAINT}{line_num :>4} |{line}\n {Colors.FAINT}|{carets}{Colors.CLEAR}\n"""
)
stream.write("\n")
@@ -219,7 +226,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/jwestman/blueprint-compiler/-/issues/new?issue
+{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue
{Colors.CLEAR}"""
)
diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py
index 333f4ac..6392eb4 100644
--- a/blueprintcompiler/gir.py
+++ b/blueprintcompiler/gir.py
@@ -289,7 +289,7 @@ class TypeType(BasicType):
return isinstance(other, TypeType)
-_BASIC_TYPES = {
+BASIC_TYPES = {
"bool": BoolType,
"string": StringType,
"int": IntType,
@@ -914,7 +914,7 @@ class Namespace(GirNode):
def get_type_by_cname(self, cname: str) -> T.Optional[GirType]:
"""Gets a type from this namespace by its C name."""
- for basic in _BASIC_TYPES.values():
+ for basic in BASIC_TYPES.values():
if basic.glib_type_name == cname:
return basic()
@@ -1036,8 +1036,8 @@ class GirContext:
return None
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
- if ns is None and name in _BASIC_TYPES:
- return _BASIC_TYPES[name]()
+ if ns is None and name in BASIC_TYPES:
+ return BASIC_TYPES[name]()
ns = ns or "Gtk"
diff --git a/blueprintcompiler/interactive_port.py b/blueprintcompiler/interactive_port.py
index 0c37885..12dd485 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/jwestman/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
+{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/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/jwestman/blueprint-compiler.git
+url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
revision = {VERSION}
depth = 1
diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py
index 5eb2b60..88d7538 100644
--- a/blueprintcompiler/language/__init__.py
+++ b/blueprintcompiler/language/__init__.py
@@ -34,10 +34,17 @@ from .gtk_scale import ExtScaleMarks
from .gtk_size_group import ExtSizeGroupWidgets
from .gtk_string_list import ExtStringListStrings
from .gtk_styles import ExtStyles
-from .gtkbuilder_child import Child, ChildExtension, ChildInternal, ChildType
+from .gtkbuilder_child import (
+ Child,
+ ChildAnnotation,
+ ChildExtension,
+ ChildInternal,
+ ChildType,
+)
from .gtkbuilder_template import Template
from .imports import GtkDirective, Import
-from .types import ClassName
+from .response_id import ExtResponse
+from .types import BracketedTypeName, ClassName, TypeName
from .ui import UI
from .values import (
ArrayValue,
diff --git a/blueprintcompiler/language/adw_breakpoint.py b/blueprintcompiler/language/adw_breakpoint.py
index 4ad5b24..3d2c10d 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) -> Value:
- return self.children[Value][0]
+ def value(self) -> T.Optional[Value]:
+ return self.children[Value][0] if len(self.children[Value]) > 0 else None
@property
def gir_class(self) -> T.Optional[GirType]:
@@ -106,7 +106,10 @@ class AdwBreakpointSetter(AstNode):
return None
@property
- def document_symbol(self) -> DocumentSymbol:
+ def document_symbol(self) -> T.Optional[DocumentSymbol]:
+ if self.value is None:
+ return None
+
return DocumentSymbol(
f"{self.object_id}.{self.property_name}",
SymbolKind.Property,
diff --git a/blueprintcompiler/language/adw_response_dialog.py b/blueprintcompiler/language/adw_response_dialog.py
index d2680fd..b1b43a4 100644
--- a/blueprintcompiler/language/adw_response_dialog.py
+++ b/blueprintcompiler/language/adw_response_dialog.py
@@ -140,21 +140,10 @@ class ExtAdwResponseDialog(AstNode):
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Adw", "MessageDialog"),
+ applies_in_subclass=[("Adw", "AlertDialog"), ("Adw", "MessageDialog")],
matches=new_statement_patterns,
)
-def complete_adw_message_dialog(lsp, ast_node, match_variables):
- yield Completion(
- "responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
- )
-
-
-@completer(
- applies_in=[ObjectContent],
- applies_in_subclass=("Adw", "AlertDialog"),
- matches=new_statement_patterns,
-)
-def complete_adw_alert_dialog(lsp, ast_node, match_variables):
+def complete_adw_message_dialog(_ctx: CompletionContext):
yield Completion(
"responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
)
diff --git a/blueprintcompiler/language/common.py b/blueprintcompiler/language/common.py
index 1cc1b3b..9bd04a5 100644
--- a/blueprintcompiler/language/common.py
+++ b/blueprintcompiler/language/common.py
@@ -34,6 +34,7 @@ from ..errors import (
CompileError,
CompileWarning,
DeprecatedWarning,
+ ErrorReference,
MultipleErrors,
UnusedWarning,
UpgradeWarning,
diff --git a/blueprintcompiler/language/contexts.py b/blueprintcompiler/language/contexts.py
index 6e26048..38d84f4 100644
--- a/blueprintcompiler/language/contexts.py
+++ b/blueprintcompiler/language/contexts.py
@@ -48,7 +48,7 @@ class ScopeCtx:
return self.node
@cached_property
- def objects(self) -> T.Dict[str, Object]:
+ def objects(self) -> T.Dict[str, AstNode]:
return {
obj.tokens["id"]: obj
for obj in self._iter_recursive(self.node)
@@ -58,7 +58,7 @@ class ScopeCtx:
def validate_unique_ids(self) -> None:
from .gtk_list_item_factory import ExtListItemFactory
- passed = {}
+ passed: T.Dict[str, AstNode] = {}
for obj in self._iter_recursive(self.node):
if obj.tokens["id"] is None:
continue
@@ -71,10 +71,16 @@ class ScopeCtx:
raise CompileError(
f"Duplicate object ID '{obj.tokens['id']}'",
token.range,
+ references=[
+ ErrorReference(
+ passed[obj.tokens["id"]].group.tokens["id"].range,
+ "previous declaration was here",
+ )
+ ],
)
passed[obj.tokens["id"]] = obj
- def _iter_recursive(self, node: AstNode):
+ def _iter_recursive(self, node: AstNode) -> T.Generator[AstNode, T.Any, None]:
yield node
for child in node.children:
if child.context[ScopeCtx] is self:
diff --git a/blueprintcompiler/language/expression.py b/blueprintcompiler/language/expression.py
index e0b4246..910cd71 100644
--- a/blueprintcompiler/language/expression.py
+++ b/blueprintcompiler/language/expression.py
@@ -21,7 +21,7 @@
from ..decompiler import decompile_element
from .common import *
from .contexts import ScopeCtx, ValueTypeCtx
-from .types import TypeName
+from .types import BracketedTypeName, TypeName
expr = Sequence()
@@ -196,7 +196,7 @@ class CastExpr(InfixExpr):
grammar = [
Keyword("as"),
AnyOf(
- ["<", TypeName, Match(">").expected()],
+ BracketedTypeName,
[
UseExact("lparen", "("),
TypeName,
@@ -211,7 +211,13 @@ class CastExpr(InfixExpr):
@property
def type(self) -> T.Optional[GirType]:
- return self.children[TypeName][0].gir_type
+ if len(self.children[BracketedTypeName]) == 1:
+ type_name = self.children[BracketedTypeName][0].type_name
+ return None if type_name is None else type_name.gir_type
+ elif len(self.children[TypeName]) == 1:
+ return self.children[TypeName][0].gir_type
+ else:
+ return None
@validate()
def cast_makes_sense(self):
@@ -302,12 +308,18 @@ expr.children = [
@decompiler("lookup", skip_children=True, cdata=True)
def decompile_lookup(
- ctx: DecompileCtx, gir: gir.GirContext, cdata: str, name: str, type: str
+ ctx: DecompileCtx,
+ gir: gir.GirContext,
+ cdata: str,
+ name: str,
+ type: T.Optional[str] = None,
):
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
ctx.print("expr ")
- if t := ctx.type_by_cname(type):
+ if type is None:
+ type = ""
+ elif t := ctx.type_by_cname(type):
type = decompile.full_name(t)
else:
type = "$" + type
@@ -327,7 +339,7 @@ def decompile_lookup(
if constant == ctx.template_class:
ctx.print("template." + name)
elif constant == "":
- ctx.print("item as <" + type + ">." + name)
+ ctx.print(f"item as <{type}>.{name}")
else:
ctx.print(constant + "." + name)
return
diff --git a/blueprintcompiler/language/gobject_property.py b/blueprintcompiler/language/gobject_property.py
index 50a7512..67f2555 100644
--- a/blueprintcompiler/language/gobject_property.py
+++ b/blueprintcompiler/language/gobject_property.py
@@ -26,7 +26,11 @@ from .values import ArrayValue, ExprValue, ObjectValue, Value
class Property(AstNode):
grammar = Statement(
- UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
+ UseIdent("name"),
+ ":",
+ AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue).expected(
+ "property value"
+ ),
)
@property
diff --git a/blueprintcompiler/language/gobject_signal.py b/blueprintcompiler/language/gobject_signal.py
index 9c27b97..b6afb09 100644
--- a/blueprintcompiler/language/gobject_signal.py
+++ b/blueprintcompiler/language/gobject_signal.py
@@ -225,8 +225,14 @@ class Signal(AstNode):
@decompiler("signal")
-def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", object=None):
+def decompile_signal(
+ ctx: DecompileCtx, gir, name, handler, swapped=None, 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})"
@@ -241,3 +247,11 @@ def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", objec
line += ";"
ctx.print(line)
return gir
+
+
+@completer(
+ [Signal],
+ [[(TokenType.PUNCTUATION, "(")]],
+)
+def signal_object_completer(ctx: CompletionContext):
+ yield from get_object_id_completions(ctx)
diff --git a/blueprintcompiler/language/gtk_a11y.py b/blueprintcompiler/language/gtk_a11y.py
index 0cc3cb3..2a895ef 100644
--- a/blueprintcompiler/language/gtk_a11y.py
+++ b/blueprintcompiler/language/gtk_a11y.py
@@ -25,12 +25,13 @@ from .gobject_object import ObjectContent, validate_parent_type
from .values import Value
-def get_property_types(gir):
+def get_property_types(gir: gir.GirContext) -> T.Dict[str, T.Optional[GirType]]:
# from
return {
"autocomplete": gir.get_type("AccessibleAutocomplete", "Gtk"),
"description": StringType(),
"has-popup": BoolType(),
+ "help-text": StringType(),
"key-shortcuts": StringType(),
"label": StringType(),
"level": IntType(),
@@ -50,7 +51,7 @@ def get_property_types(gir):
}
-def get_relation_types(gir):
+def get_relation_types(gir: gir.GirContext) -> T.Dict[str, T.Optional[GirType]]:
# from
widget = gir.get_type("Widget", "Gtk")
return {
@@ -75,7 +76,7 @@ def get_relation_types(gir):
}
-def get_state_types(gir):
+def get_state_types(gir: gir.GirContext) -> T.Dict[str, T.Optional[GirType]]:
# from
return {
"busy": BoolType(),
@@ -86,9 +87,24 @@ def get_state_types(gir):
"invalid": gir.get_type("AccessibleInvalidState", "Gtk"),
"pressed": gir.get_type("AccessibleTristate", "Gtk"),
"selected": BoolType(),
+ "visited": BoolType(),
}
+TRANSLATED = set(
+ [
+ "description",
+ "help-text",
+ "label",
+ "placeholder",
+ "role-description",
+ "value-text",
+ "col-index-text",
+ "row-index-text",
+ ]
+)
+
+
def get_types(gir):
return {
**get_property_types(gir),
@@ -121,7 +137,9 @@ class A11yProperty(AstNode):
grammar = Statement(
UseIdent("name"),
":",
- AnyOf(Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]),
+ AnyOf(
+ Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]
+ ).expected("value"),
)
@property
@@ -232,9 +250,12 @@ class ExtAccessibility(AstNode):
applies_in=[ObjectContent],
matches=new_statement_patterns,
)
-def a11y_completer(lsp, ast_node, match_variables):
+def a11y_completer(_ctx: CompletionContext):
yield Completion(
- "accessibility", CompletionItemKind.Snippet, snippet="accessibility {\n $0\n}"
+ "accessibility",
+ CompletionItemKind.Snippet,
+ snippet="accessibility {\n $0\n}",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "accessibility"),
)
@@ -242,12 +263,14 @@ def a11y_completer(lsp, ast_node, match_variables):
applies_in=[ExtAccessibility],
matches=new_statement_patterns,
)
-def a11y_name_completer(lsp, ast_node, match_variables):
- for name, type in get_types(ast_node.root.gir).items():
- yield Completion(
+def a11y_property_completer(ctx: CompletionContext):
+ for name, type in get_types(ctx.ast_node.root.gir).items():
+ yield get_property_completion(
name,
- CompletionItemKind.Property,
- docs=_get_docs(ast_node.root.gir, type.name),
+ type,
+ ctx,
+ name in TRANSLATED,
+ _get_docs(ctx.ast_node.root.gir, name),
)
diff --git a/blueprintcompiler/language/gtk_combo_box_text.py b/blueprintcompiler/language/gtk_combo_box_text.py
index 32b3486..aa1fe1d 100644
--- a/blueprintcompiler/language/gtk_combo_box_text.py
+++ b/blueprintcompiler/language/gtk_combo_box_text.py
@@ -91,11 +91,16 @@ class ExtComboBoxItems(AstNode):
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Gtk", "ComboBoxText"),
+ applies_in_subclass=[("Gtk", "ComboBoxText")],
matches=new_statement_patterns,
)
-def items_completer(lsp, ast_node, match_variables):
- yield Completion("items", CompletionItemKind.Snippet, snippet="items [$0]")
+def items_completer(_ctx: CompletionContext):
+ yield Completion(
+ "items",
+ CompletionItemKind.Snippet,
+ snippet="items [$0]",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "items"),
+ )
@decompiler("items", parent_type="Gtk.ComboBoxText")
diff --git a/blueprintcompiler/language/gtk_file_filter.py b/blueprintcompiler/language/gtk_file_filter.py
index e84afc7..36e7da4 100644
--- a/blueprintcompiler/language/gtk_file_filter.py
+++ b/blueprintcompiler/language/gtk_file_filter.py
@@ -98,15 +98,28 @@ ext_file_filter_suffixes = create_node("suffixes", "suffix")
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Gtk", "FileFilter"),
+ applies_in_subclass=[("Gtk", "FileFilter")],
matches=new_statement_patterns,
)
-def file_filter_completer(lsp, ast_node, match_variables):
+def file_filter_completer(_ctx: CompletionContext):
yield Completion(
- "mime-types", CompletionItemKind.Snippet, snippet='mime-types ["$0"]'
+ "mime-types",
+ CompletionItemKind.Snippet,
+ snippet='mime-types ["$0"]',
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "mime-types"),
+ )
+ yield Completion(
+ "patterns",
+ CompletionItemKind.Snippet,
+ snippet='patterns ["$0"]',
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "patterns"),
+ )
+ yield Completion(
+ "suffixes",
+ CompletionItemKind.Snippet,
+ snippet='suffixes ["$0"]',
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "suffixes"),
)
- yield Completion("patterns", CompletionItemKind.Snippet, snippet='patterns ["$0"]')
- yield Completion("suffixes", CompletionItemKind.Snippet, snippet='suffixes ["$0"]')
@decompiler("mime-types")
diff --git a/blueprintcompiler/language/gtk_layout.py b/blueprintcompiler/language/gtk_layout.py
index 8d3e37a..8dd3458 100644
--- a/blueprintcompiler/language/gtk_layout.py
+++ b/blueprintcompiler/language/gtk_layout.py
@@ -90,11 +90,16 @@ class ExtLayout(AstNode):
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Gtk", "Widget"),
+ applies_in_subclass=[("Gtk", "Widget")],
matches=new_statement_patterns,
)
-def layout_completer(lsp, ast_node, match_variables):
- yield Completion("layout", CompletionItemKind.Snippet, snippet="layout {\n $0\n}")
+def layout_completer(_ctx: CompletionContext):
+ yield Completion(
+ "layout",
+ CompletionItemKind.Snippet,
+ snippet="layout {\n $0\n}",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "layout"),
+ )
@decompiler("layout")
diff --git a/blueprintcompiler/language/gtk_menu.py b/blueprintcompiler/language/gtk_menu.py
index c7ef5f2..ed7ede8 100644
--- a/blueprintcompiler/language/gtk_menu.py
+++ b/blueprintcompiler/language/gtk_menu.py
@@ -243,7 +243,7 @@ from .ui import UI
applies_in=[UI],
matches=new_statement_patterns,
)
-def menu_completer(lsp, ast_node, match_variables):
+def menu_completer(_ctx: CompletionContext):
yield Completion("menu", CompletionItemKind.Snippet, snippet="menu {\n $0\n}")
@@ -251,23 +251,50 @@ def menu_completer(lsp, ast_node, match_variables):
applies_in=[Menu],
matches=new_statement_patterns,
)
-def menu_content_completer(lsp, ast_node, match_variables):
+def menu_content_completer(_ctx: CompletionContext):
yield Completion(
- "submenu", CompletionItemKind.Snippet, snippet="submenu {\n $0\n}"
+ "submenu",
+ CompletionItemKind.Snippet,
+ snippet="submenu {\n $0\n}",
+ sort_text=get_sort_key(CompletionPriority.CLASS, "1 submenu"),
)
yield Completion(
- "section", CompletionItemKind.Snippet, snippet="section {\n $0\n}"
+ "section",
+ CompletionItemKind.Snippet,
+ snippet="section {\n $0\n}",
+ sort_text=get_sort_key(CompletionPriority.CLASS, "1 section"),
+ )
+ yield Completion(
+ "item",
+ CompletionItemKind.Snippet,
+ snippet="item {\n $0\n}",
+ sort_text=get_sort_key(CompletionPriority.CLASS, "1 item"),
)
- yield Completion("item", CompletionItemKind.Snippet, snippet="item {\n $0\n}")
yield Completion(
"item (shorthand)",
CompletionItemKind.Snippet,
snippet='item (_("${1:Label}"), "${2:action-name}", "${3:icon-name}")',
+ sort_text=get_sort_key(CompletionPriority.CLASS, "0 item (shorthand)"),
)
- yield Completion("label", CompletionItemKind.Snippet, snippet="label: $0;")
- yield Completion("action", CompletionItemKind.Snippet, snippet='action: "$0";')
- yield Completion("icon", CompletionItemKind.Snippet, snippet='icon: "$0";')
+ yield Completion(
+ "label",
+ CompletionItemKind.Snippet,
+ snippet="label: $0;",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "label"),
+ )
+ yield Completion(
+ "action",
+ CompletionItemKind.Snippet,
+ snippet='action: "$0";',
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "action"),
+ )
+ yield Completion(
+ "icon",
+ CompletionItemKind.Snippet,
+ snippet='icon: "$0";',
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "icon"),
+ )
@decompiler("menu")
diff --git a/blueprintcompiler/language/gtk_scale.py b/blueprintcompiler/language/gtk_scale.py
index 1fd5ac3..5dc49d8 100644
--- a/blueprintcompiler/language/gtk_scale.py
+++ b/blueprintcompiler/language/gtk_scale.py
@@ -23,22 +23,20 @@ from .values import StringValue
class ExtScaleMark(AstNode):
- grammar = [
+ grammar = Statement(
Keyword("mark"),
Match("(").expected(),
- [
- Optional(AnyOf(UseExact("sign", "-"), UseExact("sign", "+"))),
- UseNumber("value"),
- Optional(
- [
- ",",
- UseIdent("position"),
- Optional([",", StringValue]),
- ]
- ),
- ],
- Match(")").expected(),
- ]
+ Optional(AnyOf(UseExact("sign", "-"), UseExact("sign", "+"))),
+ UseNumber("value").expected("value"),
+ Optional(
+ [
+ ",",
+ UseIdent("position").expected("position"),
+ Optional([",", to_parse_node(StringValue).expected("label")]),
+ ]
+ ),
+ end=")",
+ )
@property
def value(self) -> float:
@@ -134,20 +132,42 @@ class ExtScaleMarks(AstNode):
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Gtk", "Scale"),
+ applies_in_subclass=[("Gtk", "Scale")],
matches=new_statement_patterns,
)
-def complete_marks(lsp, ast_node, match_variables):
- yield Completion("marks", CompletionItemKind.Keyword, snippet="marks [\n\t$0\n]")
+def complete_marks(_ctx: CompletionContext):
+ yield Completion(
+ "marks",
+ CompletionItemKind.Keyword,
+ snippet="marks [\n\t$0\n]",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "marks"),
+ )
@completer(
applies_in=[ExtScaleMarks],
)
-def complete_mark(lsp, ast_node, match_variables):
+def complete_mark(_ctx: CompletionContext):
yield Completion("mark", CompletionItemKind.Keyword, snippet="mark ($0),")
+@completer(
+ applies_in=[ExtScaleMark],
+ matches=[[(TokenType.NUMBER, None), (TokenType.PUNCTUATION, ",")]],
+)
+def complete_mark_position(ctx: CompletionContext):
+ gir = ctx.ast_node.root.gir
+ response_type = gir.get_type("PositionType", "Gtk")
+ yield from [
+ Completion(
+ name,
+ kind=CompletionItemKind.EnumMember,
+ docs=member.doc,
+ )
+ for name, member in response_type.members.items()
+ ]
+
+
@decompiler("marks")
def decompile_marks(
ctx,
diff --git a/blueprintcompiler/language/gtk_size_group.py b/blueprintcompiler/language/gtk_size_group.py
index 54d85e5..e7a6a35 100644
--- a/blueprintcompiler/language/gtk_size_group.py
+++ b/blueprintcompiler/language/gtk_size_group.py
@@ -101,11 +101,16 @@ class ExtSizeGroupWidgets(AstNode):
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Gtk", "SizeGroup"),
+ applies_in_subclass=[("Gtk", "SizeGroup")],
matches=new_statement_patterns,
)
-def size_group_completer(lsp, ast_node, match_variables):
- yield Completion("widgets", CompletionItemKind.Snippet, snippet="widgets [$0]")
+def size_group_completer(_ctx: CompletionContext):
+ yield Completion(
+ "widgets",
+ CompletionItemKind.Snippet,
+ snippet="widgets [$0]",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "widgets"),
+ )
@decompiler("widgets")
diff --git a/blueprintcompiler/language/gtk_string_list.py b/blueprintcompiler/language/gtk_string_list.py
index a146f35..4d15d32 100644
--- a/blueprintcompiler/language/gtk_string_list.py
+++ b/blueprintcompiler/language/gtk_string_list.py
@@ -72,11 +72,16 @@ class ExtStringListStrings(AstNode):
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Gtk", "StringList"),
+ applies_in_subclass=[("Gtk", "StringList")],
matches=new_statement_patterns,
)
-def strings_completer(lsp, ast_node, match_variables):
- yield Completion("strings", CompletionItemKind.Snippet, snippet="strings [$0]")
+def strings_completer(_ctx: CompletionContext):
+ yield Completion(
+ "strings",
+ CompletionItemKind.Snippet,
+ snippet="strings [$0]",
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "strings"),
+ )
@decompiler("items", parent_type="Gtk.StringList")
diff --git a/blueprintcompiler/language/gtk_styles.py b/blueprintcompiler/language/gtk_styles.py
index 8617522..0836073 100644
--- a/blueprintcompiler/language/gtk_styles.py
+++ b/blueprintcompiler/language/gtk_styles.py
@@ -77,11 +77,16 @@ class ExtStyles(AstNode):
@completer(
applies_in=[ObjectContent],
- applies_in_subclass=("Gtk", "Widget"),
+ applies_in_subclass=[("Gtk", "Widget")],
matches=new_statement_patterns,
)
-def style_completer(lsp, ast_node, match_variables):
- yield Completion("styles", CompletionItemKind.Keyword, snippet='styles ["$0"]')
+def style_completer(_ctx: CompletionContext):
+ yield Completion(
+ "styles",
+ CompletionItemKind.Keyword,
+ snippet='styles ["$0"]',
+ sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "styles"),
+ )
@decompiler("style")
diff --git a/blueprintcompiler/language/gtkbuilder_child.py b/blueprintcompiler/language/gtkbuilder_child.py
index bee551c..0eb8f04 100644
--- a/blueprintcompiler/language/gtkbuilder_child.py
+++ b/blueprintcompiler/language/gtkbuilder_child.py
@@ -31,7 +31,12 @@ ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [
class ChildInternal(AstNode):
- grammar = ["internal-child", UseIdent("internal_child")]
+ grammar = [
+ "[",
+ "internal-child",
+ UseIdent("internal_child").expected("internal child name"),
+ Match("]").expected(),
+ ]
@property
def internal_child(self) -> str:
@@ -39,7 +44,7 @@ class ChildInternal(AstNode):
class ChildType(AstNode):
- grammar = UseIdent("child_type").expected("a child type")
+ grammar = ["[", UseIdent("child_type").expected("a child type"), "]"]
@property
def child_type(self) -> str:
@@ -59,7 +64,7 @@ class ChildExtension(AstNode):
class ChildAnnotation(AstNode):
- grammar = ["[", AnyOf(ChildInternal, ChildExtension, ChildType), "]"]
+ grammar = AnyOf(ChildInternal, ChildExtension, ChildType)
@property
def child(self) -> T.Union[ChildInternal, ChildExtension, ChildType]:
diff --git a/blueprintcompiler/language/response_id.py b/blueprintcompiler/language/response_id.py
index 939f71f..83843ed 100644
--- a/blueprintcompiler/language/response_id.py
+++ b/blueprintcompiler/language/response_id.py
@@ -28,19 +28,21 @@ class ExtResponse(AstNode):
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [("Gtk", "Dialog"), ("Gtk", "InfoBar")]
- grammar = [
+ grammar = Statement(
+ "[",
Keyword("action"),
Keyword("response"),
- "=",
+ Match("=").expected(),
AnyOf(
UseIdent("response_id"),
[
Optional(UseExact("sign", "-")),
UseNumber("response_id"),
],
- ),
+ ).expected("response ID"),
Optional([Keyword("default"), UseLiteral("is_default", True)]),
- ]
+ end="]",
+ )
@validate()
def parent_has_action_widgets(self) -> None:
diff --git a/blueprintcompiler/language/types.py b/blueprintcompiler/language/types.py
index fe45c4d..da41360 100644
--- a/blueprintcompiler/language/types.py
+++ b/blueprintcompiler/language/types.py
@@ -27,11 +27,11 @@ class TypeName(AstNode):
[
UseIdent("namespace"),
".",
- UseIdent("class_name"),
+ UseIdent("class_name").expected("class name"),
],
[
AnyOf("$", [".", UseLiteral("old_extern", True)]),
- UseIdent("class_name"),
+ UseIdent("class_name").expected("class name"),
UseLiteral("extern", True),
],
UseIdent("class_name"),
@@ -47,7 +47,11 @@ class TypeName(AstNode):
@validate("class_name")
def type_exists(self):
- if not self.tokens["extern"] and self.gir_ns is not None:
+ if (
+ not self.tokens["extern"]
+ and self.gir_ns is not None
+ and self.tokens["class_name"] is not None
+ ):
self.root.gir.validate_type(
self.tokens["class_name"], self.tokens["namespace"]
)
@@ -182,3 +186,14 @@ class TemplateClassName(ClassName):
self.root.gir.validate_type(
self.tokens["class_name"], self.tokens["namespace"]
)
+
+
+class BracketedTypeName(AstNode):
+ grammar = Statement("<", to_parse_node(TypeName).expected("type name"), end=">")
+
+ @property
+ def type_name(self) -> T.Optional[TypeName]:
+ if len(self.children[TypeName]) == 0:
+ return None
+
+ return self.children[TypeName][0]
diff --git a/blueprintcompiler/language/ui.py b/blueprintcompiler/language/ui.py
index d55a22a..896c0f7 100644
--- a/blueprintcompiler/language/ui.py
+++ b/blueprintcompiler/language/ui.py
@@ -110,16 +110,22 @@ class UI(AstNode):
and self.template.class_name.glib_type_name == id
)
- def import_code_action(self, ns: str, version: str) -> CodeAction:
- if len(self.children[Import]):
- pos = self.children[Import][-1].range.end
- else:
- pos = self.children[GtkDirective][0].range.end
+ def import_range(self, ns: str):
+ """Returns a range to insert a new import statement"""
+ pos = self.children[GtkDirective][0].range.end
+ # try to insert alphabetically
+ for import_ in self.children[Import]:
+ if ns.lower() > import_.namespace.lower():
+ pos = import_.range.end
+
+ return Range(pos, pos, self.group.text)
+
+ def import_code_action(self, ns: str, version: str) -> CodeAction:
return CodeAction(
f"Import {ns} {version}",
f"\nusing {ns} {version};",
- Range(pos, pos, self.group.text),
+ self.import_range(ns),
)
@cached_property
diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py
index 5556d99..2840337 100644
--- a/blueprintcompiler/language/values.py
+++ b/blueprintcompiler/language/values.py
@@ -26,7 +26,7 @@ from .common import *
from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
from .expression import Expression
from .gobject_object import Object
-from .types import TypeName
+from .types import BracketedTypeName, TypeName
class Translated(AstNode):
@@ -58,6 +58,19 @@ 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")
@@ -67,11 +80,7 @@ class TypeLiteral(AstNode):
grammar = [
"typeof",
AnyOf(
- [
- "<",
- to_parse_node(TypeName).expected("type name"),
- Match(">").expected(),
- ],
+ BracketedTypeName,
[
UseExact("lparen", "("),
to_parse_node(TypeName).expected("type name"),
@@ -85,8 +94,13 @@ class TypeLiteral(AstNode):
return gir.TypeType()
@property
- def type_name(self) -> TypeName:
- return self.children[TypeName][0]
+ def type_name(self) -> T.Optional[TypeName]:
+ if len(self.children[BracketedTypeName]) == 1:
+ return self.children[BracketedTypeName][0].type_name
+ elif len(self.children[TypeName]) == 1:
+ return self.children[TypeName][0]
+ else:
+ return None
@validate()
def validate_for_type(self) -> None:
@@ -212,12 +226,12 @@ class Flag(AstNode):
return self.tokens["value"]
@property
- def value(self) -> T.Optional[int]:
+ def value(self) -> T.Optional[str]:
type = self.context[ValueTypeCtx].value_type
if not isinstance(type, Enumeration):
return None
elif member := type.members.get(self.name):
- return member.value
+ return member.nick
else:
return None
@@ -325,7 +339,14 @@ class IdentLiteral(AstNode):
raise CompileError(
'"item" can only be used in an expression literal'
)
- elif self.ident not in ["true", "false"]:
+ elif self.ident in ["true", "false"]:
+ if expected_type is not None and not isinstance(
+ expected_type, gir.BoolType
+ ):
+ raise CompileError(
+ f"Cannot assign boolean to {expected_type.full_name}"
+ )
+ else:
raise CompileError(
f"Could not find object with ID {self.ident}",
did_you_mean=(
diff --git a/blueprintcompiler/lsp_utils.py b/blueprintcompiler/lsp_utils.py
index b938181..2a4380a 100644
--- a/blueprintcompiler/lsp_utils.py
+++ b/blueprintcompiler/lsp_utils.py
@@ -87,6 +87,7 @@ class Completion:
text: T.Optional[str] = None
snippet: T.Optional[str] = None
detail: T.Optional[str] = None
+ additional_text_edits: T.Optional[T.List["TextEdit"]] = None
def to_json(self, snippets: bool):
insert_text = self.text or self.label
@@ -114,6 +115,11 @@ class Completion:
"insertText": insert_text,
"insertTextFormat": insert_text_format,
"detail": self.detail if self.detail else None,
+ "additionalTextEdits": (
+ [edit.to_json() for edit in self.additional_text_edits]
+ if self.additional_text_edits
+ else None
+ ),
}
return {k: v for k, v in result.items() if v is not None}
diff --git a/blueprintcompiler/outputs/xml/__init__.py b/blueprintcompiler/outputs/xml/__init__.py
index 5c03761..3f52ca5 100644
--- a/blueprintcompiler/outputs/xml/__init__.py
+++ b/blueprintcompiler/outputs/xml/__init__.py
@@ -209,6 +209,7 @@ class XmlOutput(OutputFormat):
else:
xml.put_text(self._object_id(value, value.ident))
elif isinstance(value, TypeLiteral):
+ assert value.type_name is not None
xml.put_text(value.type_name.glib_type_name)
else:
if isinstance(value.value, float) and value.value == int(value.value):
@@ -308,6 +309,9 @@ 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):
diff --git a/blueprintcompiler/outputs/xml/xml_emitter.py b/blueprintcompiler/outputs/xml/xml_emitter.py
index ea91e03..d34eff4 100644
--- a/blueprintcompiler/outputs/xml/xml_emitter.py
+++ b/blueprintcompiler/outputs/xml/xml_emitter.py
@@ -73,6 +73,7 @@ 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 ae062fb..8bb4c66 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
@@ -235,7 +235,15 @@ class ParseNode:
start_idx = ctx.index
inner_ctx = ctx.create_child()
- if self._parse(inner_ctx):
+ try:
+ result = self._parse(inner_ctx)
+ except Exception as e:
+ # If an exception occurs, there's an explicit error, not just a rule that didn't match. Apply the context
+ # state so that whichever rule handles the exception (e.g. a Statement) knows where the error occurred.
+ ctx.apply_child(inner_ctx)
+ raise e
+
+ if result:
ctx.apply_child(inner_ctx)
if ctx.index == start_idx:
return ParseResult.EMPTY
@@ -269,12 +277,12 @@ class Err(ParseNode):
if self.child.parse(ctx).failed():
start_idx = ctx.start
while ctx.tokens[start_idx].type in SKIP_TOKENS:
- start_idx += 1
+ start_idx -= 1
start_token = ctx.tokens[start_idx]
- raise CompileError(
- self.message, Range(start_token.start, start_token.start, ctx.text)
- )
+ position = start_token.start if ctx.start == start_idx else start_token.end
+
+ raise CompileError(self.message, Range(position, position, ctx.text))
return True
@@ -329,8 +337,9 @@ class Statement(ParseNode):
"""ParseNode that attempts to match all of its children in sequence. If any
child raises an error, the error will be logged but parsing will continue."""
- def __init__(self, *children):
+ def __init__(self, *children, end: str = ";"):
self.children = [to_parse_node(child) for child in children]
+ self.end = end
def _parse(self, ctx) -> bool:
for child in self.children:
@@ -340,11 +349,29 @@ class Statement(ParseNode):
except CompileError as e:
ctx.errors.append(e)
ctx.set_group_incomplete()
+
+ token = ctx.peek_token()
+ if str(token) == self.end:
+ ctx.next_token()
+
return True
token = ctx.peek_token()
- if str(token) != ";":
- ctx.errors.append(CompileError("Expected `;`", token.range))
+ if str(token) != self.end:
+ start_idx = ctx.index - 1
+ while ctx.tokens[start_idx].type in SKIP_TOKENS:
+ start_idx -= 1
+ start_token = ctx.tokens[start_idx]
+
+ position = (
+ start_token.start if ctx.index - 1 == start_idx else start_token.end
+ )
+
+ ctx.errors.append(
+ CompileError(
+ f"Expected `{self.end}`", Range(position, position, ctx.text)
+ )
+ )
else:
ctx.next_token()
return True
@@ -405,7 +432,6 @@ class Until(ParseNode):
ctx.skip_unexpected_token()
except CompileError as e:
ctx.errors.append(e)
- ctx.next_token()
return True
diff --git a/docs/collect-sections.py b/docs/collect-sections.py
index e6227e7..a2dd004 100755
--- a/docs/collect-sections.py
+++ b/docs/collect-sections.py
@@ -9,7 +9,7 @@ from pathlib import Path
__all__ = ["get_docs_section"]
-DOCS_ROOT = "https://jwestman.pages.gitlab.gnome.org/blueprint-compiler"
+DOCS_ROOT = "https://gnome.pages.gitlab.gnome.org/blueprint-compiler"
sections: dict[str, "Section"] = {}
@@ -132,5 +132,8 @@ if __name__ == "__main__":
# 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
+ {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 0071d2f..8081c8d 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/jwestman/blueprint-compiler",
- "tag": "v0.14.0"
+ "url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
+ "tag": "v0.16.0"
}
]
}
diff --git a/docs/index.rst b/docs/index.rst
index 34b942c..6cd130f 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 MyAppWindow.main_text;
+ label: bind template.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 `_
diff --git a/docs/reference/extensions.rst b/docs/reference/extensions.rst
index 0961d14..2fd5dbb 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
diff --git a/docs/setup.rst b/docs/setup.rst
index 839f8f6..914c753 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/jwestman/blueprint-compiler.git
+ url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
revision = main
depth = 1
diff --git a/docs/translations.rst b/docs/translations.rst
index 7ebf929..7af2099 100644
--- a/docs/translations.rst
+++ b/docs/translations.rst
@@ -24,6 +24,8 @@ If you're using Meson's `i18n module