mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Compare commits
15 commits
c63b4a4a03
...
ee6624d567
Author | SHA1 | Date | |
---|---|---|---|
|
ee6624d567 | ||
|
a6d57cebec | ||
|
9b9fab832b | ||
|
5b0f662478 | ||
|
ac70ea7403 | ||
|
e1f972ef16 | ||
|
13e477aa25 | ||
|
ba8ec80456 | ||
|
0fe58ffc37 | ||
|
14d1892254 | ||
|
c74c5ac232 | ||
|
9e293a31e6 | ||
|
cce1af5f09 | ||
|
4dd55ab2aa | ||
|
52e651a168 |
56 changed files with 908 additions and 204 deletions
191
blueprintcompiler/annotations.py
Normal file
191
blueprintcompiler/annotations.py
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
# annotations.py
|
||||||
|
#
|
||||||
|
# Copyright 2024 James Westman <james@jwestman.net>
|
||||||
|
#
|
||||||
|
# This file is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as
|
||||||
|
# published by the Free Software Foundation; either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This file is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
# Extra information about types in common libraries that's used for things like completions.
|
||||||
|
|
||||||
|
import typing as T
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from . import gir
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Annotation:
|
||||||
|
translatable_properties: T.List[str]
|
||||||
|
|
||||||
|
|
||||||
|
def is_property_translated(property: gir.Property):
|
||||||
|
ns = property.get_containing(gir.Namespace)
|
||||||
|
ns_name = ns.name + "-" + ns.version
|
||||||
|
if annotation := _ANNOTATIONS.get(ns_name):
|
||||||
|
assert property.container is not None
|
||||||
|
return (
|
||||||
|
property.container.name + ":" + property.name
|
||||||
|
in annotation.translatable_properties
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
_ANNOTATIONS = {
|
||||||
|
"Gtk-4.0": Annotation(
|
||||||
|
translatable_properties=[
|
||||||
|
"AboutDialog:comments",
|
||||||
|
"AboutDialog:translator-credits",
|
||||||
|
"AboutDialog:website-label",
|
||||||
|
"AlertDialog:detail",
|
||||||
|
"AlertDialog:message",
|
||||||
|
"AppChooserButton:heading",
|
||||||
|
"AppChooserDialog:heading",
|
||||||
|
"AppChooserWidget:default-text",
|
||||||
|
"AssistantPage:title",
|
||||||
|
"Button:label",
|
||||||
|
"CellRendererText:markup",
|
||||||
|
"CellRendererText:placeholder-text",
|
||||||
|
"CellRendererText:text",
|
||||||
|
"CheckButton:label",
|
||||||
|
"ColorButton:title",
|
||||||
|
"ColorDialog:title",
|
||||||
|
"ColumnViewColumn:title",
|
||||||
|
"ColumnViewRow:accessible-description",
|
||||||
|
"ColumnViewRow:accessible-label",
|
||||||
|
"Entry:placeholder-text",
|
||||||
|
"Entry:primary-icon-tooltip-markup",
|
||||||
|
"Entry:primary-icon-tooltip-text",
|
||||||
|
"Entry:secondary-icon-tooltip-markup",
|
||||||
|
"Entry:secondary-icon-tooltip-text",
|
||||||
|
"EntryBuffer:text",
|
||||||
|
"Expander:label",
|
||||||
|
"FileChooserNative:accept-label",
|
||||||
|
"FileChooserNative:cancel-label",
|
||||||
|
"FileChooserWidget:subtitle",
|
||||||
|
"FileDialog:accept-label",
|
||||||
|
"FileDialog:title",
|
||||||
|
"FileDialog:initial-name",
|
||||||
|
"FileFilter:name",
|
||||||
|
"FontButton:title",
|
||||||
|
"FontDialog:title",
|
||||||
|
"Frame:label",
|
||||||
|
"Inscription:markup",
|
||||||
|
"Inscription:text",
|
||||||
|
"Label:label",
|
||||||
|
"ListItem:accessible-description",
|
||||||
|
"ListItem:accessible-label",
|
||||||
|
"LockButton:text-lock",
|
||||||
|
"LockButton:text-unlock",
|
||||||
|
"LockButton:tooltip-lock",
|
||||||
|
"LockButton:tooltip-not-authorized",
|
||||||
|
"LockButton:tooltip-unlock",
|
||||||
|
"MenuButton:label",
|
||||||
|
"MessageDialog:secondary-text",
|
||||||
|
"MessageDialog:text",
|
||||||
|
"NativeDialog:title",
|
||||||
|
"NotebookPage:menu-label",
|
||||||
|
"NotebookPage:tab-label",
|
||||||
|
"PasswordEntry:placeholder-text",
|
||||||
|
"Picture:alternative-text",
|
||||||
|
"PrintDialog:accept-label",
|
||||||
|
"PrintDialog:title",
|
||||||
|
"Printer:name",
|
||||||
|
"PrintJob:title",
|
||||||
|
"PrintOperation:custom-tab-label",
|
||||||
|
"PrintOperation:export-filename",
|
||||||
|
"PrintOperation:job-name",
|
||||||
|
"ProgressBar:text",
|
||||||
|
"SearchEntry:placeholder-text",
|
||||||
|
"ShortcutLabel:disabled-text",
|
||||||
|
"ShortcutsGroup:title",
|
||||||
|
"ShortcutsSection:title",
|
||||||
|
"ShortcutsShortcut:title",
|
||||||
|
"ShortcutsShortcut:subtitle",
|
||||||
|
"StackPage:title",
|
||||||
|
"Text:placeholder-text",
|
||||||
|
"TextBuffer:text",
|
||||||
|
"TreeViewColumn:title",
|
||||||
|
"Widget:tooltip-markup",
|
||||||
|
"Widget:tooltip-text",
|
||||||
|
"Window:title",
|
||||||
|
"Editable:text",
|
||||||
|
"FontChooser:preview-text",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"Adw-1": Annotation(
|
||||||
|
translatable_properties=[
|
||||||
|
"AboutDialog:comments",
|
||||||
|
"AboutDialog:translator-credits",
|
||||||
|
"AboutWindow:comments",
|
||||||
|
"AboutWindow:translator-credits",
|
||||||
|
"ActionRow:subtitle",
|
||||||
|
"ActionRow:title",
|
||||||
|
"AlertDialog:body",
|
||||||
|
"AlertDialog:heading",
|
||||||
|
"Avatar:text",
|
||||||
|
"Banner:button-label",
|
||||||
|
"Banner:title",
|
||||||
|
"ButtonContent:label",
|
||||||
|
"Dialog:title",
|
||||||
|
"ExpanderRow:subtitle",
|
||||||
|
"MessageDialog:body",
|
||||||
|
"MessageDialog:heading",
|
||||||
|
"NavigationPage:title",
|
||||||
|
"PreferencesGroup:description",
|
||||||
|
"PreferencesGroup:title",
|
||||||
|
"PreferencesPage:description",
|
||||||
|
"PreferencesPage:title",
|
||||||
|
"PreferencesRow:title",
|
||||||
|
"SplitButton:dropdown-tooltip",
|
||||||
|
"SplitButton:label",
|
||||||
|
"StatusPage:description",
|
||||||
|
"StatusPage:title",
|
||||||
|
"TabPage:indicator-tooltip",
|
||||||
|
"TabPage:keyword",
|
||||||
|
"TabPage:title",
|
||||||
|
"Toast:button-label",
|
||||||
|
"Toast:title",
|
||||||
|
"ViewStackPage:title",
|
||||||
|
"ViewSwitcherTitle:subtitle",
|
||||||
|
"ViewSwitcherTitle:title",
|
||||||
|
"WindowTitle:subtitle",
|
||||||
|
"WindowTitle:title",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"Shumate-1.0": Annotation(
|
||||||
|
translatable_properties=[
|
||||||
|
"License:extra-text",
|
||||||
|
"MapSource:license",
|
||||||
|
"MapSource:name",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"GtkSource-5": Annotation(
|
||||||
|
translatable_properties=[
|
||||||
|
"CompletionCell:markup",
|
||||||
|
"CompletionCell:text",
|
||||||
|
"CompletionSnippets:title",
|
||||||
|
"CompletionWords:title",
|
||||||
|
"GutterRendererText:markup",
|
||||||
|
"GutterRendererText:text",
|
||||||
|
"SearchSettings:search-text",
|
||||||
|
"Snippet:description",
|
||||||
|
"Snippet:name",
|
||||||
|
"SnippetChunk:tooltip-text",
|
||||||
|
"StyleScheme:description",
|
||||||
|
"StyleScheme:name",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
}
|
|
@ -160,6 +160,11 @@ class AstNode:
|
||||||
yield e
|
yield e
|
||||||
if e.fatal:
|
if e.fatal:
|
||||||
return
|
return
|
||||||
|
except MultipleErrors as e:
|
||||||
|
for error in e.errors:
|
||||||
|
yield error
|
||||||
|
if error.fatal:
|
||||||
|
return
|
||||||
|
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
yield from child._get_errors()
|
yield from child._get_errors()
|
||||||
|
@ -249,14 +254,7 @@ def validate(
|
||||||
if skip_incomplete and self.incomplete:
|
if skip_incomplete and self.incomplete:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
def fill_error(e: CompileError):
|
||||||
func(self)
|
|
||||||
except CompileError as e:
|
|
||||||
# If the node is only partially complete, then an error must
|
|
||||||
# have already been reported at the parsing stage
|
|
||||||
if self.incomplete:
|
|
||||||
return
|
|
||||||
|
|
||||||
if e.range is None:
|
if e.range is None:
|
||||||
e.range = (
|
e.range = (
|
||||||
Range.join(
|
Range.join(
|
||||||
|
@ -266,8 +264,26 @@ def validate(
|
||||||
or self.range
|
or self.range
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
func(self)
|
||||||
|
except CompileError as e:
|
||||||
|
# If the node is only partially complete, then an error must
|
||||||
|
# have already been reported at the parsing stage
|
||||||
|
if self.incomplete:
|
||||||
|
return
|
||||||
|
|
||||||
|
fill_error(e)
|
||||||
|
|
||||||
# Re-raise the exception
|
# Re-raise the exception
|
||||||
raise e
|
raise e
|
||||||
|
except MultipleErrors as e:
|
||||||
|
if self.incomplete:
|
||||||
|
return
|
||||||
|
|
||||||
|
for error in e.errors:
|
||||||
|
fill_error(error)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
inner._validator = True
|
inner._validator = True
|
||||||
return inner
|
return inner
|
||||||
|
|
|
@ -17,10 +17,9 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
import sys
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from . import 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 *
|
||||||
from .language.types import ClassName
|
from .language.types import ClassName
|
||||||
|
@ -31,10 +30,6 @@ from .tokenizer import Token, TokenType
|
||||||
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
|
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
|
||||||
|
|
||||||
|
|
||||||
def debug(*args, **kwargs):
|
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
) -> T.Iterator[Completion]:
|
) -> T.Iterator[Completion]:
|
||||||
|
@ -139,7 +134,7 @@ def gtk_object_completer(lsp, ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def property_completer(lsp, ast_node, match_variables):
|
def property_completer(lsp, ast_node, match_variables):
|
||||||
if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
|
if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
|
||||||
for prop_name, prop in ast_node.gir_class.properties.items():
|
for prop_name, prop in ast_node.gir_class.properties.items():
|
||||||
if (
|
if (
|
||||||
isinstance(prop.type, gir.BoolType)
|
isinstance(prop.type, gir.BoolType)
|
||||||
|
@ -154,11 +149,17 @@ def property_completer(lsp, ast_node, match_variables):
|
||||||
detail=prop.detail,
|
detail=prop.detail,
|
||||||
)
|
)
|
||||||
elif isinstance(prop.type, gir.StringType):
|
elif isinstance(prop.type, gir.StringType):
|
||||||
|
snippet = (
|
||||||
|
f'{prop_name}: _("$0");'
|
||||||
|
if annotations.is_property_translated(prop)
|
||||||
|
else f'{prop_name}: "$0";'
|
||||||
|
)
|
||||||
|
|
||||||
yield Completion(
|
yield Completion(
|
||||||
prop_name,
|
prop_name,
|
||||||
CompletionItemKind.Property,
|
CompletionItemKind.Property,
|
||||||
sort_text=f"0 {prop_name}",
|
sort_text=f"0 {prop_name}",
|
||||||
snippet=f'{prop_name}: "$0";',
|
snippet=snippet,
|
||||||
docs=prop.doc,
|
docs=prop.doc,
|
||||||
detail=prop.detail,
|
detail=prop.detail,
|
||||||
)
|
)
|
||||||
|
@ -188,7 +189,7 @@ def property_completer(lsp, ast_node, match_variables):
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[language.Property, language.BaseAttribute],
|
applies_in=[language.Property, language.A11yProperty],
|
||||||
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
|
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
|
||||||
)
|
)
|
||||||
def prop_value_completer(lsp, ast_node, match_variables):
|
def prop_value_completer(lsp, ast_node, match_variables):
|
||||||
|
@ -212,7 +213,7 @@ def prop_value_completer(lsp, ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def signal_completer(lsp, ast_node, match_variables):
|
def signal_completer(lsp, ast_node, match_variables):
|
||||||
if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
|
if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
|
||||||
for signal_name, signal in ast_node.gir_class.signals.items():
|
for signal_name, signal in ast_node.gir_class.signals.items():
|
||||||
if not isinstance(ast_node.parent, language.Object):
|
if not isinstance(ast_node.parent, language.Object):
|
||||||
name = "on"
|
name = "on"
|
||||||
|
|
|
@ -31,17 +31,6 @@ new_statement_patterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def applies_to(*ast_types):
|
|
||||||
"""Decorator describing which AST nodes the completer should apply in."""
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
for c in ast_types:
|
|
||||||
c.completers.append(func)
|
|
||||||
return func
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
|
def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
import re
|
import re
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from . import tokenizer, utils
|
from . import tokenizer
|
||||||
|
from .errors import CompilerBugError
|
||||||
from .tokenizer import TokenType
|
from .tokenizer import TokenType
|
||||||
|
|
||||||
OPENING_TOKENS = ("{", "[")
|
OPENING_TOKENS = ("{", "[")
|
||||||
|
@ -192,8 +193,8 @@ def format(data, tab_size=2, insert_space=True):
|
||||||
|
|
||||||
commit_current_line(LineType.COMMENT, newlines_before=newlines)
|
commit_current_line(LineType.COMMENT, newlines_before=newlines)
|
||||||
|
|
||||||
else:
|
else: # pragma: no cover
|
||||||
commit_current_line()
|
raise CompilerBugError()
|
||||||
|
|
||||||
elif str_item == "(" and (
|
elif str_item == "(" and (
|
||||||
re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
|
re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
|
||||||
|
|
|
@ -24,8 +24,20 @@ from functools import cached_property
|
||||||
|
|
||||||
import gi # type: ignore
|
import gi # type: ignore
|
||||||
|
|
||||||
gi.require_version("GIRepository", "2.0")
|
try:
|
||||||
from gi.repository import GIRepository # type: ignore
|
gi.require_version("GIRepository", "3.0")
|
||||||
|
from gi.repository import GIRepository # type: ignore
|
||||||
|
|
||||||
|
_repo = GIRepository.Repository()
|
||||||
|
except ValueError:
|
||||||
|
# We can remove this once we can bump the minimum dependencies
|
||||||
|
# to glib 2.80 and pygobject 3.52
|
||||||
|
# dependency('glib-2.0', version: '>= 2.80.0')
|
||||||
|
# dependency('girepository-2.0', version: '>= 2.80.0')
|
||||||
|
gi.require_version("GIRepository", "2.0")
|
||||||
|
from gi.repository import GIRepository # type: ignore
|
||||||
|
|
||||||
|
_repo = GIRepository.Repository
|
||||||
|
|
||||||
from . import typelib, xml_reader
|
from . import typelib, xml_reader
|
||||||
from .errors import CompileError, CompilerBugError
|
from .errors import CompileError, CompilerBugError
|
||||||
|
@ -42,7 +54,7 @@ def add_typelib_search_path(path: str):
|
||||||
|
|
||||||
|
|
||||||
def get_namespace(namespace: str, version: str) -> "Namespace":
|
def get_namespace(namespace: str, version: str) -> "Namespace":
|
||||||
search_paths = [*GIRepository.Repository.get_search_path(), *_user_search_paths]
|
search_paths = [*_repo.get_search_path(), *_user_search_paths]
|
||||||
|
|
||||||
filename = f"{namespace}-{version}.typelib"
|
filename = f"{namespace}-{version}.typelib"
|
||||||
|
|
||||||
|
@ -74,7 +86,7 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
|
||||||
return _available_namespaces
|
return _available_namespaces
|
||||||
|
|
||||||
search_paths: list[str] = [
|
search_paths: list[str] = [
|
||||||
*GIRepository.Repository.get_search_path(),
|
*_repo.get_search_path(),
|
||||||
*_user_search_paths,
|
*_user_search_paths,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -455,10 +467,13 @@ class Signature(GirNode):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def return_type(self) -> GirType:
|
def return_type(self) -> T.Optional[GirType]:
|
||||||
return self.get_containing(Repository)._resolve_type_id(
|
if self.tl.SIGNATURE_RETURN_TYPE == 0:
|
||||||
self.tl.SIGNATURE_RETURN_TYPE
|
return None
|
||||||
)
|
else:
|
||||||
|
return self.get_containing(Repository)._resolve_type_id(
|
||||||
|
self.tl.SIGNATURE_RETURN_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Signal(GirNode):
|
class Signal(GirNode):
|
||||||
|
@ -478,7 +493,10 @@ class Signal(GirNode):
|
||||||
args = ", ".join(
|
args = ", ".join(
|
||||||
[f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
|
[f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
|
||||||
)
|
)
|
||||||
return f"signal {self.container.full_name}::{self.name} ({args})"
|
result = f"signal {self.container.full_name}::{self.name} ({args})"
|
||||||
|
if self.gir_signature.return_type is not None:
|
||||||
|
result += f" -> {self.gir_signature.return_type.full_name}"
|
||||||
|
return result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online_docs(self) -> T.Optional[str]:
|
def online_docs(self) -> T.Optional[str]:
|
||||||
|
@ -890,14 +908,6 @@ class Namespace(GirNode):
|
||||||
if isinstance(entry, Class)
|
if isinstance(entry, Class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def interfaces(self) -> T.Mapping[str, Interface]:
|
|
||||||
return {
|
|
||||||
name: entry
|
|
||||||
for name, entry in self.entries.items()
|
|
||||||
if isinstance(entry, Interface)
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_type(self, name) -> T.Optional[GirType]:
|
def get_type(self, name) -> T.Optional[GirType]:
|
||||||
"""Gets a type (class, interface, enum, etc.) from this namespace."""
|
"""Gets a type (class, interface, enum, etc.) from this namespace."""
|
||||||
return self.entries.get(name)
|
return self.entries.get(name)
|
||||||
|
@ -921,13 +931,8 @@ class Namespace(GirNode):
|
||||||
"""Looks up a type in the scope of this namespace (including in the
|
"""Looks up a type in the scope of this namespace (including in the
|
||||||
namespace's dependencies)."""
|
namespace's dependencies)."""
|
||||||
|
|
||||||
if type_name in _BASIC_TYPES:
|
ns, name = type_name.split(".", 1)
|
||||||
return _BASIC_TYPES[type_name]()
|
return self.get_containing(Repository).get_type(name, ns)
|
||||||
elif "." in type_name:
|
|
||||||
ns, name = type_name.split(".", 1)
|
|
||||||
return self.get_containing(Repository).get_type(name, ns)
|
|
||||||
else:
|
|
||||||
return self.get_type(type_name)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online_docs(self) -> T.Optional[str]:
|
def online_docs(self) -> T.Optional[str]:
|
||||||
|
@ -946,7 +951,7 @@ class Repository(GirNode):
|
||||||
self.includes = {
|
self.includes = {
|
||||||
name: get_namespace(name, version) for name, version in deps
|
name: get_namespace(name, version) for name, version in deps
|
||||||
}
|
}
|
||||||
except:
|
except: # pragma: no cover
|
||||||
raise CompilerBugError(f"Failed to load dependencies.")
|
raise CompilerBugError(f"Failed to load dependencies.")
|
||||||
else:
|
else:
|
||||||
self.includes = {}
|
self.includes = {}
|
||||||
|
@ -954,12 +959,6 @@ class Repository(GirNode):
|
||||||
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
||||||
return self.lookup_namespace(ns).get_type(name)
|
return self.lookup_namespace(ns).get_type(name)
|
||||||
|
|
||||||
def get_type_by_cname(self, name: str) -> T.Optional[GirType]:
|
|
||||||
for ns in [self.namespace, *self.includes.values()]:
|
|
||||||
if type := ns.get_type_by_cname(name):
|
|
||||||
return type
|
|
||||||
return None
|
|
||||||
|
|
||||||
def lookup_namespace(self, ns: str):
|
def lookup_namespace(self, ns: str):
|
||||||
"""Finds a namespace among this namespace's dependencies."""
|
"""Finds a namespace among this namespace's dependencies."""
|
||||||
if ns == self.namespace.name:
|
if ns == self.namespace.name:
|
||||||
|
|
|
@ -4,7 +4,6 @@ from .adw_breakpoint import (
|
||||||
AdwBreakpointSetters,
|
AdwBreakpointSetters,
|
||||||
)
|
)
|
||||||
from .adw_response_dialog import ExtAdwResponseDialog
|
from .adw_response_dialog import ExtAdwResponseDialog
|
||||||
from .attributes import BaseAttribute
|
|
||||||
from .binding import Binding
|
from .binding import Binding
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ScopeCtx, ValueTypeCtx
|
from .contexts import ScopeCtx, ValueTypeCtx
|
||||||
|
@ -20,7 +19,7 @@ from .expression import (
|
||||||
from .gobject_object import Object, ObjectContent
|
from .gobject_object import Object, ObjectContent
|
||||||
from .gobject_property import Property
|
from .gobject_property import Property
|
||||||
from .gobject_signal import Signal
|
from .gobject_signal import Signal
|
||||||
from .gtk_a11y import ExtAccessibility
|
from .gtk_a11y import A11yProperty, ExtAccessibility
|
||||||
from .gtk_combo_box_text import ExtComboBoxItems
|
from .gtk_combo_box_text import ExtComboBoxItems
|
||||||
from .gtk_file_filter import (
|
from .gtk_file_filter import (
|
||||||
Filters,
|
Filters,
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
|
|
||||||
from ..decompiler import decompile_translatable, truthy
|
from ..decompiler import decompile_translatable, truthy
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from .values import StringValue
|
from .values import StringValue
|
||||||
|
|
||||||
|
@ -94,10 +93,6 @@ class ExtAdwResponseDialogResponse(AstNode):
|
||||||
self.value.range.text,
|
self.value.range.text,
|
||||||
)
|
)
|
||||||
|
|
||||||
@context(ValueTypeCtx)
|
|
||||||
def value_type(self) -> ValueTypeCtx:
|
|
||||||
return ValueTypeCtx(StringType())
|
|
||||||
|
|
||||||
@validate("id")
|
@validate("id")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent(
|
self.validate_unique_in_parent(
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
# attributes.py
|
|
||||||
#
|
|
||||||
# Copyright 2022 James Westman <james@jwestman.net>
|
|
||||||
#
|
|
||||||
# This file is free software; you can redistribute it and/or modify it
|
|
||||||
# under the terms of the GNU Lesser General Public License as
|
|
||||||
# published by the Free Software Foundation; either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This file is distributed in the hope that it will be useful, but
|
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
|
|
||||||
|
|
||||||
from .common import *
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAttribute(AstNode):
|
|
||||||
"""A helper class for attribute syntax of the form `name: literal_value;`"""
|
|
||||||
|
|
||||||
tag_name: str = ""
|
|
||||||
attr_name: str = "name"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.tokens["name"]
|
|
|
@ -38,10 +38,6 @@ class ExprBase(AstNode):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
|
||||||
def type_complete(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rhs(self) -> T.Optional["ExprBase"]:
|
def rhs(self) -> T.Optional["ExprBase"]:
|
||||||
if isinstance(self.parent, Expression):
|
if isinstance(self.parent, Expression):
|
||||||
|
@ -65,10 +61,6 @@ class Expression(ExprBase):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.last.type
|
return self.last.type
|
||||||
|
|
||||||
@property
|
|
||||||
def type_complete(self) -> bool:
|
|
||||||
return self.last.type_complete
|
|
||||||
|
|
||||||
|
|
||||||
class InfixExpr(ExprBase):
|
class InfixExpr(ExprBase):
|
||||||
@property
|
@property
|
||||||
|
@ -99,15 +91,6 @@ class LiteralExpr(ExprBase):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.literal.value.type
|
return self.literal.value.type
|
||||||
|
|
||||||
@property
|
|
||||||
def type_complete(self) -> bool:
|
|
||||||
from .values import IdentLiteral
|
|
||||||
|
|
||||||
if isinstance(self.literal.value, IdentLiteral):
|
|
||||||
if object := self.context[ScopeCtx].objects.get(self.literal.value.ident):
|
|
||||||
return not object.gir_class.incomplete
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class LookupOp(InfixExpr):
|
class LookupOp(InfixExpr):
|
||||||
grammar = [".", UseIdent("property")]
|
grammar = [".", UseIdent("property")]
|
||||||
|
@ -211,10 +194,6 @@ class CastExpr(InfixExpr):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.children[TypeName][0].gir_type
|
return self.children[TypeName][0].gir_type
|
||||||
|
|
||||||
@property
|
|
||||||
def type_complete(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def cast_makes_sense(self):
|
def cast_makes_sense(self):
|
||||||
if self.type is None or self.lhs.type is None:
|
if self.type is None or self.lhs.type is None:
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Property(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def document_symbol(self) -> DocumentSymbol:
|
def document_symbol(self) -> DocumentSymbol:
|
||||||
if isinstance(self.value, ObjectValue):
|
if isinstance(self.value, ObjectValue) or self.value is None:
|
||||||
detail = None
|
detail = None
|
||||||
else:
|
else:
|
||||||
detail = self.value.range.text
|
detail = self.value.range.text
|
||||||
|
|
|
@ -27,6 +27,7 @@ from .gtkbuilder_template import Template
|
||||||
class SignalFlag(AstNode):
|
class SignalFlag(AstNode):
|
||||||
grammar = AnyOf(
|
grammar = AnyOf(
|
||||||
UseExact("flag", "swapped"),
|
UseExact("flag", "swapped"),
|
||||||
|
UseExact("flag", "not-swapped"),
|
||||||
UseExact("flag", "after"),
|
UseExact("flag", "after"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,6 +41,27 @@ class SignalFlag(AstNode):
|
||||||
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
|
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def swapped_exclusive(self):
|
||||||
|
if self.flag in ["swapped", "not-swapped"]:
|
||||||
|
self.validate_unique_in_parent(
|
||||||
|
"'swapped' and 'not-swapped' flags cannot be used together",
|
||||||
|
lambda x: x.flag in ["swapped", "not-swapped"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def swapped_unnecessary(self):
|
||||||
|
if self.flag == "not-swapped" and self.parent.object_id is None:
|
||||||
|
raise CompileWarning(
|
||||||
|
"'not-swapped' is the default for handlers that do not specify an object",
|
||||||
|
actions=[CodeAction("Remove 'not-swapped' flag", "")],
|
||||||
|
)
|
||||||
|
elif self.flag == "swapped" and self.parent.object_id is not None:
|
||||||
|
raise CompileWarning(
|
||||||
|
"'swapped' is the default for handlers that specify an object",
|
||||||
|
actions=[CodeAction("Remove 'swapped' flag", "")],
|
||||||
|
)
|
||||||
|
|
||||||
@docs()
|
@docs()
|
||||||
def ref_docs(self):
|
def ref_docs(self):
|
||||||
return get_docs_section("Syntax Signal")
|
return get_docs_section("Syntax Signal")
|
||||||
|
@ -92,9 +114,17 @@ class Signal(AstNode):
|
||||||
def flags(self) -> T.List[SignalFlag]:
|
def flags(self) -> T.List[SignalFlag]:
|
||||||
return self.children[SignalFlag]
|
return self.children[SignalFlag]
|
||||||
|
|
||||||
|
# Returns True if the "swapped" flag is present, False if "not-swapped" is present, and None if neither are present.
|
||||||
|
# GtkBuilder's default if swapped is not specified is to not swap the arguments if no object is specified, and to
|
||||||
|
# swap them if an object is specified.
|
||||||
@property
|
@property
|
||||||
def is_swapped(self) -> bool:
|
def is_swapped(self) -> T.Optional[bool]:
|
||||||
return any(x.flag == "swapped" for x in self.flags)
|
for flag in self.flags:
|
||||||
|
if flag.flag == "swapped":
|
||||||
|
return True
|
||||||
|
elif flag.flag == "not-swapped":
|
||||||
|
return False
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_after(self) -> bool:
|
def is_after(self) -> bool:
|
||||||
|
@ -122,7 +152,7 @@ class Signal(AstNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_reference(self, idx: int) -> T.Optional[LocationLink]:
|
def get_reference(self, idx: int) -> T.Optional[LocationLink]:
|
||||||
if idx in self.group.tokens["object"].range:
|
if self.object_id is not None and idx in self.group.tokens["object"].range:
|
||||||
obj = self.context[ScopeCtx].objects.get(self.object_id)
|
obj = self.context[ScopeCtx].objects.get(self.object_id)
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
return LocationLink(
|
return LocationLink(
|
||||||
|
@ -194,15 +224,16 @@ class Signal(AstNode):
|
||||||
|
|
||||||
|
|
||||||
@decompiler("signal")
|
@decompiler("signal")
|
||||||
def decompile_signal(
|
def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", object=None):
|
||||||
ctx, gir, name, handler, swapped="false", after="false", object=None
|
|
||||||
):
|
|
||||||
object_name = object or ""
|
object_name = object or ""
|
||||||
name = name.replace("_", "-")
|
name = name.replace("_", "-")
|
||||||
line = f"{name} => ${handler}({object_name})"
|
line = f"{name} => ${handler}({object_name})"
|
||||||
|
|
||||||
if decompile.truthy(swapped):
|
if decompile.truthy(swapped):
|
||||||
line += " swapped"
|
line += " swapped"
|
||||||
|
elif swapped is not None:
|
||||||
|
line += " not-swapped"
|
||||||
|
|
||||||
if decompile.truthy(after):
|
if decompile.truthy(after):
|
||||||
line += " after"
|
line += " after"
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from ..decompiler import escape_quote
|
|
||||||
from .attributes import BaseAttribute
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
from .contexts import ValueTypeCtx
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
|
@ -119,7 +117,7 @@ def _get_docs(gir, name):
|
||||||
return gir_type.doc
|
return gir_type.doc
|
||||||
|
|
||||||
|
|
||||||
class A11yProperty(BaseAttribute):
|
class A11yProperty(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from .values import StringValue
|
from .values import StringValue
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class ExtListItemFactory(AstNode):
|
||||||
else:
|
else:
|
||||||
return self.root.gir.get_type("ListItem", "Gtk")
|
return self.root.gir.get_type("ListItem", "Gtk")
|
||||||
|
|
||||||
@validate("template")
|
@validate("id")
|
||||||
def container_is_builder_list(self):
|
def container_is_builder_list(self):
|
||||||
validate_parent_type(
|
validate_parent_type(
|
||||||
self,
|
self,
|
||||||
|
@ -59,7 +59,7 @@ class ExtListItemFactory(AstNode):
|
||||||
"sub-templates",
|
"sub-templates",
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("template")
|
@validate("id")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate template block")
|
self.validate_unique_in_parent("Duplicate template block")
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ class ExtListItemFactory(AstNode):
|
||||||
f"Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here"
|
f"Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here"
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("template")
|
@validate("id")
|
||||||
def type_name_upgrade(self):
|
def type_name_upgrade(self):
|
||||||
if self.type_name is None:
|
if self.type_name is None:
|
||||||
raise UpgradeWarning(
|
raise UpgradeWarning(
|
||||||
|
@ -103,10 +103,7 @@ class ExtListItemFactory(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def action_widgets(self):
|
def action_widgets(self):
|
||||||
"""
|
# The sub-template shouldn't have its own actions, this is just here to satisfy XmlOutput._emit_object_or_template
|
||||||
The sub-template shouldn't have it`s own actions this is
|
|
||||||
just hear to satisfy XmlOutput._emit_object_or_template
|
|
||||||
"""
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@docs("id")
|
@docs("id")
|
||||||
|
|
|
@ -59,14 +59,8 @@ class GtkDirective(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_namespace(self):
|
def gir_namespace(self):
|
||||||
# validate the GTK version first to make sure the more specific error
|
# For better error handling, just assume it's 4.0
|
||||||
# message is emitted
|
return gir.get_namespace("Gtk", "4.0")
|
||||||
self.gtk_version()
|
|
||||||
if self.tokens["version"] is not None:
|
|
||||||
return gir.get_namespace("Gtk", self.tokens["version"])
|
|
||||||
else:
|
|
||||||
# For better error handling, just assume it's 4.0
|
|
||||||
return gir.get_namespace("Gtk", "4.0")
|
|
||||||
|
|
||||||
@docs()
|
@docs()
|
||||||
def ref_docs(self):
|
def ref_docs(self):
|
||||||
|
@ -90,7 +84,7 @@ class Import(AstNode):
|
||||||
|
|
||||||
@validate("namespace", "version")
|
@validate("namespace", "version")
|
||||||
def namespace_exists(self):
|
def namespace_exists(self):
|
||||||
gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
gir.get_namespace(self.namespace, self.version)
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def unused(self):
|
def unused(self):
|
||||||
|
@ -106,7 +100,7 @@ class Import(AstNode):
|
||||||
@property
|
@property
|
||||||
def gir_namespace(self):
|
def gir_namespace(self):
|
||||||
try:
|
try:
|
||||||
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
return gir.get_namespace(self.namespace, self.version)
|
||||||
except CompileError:
|
except CompileError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,7 @@ class XmlOutput(OutputFormat):
|
||||||
"signal",
|
"signal",
|
||||||
name=name,
|
name=name,
|
||||||
handler=signal.handler,
|
handler=signal.handler,
|
||||||
swapped=signal.is_swapped or None,
|
swapped=signal.is_swapped,
|
||||||
after=signal.is_after or None,
|
after=signal.is_after or None,
|
||||||
object=(
|
object=(
|
||||||
self._object_id(signal, signal.object_id) if signal.object_id else None
|
self._object_id(signal, signal.object_id) if signal.object_id else None
|
||||||
|
@ -366,12 +366,13 @@ class XmlOutput(OutputFormat):
|
||||||
|
|
||||||
elif isinstance(extension, ExtScaleMarks):
|
elif isinstance(extension, ExtScaleMarks):
|
||||||
xml.start_tag("marks")
|
xml.start_tag("marks")
|
||||||
for mark in extension.children:
|
for mark in extension.marks:
|
||||||
|
label = mark.label.child if mark.label is not None else None
|
||||||
xml.start_tag(
|
xml.start_tag(
|
||||||
"mark",
|
"mark",
|
||||||
value=mark.value,
|
value=mark.value,
|
||||||
position=mark.position,
|
position=mark.position,
|
||||||
**self._translated_string_attrs(mark.label and mark.label.child),
|
**self._translated_string_attrs(label),
|
||||||
)
|
)
|
||||||
if mark.label is not None:
|
if mark.label is not None:
|
||||||
xml.put_text(mark.label.string)
|
xml.put_text(mark.label.string)
|
||||||
|
|
|
@ -40,7 +40,9 @@ class XmlEmitter:
|
||||||
self._tag_stack = []
|
self._tag_stack = []
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
def start_tag(self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None]):
|
def start_tag(
|
||||||
|
self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None, float]
|
||||||
|
):
|
||||||
self._indent()
|
self._indent()
|
||||||
self.result += f"<{tag}"
|
self.result += f"<{tag}"
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
|
|
|
@ -95,19 +95,11 @@ class ParseGroup:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
|
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
|
||||||
except TypeError as e:
|
except TypeError: # pragma: no cover
|
||||||
raise CompilerBugError(
|
raise CompilerBugError(
|
||||||
f"Failed to construct ast.{self.ast_type.__name__} from ParseGroup. See the previous stacktrace."
|
f"Failed to construct ast.{self.ast_type.__name__} from ParseGroup. See the previous stacktrace."
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
result = str(self.ast_type.__name__)
|
|
||||||
result += "".join([f"\n{key}: {val}" for key, val in self.keys.items()]) + "\n"
|
|
||||||
result += "\n".join(
|
|
||||||
[str(child) for children in self.children.values() for child in children]
|
|
||||||
)
|
|
||||||
return result.replace("\n", "\n ")
|
|
||||||
|
|
||||||
|
|
||||||
class ParseContext:
|
class ParseContext:
|
||||||
"""Contains the state of the parser."""
|
"""Contains the state of the parser."""
|
||||||
|
@ -265,10 +257,6 @@ class ParseNode:
|
||||||
"""Convenience method for err()."""
|
"""Convenience method for err()."""
|
||||||
return self.err("Expected " + expect)
|
return self.err("Expected " + expect)
|
||||||
|
|
||||||
def warn(self, message) -> "Warning":
|
|
||||||
"""Causes this ParseNode to emit a warning if it parses successfully."""
|
|
||||||
return Warning(self, message)
|
|
||||||
|
|
||||||
|
|
||||||
class Err(ParseNode):
|
class Err(ParseNode):
|
||||||
"""ParseNode that emits a compile error if it fails to parse."""
|
"""ParseNode that emits a compile error if it fails to parse."""
|
||||||
|
@ -290,27 +278,6 @@ class Err(ParseNode):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Warning(ParseNode):
|
|
||||||
"""ParseNode that emits a compile warning if it parses successfully."""
|
|
||||||
|
|
||||||
def __init__(self, child, message: str):
|
|
||||||
self.child = to_parse_node(child)
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def _parse(self, ctx: ParseContext):
|
|
||||||
ctx.skip()
|
|
||||||
start_idx = ctx.index
|
|
||||||
if self.child.parse(ctx).succeeded():
|
|
||||||
start_token = ctx.tokens[start_idx]
|
|
||||||
end_token = ctx.tokens[ctx.index]
|
|
||||||
ctx.warnings.append(
|
|
||||||
CompileWarning(self.message, start_token.start, end_token.end)
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Fail(ParseNode):
|
class Fail(ParseNode):
|
||||||
"""ParseNode that emits a compile error if it parses successfully."""
|
"""ParseNode that emits a compile error if it parses successfully."""
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ Signal Handlers
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
Signal = <name::ref:`IDENT<Syntax IDENT>`> ('::' <detail::ref:`IDENT<Syntax IDENT>`>)? '=>' '$' <handler::ref:`IDENT<Syntax IDENT>`> '(' <object::ref:`IDENT<Syntax IDENT>`>? ')' (SignalFlag)* ';'
|
Signal = <name::ref:`IDENT<Syntax IDENT>`> ('::' <detail::ref:`IDENT<Syntax IDENT>`>)? '=>' '$' <handler::ref:`IDENT<Syntax IDENT>`> '(' <object::ref:`IDENT<Syntax IDENT>`>? ')' (SignalFlag)* ';'
|
||||||
SignalFlag = 'after' | 'swapped'
|
SignalFlag = 'after' | 'swapped' | 'not-swapped'
|
||||||
|
|
||||||
Signals are one way to respond to user input (another is `actions <https://docs.gtk.org/gtk4/actions.html>`_, which use the `action-name property <https://docs.gtk.org/gtk4/property.Actionable.action-name.html>`_).
|
Signals are one way to respond to user input (another is `actions <https://docs.gtk.org/gtk4/actions.html>`_, which use the `action-name property <https://docs.gtk.org/gtk4/property.Actionable.action-name.html>`_).
|
||||||
|
|
||||||
|
@ -99,6 +99,8 @@ Signals provide a handle for your code to listen to events in the UI. The handle
|
||||||
|
|
||||||
Optionally, you can provide an object ID to use when connecting the signal.
|
Optionally, you can provide an object ID to use when connecting the signal.
|
||||||
|
|
||||||
|
The ``swapped`` flag is used to swap the order of the object and userdata arguments in C applications. If an object argument is specified, then this is the default behavior, so the ``not-swapped`` flag can be used to prevent the swap.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
@ -108,7 +110,6 @@ Example
|
||||||
clicked => $on_button_clicked();
|
clicked => $on_button_clicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.. _Syntax Child:
|
.. _Syntax Child:
|
||||||
|
|
||||||
Children
|
Children
|
||||||
|
|
481
docs/tutorial.rst
Normal file
481
docs/tutorial.rst
Normal file
|
@ -0,0 +1,481 @@
|
||||||
|
========
|
||||||
|
Tutorial
|
||||||
|
========
|
||||||
|
|
||||||
|
.. margin at column 75
|
||||||
|
|
||||||
|
Read this if you want to learn how to use Blueprint and never used
|
||||||
|
the XML syntax that can be read by GtkBuilder.
|
||||||
|
|
||||||
|
For compatibility with Blueprint IDE extensions, blueprint files
|
||||||
|
should end with ``.blp``.
|
||||||
|
|
||||||
|
|
||||||
|
Namespaces
|
||||||
|
----------
|
||||||
|
|
||||||
|
Blueprint needs the widget library to be imported. These include Gtk,
|
||||||
|
Libadwaita, Shumate, etc. To import a namespace, write ``using`` followed
|
||||||
|
by the library and version number.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
The Gtk import is required in all blueprints and the minor version
|
||||||
|
number must be 0.
|
||||||
|
|
||||||
|
|
||||||
|
Comments
|
||||||
|
--------
|
||||||
|
|
||||||
|
Blueprint has inline or multi-line comments
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
// This is an inline comment
|
||||||
|
/* This is
|
||||||
|
a multiline
|
||||||
|
comment */
|
||||||
|
|
||||||
|
Multi-line comments can't have inner multi-line comments. The compiler
|
||||||
|
will interpret the inner comment's closing token as the outer comment's
|
||||||
|
closing token. For example, the following will not compile:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
// Bad comment below:
|
||||||
|
/* Outer comment
|
||||||
|
/* Inner comment */
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
Widgets
|
||||||
|
-------
|
||||||
|
|
||||||
|
Create widgets in the following format:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Namespace.WidgetClass {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
The Gtk namespace is implied for widgets, so you can just write the
|
||||||
|
widget class
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Other namespaces must be written explicitly.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Adw.Leaflet {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Consult the widget library's documentation for a list of widgets.
|
||||||
|
A good place to start is
|
||||||
|
`the Gtk4 widget list <https://docs.gtk.org/gtk4/index.html>`_.
|
||||||
|
|
||||||
|
Naming Widgets
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Widgets can be given a **name/ID** so that they can be referenced by your
|
||||||
|
program or other widgets in the blueprint.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Namespace.WidgetClass widget_id {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Any time you want to use this widget as a property (more about that in the
|
||||||
|
next section) or something else, write the widget's **ID** (e.g.
|
||||||
|
``main_window``).
|
||||||
|
|
||||||
|
|
||||||
|
Properties
|
||||||
|
----------
|
||||||
|
|
||||||
|
Every widget has properties defined by their GObject class.
|
||||||
|
For example, the Libadwaita documentation lists the
|
||||||
|
`properties of the Toast class <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.2/class.Toast.html#properties>`_.
|
||||||
|
Write properties inside the curly brackets of a widget:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Namespace.WidgetClass {
|
||||||
|
property-name: value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties values are *all lowercase* (except strings) and must end with a
|
||||||
|
semicolon (``;``).
|
||||||
|
|
||||||
|
Property Types
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
These are the **types** of values that can be used in properties:
|
||||||
|
- Booleans: ``true``, ``false``
|
||||||
|
- Numbers: e.g. ``1``, ``1.5``, ``-2``, ``-2.5``
|
||||||
|
- Strings (single- or double-quoted): e.g. ``"a string"``, ``'another string'``
|
||||||
|
- Enums
|
||||||
|
- Widgets
|
||||||
|
|
||||||
|
Properties are **strongly typed**, so you can't use, for example, a string
|
||||||
|
for the orientation property, which requires an ``Orientation`` enum
|
||||||
|
vartiant as its value.
|
||||||
|
|
||||||
|
Enum Properties
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In the Gtk documentation, enum variants have long names and are
|
||||||
|
capitalized. For example, these are the
|
||||||
|
`Orientation <https://docs.gtk.org/gtk4/enum.Orientation.html>`_
|
||||||
|
enum variants:
|
||||||
|
|
||||||
|
- GTK_ORIENTATION_HORIZONTAL
|
||||||
|
- GTK_ORIENTATION_VERTICAL
|
||||||
|
|
||||||
|
In the blueprint, you would only write the *variant* part of the enum in
|
||||||
|
*lowercase*, just like you would in the XML.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget Properties
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some widgets take other widgets as properties. For example, the
|
||||||
|
``Gtk.StackSidebar`` has a stack property which takes a ``Gtk.Stack`` widget.
|
||||||
|
You can create a new widget for the value, or you can reference another
|
||||||
|
widget by its **ID**.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
StackSidebar {
|
||||||
|
stack: Stack { };
|
||||||
|
}
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
StackSidebar {
|
||||||
|
stack: my_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stack my_stack {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Note the use of a semicolon at the end of the property in both cases.
|
||||||
|
Inline widget properties are not exempt of this rule.
|
||||||
|
|
||||||
|
|
||||||
|
Property Bindings
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If you want a widget's property to have the same value as another widget's
|
||||||
|
property (without hard-coding the value), you could ``bind`` two widgets'
|
||||||
|
properties of the same type. Bindings must reference a *source* widget by
|
||||||
|
**ID**. As long as the two properties have the same type, you can bind
|
||||||
|
properties of different names and of widgets with different widget classes.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box my_box {
|
||||||
|
halign: fill; // Source
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
valign: bind my_box.halign; // Target
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding Flags
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Modify the behavior of bindings with flags. Flags are written after the
|
||||||
|
binding. The default behavior is that the *Target*'s value will be
|
||||||
|
changed to the *Source*'s value when the binding is created and when the
|
||||||
|
*Source* value changes.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Box my_box {
|
||||||
|
hexpand: true; // Source
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
vexpand: bind my_box.hexpand inverted bidirectional; // Target
|
||||||
|
}
|
||||||
|
|
||||||
|
no-sync-create
|
||||||
|
Prevent setting the *Tartget* with the *Source*'s value,
|
||||||
|
updating the target value when the *Source* value changes, not when
|
||||||
|
the binding is first created. Useful when the *Target* property has
|
||||||
|
another initial value that is not the *Source* value.
|
||||||
|
|
||||||
|
bidirectional
|
||||||
|
When either the *Source* or *Target* value is modified, the other's
|
||||||
|
value will be updated. For example, if the logic of the program
|
||||||
|
changes the Button's vexpand value to ``false``, then the Box's halign
|
||||||
|
value will also be updated to ``false``.
|
||||||
|
|
||||||
|
inverted
|
||||||
|
If the property is a boolean, the value of the bind can be negated
|
||||||
|
with this flag. For example, if the Box's hexpand property is ``true``,
|
||||||
|
the Button's vexpand property will be ``false`` in the code above.
|
||||||
|
|
||||||
|
|
||||||
|
Signals
|
||||||
|
-------
|
||||||
|
|
||||||
|
Gtk allows you to register signals in your program. This can be done by
|
||||||
|
getting the object from the GtkBuilder and connecting a handler to the
|
||||||
|
signal. Or register the handler with the application and reference it in
|
||||||
|
the blueprint.
|
||||||
|
|
||||||
|
Signals have an *event name*, a *handler* (aka callback), and optionally
|
||||||
|
some *flags*. Each widget will have a set of defined signals. Consult the
|
||||||
|
widget's documentation for a list of its signals.
|
||||||
|
|
||||||
|
To register a handler with the application, consult the documentation for
|
||||||
|
your language's bindings of Gtk.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
WidgetClass {
|
||||||
|
event_name => handler_name() flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. TODO: add a list of flags and their descriptions
|
||||||
|
|
||||||
|
By default, signals in the blueprint will pass the widget that the signal
|
||||||
|
is for as an argument to the *handler*. However, you can specify the
|
||||||
|
widget that is passed to the handler by referencing its **ID** inside the
|
||||||
|
parenthesis.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Label my_label {
|
||||||
|
label: "Hide me";
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
clicked => hide_widget(my_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Custom Widget Classes
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Some programs have custom widgets defined in their logic, so blueprint
|
||||||
|
won't know that they exist. Writing widgets not defined in the GIR will
|
||||||
|
result in an error. Prepend a custom widget with a period (``.``) to prevent the
|
||||||
|
compiler from trying to validate the widget. This is essentially saying
|
||||||
|
the widget has no *namespace*.
|
||||||
|
|
||||||
|
To register a custom widget with the application consult the documentation
|
||||||
|
for your language's bindings of Gtk.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
.MyCustomWidget {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Templates
|
||||||
|
---------
|
||||||
|
.. TODO
|
||||||
|
|
||||||
|
|
||||||
|
CSS Style Classes
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. TODO: Unsure if to group styles with widget-specific items
|
||||||
|
|
||||||
|
Widgets can be given style classes that can be used with your CSS or
|
||||||
|
`predefined styles <https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.2/style-classes.html>`_
|
||||||
|
in libraries like Libadwaita.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Button {
|
||||||
|
label: "Click me";
|
||||||
|
styles ["my-style", "pill"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Note the lack of a *colon* after "styles" and a *semicolon* at the end of
|
||||||
|
the line. This syntax looks like the properties syntax, but it compiles to
|
||||||
|
XML completely different from properties.
|
||||||
|
|
||||||
|
Consult your language's bindings of Gtk to use a CSS file.
|
||||||
|
|
||||||
|
Non-property Elements
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some widgets will have elements which are not properties, but they sort
|
||||||
|
of act like properties. Most of the time they will be specific only to a
|
||||||
|
certain widget. *Styles* is one of these elements, except that styles can
|
||||||
|
be used for any widget. Similar to how every widget has styles,
|
||||||
|
``Gtk.ComboBoxText`` has *items*:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Gtk.ComboBoxText {
|
||||||
|
items [
|
||||||
|
item1: "Item 1",
|
||||||
|
item2: _("Items can be translated"),
|
||||||
|
"The item ID is not required",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
See :doc:`examples <examples#widget-specific-items>` for a list of more of these
|
||||||
|
widget-specific items.
|
||||||
|
|
||||||
|
|
||||||
|
Menus
|
||||||
|
-----
|
||||||
|
|
||||||
|
Menus are usually the widgets that are placed along the top-bar of a
|
||||||
|
window, or pop up when you right-click some other widget. In Blueprint, a
|
||||||
|
``menu`` is a ``Gio.MenuModel`` that can be shown by MenuButtons or other
|
||||||
|
widgets.
|
||||||
|
|
||||||
|
In Blueprint, a ``menu`` can have *items*, *sections*, and *submenus*.
|
||||||
|
Like widgets, a ``menu`` can also be given an **ID**.
|
||||||
|
The `Menu Model section of the Gtk.PopoverMenu documentation <https://docs.gtk.org/gtk4/class.PopoverMenu.html#menu-models>`_
|
||||||
|
has complete details on the menu model.
|
||||||
|
|
||||||
|
Here is an example of a menu:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
menu my_menu {
|
||||||
|
section {
|
||||||
|
label: "File";
|
||||||
|
item {
|
||||||
|
label: "Open";
|
||||||
|
action: "win.open";
|
||||||
|
icon-name: "document-open-symbolic";
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
label: "Save";
|
||||||
|
action: "win.save";
|
||||||
|
icon-name: "document-save-symbolic";
|
||||||
|
}
|
||||||
|
submenu {
|
||||||
|
label: "Save As";
|
||||||
|
icon-name: "document-save-as-symbolic";
|
||||||
|
item {
|
||||||
|
label: "PDF";
|
||||||
|
action: "win.save_as_pdf";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
There is a shorthand for *items*. Items require at least a label. The
|
||||||
|
action and icon-name are optional.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
menu {
|
||||||
|
item ( "Item 2" )
|
||||||
|
item ( "Item 2", "app.action", "icon-name" )
|
||||||
|
}
|
||||||
|
|
||||||
|
A widget that uses a ``menu`` is ``Gtk.MenuButton``. It has the *menu-model*
|
||||||
|
property, which takes a menu. Write the menu at the root of the blueprint
|
||||||
|
(meaning not inside any widgets) and reference it by **ID**.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
MenuButton {
|
||||||
|
menu-model: my_menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Child Types
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Child types describe how a child widget is placed on a parent widget. For
|
||||||
|
example, HeaderBar widgets can have children placed either at the *start*
|
||||||
|
or the *end* of the HeaderBar. Child widgets of HeaderBars can have the
|
||||||
|
*start* or *end* types. Values for child types a widget can have are
|
||||||
|
defined in the widget's documentation.
|
||||||
|
|
||||||
|
Child types in blueprint are written between square brackets (``[`` ``]``) and before
|
||||||
|
the child the type is for.
|
||||||
|
|
||||||
|
The following blueprint code...
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
HeaderBar {
|
||||||
|
[start]
|
||||||
|
Button {
|
||||||
|
label: "Button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\... would look like this:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
| Button |
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
And the following blueprint code...
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
HeaderBar {
|
||||||
|
[end]
|
||||||
|
Button {
|
||||||
|
label: "Button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\... would look like this:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
| Button |
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
|
||||||
|
Translatable Strings
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Mark any string as translatable using this syntax: ``_("...")``.
|
||||||
|
|
||||||
|
Two strings that are the same in English could be translated in different
|
||||||
|
ways in other languages because of different *contexts*. Translatable
|
||||||
|
strings with context look like this: ``C_("context", "...")``. An example
|
||||||
|
where a context is needed is the word "have", which in Spanish could
|
||||||
|
translate to "tener" or "haber".
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: C_("1st have", "have");
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: C_("2nd have", "have");
|
||||||
|
}
|
||||||
|
|
||||||
|
See `translations <translations.html>`_ for more details.
|
2
tests/formatting/comment_in.blp
Normal file
2
tests/formatting/comment_in.blp
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
//comment
|
2
tests/formatting/comment_out.blp
Normal file
2
tests/formatting/comment_out.blp
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
// comment
|
5
tests/sample_errors/float_to_int_assignment.blp
Normal file
5
tests/sample_errors/float_to_int_assignment.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Entry {
|
||||||
|
margin-bottom: 10.5;
|
||||||
|
}
|
1
tests/sample_errors/float_to_int_assignment.err
Normal file
1
tests/sample_errors/float_to_int_assignment.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,18,4,Cannot convert 10.5 to integer
|
3
tests/sample_errors/int_object.blp
Normal file
3
tests/sample_errors/int_object.blp
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
int {}
|
1
tests/sample_errors/int_object.err
Normal file
1
tests/sample_errors/int_object.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
3,1,3,int is not a class
|
7
tests/sample_errors/menu_assignment.blp
Normal file
7
tests/sample_errors/menu_assignment.blp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Overlay {
|
||||||
|
child: my_menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu my_menu {}
|
1
tests/sample_errors/menu_assignment.err
Normal file
1
tests/sample_errors/menu_assignment.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,10,7,Cannot assign Gio.Menu to Gtk.Widget
|
5
tests/sample_errors/signal_exclusive_flags.blp
Normal file
5
tests/sample_errors/signal_exclusive_flags.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
$MyObject obj {
|
||||||
|
signal1 => $handler() swapped not-swapped;
|
||||||
|
}
|
1
tests/sample_errors/signal_exclusive_flags.err
Normal file
1
tests/sample_errors/signal_exclusive_flags.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,33,11,'swapped' and 'not-swapped' flags cannot be used together
|
6
tests/sample_errors/signal_unnecessary_flags.blp
Normal file
6
tests/sample_errors/signal_unnecessary_flags.blp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
$MyObject obj {
|
||||||
|
signal1 => $handler() not-swapped;
|
||||||
|
signal2 => $handler(obj) swapped;
|
||||||
|
}
|
2
tests/sample_errors/signal_unnecessary_flags.err
Normal file
2
tests/sample_errors/signal_unnecessary_flags.err
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
4,25,11,'not-swapped' is the default for handlers that do not specify an object
|
||||||
|
5,28,7,'swapped' is the default for handlers that specify an object
|
5
tests/sample_errors/string_to_num_assignment.blp
Normal file
5
tests/sample_errors/string_to_num_assignment.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Entry {
|
||||||
|
margin-bottom: "10";
|
||||||
|
}
|
1
tests/sample_errors/string_to_num_assignment.err
Normal file
1
tests/sample_errors/string_to_num_assignment.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,18,4,Cannot convert string to number
|
5
tests/sample_errors/string_to_object_assignment.blp
Normal file
5
tests/sample_errors/string_to_object_assignment.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Button {
|
||||||
|
child: "Click me";
|
||||||
|
}
|
1
tests/sample_errors/string_to_object_assignment.err
Normal file
1
tests/sample_errors/string_to_object_assignment.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,10,10,Cannot convert string to Gtk.Widget
|
6
tests/sample_errors/string_to_type_assignment.blp
Normal file
6
tests/sample_errors/string_to_type_assignment.blp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Gio 2.0;
|
||||||
|
|
||||||
|
Gio.ListStore {
|
||||||
|
item-type: "Button";
|
||||||
|
}
|
1
tests/sample_errors/string_to_type_assignment.err
Normal file
1
tests/sample_errors/string_to_type_assignment.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
5,14,8,Cannot convert string to GType
|
5
tests/sample_errors/translated_assignment.blp
Normal file
5
tests/sample_errors/translated_assignment.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Button {
|
||||||
|
child: _("Click me");
|
||||||
|
}
|
1
tests/sample_errors/translated_assignment.err
Normal file
1
tests/sample_errors/translated_assignment.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,10,13,Cannot convert translated string to Gtk.Widget
|
5
tests/sample_errors/typeof_assignment.blp
Normal file
5
tests/sample_errors/typeof_assignment.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Button {
|
||||||
|
label: typeof<Button>;
|
||||||
|
}
|
1
tests/sample_errors/typeof_assignment.err
Normal file
1
tests/sample_errors/typeof_assignment.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,10,14,Cannot convert GType to string
|
1
tests/sample_errors/unrecognized_syntax.blp
Normal file
1
tests/sample_errors/unrecognized_syntax.blp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
~
|
1
tests/sample_errors/unrecognized_syntax.err
Normal file
1
tests/sample_errors/unrecognized_syntax.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1,1,0,Could not determine what kind of syntax is meant here
|
5
tests/sample_errors/upgrade_sync_create.blp
Normal file
5
tests/sample_errors/upgrade_sync_create.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Button btn {
|
||||||
|
label: bind btn.label sync-create;
|
||||||
|
}
|
1
tests/sample_errors/upgrade_sync_create.err
Normal file
1
tests/sample_errors/upgrade_sync_create.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,25,11,'sync-create' is now the default. Use 'no-sync-create' if this is not wanted.
|
5
tests/sample_errors/upgrade_template_list_item.blp
Normal file
5
tests/sample_errors/upgrade_template_list_item.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
BuilderListItemFactory {
|
||||||
|
template {}
|
||||||
|
}
|
1
tests/sample_errors/upgrade_template_list_item.err
Normal file
1
tests/sample_errors/upgrade_template_list_item.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,3,8,Expected type name after 'template' keyword
|
|
@ -4,6 +4,7 @@ Box {
|
||||||
visible: bind box2.visible inverted;
|
visible: bind box2.visible inverted;
|
||||||
orientation: bind box2.orientation;
|
orientation: bind box2.orientation;
|
||||||
spacing: bind box2.spacing no-sync-create;
|
spacing: bind box2.spacing no-sync-create;
|
||||||
|
tooltip-text: bind box2.tooltip-text bidirectional;
|
||||||
}
|
}
|
||||||
|
|
||||||
Box box2 {
|
Box box2 {
|
||||||
|
|
|
@ -10,6 +10,7 @@ corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||||
<property name="visible" bind-source="box2" bind-property="visible" bind-flags="sync-create|invert-boolean"/>
|
<property name="visible" bind-source="box2" bind-property="visible" bind-flags="sync-create|invert-boolean"/>
|
||||||
<property name="orientation" bind-source="box2" bind-property="orientation" bind-flags="sync-create"/>
|
<property name="orientation" bind-source="box2" bind-property="orientation" bind-flags="sync-create"/>
|
||||||
<property name="spacing" bind-source="box2" bind-property="spacing"/>
|
<property name="spacing" bind-source="box2" bind-property="spacing"/>
|
||||||
|
<property name="tooltip-text" bind-source="box2" bind-property="tooltip-text" bind-flags="sync-create|bidirectional"/>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkBox" id="box2">
|
<object class="GtkBox" id="box2">
|
||||||
<property name="spacing">6</property>
|
<property name="spacing">6</property>
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
Box {
|
|
||||||
visible: bind box2.visible inverted;
|
|
||||||
orientation: bind box2.orientation;
|
|
||||||
spacing: bind box2.spacing no-sync-create;
|
|
||||||
}
|
|
||||||
|
|
||||||
Box box2 {
|
|
||||||
spacing: 6;
|
|
||||||
}
|
|
5
tests/samples/signal_not_swapped.blp
Normal file
5
tests/samples/signal_not_swapped.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Button obj {
|
||||||
|
clicked => $handler(obj) not-swapped;
|
||||||
|
}
|
12
tests/samples/signal_not_swapped.ui
Normal file
12
tests/samples/signal_not_swapped.ui
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?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="GtkButton" id="obj">
|
||||||
|
<signal name="clicked" handler="handler" swapped="False" object="obj"/>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -46,3 +46,4 @@ class TestFormatter(unittest.TestCase):
|
||||||
self.assert_format_test("in2.blp", "out.blp")
|
self.assert_format_test("in2.blp", "out.blp")
|
||||||
self.assert_format_test("correct1.blp", "correct1.blp")
|
self.assert_format_test("correct1.blp", "correct1.blp")
|
||||||
self.assert_format_test("string_in.blp", "string_out.blp")
|
self.assert_format_test("string_in.blp", "string_out.blp")
|
||||||
|
self.assert_format_test("comment_in.blp", "comment_out.blp")
|
||||||
|
|
|
@ -28,6 +28,7 @@ gi.require_version("Gtk", "4.0")
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
from blueprintcompiler import decompiler, parser, tokenizer, utils
|
from blueprintcompiler import decompiler, parser, tokenizer, utils
|
||||||
|
from blueprintcompiler.ast_utils import AstNode
|
||||||
from blueprintcompiler.completions import complete
|
from blueprintcompiler.completions import complete
|
||||||
from blueprintcompiler.errors import (
|
from blueprintcompiler.errors import (
|
||||||
CompileError,
|
CompileError,
|
||||||
|
@ -61,11 +62,14 @@ class TestSamples(unittest.TestCase):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def assert_ast_doesnt_crash(self, text, tokens, ast):
|
def assert_ast_doesnt_crash(self, text, tokens, ast: AstNode):
|
||||||
|
lsp = LanguageServer()
|
||||||
for i in range(len(text)):
|
for i in range(len(text)):
|
||||||
ast.get_docs(i)
|
ast.get_docs(i)
|
||||||
for i in range(len(text)):
|
for i in range(len(text)):
|
||||||
list(complete(LanguageServer(), ast, tokens, i))
|
list(complete(lsp, ast, tokens, i))
|
||||||
|
for i in range(len(text)):
|
||||||
|
ast.get_reference(i)
|
||||||
ast.get_document_symbols()
|
ast.get_document_symbols()
|
||||||
|
|
||||||
def assert_sample(self, name, skip_run=False):
|
def assert_sample(self, name, skip_run=False):
|
||||||
|
@ -196,6 +200,7 @@ class TestSamples(unittest.TestCase):
|
||||||
"expr_closure_args",
|
"expr_closure_args",
|
||||||
"parseable",
|
"parseable",
|
||||||
"signal",
|
"signal",
|
||||||
|
"signal_not_swapped",
|
||||||
"template",
|
"template",
|
||||||
"template_binding",
|
"template_binding",
|
||||||
"template_binding_extern",
|
"template_binding_extern",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue