mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Compare commits
35 commits
72583be267
...
72319b29c6
Author | SHA1 | Date | |
---|---|---|---|
|
72319b29c6 | ||
|
67983aee2e | ||
|
1205fc42ea | ||
|
e9206809d6 | ||
|
d5b2ee3589 | ||
|
8f3ae9a626 | ||
|
e5d6910626 | ||
|
b26433d865 | ||
|
866092ccf7 | ||
|
1e9b01bab9 | ||
|
b9910db849 | ||
|
d0394136cf | ||
|
860580e560 | ||
|
64b96137f5 | ||
|
f5cef37db8 | ||
|
3d0593bc2b | ||
|
bf4d8579b6 | ||
|
2e42dc6848 | ||
|
a12d3f5c81 | ||
|
a83c7e936d | ||
|
3816f4fe8d | ||
|
e9d61cb6f9 | ||
|
6a77bfee0a | ||
|
f50b898e4c | ||
|
cc09f3d3bb | ||
|
f93d5d2acd | ||
|
394014429e | ||
|
a4e0c3701b | ||
|
c1fbcef6d0 | ||
|
404ae76787 | ||
|
04ef0944db | ||
|
aa13c8f5af | ||
|
e07da3c339 | ||
|
2ae41020ab | ||
|
f48b840cfa |
65 changed files with 1078 additions and 307 deletions
|
@ -3,7 +3,7 @@ stages:
|
||||||
- pages
|
- pages
|
||||||
|
|
||||||
build:
|
build:
|
||||||
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
|
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- black --check --diff ./ tests
|
- black --check --diff ./ tests
|
||||||
|
@ -33,7 +33,7 @@ build:
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
|
|
||||||
fuzz:
|
fuzz:
|
||||||
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
|
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- meson _build
|
- meson _build
|
||||||
|
|
|
@ -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.
|
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.
|
4. Create a "Post-release version bump" commit.
|
||||||
5. Go to the Releases page in GitLab and create a new release from the tag.
|
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
|
## Related projects
|
||||||
|
|
||||||
|
|
32
NEWS.md
32
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
|
# v0.14.0
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
27
blueprint-compiler.doap
Normal file
27
blueprint-compiler.doap
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
|
||||||
|
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||||
|
xmlns:gnome="http://api.gnome.org/doap-extensions#"
|
||||||
|
xmlns="http://usefulinc.com/ns/doap#">
|
||||||
|
|
||||||
|
<name xml:lang="en">Blueprint</name>
|
||||||
|
<shortdesc xml:lang="en">A modern language for creating GTK interfaces</shortdesc>
|
||||||
|
<description xml:lang="en">Blueprint is a language and associated tooling for building user interfaces for GTK.</description>
|
||||||
|
<category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
|
||||||
|
<programming-language>Python</programming-language>
|
||||||
|
|
||||||
|
<homepage
|
||||||
|
rdf:resource="https://gnome.gitlab.gnome.org/blueprint-compiler/" />
|
||||||
|
<download-page
|
||||||
|
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/-/releases" />
|
||||||
|
<bug-database
|
||||||
|
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/issues" />
|
||||||
|
|
||||||
|
<maintainer>
|
||||||
|
<foaf:Person>
|
||||||
|
<foaf:name>James Westman</foaf:name>
|
||||||
|
<foaf:mbox rdf:resource="mailto:james@jwestman.net" />
|
||||||
|
<gnome:userid>jwestman</gnome:userid>
|
||||||
|
</foaf:Person>
|
||||||
|
</maintainer>
|
||||||
|
</Project>
|
|
@ -196,6 +196,13 @@ class AstNode:
|
||||||
|
|
||||||
return None
|
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]:
|
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
yield from child.get_semantic_tokens()
|
yield from child.get_semantic_tokens()
|
||||||
|
|
|
@ -21,9 +21,20 @@ import typing as T
|
||||||
|
|
||||||
from . import annotations, gir, language
|
from . import annotations, gir, language
|
||||||
from .ast_utils import AstNode
|
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 .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 .parser import SKIP_TOKENS
|
||||||
from .tokenizer import Token, TokenType
|
from .tokenizer import Token, TokenType
|
||||||
|
|
||||||
|
@ -31,15 +42,13 @@ Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
|
||||||
|
|
||||||
|
|
||||||
def _complete(
|
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]:
|
) -> 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] = []
|
prev_tokens: T.List[Token] = []
|
||||||
|
|
||||||
# collect the 5 previous non-skipped tokens
|
# collect the 5 previous non-skipped tokens
|
||||||
|
@ -49,8 +58,8 @@ def _complete(
|
||||||
prev_tokens.insert(0, token)
|
prev_tokens.insert(0, token)
|
||||||
token_idx -= 1
|
token_idx -= 1
|
||||||
|
|
||||||
for completer in ast_node.completers:
|
for completer in completers:
|
||||||
yield from completer(prev_tokens, ast_node, lsp)
|
yield from completer(prev_tokens, next_token, ast_node, lsp, idx)
|
||||||
|
|
||||||
|
|
||||||
def complete(
|
def complete(
|
||||||
|
@ -62,147 +71,258 @@ def complete(
|
||||||
if token.start < idx <= token.end:
|
if token.start < idx <= token.end:
|
||||||
token_idx = i
|
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
|
# 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
|
idx = tokens[token_idx].start
|
||||||
token_idx -= 1
|
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])
|
@completer([language.GtkDirective])
|
||||||
def using_gtk(lsp, ast_node, match_variables):
|
def using_gtk(_ctx: CompletionContext):
|
||||||
yield Completion(
|
yield Completion(
|
||||||
"using Gtk 4.0", CompletionItemKind.Keyword, snippet="using Gtk 4.0;\n"
|
"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(
|
@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,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def namespace(lsp, ast_node, match_variables):
|
def namespace(ctx: CompletionContext):
|
||||||
yield Completion("Gtk", CompletionItemKind.Module, text="Gtk.")
|
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:
|
if ns.gir_namespace is not None:
|
||||||
yield Completion(
|
yield Completion(
|
||||||
ns.gir_namespace.name,
|
ns.gir_namespace.name,
|
||||||
CompletionItemKind.Module,
|
CompletionItemKind.Module,
|
||||||
text=ns.gir_namespace.name + ".",
|
text=ns.gir_namespace.name + ".",
|
||||||
|
sort_text=get_sort_key(
|
||||||
|
CompletionPriority.NAMESPACE, ns.gir_namespace.name
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield from _available_namespace_completions(ctx)
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[language.UI, language.ObjectContent, language.Template],
|
applies_in=[
|
||||||
|
language.UI,
|
||||||
|
language.ObjectContent,
|
||||||
|
language.Template,
|
||||||
|
language.TypeName,
|
||||||
|
language.BracketedTypeName,
|
||||||
|
],
|
||||||
matches=[
|
matches=[
|
||||||
[(TokenType.IDENT, None), (TokenType.OP, "."), (TokenType.IDENT, None)],
|
[(TokenType.IDENT, None), (TokenType.OP, "."), (TokenType.IDENT, None)],
|
||||||
[(TokenType.IDENT, None), (TokenType.OP, ".")],
|
[(TokenType.IDENT, None), (TokenType.OP, ".")],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def object_completer(lsp, ast_node, match_variables):
|
def object_completer(ctx: CompletionContext):
|
||||||
ns = ast_node.root.gir.namespaces.get(match_variables[0])
|
ns = ctx.ast_node.root.gir.namespaces.get(ctx.match_variables[0])
|
||||||
if ns is not None:
|
if ns is not None:
|
||||||
for c in ns.classes.values():
|
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(
|
yield Completion(
|
||||||
c.name,
|
c.name,
|
||||||
CompletionItemKind.Class,
|
CompletionItemKind.Class,
|
||||||
snippet=f"{c.name} {{\n $0\n}}",
|
sort_text=get_sort_key(CompletionPriority.CLASS, c.name),
|
||||||
|
snippet=snippet,
|
||||||
docs=c.doc,
|
docs=c.doc,
|
||||||
detail=c.detail,
|
detail=c.detail,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@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,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def gtk_object_completer(lsp, ast_node, match_variables):
|
def gtk_object_completer(ctx: CompletionContext):
|
||||||
ns = ast_node.root.gir.namespaces.get("Gtk")
|
ns = ctx.ast_node.root.gir.namespaces.get("Gtk")
|
||||||
if ns is not None:
|
if ns is not None:
|
||||||
for c in ns.classes.values():
|
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(
|
yield Completion(
|
||||||
c.name,
|
c.name,
|
||||||
CompletionItemKind.Class,
|
CompletionItemKind.Class,
|
||||||
snippet=f"{c.name} {{\n $0\n}}",
|
sort_text=get_sort_key(CompletionPriority.CLASS, c.name),
|
||||||
|
snippet=snippet,
|
||||||
docs=c.doc,
|
docs=c.doc,
|
||||||
detail=c.detail,
|
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(
|
@completer(
|
||||||
applies_in=[language.ObjectContent],
|
applies_in=[language.ObjectContent],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def property_completer(lsp, ast_node, match_variables):
|
def property_completer(ctx: CompletionContext):
|
||||||
if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
|
assert isinstance(ctx.ast_node, language.ObjectContent)
|
||||||
for prop_name, prop in ast_node.gir_class.properties.items():
|
if ctx.ast_node.gir_class and hasattr(ctx.ast_node.gir_class, "properties"):
|
||||||
if (
|
for prop_name, prop in ctx.ast_node.gir_class.properties.items():
|
||||||
isinstance(prop.type, gir.BoolType)
|
yield get_property_completion(
|
||||||
and lsp.client_supports_completion_choice
|
prop_name,
|
||||||
):
|
prop.type,
|
||||||
yield Completion(
|
ctx,
|
||||||
prop_name,
|
annotations.is_property_translated(prop),
|
||||||
CompletionItemKind.Property,
|
prop.doc,
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[language.Property, language.A11yProperty],
|
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):
|
def prop_value_completer(ctx: CompletionContext):
|
||||||
if (vt := ast_node.value_type) is not None:
|
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):
|
if isinstance(vt.value_type, gir.Enumeration):
|
||||||
for name, member in vt.value_type.members.items():
|
for name, member in vt.value_type.members.items():
|
||||||
yield Completion(
|
yield Completion(
|
||||||
|
@ -210,43 +330,148 @@ def prop_value_completer(lsp, ast_node, match_variables):
|
||||||
CompletionItemKind.EnumMember,
|
CompletionItemKind.EnumMember,
|
||||||
docs=member.doc,
|
docs=member.doc,
|
||||||
detail=member.detail,
|
detail=member.detail,
|
||||||
|
sort_text=get_sort_key(CompletionPriority.ENUM_MEMBER, name),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif isinstance(vt.value_type, gir.BoolType):
|
elif isinstance(vt.value_type, gir.BoolType):
|
||||||
yield Completion("true", CompletionItemKind.Constant)
|
yield Completion(
|
||||||
yield Completion("false", CompletionItemKind.Constant)
|
"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(
|
@completer(
|
||||||
applies_in=[language.ObjectContent],
|
applies_in=[language.ObjectContent],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def signal_completer(lsp, ast_node, match_variables):
|
def signal_completer(ctx: CompletionContext):
|
||||||
if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
|
assert isinstance(ctx.ast_node, language.ObjectContent)
|
||||||
for signal_name, signal in ast_node.gir_class.signals.items():
|
|
||||||
if not isinstance(ast_node.parent, language.Object):
|
if ctx.ast_node.gir_class and hasattr(ctx.ast_node.gir_class, "signals"):
|
||||||
name = "on"
|
for signal_name, signal in ctx.ast_node.gir_class.signals.items():
|
||||||
|
if str(ctx.next_token) == "=>":
|
||||||
|
snippet = signal_name
|
||||||
else:
|
else:
|
||||||
name = "on_" + (
|
if not isinstance(ctx.ast_node.parent, language.Object):
|
||||||
ast_node.parent.children[ClassName][0].tokens["id"]
|
name = "on"
|
||||||
or ast_node.parent.children[ClassName][0]
|
else:
|
||||||
.tokens["class_name"]
|
name = "on_" + (
|
||||||
.lower()
|
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(
|
yield Completion(
|
||||||
signal_name,
|
signal_name,
|
||||||
CompletionItemKind.Event,
|
CompletionItemKind.Event,
|
||||||
sort_text=f"1 {signal_name}",
|
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, signal_name),
|
||||||
snippet=f"{signal_name} => \\$${{1:${name}_{signal_name.replace('-', '_')}}}()$0;",
|
snippet=snippet,
|
||||||
docs=signal.doc,
|
docs=signal.doc,
|
||||||
detail=signal.detail,
|
detail=signal.detail,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@completer(applies_in=[language.UI], matches=new_statement_patterns)
|
@completer(applies_in=[language.UI], matches=new_statement_patterns)
|
||||||
def template_completer(lsp, ast_node, match_variables):
|
def template_completer(_ctx: CompletionContext):
|
||||||
yield Completion(
|
yield Completion(
|
||||||
"template",
|
"template",
|
||||||
CompletionItemKind.Snippet,
|
CompletionItemKind.Snippet,
|
||||||
snippet="template ${1:ClassName} : ${2:ParentClass} {\n $0\n}",
|
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,
|
||||||
|
)
|
||||||
|
|
|
@ -19,28 +19,78 @@
|
||||||
|
|
||||||
|
|
||||||
import typing as T
|
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
|
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 = [
|
new_statement_patterns = [
|
||||||
[(TokenType.PUNCTUATION, "{")],
|
[(TokenType.PUNCTUATION, "{")],
|
||||||
[(TokenType.PUNCTUATION, "}")],
|
[(TokenType.PUNCTUATION, "}")],
|
||||||
[(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 completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
|
||||||
def decorator(func):
|
def decorator(func: T.Callable[[CompletionContext], T.Generator[Completion]]):
|
||||||
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
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
|
# For completers that apply in ObjectContent nodes, we can further
|
||||||
# check that the object is the right class
|
# check that the object is the right class
|
||||||
if applies_in_subclass is not None:
|
if applies_in_subclass is not None:
|
||||||
type = ast_node.root.gir.get_type(
|
parent_obj = ast_node
|
||||||
applies_in_subclass[1], applies_in_subclass[0]
|
while parent_obj is not None and not hasattr(parent_obj, "gir_class"):
|
||||||
)
|
parent_obj = parent_obj.parent
|
||||||
if not ast_node.gir_class or not ast_node.gir_class.assignable_to(type):
|
|
||||||
|
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
|
return
|
||||||
|
|
||||||
any_match = len(matches) == 0
|
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:
|
if not any_match:
|
||||||
return
|
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:
|
completers.append(inner)
|
||||||
c.completers.append(inner)
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
return decorator
|
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),
|
||||||
|
)
|
||||||
|
|
|
@ -255,7 +255,11 @@ def decompile_element(
|
||||||
|
|
||||||
ctx._node_stack.append(xml)
|
ctx._node_stack.append(xml)
|
||||||
ctx.start_block()
|
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:
|
if not decompiler._skip_children:
|
||||||
for child in xml.children:
|
for child in xml.children:
|
||||||
|
@ -266,8 +270,6 @@ def decompile_element(
|
||||||
|
|
||||||
except UnsupportedError as e:
|
except UnsupportedError as e:
|
||||||
raise e
|
raise e
|
||||||
except TypeError as e:
|
|
||||||
raise UnsupportedError(tag=xml.tag)
|
|
||||||
|
|
||||||
|
|
||||||
def decompile(data: str) -> str:
|
def decompile(data: str) -> str:
|
||||||
|
|
|
@ -92,29 +92,38 @@ class CompileError(PrintableError):
|
||||||
def pretty_print(self, filename: str, code: str, stream=sys.stdout) -> None:
|
def pretty_print(self, filename: str, code: str, stream=sys.stdout) -> None:
|
||||||
assert self.range is not None
|
assert self.range is not None
|
||||||
|
|
||||||
line_num, col_num = utils.idx_to_pos(self.range.start + 1, code)
|
def format_line(range: Range):
|
||||||
end_line_num, end_col_num = utils.idx_to_pos(self.range.end + 1, code)
|
line_num, col_num = utils.idx_to_pos(range.start, code)
|
||||||
line = code.splitlines(True)[line_num] if code != "" else ""
|
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
|
# Display 1-based line numbers
|
||||||
line_num += 1
|
line_num += 1
|
||||||
end_line_num += 1
|
end_line_num += 1
|
||||||
|
col_num += 1
|
||||||
|
end_col_num += 1
|
||||||
|
|
||||||
n_spaces = col_num - 1
|
n_spaces = col_num - 1
|
||||||
n_carets = (
|
n_carets = (
|
||||||
(end_col_num - col_num)
|
(end_col_num - col_num)
|
||||||
if line_num == end_line_num
|
if line_num == end_line_num
|
||||||
else (len(line) - n_spaces - 1)
|
else (len(line) - n_spaces - 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
n_spaces += line.count("\t", 0, col_num)
|
n_spaces += line.count("\t", 0, col_num)
|
||||||
n_carets += line.count("\t", col_num, col_num + n_carets)
|
n_carets += line.count("\t", col_num, col_num + n_carets)
|
||||||
line = line.replace("\t", " ")
|
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(
|
stream.write(
|
||||||
f"""{self.color}{Colors.BOLD}{self.category}: {self.message}{Colors.CLEAR}
|
f"""{self.color}{Colors.BOLD}{self.category}: {self.message}{Colors.CLEAR}
|
||||||
at {filename} line {line_num} column {col_num}:
|
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:
|
for hint in self.hints:
|
||||||
|
@ -139,14 +148,12 @@ at {filename} line {line_num} column {col_num}:
|
||||||
)
|
)
|
||||||
|
|
||||||
for ref in self.references:
|
for ref in self.references:
|
||||||
line_num, col_num = utils.idx_to_pos(ref.range.start + 1, code)
|
line_num, col_num, line, carets = format_line(ref.range)
|
||||||
line = code.splitlines(True)[line_num]
|
|
||||||
line_num += 1
|
|
||||||
|
|
||||||
stream.write(
|
stream.write(
|
||||||
f"""{Colors.FAINT}note: {ref.message}:
|
f"""{Colors.FAINT}note: {ref.message}:
|
||||||
at {filename} line {line_num} column {col_num}:
|
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")
|
stream.write("\n")
|
||||||
|
@ -219,7 +226,7 @@ def report_bug(): # pragma: no cover
|
||||||
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
|
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
|
||||||
The blueprint-compiler program has crashed. Please report the above stacktrace,
|
The blueprint-compiler program has crashed. Please report the above stacktrace,
|
||||||
along with the input file(s) if possible, on GitLab:
|
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}"""
|
{Colors.CLEAR}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -289,7 +289,7 @@ class TypeType(BasicType):
|
||||||
return isinstance(other, TypeType)
|
return isinstance(other, TypeType)
|
||||||
|
|
||||||
|
|
||||||
_BASIC_TYPES = {
|
BASIC_TYPES = {
|
||||||
"bool": BoolType,
|
"bool": BoolType,
|
||||||
"string": StringType,
|
"string": StringType,
|
||||||
"int": IntType,
|
"int": IntType,
|
||||||
|
@ -914,7 +914,7 @@ class Namespace(GirNode):
|
||||||
|
|
||||||
def get_type_by_cname(self, cname: str) -> T.Optional[GirType]:
|
def get_type_by_cname(self, cname: str) -> T.Optional[GirType]:
|
||||||
"""Gets a type from this namespace by its C name."""
|
"""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:
|
if basic.glib_type_name == cname:
|
||||||
return basic()
|
return basic()
|
||||||
|
|
||||||
|
@ -1036,8 +1036,8 @@ class GirContext:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
||||||
if ns is None and name in _BASIC_TYPES:
|
if ns is None and name in BASIC_TYPES:
|
||||||
return _BASIC_TYPES[name]()
|
return BASIC_TYPES[name]()
|
||||||
|
|
||||||
ns = ns or "Gtk"
|
ns = ns or "Gtk"
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
|
||||||
print(
|
print(
|
||||||
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
|
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:
|
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")
|
return CouldNotPort("does not compile")
|
||||||
|
@ -136,7 +136,7 @@ def step1():
|
||||||
wrap.write(
|
wrap.write(
|
||||||
f"""[wrap-git]
|
f"""[wrap-git]
|
||||||
directory = blueprint-compiler
|
directory = blueprint-compiler
|
||||||
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
|
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
|
||||||
revision = {VERSION}
|
revision = {VERSION}
|
||||||
depth = 1
|
depth = 1
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,17 @@ from .gtk_scale import ExtScaleMarks
|
||||||
from .gtk_size_group import ExtSizeGroupWidgets
|
from .gtk_size_group import ExtSizeGroupWidgets
|
||||||
from .gtk_string_list import ExtStringListStrings
|
from .gtk_string_list import ExtStringListStrings
|
||||||
from .gtk_styles import ExtStyles
|
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 .gtkbuilder_template import Template
|
||||||
from .imports import GtkDirective, Import
|
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 .ui import UI
|
||||||
from .values import (
|
from .values import (
|
||||||
ArrayValue,
|
ArrayValue,
|
||||||
|
|
|
@ -81,8 +81,8 @@ class AdwBreakpointSetter(AstNode):
|
||||||
return self.tokens["property"]
|
return self.tokens["property"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> Value:
|
def value(self) -> T.Optional[Value]:
|
||||||
return self.children[Value][0]
|
return self.children[Value][0] if len(self.children[Value]) > 0 else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_class(self) -> T.Optional[GirType]:
|
def gir_class(self) -> T.Optional[GirType]:
|
||||||
|
@ -106,7 +106,10 @@ class AdwBreakpointSetter(AstNode):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def document_symbol(self) -> DocumentSymbol:
|
def document_symbol(self) -> T.Optional[DocumentSymbol]:
|
||||||
|
if self.value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
return DocumentSymbol(
|
return DocumentSymbol(
|
||||||
f"{self.object_id}.{self.property_name}",
|
f"{self.object_id}.{self.property_name}",
|
||||||
SymbolKind.Property,
|
SymbolKind.Property,
|
||||||
|
|
|
@ -140,21 +140,10 @@ class ExtAdwResponseDialog(AstNode):
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Adw", "MessageDialog"),
|
applies_in_subclass=[("Adw", "AlertDialog"), ("Adw", "MessageDialog")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def complete_adw_message_dialog(lsp, ast_node, match_variables):
|
def complete_adw_message_dialog(_ctx: CompletionContext):
|
||||||
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):
|
|
||||||
yield Completion(
|
yield Completion(
|
||||||
"responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
|
"responses", CompletionItemKind.Keyword, snippet="responses [\n\t$0\n]"
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,6 +34,7 @@ from ..errors import (
|
||||||
CompileError,
|
CompileError,
|
||||||
CompileWarning,
|
CompileWarning,
|
||||||
DeprecatedWarning,
|
DeprecatedWarning,
|
||||||
|
ErrorReference,
|
||||||
MultipleErrors,
|
MultipleErrors,
|
||||||
UnusedWarning,
|
UnusedWarning,
|
||||||
UpgradeWarning,
|
UpgradeWarning,
|
||||||
|
|
|
@ -48,7 +48,7 @@ class ScopeCtx:
|
||||||
return self.node
|
return self.node
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def objects(self) -> T.Dict[str, Object]:
|
def objects(self) -> T.Dict[str, AstNode]:
|
||||||
return {
|
return {
|
||||||
obj.tokens["id"]: obj
|
obj.tokens["id"]: obj
|
||||||
for obj in self._iter_recursive(self.node)
|
for obj in self._iter_recursive(self.node)
|
||||||
|
@ -58,7 +58,7 @@ class ScopeCtx:
|
||||||
def validate_unique_ids(self) -> None:
|
def validate_unique_ids(self) -> None:
|
||||||
from .gtk_list_item_factory import ExtListItemFactory
|
from .gtk_list_item_factory import ExtListItemFactory
|
||||||
|
|
||||||
passed = {}
|
passed: T.Dict[str, AstNode] = {}
|
||||||
for obj in self._iter_recursive(self.node):
|
for obj in self._iter_recursive(self.node):
|
||||||
if obj.tokens["id"] is None:
|
if obj.tokens["id"] is None:
|
||||||
continue
|
continue
|
||||||
|
@ -71,10 +71,16 @@ class ScopeCtx:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"Duplicate object ID '{obj.tokens['id']}'",
|
f"Duplicate object ID '{obj.tokens['id']}'",
|
||||||
token.range,
|
token.range,
|
||||||
|
references=[
|
||||||
|
ErrorReference(
|
||||||
|
passed[obj.tokens["id"]].group.tokens["id"].range,
|
||||||
|
"previous declaration was here",
|
||||||
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
passed[obj.tokens["id"]] = obj
|
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
|
yield node
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
if child.context[ScopeCtx] is self:
|
if child.context[ScopeCtx] is self:
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
from ..decompiler import decompile_element
|
from ..decompiler import decompile_element
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ScopeCtx, ValueTypeCtx
|
from .contexts import ScopeCtx, ValueTypeCtx
|
||||||
from .types import TypeName
|
from .types import BracketedTypeName, TypeName
|
||||||
|
|
||||||
expr = Sequence()
|
expr = Sequence()
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ class CastExpr(InfixExpr):
|
||||||
grammar = [
|
grammar = [
|
||||||
Keyword("as"),
|
Keyword("as"),
|
||||||
AnyOf(
|
AnyOf(
|
||||||
["<", TypeName, Match(">").expected()],
|
BracketedTypeName,
|
||||||
[
|
[
|
||||||
UseExact("lparen", "("),
|
UseExact("lparen", "("),
|
||||||
TypeName,
|
TypeName,
|
||||||
|
@ -211,7 +211,13 @@ class CastExpr(InfixExpr):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> T.Optional[GirType]:
|
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()
|
@validate()
|
||||||
def cast_makes_sense(self):
|
def cast_makes_sense(self):
|
||||||
|
@ -302,12 +308,18 @@ expr.children = [
|
||||||
|
|
||||||
@decompiler("lookup", skip_children=True, cdata=True)
|
@decompiler("lookup", skip_children=True, cdata=True)
|
||||||
def decompile_lookup(
|
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":
|
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
|
||||||
ctx.print("expr ")
|
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)
|
type = decompile.full_name(t)
|
||||||
else:
|
else:
|
||||||
type = "$" + type
|
type = "$" + type
|
||||||
|
@ -327,7 +339,7 @@ def decompile_lookup(
|
||||||
if constant == ctx.template_class:
|
if constant == ctx.template_class:
|
||||||
ctx.print("template." + name)
|
ctx.print("template." + name)
|
||||||
elif constant == "":
|
elif constant == "":
|
||||||
ctx.print("item as <" + type + ">." + name)
|
ctx.print(f"item as <{type}>.{name}")
|
||||||
else:
|
else:
|
||||||
ctx.print(constant + "." + name)
|
ctx.print(constant + "." + name)
|
||||||
return
|
return
|
||||||
|
|
|
@ -26,7 +26,11 @@ from .values import ArrayValue, ExprValue, ObjectValue, Value
|
||||||
|
|
||||||
class Property(AstNode):
|
class Property(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
|
UseIdent("name"),
|
||||||
|
":",
|
||||||
|
AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue).expected(
|
||||||
|
"property value"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -225,8 +225,14 @@ class Signal(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@decompiler("signal")
|
@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 ""
|
object_name = object or ""
|
||||||
|
|
||||||
|
if object_name == ctx.template_class:
|
||||||
|
object_name = "template"
|
||||||
|
|
||||||
name = name.replace("_", "-")
|
name = name.replace("_", "-")
|
||||||
line = f"{name} => ${handler}({object_name})"
|
line = f"{name} => ${handler}({object_name})"
|
||||||
|
|
||||||
|
@ -241,3 +247,11 @@ def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", objec
|
||||||
line += ";"
|
line += ";"
|
||||||
ctx.print(line)
|
ctx.print(line)
|
||||||
return gir
|
return gir
|
||||||
|
|
||||||
|
|
||||||
|
@completer(
|
||||||
|
[Signal],
|
||||||
|
[[(TokenType.PUNCTUATION, "(")]],
|
||||||
|
)
|
||||||
|
def signal_object_completer(ctx: CompletionContext):
|
||||||
|
yield from get_object_id_completions(ctx)
|
||||||
|
|
|
@ -25,12 +25,13 @@ from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from .values import Value
|
from .values import Value
|
||||||
|
|
||||||
|
|
||||||
def get_property_types(gir):
|
def get_property_types(gir: gir.GirContext) -> T.Dict[str, T.Optional[GirType]]:
|
||||||
# from <https://docs.gtk.org/gtk4/enum.AccessibleProperty.html>
|
# from <https://docs.gtk.org/gtk4/enum.AccessibleProperty.html>
|
||||||
return {
|
return {
|
||||||
"autocomplete": gir.get_type("AccessibleAutocomplete", "Gtk"),
|
"autocomplete": gir.get_type("AccessibleAutocomplete", "Gtk"),
|
||||||
"description": StringType(),
|
"description": StringType(),
|
||||||
"has-popup": BoolType(),
|
"has-popup": BoolType(),
|
||||||
|
"help-text": StringType(),
|
||||||
"key-shortcuts": StringType(),
|
"key-shortcuts": StringType(),
|
||||||
"label": StringType(),
|
"label": StringType(),
|
||||||
"level": IntType(),
|
"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 <https://docs.gtk.org/gtk4/enum.AccessibleRelation.html>
|
# from <https://docs.gtk.org/gtk4/enum.AccessibleRelation.html>
|
||||||
widget = gir.get_type("Widget", "Gtk")
|
widget = gir.get_type("Widget", "Gtk")
|
||||||
return {
|
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 <https://docs.gtk.org/gtk4/enum.AccessibleState.html>
|
# from <https://docs.gtk.org/gtk4/enum.AccessibleState.html>
|
||||||
return {
|
return {
|
||||||
"busy": BoolType(),
|
"busy": BoolType(),
|
||||||
|
@ -86,9 +87,24 @@ def get_state_types(gir):
|
||||||
"invalid": gir.get_type("AccessibleInvalidState", "Gtk"),
|
"invalid": gir.get_type("AccessibleInvalidState", "Gtk"),
|
||||||
"pressed": gir.get_type("AccessibleTristate", "Gtk"),
|
"pressed": gir.get_type("AccessibleTristate", "Gtk"),
|
||||||
"selected": BoolType(),
|
"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):
|
def get_types(gir):
|
||||||
return {
|
return {
|
||||||
**get_property_types(gir),
|
**get_property_types(gir),
|
||||||
|
@ -121,7 +137,9 @@ class A11yProperty(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
AnyOf(Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]),
|
AnyOf(
|
||||||
|
Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]
|
||||||
|
).expected("value"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -232,9 +250,12 @@ class ExtAccessibility(AstNode):
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def a11y_completer(lsp, ast_node, match_variables):
|
def a11y_completer(_ctx: CompletionContext):
|
||||||
yield Completion(
|
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],
|
applies_in=[ExtAccessibility],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def a11y_name_completer(lsp, ast_node, match_variables):
|
def a11y_property_completer(ctx: CompletionContext):
|
||||||
for name, type in get_types(ast_node.root.gir).items():
|
for name, type in get_types(ctx.ast_node.root.gir).items():
|
||||||
yield Completion(
|
yield get_property_completion(
|
||||||
name,
|
name,
|
||||||
CompletionItemKind.Property,
|
type,
|
||||||
docs=_get_docs(ast_node.root.gir, type.name),
|
ctx,
|
||||||
|
name in TRANSLATED,
|
||||||
|
_get_docs(ctx.ast_node.root.gir, name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -91,11 +91,16 @@ class ExtComboBoxItems(AstNode):
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "ComboBoxText"),
|
applies_in_subclass=[("Gtk", "ComboBoxText")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def items_completer(lsp, ast_node, match_variables):
|
def items_completer(_ctx: CompletionContext):
|
||||||
yield Completion("items", CompletionItemKind.Snippet, snippet="items [$0]")
|
yield Completion(
|
||||||
|
"items",
|
||||||
|
CompletionItemKind.Snippet,
|
||||||
|
snippet="items [$0]",
|
||||||
|
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "items"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("items", parent_type="Gtk.ComboBoxText")
|
@decompiler("items", parent_type="Gtk.ComboBoxText")
|
||||||
|
|
|
@ -98,15 +98,28 @@ ext_file_filter_suffixes = create_node("suffixes", "suffix")
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "FileFilter"),
|
applies_in_subclass=[("Gtk", "FileFilter")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def file_filter_completer(lsp, ast_node, match_variables):
|
def file_filter_completer(_ctx: CompletionContext):
|
||||||
yield Completion(
|
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")
|
@decompiler("mime-types")
|
||||||
|
|
|
@ -90,11 +90,16 @@ class ExtLayout(AstNode):
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "Widget"),
|
applies_in_subclass=[("Gtk", "Widget")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def layout_completer(lsp, ast_node, match_variables):
|
def layout_completer(_ctx: CompletionContext):
|
||||||
yield Completion("layout", CompletionItemKind.Snippet, snippet="layout {\n $0\n}")
|
yield Completion(
|
||||||
|
"layout",
|
||||||
|
CompletionItemKind.Snippet,
|
||||||
|
snippet="layout {\n $0\n}",
|
||||||
|
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "layout"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("layout")
|
@decompiler("layout")
|
||||||
|
|
|
@ -243,7 +243,7 @@ from .ui import UI
|
||||||
applies_in=[UI],
|
applies_in=[UI],
|
||||||
matches=new_statement_patterns,
|
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}")
|
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],
|
applies_in=[Menu],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def menu_content_completer(lsp, ast_node, match_variables):
|
def menu_content_completer(_ctx: CompletionContext):
|
||||||
yield Completion(
|
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(
|
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(
|
yield Completion(
|
||||||
"item (shorthand)",
|
"item (shorthand)",
|
||||||
CompletionItemKind.Snippet,
|
CompletionItemKind.Snippet,
|
||||||
snippet='item (_("${1:Label}"), "${2:action-name}", "${3:icon-name}")',
|
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(
|
||||||
yield Completion("action", CompletionItemKind.Snippet, snippet='action: "$0";')
|
"label",
|
||||||
yield Completion("icon", CompletionItemKind.Snippet, snippet='icon: "$0";')
|
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")
|
@decompiler("menu")
|
||||||
|
|
|
@ -23,22 +23,20 @@ from .values import StringValue
|
||||||
|
|
||||||
|
|
||||||
class ExtScaleMark(AstNode):
|
class ExtScaleMark(AstNode):
|
||||||
grammar = [
|
grammar = Statement(
|
||||||
Keyword("mark"),
|
Keyword("mark"),
|
||||||
Match("(").expected(),
|
Match("(").expected(),
|
||||||
[
|
Optional(AnyOf(UseExact("sign", "-"), UseExact("sign", "+"))),
|
||||||
Optional(AnyOf(UseExact("sign", "-"), UseExact("sign", "+"))),
|
UseNumber("value").expected("value"),
|
||||||
UseNumber("value"),
|
Optional(
|
||||||
Optional(
|
[
|
||||||
[
|
",",
|
||||||
",",
|
UseIdent("position").expected("position"),
|
||||||
UseIdent("position"),
|
Optional([",", to_parse_node(StringValue).expected("label")]),
|
||||||
Optional([",", StringValue]),
|
]
|
||||||
]
|
),
|
||||||
),
|
end=")",
|
||||||
],
|
)
|
||||||
Match(")").expected(),
|
|
||||||
]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> float:
|
def value(self) -> float:
|
||||||
|
@ -134,20 +132,42 @@ class ExtScaleMarks(AstNode):
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "Scale"),
|
applies_in_subclass=[("Gtk", "Scale")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def complete_marks(lsp, ast_node, match_variables):
|
def complete_marks(_ctx: CompletionContext):
|
||||||
yield Completion("marks", CompletionItemKind.Keyword, snippet="marks [\n\t$0\n]")
|
yield Completion(
|
||||||
|
"marks",
|
||||||
|
CompletionItemKind.Keyword,
|
||||||
|
snippet="marks [\n\t$0\n]",
|
||||||
|
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "marks"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ExtScaleMarks],
|
applies_in=[ExtScaleMarks],
|
||||||
)
|
)
|
||||||
def complete_mark(lsp, ast_node, match_variables):
|
def complete_mark(_ctx: CompletionContext):
|
||||||
yield Completion("mark", CompletionItemKind.Keyword, snippet="mark ($0),")
|
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")
|
@decompiler("marks")
|
||||||
def decompile_marks(
|
def decompile_marks(
|
||||||
ctx,
|
ctx,
|
||||||
|
|
|
@ -101,11 +101,16 @@ class ExtSizeGroupWidgets(AstNode):
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "SizeGroup"),
|
applies_in_subclass=[("Gtk", "SizeGroup")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def size_group_completer(lsp, ast_node, match_variables):
|
def size_group_completer(_ctx: CompletionContext):
|
||||||
yield Completion("widgets", CompletionItemKind.Snippet, snippet="widgets [$0]")
|
yield Completion(
|
||||||
|
"widgets",
|
||||||
|
CompletionItemKind.Snippet,
|
||||||
|
snippet="widgets [$0]",
|
||||||
|
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "widgets"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("widgets")
|
@decompiler("widgets")
|
||||||
|
|
|
@ -72,11 +72,16 @@ class ExtStringListStrings(AstNode):
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "StringList"),
|
applies_in_subclass=[("Gtk", "StringList")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def strings_completer(lsp, ast_node, match_variables):
|
def strings_completer(_ctx: CompletionContext):
|
||||||
yield Completion("strings", CompletionItemKind.Snippet, snippet="strings [$0]")
|
yield Completion(
|
||||||
|
"strings",
|
||||||
|
CompletionItemKind.Snippet,
|
||||||
|
snippet="strings [$0]",
|
||||||
|
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "strings"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("items", parent_type="Gtk.StringList")
|
@decompiler("items", parent_type="Gtk.StringList")
|
||||||
|
|
|
@ -77,11 +77,16 @@ class ExtStyles(AstNode):
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
applies_in_subclass=("Gtk", "Widget"),
|
applies_in_subclass=[("Gtk", "Widget")],
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def style_completer(lsp, ast_node, match_variables):
|
def style_completer(_ctx: CompletionContext):
|
||||||
yield Completion("styles", CompletionItemKind.Keyword, snippet='styles ["$0"]')
|
yield Completion(
|
||||||
|
"styles",
|
||||||
|
CompletionItemKind.Keyword,
|
||||||
|
snippet='styles ["$0"]',
|
||||||
|
sort_text=get_sort_key(CompletionPriority.OBJECT_MEMBER, "styles"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("style")
|
@decompiler("style")
|
||||||
|
|
|
@ -31,7 +31,12 @@ ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [
|
||||||
|
|
||||||
|
|
||||||
class ChildInternal(AstNode):
|
class ChildInternal(AstNode):
|
||||||
grammar = ["internal-child", UseIdent("internal_child")]
|
grammar = [
|
||||||
|
"[",
|
||||||
|
"internal-child",
|
||||||
|
UseIdent("internal_child").expected("internal child name"),
|
||||||
|
Match("]").expected(),
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def internal_child(self) -> str:
|
def internal_child(self) -> str:
|
||||||
|
@ -39,7 +44,7 @@ class ChildInternal(AstNode):
|
||||||
|
|
||||||
|
|
||||||
class ChildType(AstNode):
|
class ChildType(AstNode):
|
||||||
grammar = UseIdent("child_type").expected("a child type")
|
grammar = ["[", UseIdent("child_type").expected("a child type"), "]"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def child_type(self) -> str:
|
def child_type(self) -> str:
|
||||||
|
@ -59,7 +64,7 @@ class ChildExtension(AstNode):
|
||||||
|
|
||||||
|
|
||||||
class ChildAnnotation(AstNode):
|
class ChildAnnotation(AstNode):
|
||||||
grammar = ["[", AnyOf(ChildInternal, ChildExtension, ChildType), "]"]
|
grammar = AnyOf(ChildInternal, ChildExtension, ChildType)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def child(self) -> T.Union[ChildInternal, ChildExtension, ChildType]:
|
def child(self) -> T.Union[ChildInternal, ChildExtension, ChildType]:
|
||||||
|
|
|
@ -28,19 +28,21 @@ class ExtResponse(AstNode):
|
||||||
|
|
||||||
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [("Gtk", "Dialog"), ("Gtk", "InfoBar")]
|
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [("Gtk", "Dialog"), ("Gtk", "InfoBar")]
|
||||||
|
|
||||||
grammar = [
|
grammar = Statement(
|
||||||
|
"[",
|
||||||
Keyword("action"),
|
Keyword("action"),
|
||||||
Keyword("response"),
|
Keyword("response"),
|
||||||
"=",
|
Match("=").expected(),
|
||||||
AnyOf(
|
AnyOf(
|
||||||
UseIdent("response_id"),
|
UseIdent("response_id"),
|
||||||
[
|
[
|
||||||
Optional(UseExact("sign", "-")),
|
Optional(UseExact("sign", "-")),
|
||||||
UseNumber("response_id"),
|
UseNumber("response_id"),
|
||||||
],
|
],
|
||||||
),
|
).expected("response ID"),
|
||||||
Optional([Keyword("default"), UseLiteral("is_default", True)]),
|
Optional([Keyword("default"), UseLiteral("is_default", True)]),
|
||||||
]
|
end="]",
|
||||||
|
)
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def parent_has_action_widgets(self) -> None:
|
def parent_has_action_widgets(self) -> None:
|
||||||
|
|
|
@ -27,11 +27,11 @@ class TypeName(AstNode):
|
||||||
[
|
[
|
||||||
UseIdent("namespace"),
|
UseIdent("namespace"),
|
||||||
".",
|
".",
|
||||||
UseIdent("class_name"),
|
UseIdent("class_name").expected("class name"),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
AnyOf("$", [".", UseLiteral("old_extern", True)]),
|
AnyOf("$", [".", UseLiteral("old_extern", True)]),
|
||||||
UseIdent("class_name"),
|
UseIdent("class_name").expected("class name"),
|
||||||
UseLiteral("extern", True),
|
UseLiteral("extern", True),
|
||||||
],
|
],
|
||||||
UseIdent("class_name"),
|
UseIdent("class_name"),
|
||||||
|
@ -47,7 +47,11 @@ class TypeName(AstNode):
|
||||||
|
|
||||||
@validate("class_name")
|
@validate("class_name")
|
||||||
def type_exists(self):
|
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.root.gir.validate_type(
|
||||||
self.tokens["class_name"], self.tokens["namespace"]
|
self.tokens["class_name"], self.tokens["namespace"]
|
||||||
)
|
)
|
||||||
|
@ -182,3 +186,14 @@ class TemplateClassName(ClassName):
|
||||||
self.root.gir.validate_type(
|
self.root.gir.validate_type(
|
||||||
self.tokens["class_name"], self.tokens["namespace"]
|
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]
|
||||||
|
|
|
@ -110,16 +110,22 @@ class UI(AstNode):
|
||||||
and self.template.class_name.glib_type_name == id
|
and self.template.class_name.glib_type_name == id
|
||||||
)
|
)
|
||||||
|
|
||||||
def import_code_action(self, ns: str, version: str) -> CodeAction:
|
def import_range(self, ns: str):
|
||||||
if len(self.children[Import]):
|
"""Returns a range to insert a new import statement"""
|
||||||
pos = self.children[Import][-1].range.end
|
pos = self.children[GtkDirective][0].range.end
|
||||||
else:
|
|
||||||
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(
|
return CodeAction(
|
||||||
f"Import {ns} {version}",
|
f"Import {ns} {version}",
|
||||||
f"\nusing {ns} {version};",
|
f"\nusing {ns} {version};",
|
||||||
Range(pos, pos, self.group.text),
|
self.import_range(ns),
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|
|
@ -26,7 +26,7 @@ from .common import *
|
||||||
from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
|
from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
|
||||||
from .expression import Expression
|
from .expression import Expression
|
||||||
from .gobject_object import Object
|
from .gobject_object import Object
|
||||||
from .types import TypeName
|
from .types import BracketedTypeName, TypeName
|
||||||
|
|
||||||
|
|
||||||
class Translated(AstNode):
|
class Translated(AstNode):
|
||||||
|
@ -58,6 +58,19 @@ class Translated(AstNode):
|
||||||
f"Cannot convert translated string to {expected_type.full_name}"
|
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()
|
@docs()
|
||||||
def ref_docs(self):
|
def ref_docs(self):
|
||||||
return get_docs_section("Syntax Translated")
|
return get_docs_section("Syntax Translated")
|
||||||
|
@ -67,11 +80,7 @@ class TypeLiteral(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
"typeof",
|
"typeof",
|
||||||
AnyOf(
|
AnyOf(
|
||||||
[
|
BracketedTypeName,
|
||||||
"<",
|
|
||||||
to_parse_node(TypeName).expected("type name"),
|
|
||||||
Match(">").expected(),
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
UseExact("lparen", "("),
|
UseExact("lparen", "("),
|
||||||
to_parse_node(TypeName).expected("type name"),
|
to_parse_node(TypeName).expected("type name"),
|
||||||
|
@ -85,8 +94,13 @@ class TypeLiteral(AstNode):
|
||||||
return gir.TypeType()
|
return gir.TypeType()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_name(self) -> TypeName:
|
def type_name(self) -> T.Optional[TypeName]:
|
||||||
return self.children[TypeName][0]
|
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()
|
@validate()
|
||||||
def validate_for_type(self) -> None:
|
def validate_for_type(self) -> None:
|
||||||
|
@ -212,12 +226,12 @@ class Flag(AstNode):
|
||||||
return self.tokens["value"]
|
return self.tokens["value"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> T.Optional[int]:
|
def value(self) -> T.Optional[str]:
|
||||||
type = self.context[ValueTypeCtx].value_type
|
type = self.context[ValueTypeCtx].value_type
|
||||||
if not isinstance(type, Enumeration):
|
if not isinstance(type, Enumeration):
|
||||||
return None
|
return None
|
||||||
elif member := type.members.get(self.name):
|
elif member := type.members.get(self.name):
|
||||||
return member.value
|
return member.nick
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -325,7 +339,14 @@ class IdentLiteral(AstNode):
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
'"item" can only be used in an expression literal'
|
'"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(
|
raise CompileError(
|
||||||
f"Could not find object with ID {self.ident}",
|
f"Could not find object with ID {self.ident}",
|
||||||
did_you_mean=(
|
did_you_mean=(
|
||||||
|
|
|
@ -87,6 +87,7 @@ class Completion:
|
||||||
text: T.Optional[str] = None
|
text: T.Optional[str] = None
|
||||||
snippet: T.Optional[str] = None
|
snippet: T.Optional[str] = None
|
||||||
detail: T.Optional[str] = None
|
detail: T.Optional[str] = None
|
||||||
|
additional_text_edits: T.Optional[T.List["TextEdit"]] = None
|
||||||
|
|
||||||
def to_json(self, snippets: bool):
|
def to_json(self, snippets: bool):
|
||||||
insert_text = self.text or self.label
|
insert_text = self.text or self.label
|
||||||
|
@ -114,6 +115,11 @@ class Completion:
|
||||||
"insertText": insert_text,
|
"insertText": insert_text,
|
||||||
"insertTextFormat": insert_text_format,
|
"insertTextFormat": insert_text_format,
|
||||||
"detail": self.detail if self.detail else None,
|
"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}
|
return {k: v for k, v in result.items() if v is not None}
|
||||||
|
|
||||||
|
|
|
@ -209,6 +209,7 @@ class XmlOutput(OutputFormat):
|
||||||
else:
|
else:
|
||||||
xml.put_text(self._object_id(value, value.ident))
|
xml.put_text(self._object_id(value, value.ident))
|
||||||
elif isinstance(value, TypeLiteral):
|
elif isinstance(value, TypeLiteral):
|
||||||
|
assert value.type_name is not None
|
||||||
xml.put_text(value.type_name.glib_type_name)
|
xml.put_text(value.type_name.glib_type_name)
|
||||||
else:
|
else:
|
||||||
if isinstance(value.value, float) and value.value == int(value.value):
|
if isinstance(value.value, float) and value.value == int(value.value):
|
||||||
|
@ -308,6 +309,9 @@ class XmlOutput(OutputFormat):
|
||||||
|
|
||||||
elif isinstance(extension, AdwBreakpointSetters):
|
elif isinstance(extension, AdwBreakpointSetters):
|
||||||
for setter in extension.setters:
|
for setter in extension.setters:
|
||||||
|
if setter.value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
attrs = {}
|
attrs = {}
|
||||||
|
|
||||||
if isinstance(setter.value.child, Translated):
|
if isinstance(setter.value.child, Translated):
|
||||||
|
|
|
@ -73,6 +73,7 @@ class XmlEmitter:
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
def put_cdata(self, text: str):
|
def put_cdata(self, text: str):
|
||||||
|
text = text.replace("]]>", "]]]]><![CDATA[>")
|
||||||
self.result += f"<![CDATA[{text}]]>"
|
self.result += f"<![CDATA[{text}]]>"
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# 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
|
import typing as T
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -235,7 +235,15 @@ class ParseNode:
|
||||||
start_idx = ctx.index
|
start_idx = ctx.index
|
||||||
inner_ctx = ctx.create_child()
|
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)
|
ctx.apply_child(inner_ctx)
|
||||||
if ctx.index == start_idx:
|
if ctx.index == start_idx:
|
||||||
return ParseResult.EMPTY
|
return ParseResult.EMPTY
|
||||||
|
@ -269,12 +277,12 @@ class Err(ParseNode):
|
||||||
if self.child.parse(ctx).failed():
|
if self.child.parse(ctx).failed():
|
||||||
start_idx = ctx.start
|
start_idx = ctx.start
|
||||||
while ctx.tokens[start_idx].type in SKIP_TOKENS:
|
while ctx.tokens[start_idx].type in SKIP_TOKENS:
|
||||||
start_idx += 1
|
start_idx -= 1
|
||||||
start_token = ctx.tokens[start_idx]
|
start_token = ctx.tokens[start_idx]
|
||||||
|
|
||||||
raise CompileError(
|
position = start_token.start if ctx.start == start_idx else start_token.end
|
||||||
self.message, Range(start_token.start, start_token.start, ctx.text)
|
|
||||||
)
|
raise CompileError(self.message, Range(position, position, ctx.text))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -329,8 +337,9 @@ class Statement(ParseNode):
|
||||||
"""ParseNode that attempts to match all of its children in sequence. If any
|
"""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."""
|
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.children = [to_parse_node(child) for child in children]
|
||||||
|
self.end = end
|
||||||
|
|
||||||
def _parse(self, ctx) -> bool:
|
def _parse(self, ctx) -> bool:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
|
@ -340,11 +349,29 @@ class Statement(ParseNode):
|
||||||
except CompileError as e:
|
except CompileError as e:
|
||||||
ctx.errors.append(e)
|
ctx.errors.append(e)
|
||||||
ctx.set_group_incomplete()
|
ctx.set_group_incomplete()
|
||||||
|
|
||||||
|
token = ctx.peek_token()
|
||||||
|
if str(token) == self.end:
|
||||||
|
ctx.next_token()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
token = ctx.peek_token()
|
token = ctx.peek_token()
|
||||||
if str(token) != ";":
|
if str(token) != self.end:
|
||||||
ctx.errors.append(CompileError("Expected `;`", token.range))
|
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:
|
else:
|
||||||
ctx.next_token()
|
ctx.next_token()
|
||||||
return True
|
return True
|
||||||
|
@ -405,7 +432,6 @@ class Until(ParseNode):
|
||||||
ctx.skip_unexpected_token()
|
ctx.skip_unexpected_token()
|
||||||
except CompileError as e:
|
except CompileError as e:
|
||||||
ctx.errors.append(e)
|
ctx.errors.append(e)
|
||||||
ctx.next_token()
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from pathlib import Path
|
||||||
|
|
||||||
__all__ = ["get_docs_section"]
|
__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"] = {}
|
sections: dict[str, "Section"] = {}
|
||||||
|
@ -132,5 +132,8 @@ if __name__ == "__main__":
|
||||||
# print the sections to a json file
|
# print the sections to a json file
|
||||||
with open(outfile, "w") as f:
|
with open(outfile, "w") as f:
|
||||||
json.dump(
|
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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,8 +16,8 @@ a module in your flatpak manifest:
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
|
"url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
|
||||||
"tag": "v0.14.0"
|
"tag": "v0.16.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
|
||||||
|
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
|
|
||||||
template MyAppWindow : ApplicationWindow {
|
template $MyAppWindow: ApplicationWindow {
|
||||||
default-width: 600;
|
default-width: 600;
|
||||||
default-height: 300;
|
default-height: 300;
|
||||||
title: _("Hello, Blueprint!");
|
title: _("Hello, Blueprint!");
|
||||||
|
@ -35,7 +35,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
|
||||||
HeaderBar {}
|
HeaderBar {}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
label: bind MyAppWindow.main_text;
|
label: bind template.main_text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ Features
|
||||||
Links
|
Links
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- `Source code <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
|
- `Source code <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
|
||||||
- `Workbench <https://github.com/sonnyp/Workbench>`_ lets you try, preview and export Blueprint
|
- `Workbench <https://github.com/sonnyp/Workbench>`_ lets you try, preview and export Blueprint
|
||||||
- `GNOME Builder <https://developer.gnome.org/documentation/introduction/builder.html>`_ provides builtin support
|
- `GNOME Builder <https://developer.gnome.org/documentation/introduction/builder.html>`_ provides builtin support
|
||||||
- `Vim syntax highlighting plugin by thetek42 <https://github.com/thetek42/vim-blueprint-syntax>`_
|
- `Vim syntax highlighting plugin by thetek42 <https://github.com/thetek42/vim-blueprint-syntax>`_
|
||||||
|
|
|
@ -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() <https://docs.gtk.org/gtk4/vfunc.Buildable.custom_tag_start.html>`_ for internal details.
|
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() <https://docs.gtk.org/gtk4/vfunc.Buildable.custom_tag_start.html>`_ 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 <https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues>`_.
|
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 <https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues>`_.
|
||||||
|
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ Setting up Blueprint on a new or existing project
|
||||||
Using the porting tool
|
Using the porting tool
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Clone `blueprint-compiler <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
|
Clone `blueprint-compiler <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
|
||||||
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
|
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
|
||||||
or you can leave it uninstalled.
|
or you can leave it uninstalled.
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ blueprint-compiler works as a meson subproject.
|
||||||
|
|
||||||
[wrap-git]
|
[wrap-git]
|
||||||
directory = blueprint-compiler
|
directory = blueprint-compiler
|
||||||
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
|
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
|
||||||
revision = main
|
revision = main
|
||||||
depth = 1
|
depth = 1
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ If you're using Meson's `i18n module <https://mesonbuild.com/i18n-module.html#i1
|
||||||
|
|
||||||
i18n.gettext('package name', preset: 'glib')
|
i18n.gettext('package name', preset: 'glib')
|
||||||
|
|
||||||
|
You must use double quotes for the translated strings in order for gettext to recognize them. Newer versions of blueprint will warn you if you use single quotes.
|
||||||
|
|
||||||
Contexts
|
Contexts
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
project('blueprint-compiler',
|
project('blueprint-compiler',
|
||||||
version: '0.14.0',
|
version: '0.16.0',
|
||||||
)
|
)
|
||||||
|
|
||||||
prefix = get_option('prefix')
|
prefix = get_option('prefix')
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
4,6,22,Action widget must have ID
|
4,5,24,Action widget must have ID
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
4,6,18,Gtk.Box doesn't have action widgets
|
4,5,20,Gtk.Box doesn't have action widgets
|
||||||
|
|
5
tests/sample_errors/convert_bool_to_obj.blp
Normal file
5
tests/sample_errors/convert_bool_to_obj.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Button {
|
||||||
|
child: false;
|
||||||
|
}
|
1
tests/sample_errors/convert_bool_to_obj.err
Normal file
1
tests/sample_errors/convert_bool_to_obj.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,10,5,Cannot assign boolean to Gtk.Widget
|
|
@ -1 +1 @@
|
||||||
1,0,0,File must start with a "using Gtk" directive (e.g. `using Gtk 4.0;`)
|
1,1,0,File must start with a "using Gtk" directive (e.g. `using Gtk 4.0;`)
|
|
@ -1 +1 @@
|
||||||
6,1,1,Expected `;`
|
5,4,0,Expected `;`
|
|
@ -1,2 +1 @@
|
||||||
5,1,0,Expected a signal detail name
|
4,11,0,Expected a signal detail name
|
||||||
4,9,3,Unexpected tokens
|
|
|
@ -1,2 +1 @@
|
||||||
4,5,21,Attributes are not permitted at the top level of a menu
|
4,5,21,Attributes are not permitted at the top level of a menu
|
||||||
4,16,10,Unexpected tokens
|
|
|
@ -1 +1 @@
|
||||||
1,11,0,Expected a version number for GTK
|
1,10,0,Expected a version number for GTK
|
||||||
|
|
5
tests/sample_errors/single_quoted_translated.blp
Normal file
5
tests/sample_errors/single_quoted_translated.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: _('Hello, World!');
|
||||||
|
}
|
1
tests/sample_errors/single_quoted_translated.err
Normal file
1
tests/sample_errors/single_quoted_translated.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,12,15,gettext may not recognize single-quoted strings
|
|
@ -7,7 +7,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk" version="4.0"/>
|
<requires lib="gtk" version="4.0"/>
|
||||||
<object class="GApplication">
|
<object class="GApplication">
|
||||||
<property name="flags">1|4</property>
|
<property name="flags">is-service|handles-open</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkEventControllerScroll">
|
<object class="GtkEventControllerScroll">
|
||||||
<property name="flags">1</property>
|
<property name="flags">1</property>
|
||||||
|
|
15
tests/samples/issue_187.ui
Normal file
15
tests/samples/issue_187.ui
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="GtkListItem">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<binding name="label">
|
||||||
|
<lookup type="RecentObject" name="filename">
|
||||||
|
<lookup name="item">GtkListItem</lookup>
|
||||||
|
</lookup>
|
||||||
|
</binding>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</template>
|
||||||
|
</interface>
|
7
tests/samples/issue_187_dec.blp
Normal file
7
tests/samples/issue_187_dec.blp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
template ListItem {
|
||||||
|
child: Label {
|
||||||
|
label: bind template.item as <$RecentObject>.filename;
|
||||||
|
};
|
||||||
|
}
|
17
tests/samples/list_factory_nested.blp
Normal file
17
tests/samples/list_factory_nested.blp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Gtk.ListView {
|
||||||
|
factory: Gtk.BuilderListItemFactory list_item_factory {
|
||||||
|
template ListItem {
|
||||||
|
child: Gtk.ListView {
|
||||||
|
factory: Gtk.BuilderListItemFactory list_item_factory {
|
||||||
|
template ListItem {
|
||||||
|
child: Gtk.Label {
|
||||||
|
label: bind template.item as <$MyObject>.name;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
44
tests/samples/list_factory_nested.ui
Normal file
44
tests/samples/list_factory_nested.ui
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
DO NOT EDIT!
|
||||||
|
This file was @generated by blueprint-compiler. Instead, edit the
|
||||||
|
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
|
-->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<object class="GtkListView">
|
||||||
|
<property name="factory">
|
||||||
|
<object class="GtkBuilderListItemFactory" id="list_item_factory">
|
||||||
|
<property name="bytes"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="GtkListItem">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkListView">
|
||||||
|
<property name="factory">
|
||||||
|
<object class="GtkBuilderListItemFactory" id="list_item_factory">
|
||||||
|
<property name="bytes"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="GtkListItem">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<binding name="label">
|
||||||
|
<lookup name="name" type="MyObject">
|
||||||
|
<lookup name="item" type="GtkListItem">
|
||||||
|
<constant>GtkListItem</constant>
|
||||||
|
</lookup>
|
||||||
|
</lookup>
|
||||||
|
</binding>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</template>
|
||||||
|
</interface>]]]]><![CDATA[></property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</template>
|
||||||
|
</interface>]]></property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</interface>
|
18
tests/samples/list_factory_nested_dec.blp
Normal file
18
tests/samples/list_factory_nested_dec.blp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
factory: BuilderListItemFactory list_item_factory {
|
||||||
|
template ListItem {
|
||||||
|
child: ListView {
|
||||||
|
factory: BuilderListItemFactory list_item_factory {
|
||||||
|
template ListItem {
|
||||||
|
child: Label {
|
||||||
|
label: bind template.item as <$MyObject>.name;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
7
tests/samples/signal_template_object.blp
Normal file
7
tests/samples/signal_template_object.blp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
template $MyTemplate {
|
||||||
|
Button {
|
||||||
|
clicked => $my_signal_handler(template);
|
||||||
|
}
|
||||||
|
}
|
16
tests/samples/signal_template_object.ui
Normal file
16
tests/samples/signal_template_object.ui
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
DO NOT EDIT!
|
||||||
|
This file was @generated by blueprint-compiler. Instead, edit the
|
||||||
|
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
|
-->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="MyTemplate">
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<signal name="clicked" handler="my_signal_handler" object="MyTemplate"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -64,11 +64,11 @@ class TestSamples(unittest.TestCase):
|
||||||
|
|
||||||
def assert_ast_doesnt_crash(self, text, tokens, ast: AstNode):
|
def assert_ast_doesnt_crash(self, text, tokens, ast: AstNode):
|
||||||
lsp = LanguageServer()
|
lsp = LanguageServer()
|
||||||
for i in range(len(text)):
|
for i in range(len(text) + 1):
|
||||||
ast.get_docs(i)
|
ast.get_docs(i)
|
||||||
for i in range(len(text)):
|
for i in range(len(text) + 1):
|
||||||
list(complete(lsp, ast, tokens, i))
|
list(complete(lsp, ast, tokens, i))
|
||||||
for i in range(len(text)):
|
for i in range(len(text) + 1):
|
||||||
ast.get_reference(i)
|
ast.get_reference(i)
|
||||||
ast.get_document_symbols()
|
ast.get_document_symbols()
|
||||||
|
|
||||||
|
@ -143,9 +143,9 @@ class TestSamples(unittest.TestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
def error_str(error: CompileError):
|
def error_str(error: CompileError):
|
||||||
line, col = utils.idx_to_pos(error.range.start + 1, blueprint)
|
line, col = utils.idx_to_pos(error.range.start, blueprint)
|
||||||
len = error.range.length
|
len = error.range.length
|
||||||
return ",".join([str(line + 1), str(col), str(len), error.message])
|
return ",".join([str(line + 1), str(col + 1), str(len), error.message])
|
||||||
|
|
||||||
actual = "\n".join([error_str(error) for error in errors])
|
actual = "\n".join([error_str(error) for error in errors])
|
||||||
|
|
||||||
|
@ -181,11 +181,7 @@ class TestSamples(unittest.TestCase):
|
||||||
|
|
||||||
def test_samples(self):
|
def test_samples(self):
|
||||||
# list the samples directory
|
# list the samples directory
|
||||||
samples = [
|
samples = [f.stem for f in Path(__file__).parent.glob("samples/*.blp")]
|
||||||
f.stem
|
|
||||||
for f in Path(__file__).parent.glob("samples/*.blp")
|
|
||||||
if not f.stem.endswith("_dec")
|
|
||||||
]
|
|
||||||
samples.sort()
|
samples.sort()
|
||||||
for sample in samples:
|
for sample in samples:
|
||||||
REQUIRE_ADW_1_4 = ["adw_breakpoint"]
|
REQUIRE_ADW_1_4 = ["adw_breakpoint"]
|
||||||
|
@ -202,6 +198,7 @@ class TestSamples(unittest.TestCase):
|
||||||
"parseable",
|
"parseable",
|
||||||
"signal",
|
"signal",
|
||||||
"signal_not_swapped",
|
"signal_not_swapped",
|
||||||
|
"signal_template_object",
|
||||||
"template",
|
"template",
|
||||||
"template_binding",
|
"template_binding",
|
||||||
"template_binding_extern",
|
"template_binding_extern",
|
||||||
|
@ -215,7 +212,7 @@ class TestSamples(unittest.TestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Decompiler-only tests
|
# Decompiler-only tests
|
||||||
SKIP_COMPILE = ["issue_177", "translator_comments"]
|
SKIP_COMPILE = ["issue_177", "issue_187", "translator_comments"]
|
||||||
|
|
||||||
SKIP_DECOMPILE = [
|
SKIP_DECOMPILE = [
|
||||||
# Comments are not preserved in either direction
|
# Comments are not preserved in either direction
|
||||||
|
@ -228,7 +225,7 @@ class TestSamples(unittest.TestCase):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with self.subTest(sample):
|
with self.subTest(sample):
|
||||||
if sample not in SKIP_COMPILE:
|
if sample not in SKIP_COMPILE and not sample.endswith("_dec"):
|
||||||
self.assert_sample(sample, skip_run=sample in SKIP_RUN)
|
self.assert_sample(sample, skip_run=sample in SKIP_RUN)
|
||||||
|
|
||||||
with self.subTest("decompile/" + sample):
|
with self.subTest("decompile/" + sample):
|
||||||
|
|
|
@ -25,7 +25,7 @@ from blueprintcompiler.tokenizer import Token, TokenType, tokenize
|
||||||
|
|
||||||
|
|
||||||
class TestTokenizer(unittest.TestCase):
|
class TestTokenizer(unittest.TestCase):
|
||||||
def assert_tokenize(self, string: str, expect: [Token]):
|
def assert_tokenize(self, string: str, expect: list[Token]):
|
||||||
try:
|
try:
|
||||||
tokens = tokenize(string)
|
tokens = tokenize(string)
|
||||||
self.assertEqual(len(tokens), len(expect))
|
self.assertEqual(len(tokens), len(expect))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue