diff --git a/blueprintcompiler/completions.py b/blueprintcompiler/completions.py index 5d36739..b10ec3e 100644 --- a/blueprintcompiler/completions.py +++ b/blueprintcompiler/completions.py @@ -177,6 +177,15 @@ def property_completer(lsp, ast_node, match_variables): docs=prop.doc, detail=prop.detail, ) + elif prop.type.full_name == "Gtk.Expression": + yield Completion( + prop_name, + CompletionItemKind.Property, + sort_text=f"0 {prop_name}", + snippet=f"{prop_name}: expr $0;", + docs=prop.doc, + detail=prop.detail, + ) else: yield Completion( prop_name, diff --git a/blueprintcompiler/formatter.py b/blueprintcompiler/formatter.py index 35da5d2..f438675 100644 --- a/blueprintcompiler/formatter.py +++ b/blueprintcompiler/formatter.py @@ -146,8 +146,10 @@ def format(data, tab_size=2, insert_space=True): is_child_type = False elif str_item in CLOSING_TOKENS: - if str_item == "]" and last_not_whitespace != ",": + if str_item == "]" and str(last_not_whitespace) != "[": current_line = current_line[:-1] + if str(last_not_whitespace) != ",": + current_line += "," commit_current_line() current_line = "]" elif str(last_not_whitespace) in OPENING_TOKENS: @@ -191,6 +193,9 @@ def format(data, tab_size=2, insert_space=True): elif prev_line_type in require_extra_newline: newlines = 2 + current_line = "\n".join( + [line.rstrip() for line in current_line.split("\n")] + ) commit_current_line(LineType.COMMENT, newlines_before=newlines) else: # pragma: no cover diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py index e797eaa..5eb2b60 100644 --- a/blueprintcompiler/language/__init__.py +++ b/blueprintcompiler/language/__init__.py @@ -41,6 +41,7 @@ from .types import ClassName from .ui import UI from .values import ( ArrayValue, + ExprValue, Flag, Flags, IdentLiteral, diff --git a/blueprintcompiler/language/contexts.py b/blueprintcompiler/language/contexts.py index c5e97b3..6e26048 100644 --- a/blueprintcompiler/language/contexts.py +++ b/blueprintcompiler/language/contexts.py @@ -79,3 +79,9 @@ class ScopeCtx: for child in node.children: if child.context[ScopeCtx] is self: yield from self._iter_recursive(child) + + +@dataclass +class ExprValueCtx: + """Indicates that the context is an expression literal, where the + "item" keyword may be used.""" diff --git a/blueprintcompiler/language/expression.py b/blueprintcompiler/language/expression.py index f305035..e0b4246 100644 --- a/blueprintcompiler/language/expression.py +++ b/blueprintcompiler/language/expression.py @@ -81,6 +81,16 @@ class LiteralExpr(ExprBase): or self.root.is_legacy_template(self.literal.value.ident) ) + @property + def is_this(self) -> bool: + from .values import IdentLiteral + + return ( + not self.is_object + and isinstance(self.literal.value, IdentLiteral) + and self.literal.value.ident == "item" + ) + @property def literal(self): from .values import Literal @@ -91,6 +101,15 @@ class LiteralExpr(ExprBase): def type(self) -> T.Optional[GirType]: return self.literal.value.type + @validate() + def item_validations(self): + if self.is_this: + if not isinstance(self.rhs, CastExpr): + raise CompileError('"item" must be cast to its object type') + + if not isinstance(self.rhs.rhs, LookupOp): + raise CompileError('"item" can only be used for looking up properties') + class LookupOp(InfixExpr): grammar = [".", UseIdent("property")] @@ -285,6 +304,9 @@ expr.children = [ def decompile_lookup( ctx: DecompileCtx, gir: gir.GirContext, cdata: str, name: str, type: str ): + if ctx.parent_node is not None and ctx.parent_node.tag == "property": + ctx.print("expr ") + if t := ctx.type_by_cname(type): type = decompile.full_name(t) else: @@ -304,6 +326,8 @@ def decompile_lookup( if constant is not None: if constant == ctx.template_class: ctx.print("template." + name) + elif constant == "": + ctx.print("item as <" + type + ">." + name) else: ctx.print(constant + "." + name) return @@ -318,6 +342,9 @@ def decompile_lookup( def decompile_constant( ctx: DecompileCtx, gir: gir.GirContext, cdata: str, type: T.Optional[str] = None ): + if ctx.parent_node is not None and ctx.parent_node.tag == "property": + ctx.print("expr ") + if type is None: if cdata == ctx.template_class: ctx.print("template") @@ -330,6 +357,9 @@ def decompile_constant( @decompiler("closure", skip_children=True) def decompile_closure(ctx: DecompileCtx, gir: gir.GirContext, function: str, type: str): + if ctx.parent_node is not None and ctx.parent_node.tag == "property": + ctx.print("expr ") + if t := ctx.type_by_cname(type): type = decompile.full_name(t) else: diff --git a/blueprintcompiler/language/gobject_object.py b/blueprintcompiler/language/gobject_object.py index 54cb297..1def15b 100644 --- a/blueprintcompiler/language/gobject_object.py +++ b/blueprintcompiler/language/gobject_object.py @@ -28,7 +28,18 @@ from .common import * from .response_id import ExtResponse from .types import ClassName, ConcreteClassName -RESERVED_IDS = {"this", "self", "template", "true", "false", "null", "none"} +RESERVED_IDS = { + "this", + "self", + "template", + "true", + "false", + "null", + "none", + "item", + "expr", + "typeof", +} class ObjectContent(AstNode): diff --git a/blueprintcompiler/language/gobject_property.py b/blueprintcompiler/language/gobject_property.py index b553909..50a7512 100644 --- a/blueprintcompiler/language/gobject_property.py +++ b/blueprintcompiler/language/gobject_property.py @@ -21,13 +21,12 @@ from .binding import Binding from .common import * from .contexts import ValueTypeCtx -from .gtkbuilder_template import Template -from .values import ArrayValue, ObjectValue, Value +from .values import ArrayValue, ExprValue, ObjectValue, Value class Property(AstNode): grammar = Statement( - UseIdent("name"), ":", AnyOf(Binding, ObjectValue, Value, ArrayValue) + UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue) ) @property @@ -35,7 +34,7 @@ class Property(AstNode): return self.tokens["name"] @property - def value(self) -> T.Union[Binding, ObjectValue, Value, ArrayValue]: + def value(self) -> T.Union[Binding, ExprValue, ObjectValue, Value, ArrayValue]: return self.children[0] @property diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py index 63cf4fc..5556d99 100644 --- a/blueprintcompiler/language/values.py +++ b/blueprintcompiler/language/values.py @@ -23,7 +23,8 @@ from blueprintcompiler.gir import ArrayType from blueprintcompiler.lsp_utils import SemanticToken from .common import * -from .contexts import ScopeCtx, ValueTypeCtx +from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx +from .expression import Expression from .gobject_object import Object from .types import TypeName @@ -319,7 +320,12 @@ class IdentLiteral(AstNode): if self.ident == "null": if not self.context[ValueTypeCtx].allow_null: raise CompileError("null is not permitted here") - else: + elif self.ident == "item": + if not self.context[ExprValueCtx]: + raise CompileError( + '"item" can only be used in an expression literal' + ) + elif self.ident not in ["true", "false"]: raise CompileError( f"Could not find object with ID {self.ident}", did_you_mean=( @@ -407,6 +413,35 @@ class ObjectValue(AstNode): ) +class ExprValue(AstNode): + grammar = [Keyword("expr"), Expression] + + @property + def expression(self) -> Expression: + return self.children[Expression][0] + + @validate("expr") + def validate_for_type(self) -> None: + expected_type = self.parent.context[ValueTypeCtx].value_type + expr_type = self.root.gir.get_type("Expression", "Gtk") + if expected_type is not None and not expected_type.assignable_to(expr_type): + raise CompileError( + f"Cannot convert Gtk.Expression to {expected_type.full_name}" + ) + + @docs("expr") + def ref_docs(self): + return get_docs_section("Syntax ExprValue") + + @context(ExprValueCtx) + def expr_literal(self): + return ExprValueCtx() + + @context(ValueTypeCtx) + def value_type(self): + return ValueTypeCtx(None, must_infer_type=True) + + class Value(AstNode): grammar = AnyOf(Translated, Flags, Literal) @@ -452,6 +487,14 @@ class ArrayValue(AstNode): range=quoted_literal.range, ) ) + elif isinstance(value.child, Translated): + errors.append( + CompileError( + "Arrays can't contain translated strings", + range=value.child.range, + ) + ) + if len(errors) > 0: raise MultipleErrors(errors) diff --git a/blueprintcompiler/lsp.py b/blueprintcompiler/lsp.py index 0659154..c4076b4 100644 --- a/blueprintcompiler/lsp.py +++ b/blueprintcompiler/lsp.py @@ -118,6 +118,7 @@ class LanguageServer: self.client_capabilities = {} self.client_supports_completion_choice = False self._open_files: T.Dict[str, OpenFile] = {} + self._exited = False def run(self): # Read tags from gir files. During normal compilation these are @@ -125,7 +126,7 @@ class LanguageServer: xml_reader.PARSE_GIR.add("doc") try: - while True: + while not self._exited: line = "" content_len = -1 while content_len == -1 or (line != "\n" and line != "\r\n"): @@ -221,6 +222,14 @@ class LanguageServer: }, ) + @command("shutdown") + def shutdown(self, id, params): + self._send_response(id, None) + + @command("exit") + def exit(self, id, params): + self._exited = True + @command("textDocument/didOpen") def didOpen(self, id, params): doc = params.get("textDocument") diff --git a/blueprintcompiler/outputs/xml/__init__.py b/blueprintcompiler/outputs/xml/__init__.py index a21b6fb..5c03761 100644 --- a/blueprintcompiler/outputs/xml/__init__.py +++ b/blueprintcompiler/outputs/xml/__init__.py @@ -134,6 +134,11 @@ class XmlOutput(OutputFormat): self._emit_expression(value.expression, xml) xml.end_tag() + elif isinstance(value, ExprValue): + xml.start_tag("property", **props) + self._emit_expression(value.expression, xml) + xml.end_tag() + elif isinstance(value, ObjectValue): xml.start_tag("property", **props) self._emit_object(value.object, xml) @@ -218,12 +223,6 @@ class XmlOutput(OutputFormat): xml.put_text( "|".join([str(flag.value or flag.name) for flag in value.child.flags]) ) - elif isinstance(value.child, Translated): - raise CompilerBugError("translated values must be handled in the parent") - elif isinstance(value.child, TypeLiteral): - xml.put_text(value.child.type_name.glib_type_name) - elif isinstance(value.child, ObjectValue): - self._emit_object(value.child.object, xml) else: raise CompilerBugError() @@ -245,6 +244,9 @@ class XmlOutput(OutputFormat): raise CompilerBugError() def _emit_literal_expr(self, expr: LiteralExpr, xml: XmlEmitter): + if expr.is_this: + return + if expr.is_object: xml.start_tag("constant") else: diff --git a/docs/reference/expressions.rst b/docs/reference/expressions.rst index 8688ff0..3d523d1 100644 --- a/docs/reference/expressions.rst +++ b/docs/reference/expressions.rst @@ -42,8 +42,8 @@ Expressions are composed of property lookups and/or closures. Property lookups a .. _Syntax LookupExpression: -Lookup Expressions ------------------- +Lookups +------- .. rst-class:: grammar-block @@ -56,8 +56,8 @@ The type of a property expression is the type of the property it refers to. .. _Syntax ClosureExpression: -Closure Expressions -------------------- +Closures +-------- .. rst-class:: grammar-block @@ -72,8 +72,8 @@ Blueprint doesn't know the closure's return type, so closure expressions must be .. _Syntax CastExpression: -Cast Expressions ----------------- +Casts +----- .. rst-class:: grammar-block @@ -81,7 +81,32 @@ Cast Expressions Cast expressions allow Blueprint to know the type of an expression when it can't otherwise determine it. This is necessary for closures and for properties of application-defined types. +Example +~~~~~~~ + .. code-block:: blueprint // Cast the result of the closure so blueprint knows it's a string - label: bind $my_closure() as \ No newline at end of file + label: bind $format_bytes(template.file-size) as + +.. _Syntax ExprValue: + +Expression Values +----------------- + +.. rst-class:: grammar-block + + ExprValue = 'expr' :ref:`Expression` + +Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter `_ has an ``expression`` property of type `Gtk.Expression `_. This expression is evaluated for every item in a list model to determine whether the item should be filtered. + +To define an expression for such a property, use ``expr`` instead of ``bind``. Inside the expression, you can use the ``item`` keyword to refer to the item being evaluated. You must cast the item to the correct type using the ``as`` keyword, and you can only use ``item`` in a property lookup--you may not pass it to a closure. + +Example +~~~~~~~ + +.. code-block:: blueprint + + BoolFilter { + expression: expr item as <$UserAccount>.active; + } diff --git a/docs/reference/objects.rst b/docs/reference/objects.rst index 09f5af8..6f76da6 100644 --- a/docs/reference/objects.rst +++ b/docs/reference/objects.rst @@ -58,7 +58,7 @@ Properties .. rst-class:: grammar-block - Property = `> ':' ( :ref:`Binding` | :ref:`ObjectValue` | :ref:`Value` ) ';' + Property = `> ':' ( :ref:`Binding` | :ref:`ExprValue` | :ref:`ObjectValue` | :ref:`Value` ) ';' Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container. diff --git a/docs/tutorial.rst b/docs/tutorial.rst new file mode 100644 index 0000000..7fdbf5a --- /dev/null +++ b/docs/tutorial.rst @@ -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 `_. + +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 `_. +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 `_ +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 `_ +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 ` 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 `_ +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 `_ for more details. diff --git a/tests/formatting/comment_in.blp b/tests/formatting/comment_in.blp index 32a907c..88b825a 100644 --- a/tests/formatting/comment_in.blp +++ b/tests/formatting/comment_in.blp @@ -1,2 +1,4 @@ using Gtk 4.0; -//comment \ No newline at end of file +//comment +// Trailing whitespace: +// diff --git a/tests/formatting/comment_out.blp b/tests/formatting/comment_out.blp index d5dca95..91e647a 100644 --- a/tests/formatting/comment_out.blp +++ b/tests/formatting/comment_out.blp @@ -1,2 +1,4 @@ using Gtk 4.0; // comment +// Trailing whitespace: +// diff --git a/tests/formatting/lists_in.blp b/tests/formatting/lists_in.blp new file mode 100644 index 0000000..66b37a2 --- /dev/null +++ b/tests/formatting/lists_in.blp @@ -0,0 +1,21 @@ +using Gtk 4.0; + +Box { + styles [] +} + +Box { + styles ["a"] +} + +Box { + styles ["a",] +} + +Box { + styles ["a", "b"] +} + +Box { + styles ["a", "b",] +} diff --git a/tests/formatting/lists_out.blp b/tests/formatting/lists_out.blp new file mode 100644 index 0000000..7f1fe4a --- /dev/null +++ b/tests/formatting/lists_out.blp @@ -0,0 +1,31 @@ +using Gtk 4.0; + +Box { + styles [] +} + +Box { + styles [ + "a", + ] +} + +Box { + styles [ + "a", + ] +} + +Box { + styles [ + "a", + "b", + ] +} + +Box { + styles [ + "a", + "b", + ] +} diff --git a/tests/formatting/out.blp b/tests/formatting/out.blp index 9d9a8b4..b84c25f 100644 --- a/tests/formatting/out.blp +++ b/tests/formatting/out.blp @@ -11,7 +11,7 @@ Overlay { notify::icon-name => $on_icon_name_changed(label) swapped; styles [ - "destructive" + "destructive", ] } diff --git a/tests/sample_errors/expr_item_not_cast.blp b/tests/sample_errors/expr_item_not_cast.blp new file mode 100644 index 0000000..76a1d89 --- /dev/null +++ b/tests/sample_errors/expr_item_not_cast.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +BoolFilter { + expression: expr item.visible; +} diff --git a/tests/sample_errors/expr_item_not_cast.err b/tests/sample_errors/expr_item_not_cast.err new file mode 100644 index 0000000..f6cf7d4 --- /dev/null +++ b/tests/sample_errors/expr_item_not_cast.err @@ -0,0 +1 @@ +4,20,4,"item" must be cast to its object type \ No newline at end of file diff --git a/tests/sample_errors/expr_value_assignment.blp b/tests/sample_errors/expr_value_assignment.blp new file mode 100644 index 0000000..51d778f --- /dev/null +++ b/tests/sample_errors/expr_value_assignment.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +Label { + label: expr 1; +} diff --git a/tests/sample_errors/expr_value_assignment.err b/tests/sample_errors/expr_value_assignment.err new file mode 100644 index 0000000..1c7092a --- /dev/null +++ b/tests/sample_errors/expr_value_assignment.err @@ -0,0 +1 @@ +4,10,4,Cannot convert Gtk.Expression to string \ No newline at end of file diff --git a/tests/sample_errors/expr_value_closure_arg.blp b/tests/sample_errors/expr_value_closure_arg.blp new file mode 100644 index 0000000..7f828c4 --- /dev/null +++ b/tests/sample_errors/expr_value_closure_arg.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +BoolFilter { + expression: expr $closure(item as ) as ; +} diff --git a/tests/sample_errors/expr_value_closure_arg.err b/tests/sample_errors/expr_value_closure_arg.err new file mode 100644 index 0000000..b9e19f8 --- /dev/null +++ b/tests/sample_errors/expr_value_closure_arg.err @@ -0,0 +1 @@ +4,29,4,"item" can only be used for looking up properties \ No newline at end of file diff --git a/tests/sample_errors/expr_value_item.blp b/tests/sample_errors/expr_value_item.blp new file mode 100644 index 0000000..141c806 --- /dev/null +++ b/tests/sample_errors/expr_value_item.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +BoolFilter { + expression: expr item as