This commit is contained in:
gregorni 2023-07-24 16:40:45 +02:00
commit 5cc411e955
40 changed files with 298 additions and 220 deletions

View file

@ -19,7 +19,7 @@ build:
- ninja -C _build docs/en - ninja -C _build docs/en
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git - git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
- cd blueprint-regression-tests - cd blueprint-regression-tests
- git checkout 3077f669fc9c8e3ceb4da85e6bda680c297c58a2 - git checkout 9bfb9325d75a9985310230f119579f07df519e60
- ./test.sh - ./test.sh
- cd .. - cd ..
coverage: '/TOTAL.*\s([.\d]+)%/' coverage: '/TOTAL.*\s([.\d]+)%/'

29
NEWS.md
View file

@ -1,3 +1,32 @@
# v0.10.0
## Added
- The hover documentation now includes a link to the online documentation for the symbol, if available.
- Added hover documentation for the Adw.Breakpoint extensions, `condition` and `setters`.
## Changed
- Decompiling an empty file now produces an empty file rather than an error. (AkshayWarrier)
- More relevant documentation is shown when hovering over an identifier literal (such as an enum value or an object ID).
## Fixed
- Fixed an issue with the language server not conforming the spec. (seshotake)
- Fixed the signature section of the hover documentation for properties and signals.
- Fixed a bug where documentation was sometimes shown for a different symbol with the same name.
- Fixed a bug where documentation was not shown for accessibility properties that contain `-`.
- Number literals are now correctly parsed as floats if they contain a `.`, even if they are divisible by 1.
## Removed
- The `bind-property` keyword has been removed. Use `bind` instead. The old syntax is still accepted with a warning.
## Documentation
- Fixed the grammar for Extension, which was missing ExtAdwBreakpoint.
# v0.8.1 # v0.8.1
## Breaking Changes ## Breaking Changes

View file

@ -128,6 +128,10 @@ class CompileWarning(CompileError):
color = Colors.YELLOW color = Colors.YELLOW
class DeprecatedWarning(CompileWarning):
pass
class UpgradeWarning(CompileWarning): class UpgradeWarning(CompileWarning):
category = "upgrade" category = "upgrade"
color = Colors.PURPLE color = Colors.PURPLE

View file

@ -136,6 +136,14 @@ class GirType:
def incomplete(self) -> bool: def incomplete(self) -> bool:
return False return False
@property
def deprecated(self) -> bool:
return False
@property
def deprecated_doc(self) -> T.Optional[str]:
return None
class ExternType(GirType): class ExternType(GirType):
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
@ -330,6 +338,13 @@ class GirNode:
def type(self) -> GirType: def type(self) -> GirType:
raise NotImplementedError() raise NotImplementedError()
@property
def deprecated_doc(self) -> T.Optional[str]:
try:
return self.xml.get_elements("doc-deprecated")[0].cdata.strip()
except:
return None
class Property(GirNode): class Property(GirNode):
xml_tag = "property" xml_tag = "property"
@ -365,6 +380,10 @@ class Property(GirNode):
else: else:
return None return None
@property
def deprecated(self) -> bool:
return self.tl.PROP_DEPRECATED == 1
class Argument(GirNode): class Argument(GirNode):
def __init__(self, container: GirNode, tl: typelib.Typelib) -> None: def __init__(self, container: GirNode, tl: typelib.Typelib) -> None:
@ -427,6 +446,10 @@ class Signal(GirNode):
else: else:
return None return None
@property
def deprecated(self) -> bool:
return self.tl.SIGNAL_DEPRECATED == 1
class Interface(GirNode, GirType): class Interface(GirNode, GirType):
xml_tag = "interface" xml_tag = "interface"
@ -488,6 +511,10 @@ class Interface(GirNode, GirType):
else: else:
return None return None
@property
def deprecated(self) -> bool:
return self.tl.INTERFACE_DEPRECATED == 1
class Class(GirNode, GirType): class Class(GirNode, GirType):
xml_tag = "class" xml_tag = "class"
@ -609,6 +636,10 @@ class Class(GirNode, GirType):
else: else:
return None return None
@property
def deprecated(self) -> bool:
return self.tl.OBJ_DEPRECATED == 1
class TemplateType(GirType): class TemplateType(GirType):
def __init__(self, name: str, parent: T.Optional[GirType]): def __init__(self, name: str, parent: T.Optional[GirType]):
@ -722,6 +753,10 @@ class Enumeration(GirNode, GirType):
else: else:
return None return None
@property
def deprecated(self) -> bool:
return self.tl.ENUM_DEPRECATED == 1
class Boxed(GirNode, GirType): class Boxed(GirNode, GirType):
xml_tag = "glib:boxed" xml_tag = "glib:boxed"
@ -743,6 +778,10 @@ class Boxed(GirNode, GirType):
else: else:
return None return None
@property
def deprecated(self) -> bool:
return self.tl.STRUCT_DEPRECATED == 1
class Bitfield(Enumeration): class Bitfield(Enumeration):
xml_tag = "bitfield" xml_tag = "bitfield"

View file

@ -38,7 +38,6 @@ from .gtk_styles import ExtStyles
from .gtkbuilder_child import Child, ChildExtension, ChildInternal, ChildType from .gtkbuilder_child import Child, ChildExtension, ChildInternal, ChildType
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
from .imports import GtkDirective, Import from .imports import GtkDirective, Import
from .property_binding import PropertyBinding
from .types import ClassName from .types import ClassName
from .ui import UI from .ui import UI
from .values import ( from .values import (

View file

@ -23,16 +23,57 @@ from .common import *
from .expression import Expression, LiteralExpr, LookupOp from .expression import Expression, LiteralExpr, LookupOp
class BindingFlag(AstNode):
grammar = [
AnyOf(
UseExact("flag", "inverted"),
UseExact("flag", "bidirectional"),
UseExact("flag", "no-sync-create"),
UseExact("flag", "sync-create"),
)
]
@property
def flag(self) -> str:
return self.tokens["flag"]
@validate()
def sync_create(self):
if self.flag == "sync-create":
raise UpgradeWarning(
"'sync-create' is now the default. Use 'no-sync-create' if this is not wanted.",
actions=[CodeAction("remove 'sync-create'", "")],
)
@validate()
def unique(self):
self.validate_unique_in_parent(
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
)
@validate()
def flags_only_if_simple(self):
if self.parent.simple_binding is None:
raise CompileError(
"Only bindings with a single lookup can have flags",
)
class Binding(AstNode): class Binding(AstNode):
grammar = [ grammar = [
Keyword("bind"), AnyOf(Keyword("bind"), UseExact("bind", "bind-property")),
Expression, Expression,
ZeroOrMore(BindingFlag),
] ]
@property @property
def expression(self) -> Expression: def expression(self) -> Expression:
return self.children[Expression][0] return self.children[Expression][0]
@property
def flags(self) -> T.List[BindingFlag]:
return self.children[BindingFlag]
@property @property
def simple_binding(self) -> T.Optional["SimpleBinding"]: def simple_binding(self) -> T.Optional["SimpleBinding"]:
if isinstance(self.expression.last, LookupOp): if isinstance(self.expression.last, LookupOp):
@ -40,14 +81,29 @@ class Binding(AstNode):
from .values import IdentLiteral from .values import IdentLiteral
if isinstance(self.expression.last.lhs.literal.value, IdentLiteral): if isinstance(self.expression.last.lhs.literal.value, IdentLiteral):
flags = [x.flag for x in self.flags]
return SimpleBinding( return SimpleBinding(
self.expression.last.lhs.literal.value.ident, self.expression.last.lhs.literal.value.ident,
self.expression.last.property_name, self.expression.last.property_name,
no_sync_create="no-sync-create" in flags,
bidirectional="bidirectional" in flags,
inverted="inverted" in flags,
) )
return None return None
@validate("bind")
def bind_property(self):
if self.tokens["bind"] == "bind-property":
raise UpgradeWarning(
"'bind-property' is no longer needed. Use 'bind' instead. (blueprint 0.8.2)",
actions=[CodeAction("use 'bind'", "bind")],
)
@dataclass @dataclass
class SimpleBinding: class SimpleBinding:
source: str source: str
property_name: str property_name: str
no_sync_create: bool = False
bidirectional: bool = False
inverted: bool = False

View file

@ -33,6 +33,7 @@ from ..errors import (
CodeAction, CodeAction,
CompileError, CompileError,
CompileWarning, CompileWarning,
DeprecatedWarning,
MultipleErrors, MultipleErrors,
UpgradeWarning, UpgradeWarning,
) )

View file

@ -30,6 +30,7 @@ from .gtkbuilder_template import Template
class ValueTypeCtx: class ValueTypeCtx:
value_type: T.Optional[GirType] value_type: T.Optional[GirType]
allow_null: bool = False allow_null: bool = False
must_infer_type: bool = False
@dataclass @dataclass

View file

@ -114,7 +114,7 @@ class LookupOp(InfixExpr):
@context(ValueTypeCtx) @context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx: def value_type(self) -> ValueTypeCtx:
return ValueTypeCtx(None) return ValueTypeCtx(None, must_infer_type=True)
@property @property
def property_name(self) -> str: def property_name(self) -> str:
@ -133,6 +133,10 @@ class LookupOp(InfixExpr):
@validate("property") @validate("property")
def property_exists(self): def property_exists(self):
if self.lhs.type is None: if self.lhs.type is None:
# Literal values throw their own errors if the type isn't known
if isinstance(self.lhs, LiteralExpr):
return
raise CompileError( raise CompileError(
f"Could not determine the type of the preceding expression", f"Could not determine the type of the preceding expression",
hints=[ hints=[

View file

@ -22,21 +22,18 @@ from .binding import Binding
from .common import * from .common import *
from .contexts import ValueTypeCtx from .contexts import ValueTypeCtx
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
from .property_binding import PropertyBinding
from .values import ObjectValue, Value from .values import ObjectValue, Value
class Property(AstNode): class Property(AstNode):
grammar = Statement( grammar = Statement(UseIdent("name"), ":", AnyOf(Binding, ObjectValue, Value))
UseIdent("name"), ":", AnyOf(PropertyBinding, Binding, ObjectValue, Value)
)
@property @property
def name(self) -> str: def name(self) -> str:
return self.tokens["name"] return self.tokens["name"]
@property @property
def value(self) -> T.Union[PropertyBinding, Binding, ObjectValue, Value]: def value(self) -> T.Union[Binding, ObjectValue, Value]:
return self.children[0] return self.children[0]
@property @property
@ -44,14 +41,16 @@ class Property(AstNode):
return self.parent.parent.gir_class return self.parent.parent.gir_class
@property @property
def gir_property(self): def gir_property(self) -> T.Optional[gir.Property]:
if self.gir_class is not None and not isinstance(self.gir_class, ExternType): if self.gir_class is not None and not isinstance(self.gir_class, ExternType):
return self.gir_class.properties.get(self.tokens["name"]) return self.gir_class.properties.get(self.tokens["name"])
else:
return None
@validate() @validate()
def binding_valid(self): def binding_valid(self):
if ( if (
(isinstance(self.value, PropertyBinding) or isinstance(self.value, Binding)) isinstance(self.value, Binding)
and self.gir_property is not None and self.gir_property is not None
and self.gir_property.construct_only and self.gir_property.construct_only
): ):
@ -94,6 +93,17 @@ class Property(AstNode):
check=lambda child: child.tokens["name"] == self.tokens["name"], check=lambda child: child.tokens["name"] == self.tokens["name"],
) )
@validate("name")
def deprecated(self) -> None:
if self.gir_property is not None and self.gir_property.deprecated:
hints = []
if self.gir_property.deprecated_doc:
hints.append(self.gir_property.deprecated_doc)
raise DeprecatedWarning(
f"{self.gir_property.signature} is deprecated",
hints=hints,
)
@docs("name") @docs("name")
def property_docs(self): def property_docs(self):
if self.gir_property is not None: if self.gir_property is not None:

View file

@ -95,9 +95,11 @@ class Signal(AstNode):
return any(x.flag == "after" for x in self.flags) return any(x.flag == "after" for x in self.flags)
@property @property
def gir_signal(self): def gir_signal(self) -> T.Optional[gir.Signal]:
if self.gir_class is not None and not isinstance(self.gir_class, ExternType): if self.gir_class is not None and not isinstance(self.gir_class, ExternType):
return self.gir_class.signals.get(self.tokens["name"]) return self.gir_class.signals.get(self.tokens["name"])
else:
return None
@property @property
def gir_class(self): def gir_class(self):
@ -134,6 +136,17 @@ class Signal(AstNode):
if self.context[ScopeCtx].objects.get(object_id) is None: if self.context[ScopeCtx].objects.get(object_id) is None:
raise CompileError(f"Could not find object with ID '{object_id}'") raise CompileError(f"Could not find object with ID '{object_id}'")
@validate("name")
def deprecated(self) -> None:
if self.gir_signal is not None and self.gir_signal.deprecated:
hints = []
if self.gir_signal.deprecated_doc:
hints.append(self.gir_signal.deprecated_doc)
raise DeprecatedWarning(
f"{self.gir_signal.signature} is deprecated",
hints=hints,
)
@docs("name") @docs("name")
def signal_docs(self): def signal_docs(self):
if self.gir_signal is not None: if self.gir_signal is not None:

View file

@ -1,145 +0,0 @@
# property_binding.py
#
# Copyright 2023 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 *
from .contexts import ScopeCtx
from .gobject_object import Object
class PropertyBindingFlag(AstNode):
grammar = [
AnyOf(
UseExact("flag", "inverted"),
UseExact("flag", "bidirectional"),
UseExact("flag", "no-sync-create"),
UseExact("flag", "sync-create"),
)
]
@property
def flag(self) -> str:
return self.tokens["flag"]
@validate()
def sync_create(self):
if self.flag == "sync-create":
raise UpgradeWarning(
"'sync-create' is now the default. Use 'no-sync-create' if this is not wanted.",
actions=[CodeAction("remove 'sync-create'", "")],
)
@validate()
def unique(self):
self.validate_unique_in_parent(
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
)
class PropertyBinding(AstNode):
grammar = AnyOf(
[
Keyword("bind-property"),
UseIdent("source"),
".",
UseIdent("property"),
ZeroOrMore(PropertyBindingFlag),
],
[
Keyword("bind"),
UseIdent("source"),
".",
UseIdent("property"),
PropertyBindingFlag,
ZeroOrMore(PropertyBindingFlag),
],
)
@property
def source(self) -> str:
return self.tokens["source"]
@property
def source_obj(self) -> T.Optional[Object]:
if self.root.is_legacy_template(self.source):
return self.root.template
return self.context[ScopeCtx].objects.get(self.source)
@property
def property_name(self) -> str:
return self.tokens["property"]
@property
def flags(self) -> T.List[PropertyBindingFlag]:
return self.children[PropertyBindingFlag]
@property
def inverted(self) -> bool:
return any([f.flag == "inverted" for f in self.flags])
@property
def bidirectional(self) -> bool:
return any([f.flag == "bidirectional" for f in self.flags])
@property
def no_sync_create(self) -> bool:
return any([f.flag == "no-sync-create" for f in self.flags])
@validate("source")
def source_object_exists(self) -> None:
if self.source_obj is None:
raise CompileError(
f"Could not find object with ID {self.source}",
did_you_mean=(self.source, self.context[ScopeCtx].objects.keys()),
)
@validate("property")
def property_exists(self) -> None:
if self.source_obj is None:
return
gir_class = self.source_obj.gir_class
if gir_class is None or gir_class.incomplete:
# Objects that we have no gir data on should not be validated
# This happens for classes defined by the app itself
return
if (
isinstance(gir_class, gir.Class)
and gir_class.properties.get(self.property_name) is None
):
raise CompileError(
f"{gir_class.full_name} does not have a property called {self.property_name}"
)
@validate("bind")
def old_bind(self):
if self.tokens["bind"]:
raise UpgradeWarning(
"Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags",
actions=[CodeAction("Use 'bind-property'", "bind-property")],
)
@validate("source")
def legacy_template(self):
if self.root.is_legacy_template(self.source):
raise UpgradeWarning(
"Use 'template' instead of the class name (introduced in 0.8.0)",
actions=[CodeAction("Use 'template'", "template")],
)

View file

@ -57,6 +57,17 @@ class TypeName(AstNode):
if not self.tokens["extern"]: if not self.tokens["extern"]:
self.root.gir.validate_ns(self.tokens["namespace"]) self.root.gir.validate_ns(self.tokens["namespace"])
@validate()
def deprecated(self) -> None:
if self.gir_type and self.gir_type.deprecated:
hints = []
if self.gir_type.deprecated_doc:
hints.append(self.gir_type.deprecated_doc)
raise DeprecatedWarning(
f"{self.gir_type.full_name} is deprecated",
hints=hints,
)
@property @property
def gir_ns(self): def gir_ns(self):
if not self.tokens["extern"]: if not self.tokens["extern"]:

View file

@ -291,7 +291,7 @@ class IdentLiteral(AstNode):
actions=[CodeAction("Use 'template'", "template")], actions=[CodeAction("Use 'template'", "template")],
) )
elif expected_type is not None: elif expected_type is not None or self.context[ValueTypeCtx].must_infer_type:
object = self.context[ScopeCtx].objects.get(self.ident) object = self.context[ScopeCtx].objects.get(self.ident)
if object is None: if object is None:
if self.ident == "null": if self.ident == "null":
@ -305,7 +305,11 @@ class IdentLiteral(AstNode):
self.context[ScopeCtx].objects.keys(), self.context[ScopeCtx].objects.keys(),
), ),
) )
elif object.gir_class and not object.gir_class.assignable_to(expected_type): elif (
expected_type is not None
and object.gir_class is not None
and not object.gir_class.assignable_to(expected_type)
):
raise CompileError( raise CompileError(
f"Cannot assign {object.gir_class.full_name} to {expected_type.full_name}" f"Cannot assign {object.gir_class.full_name} to {expected_type.full_name}"
) )

View file

@ -391,6 +391,9 @@ class LanguageServer:
else DiagnosticSeverity.Error, else DiagnosticSeverity.Error,
} }
if isinstance(err, DeprecationWarning):
result["tags"] = [DiagnosticTag.Deprecated]
if len(err.references) > 0: if len(err.references) > 0:
result["relatedInformation"] = [ result["relatedInformation"] = [
{ {

View file

@ -119,6 +119,11 @@ class DiagnosticSeverity(enum.IntEnum):
Hint = 4 Hint = 4
class DiagnosticTag(enum.IntEnum):
Unnecessary = 1
Deprecated = 2
@dataclass @dataclass
class SemanticToken: class SemanticToken:
start: int start: int

View file

@ -116,27 +116,21 @@ class XmlOutput(OutputFormat):
if simple := value.simple_binding: if simple := value.simple_binding:
props["bind-source"] = self._object_id(value, simple.source) props["bind-source"] = self._object_id(value, simple.source)
props["bind-property"] = simple.property_name props["bind-property"] = simple.property_name
props["bind-flags"] = "sync-create" flags = []
if not simple.no_sync_create:
flags.append("sync-create")
if simple.inverted:
flags.append("invert-boolean")
if simple.bidirectional:
flags.append("bidirectional")
props["bind-flags"] = "|".join(flags) or None
xml.put_self_closing("property", **props) xml.put_self_closing("property", **props)
else: else:
xml.start_tag("binding", **props) xml.start_tag("binding", **props)
self._emit_expression(value.expression, xml) self._emit_expression(value.expression, xml)
xml.end_tag() xml.end_tag()
elif isinstance(value, PropertyBinding):
bind_flags = []
if not value.no_sync_create:
bind_flags.append("sync-create")
if value.inverted:
bind_flags.append("invert-boolean")
if value.bidirectional:
bind_flags.append("bidirectional")
props["bind-source"] = self._object_id(value, value.source)
props["bind-property"] = value.property_name
props["bind-flags"] = "|".join(bind_flags) or None
xml.put_self_closing("property", **props)
elif isinstance(value, ObjectValue): elif isinstance(value, ObjectValue):
xml.start_tag("property", **props) xml.start_tag("property", **props)
self._emit_object(value.object, xml) self._emit_object(value.object, xml)
@ -199,7 +193,10 @@ class XmlOutput(OutputFormat):
elif isinstance(value, TypeLiteral): elif isinstance(value, TypeLiteral):
xml.put_text(value.type_name.glib_type_name) xml.put_text(value.type_name.glib_type_name)
else: else:
xml.put_text(value.value) if isinstance(value.value, float) and value.value == int(value.value):
xml.put_text(int(value.value))
else:
xml.put_text(value.value)
def _emit_value(self, value: Value, xml: XmlEmitter): def _emit_value(self, value: Value, xml: XmlEmitter):
if isinstance(value.child, Literal): if isinstance(value.child, Literal):

View file

@ -520,8 +520,6 @@ class UseNumber(ParseNode):
return False return False
number = token.get_number() number = token.get_number()
if number % 1.0 == 0:
number = int(number)
ctx.set_group_val(self.key, number, token) ctx.set_group_val(self.key, number, token)
return True return True

View file

@ -70,8 +70,10 @@ class Token:
try: try:
if string.startswith("0x"): if string.startswith("0x"):
return int(string, 16) return int(string, 16)
else: elif "." in string:
return float(string) return float(string)
else:
return int(string)
except: except:
raise CompileError( raise CompileError(
f"{str(self)} is not a valid number literal", self.start, self.end f"{str(self)} is not a valid number literal", self.start, self.end

View file

@ -150,11 +150,15 @@ class Typelib:
BLOB_NAME = Field(0x4, "string") BLOB_NAME = Field(0x4, "string")
STRUCT_DEPRECATED = Field(0x2, "u16", 0, 1)
ENUM_DEPRECATED = Field(0x2, "u16", 0, 1)
ENUM_GTYPE_NAME = Field(0x8, "string") ENUM_GTYPE_NAME = Field(0x8, "string")
ENUM_N_VALUES = Field(0x10, "u16") ENUM_N_VALUES = Field(0x10, "u16")
ENUM_N_METHODS = Field(0x12, "u16") ENUM_N_METHODS = Field(0x12, "u16")
ENUM_VALUES = Field(0x18, "offset") ENUM_VALUES = Field(0x18, "offset")
INTERFACE_DEPRECATED = Field(0x2, "u16", 0, 1)
INTERFACE_GTYPE_NAME = Field(0x8, "string") INTERFACE_GTYPE_NAME = Field(0x8, "string")
INTERFACE_N_PREREQUISITES = Field(0x12, "u16") INTERFACE_N_PREREQUISITES = Field(0x12, "u16")
INTERFACE_N_PROPERTIES = Field(0x14, "u16") INTERFACE_N_PROPERTIES = Field(0x14, "u16")

View file

@ -16,7 +16,7 @@ a module in your flatpak manifest:
{ {
"type": "git", "type": "git",
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler", "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
"tag": "v0.8.1" "tag": "v0.10.0"
} }
] ]
} }

View file

@ -58,7 +58,7 @@ Properties
.. rst-class:: grammar-block .. rst-class:: grammar-block
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`PropertyBinding<Syntax PropertyBinding>` | :ref:`Binding<Syntax Binding>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';' Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container. Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container.

View file

@ -102,34 +102,6 @@ Use ``C_("context", "...")`` to add a *message context* to a string to disambigu
} }
.. _Syntax PropertyBinding:
Property Bindings
-----------------
.. rst-class:: grammar-block
PropertyBinding = 'bind-property' <source::ref:`IDENT<Syntax IDENT>`> '.' <property::ref:`IDENT<Syntax IDENT>`> (PropertyBindingFlag)*
PropertyBindingFlag = 'inverted' | 'bidirectional' | 'no-sync-create'
Bindings keep a property updated as another property changes. They can be used to keep the UI in sync with application data, or to connect two parts of the UI.
Example
~~~~~~~
.. code-block:: blueprintui
/* Use property bindings to show a label when a switch
* is active, without any application code */
Switch advanced_feature {}
Label warning {
visible: bind-property advanced_feature.active;
label: _("This is an advanced feature. Use with caution!");
}
.. _Syntax Binding: .. _Syntax Binding:
Expression Bindings Expression Bindings
@ -139,7 +111,27 @@ Expression Bindings
Binding = 'bind' :ref:`Expression<Syntax Expression>` Binding = 'bind' :ref:`Expression<Syntax Expression>`
Expression bindings serve the same purpose as property bindings, but are more powerful. They can call application code to compute the value of a property, and they can do multi-step property lookups. See :ref:`the expressions page<Syntax Expression>`. Bindings keep a property updated as other properties change. They can be used to keep the UI in sync with application data, or to connect two parts of the UI.
The simplest bindings connect to a property of another object in the blueprint. When that other property changes, the bound property updates as well. More advanced bindings can do multi-step property lookups and can even call application code to compute values. See :ref:`the expressions page<Syntax Expression>`.
Example
~~~~~~~
.. code-block:: blueprintui
/* Use bindings to show a label when a switch
* is active, without any application code */
Switch advanced_feature {}
Label warning {
visible: bind-property advanced_feature.active;
label: _("This is an advanced feature. Use with caution!");
}
.. code-block: blueprintui
.. _Syntax ObjectValue: .. _Syntax ObjectValue:

View file

@ -1,5 +1,5 @@
project('blueprint-compiler', project('blueprint-compiler',
version: '0.8.1', version: '0.10.1',
) )
subdir('docs') subdir('docs')

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label {
label: bind $my_closure() as <Label> bidirectional;
}

View file

@ -0,0 +1 @@
4,40,13,Only bindings with a single lookup can have flags

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label {
label: bind something.other;
}

View file

@ -0,0 +1 @@
4,15,9,Could not find object with ID something

View file

@ -0,0 +1,10 @@
using Gtk 4.0;
using Gio 2.0;
Dialog {
use-header-bar: 1;
}
Window {
keys-changed => $on_window_keys_changed();
}

View file

@ -0,0 +1 @@
4,1,6,Gtk.Dialog is deprecated

View file

@ -1,2 +1,3 @@
3,10,12,Use type syntax here (introduced in blueprint 0.8.0) 3,10,12,Use type syntax here (introduced in blueprint 0.8.0)
8,1,6,Gtk.Dialog is deprecated
9,18,12,Use 'template' instead of the class name (introduced in 0.8.0) 9,18,12,Use 'template' instead of the class name (introduced in 0.8.0)

View file

@ -1,9 +1,8 @@
using Gtk 4.0; using Gtk 4.0;
Box { Box {
visible: bind box2.visible inverted; visible: bind-property box2.visible inverted;
orientation: bind box2.orientation; orientation: bind-property box2.orientation;
spacing: bind box2.spacing no-sync-create;
} }
Box box2 { Box box2 {

View file

@ -1,2 +1,2 @@
4,12,4,Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags 4,12,13,'bind-property' is no longer needed. Use 'bind' instead. (blueprint 0.8.2)
6,12,4,Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags 5,16,13,'bind-property' is no longer needed. Use 'bind' instead. (blueprint 0.8.2)

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Adjustment {
value: bind 1.0 as <double>;
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkAdjustment">
<binding name="value">
<constant type="gfloat">1</constant>
</binding>
</object>
</interface>

View file

@ -2,7 +2,7 @@ using Gtk 4.0;
Gtk.Label { Gtk.Label {
xalign: .5; xalign: .5;
yalign: 0.0;
height-request: 1_000_000; height-request: 1_000_000;
margin-top: 0x30; margin-top: 0x30;
} }

View file

@ -3,6 +3,7 @@
<requires lib="gtk" version="4.0"/> <requires lib="gtk" version="4.0"/>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="xalign">0.5</property> <property name="xalign">0.5</property>
<property name="yalign">0</property>
<property name="height-request">1000000</property> <property name="height-request">1000000</property>
<property name="margin-top">48</property> <property name="margin-top">48</property>
</object> </object>

View file

@ -1,9 +1,9 @@
using Gtk 4.0; using Gtk 4.0;
Box { Box {
visible: bind-property box2.visible inverted; visible: bind box2.visible inverted;
orientation: bind box2.orientation; orientation: bind box2.orientation;
spacing: bind-property box2.spacing no-sync-create; spacing: bind box2.spacing no-sync-create;
} }
Box box2 { Box box2 {

View file

@ -1,5 +1,5 @@
using Gtk 4.0; using Gtk 4.0;
template $MyTemplate { template $MyTemplate {
object: bind-property template.object2; object: bind template.object2;
} }

View file

@ -29,7 +29,12 @@ from gi.repository import Gtk
from blueprintcompiler import decompiler, parser, tokenizer, utils from blueprintcompiler import decompiler, parser, tokenizer, utils
from blueprintcompiler.completions import complete from blueprintcompiler.completions import complete
from blueprintcompiler.errors import CompileError, MultipleErrors, PrintableError from blueprintcompiler.errors import (
CompileError,
DeprecatedWarning,
MultipleErrors,
PrintableError,
)
from blueprintcompiler.outputs.xml import XmlOutput from blueprintcompiler.outputs.xml import XmlOutput
from blueprintcompiler.tokenizer import Token, TokenType, tokenize from blueprintcompiler.tokenizer import Token, TokenType, tokenize
@ -54,6 +59,14 @@ class TestSamples(unittest.TestCase):
tokens = tokenizer.tokenize(blueprint) tokens = tokenizer.tokenize(blueprint)
ast, errors, warnings = parser.parse(tokens) ast, errors, warnings = parser.parse(tokens)
# Ignore deprecation warnings because some of the things we're testing
# are deprecated
warnings = [
warning
for warning in warnings
if not isinstance(warning, DeprecatedWarning)
]
if errors: if errors:
raise errors raise errors
if len(warnings): if len(warnings):