mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Compare commits
14 commits
e07da3c339
...
c1fbcef6d0
Author | SHA1 | Date | |
---|---|---|---|
|
c1fbcef6d0 | ||
|
404ae76787 | ||
|
04ef0944db | ||
|
aa13c8f5af | ||
|
29e4a56bfc | ||
|
8c6f8760f7 | ||
|
b9f58aeab5 | ||
|
55e5095fba | ||
|
f3faf4b993 | ||
|
d6f4b88d35 | ||
|
a6d57cebec | ||
|
9b9fab832b | ||
|
5b0f662478 | ||
|
ac70ea7403 |
94 changed files with 850 additions and 232 deletions
|
@ -8,7 +8,7 @@ in the NEWS file.
|
|||
3. Make a new commit with just these two changes. Use `Release v{version}` as the commit message. Tag the commit as `v{version}` and push the tag.
|
||||
4. Create a "Post-release version bump" commit.
|
||||
5. Go to the Releases page in GitLab and create a new release from the tag.
|
||||
6. Announce the release through relevant channels (Twitter, TWIG, etc.)
|
||||
6. Announce the release through relevant channels (Mastodon, TWIG, etc.)
|
||||
|
||||
## Related projects
|
||||
|
||||
|
|
32
NEWS.md
32
NEWS.md
|
@ -1,3 +1,35 @@
|
|||
# v0.16.0
|
||||
|
||||
## Added
|
||||
- Added more "go to reference" implementations in the language server
|
||||
- Added semantic token support for flag members in the language server
|
||||
- Added property documentation to the hover tooltip for notify signals
|
||||
- The language server now shows relevant sections of the reference documentation when hovering over keywords and symbols
|
||||
- Added `not-swapped` flag to signal handlers, which may be needed for signal handlers that specify an object
|
||||
- Added expression literals, which allow you to specify a Gtk.Expression property (as opposed to the existing expression support, which is for property bindings)
|
||||
|
||||
## Changed
|
||||
- The formatter adds trailing commas to lists (Alexey Yerin)
|
||||
- The formatter removes trailing whitespace from comments (Alexey Yerin)
|
||||
- Autocompleting a commonly translated property automatically adds the `_("")` syntax
|
||||
- Marking a single-quoted string as translatable now generates a warning, since gettext does not recognize it when using the configuration recommended in the blueprint documentation
|
||||
|
||||
## Fixed
|
||||
- Added support for libgirepository-2.0 so that blueprint doesn't crash due to import conflicts on newer versions of PyGObject (Jordan Petridis)
|
||||
- Fixed a bug when decompiling/porting files with enum values
|
||||
- Fixed several issues where tests would fail with versions of GTK that added new deprecations
|
||||
- Addressed a problem with the language server protocol in some editors (Luoyayu)
|
||||
- Fixed an issue where the compiler would crash instead of reporting compiler errors
|
||||
- Fixed a crash in the language server that occurred when a detailed signal (e.g. `notify::*`) was not complete
|
||||
- The language server now properly implements the shutdown command, fixing support for some editors and improving robustness when restarting (Alexey Yerin)
|
||||
- Marking a string in an array as translatable now generates an error, since it doesn't work
|
||||
-
|
||||
|
||||
## Documentation
|
||||
- Added mention of `null` in the Literal Values section
|
||||
- Add apps to Built with Blueprint section (Benedek Dévényi, Vladimir Vaskov)
|
||||
- Corrected and updated many parts of the documentation
|
||||
|
||||
# v0.14.0
|
||||
|
||||
## Added
|
||||
|
|
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
|
||||
if e.fatal:
|
||||
return
|
||||
except MultipleErrors as e:
|
||||
for error in e.errors:
|
||||
yield error
|
||||
if error.fatal:
|
||||
return
|
||||
|
||||
for child in self.children:
|
||||
yield from child._get_errors()
|
||||
|
@ -249,14 +254,7 @@ def validate(
|
|||
if skip_incomplete and self.incomplete:
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
def fill_error(e: CompileError):
|
||||
if e.range is None:
|
||||
e.range = (
|
||||
Range.join(
|
||||
|
@ -266,8 +264,26 @@ def validate(
|
|||
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
|
||||
raise e
|
||||
except MultipleErrors as e:
|
||||
if self.incomplete:
|
||||
return
|
||||
|
||||
for error in e.errors:
|
||||
fill_error(error)
|
||||
|
||||
raise e
|
||||
|
||||
inner._validator = True
|
||||
return inner
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import sys
|
||||
import typing as T
|
||||
|
||||
from . import gir, language
|
||||
from . import annotations, gir, language
|
||||
from .ast_utils import AstNode
|
||||
from .completions_utils import *
|
||||
from .language.types import ClassName
|
||||
|
@ -31,10 +30,6 @@ from .tokenizer import Token, TokenType
|
|||
Pattern = T.List[T.Tuple[TokenType, T.Optional[str]]]
|
||||
|
||||
|
||||
def debug(*args, **kwargs):
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def _complete(
|
||||
lsp, ast_node: AstNode, tokens: T.List[Token], idx: int, token_idx: int
|
||||
) -> T.Iterator[Completion]:
|
||||
|
@ -139,7 +134,7 @@ def gtk_object_completer(lsp, ast_node, match_variables):
|
|||
matches=new_statement_patterns,
|
||||
)
|
||||
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():
|
||||
if (
|
||||
isinstance(prop.type, gir.BoolType)
|
||||
|
@ -154,11 +149,17 @@ def property_completer(lsp, ast_node, match_variables):
|
|||
detail=prop.detail,
|
||||
)
|
||||
elif isinstance(prop.type, gir.StringType):
|
||||
snippet = (
|
||||
f'{prop_name}: _("$0");'
|
||||
if annotations.is_property_translated(prop)
|
||||
else f'{prop_name}: "$0";'
|
||||
)
|
||||
|
||||
yield Completion(
|
||||
prop_name,
|
||||
CompletionItemKind.Property,
|
||||
sort_text=f"0 {prop_name}",
|
||||
snippet=f'{prop_name}: "$0";',
|
||||
snippet=snippet,
|
||||
docs=prop.doc,
|
||||
detail=prop.detail,
|
||||
)
|
||||
|
@ -176,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,
|
||||
|
@ -188,7 +198,7 @@ def property_completer(lsp, ast_node, match_variables):
|
|||
|
||||
|
||||
@completer(
|
||||
applies_in=[language.Property, language.BaseAttribute],
|
||||
applies_in=[language.Property, language.A11yProperty],
|
||||
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
|
||||
)
|
||||
def prop_value_completer(lsp, ast_node, match_variables):
|
||||
|
@ -212,7 +222,7 @@ def prop_value_completer(lsp, ast_node, match_variables):
|
|||
matches=new_statement_patterns,
|
||||
)
|
||||
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():
|
||||
if not isinstance(ast_node.parent, language.Object):
|
||||
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 decorator(func):
|
||||
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
import re
|
||||
from enum import Enum
|
||||
|
||||
from . import tokenizer, utils
|
||||
from . import tokenizer
|
||||
from .errors import CompilerBugError
|
||||
from .tokenizer import TokenType
|
||||
|
||||
OPENING_TOKENS = ("{", "[")
|
||||
|
@ -145,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:
|
||||
|
@ -190,10 +193,13 @@ 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:
|
||||
commit_current_line()
|
||||
else: # pragma: no cover
|
||||
raise CompilerBugError()
|
||||
|
||||
elif str_item == "(" and (
|
||||
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
|
||||
|
||||
gi.require_version("GIRepository", "2.0")
|
||||
from gi.repository import GIRepository # type: ignore
|
||||
try:
|
||||
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 .errors import CompileError, CompilerBugError
|
||||
|
@ -42,7 +54,7 @@ def add_typelib_search_path(path: str):
|
|||
|
||||
|
||||
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"
|
||||
|
||||
|
@ -74,7 +86,7 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
|
|||
return _available_namespaces
|
||||
|
||||
search_paths: list[str] = [
|
||||
*GIRepository.Repository.get_search_path(),
|
||||
*_repo.get_search_path(),
|
||||
*_user_search_paths,
|
||||
]
|
||||
|
||||
|
@ -455,10 +467,13 @@ class Signature(GirNode):
|
|||
return result
|
||||
|
||||
@cached_property
|
||||
def return_type(self) -> GirType:
|
||||
return self.get_containing(Repository)._resolve_type_id(
|
||||
self.tl.SIGNATURE_RETURN_TYPE
|
||||
)
|
||||
def return_type(self) -> T.Optional[GirType]:
|
||||
if self.tl.SIGNATURE_RETURN_TYPE == 0:
|
||||
return None
|
||||
else:
|
||||
return self.get_containing(Repository)._resolve_type_id(
|
||||
self.tl.SIGNATURE_RETURN_TYPE
|
||||
)
|
||||
|
||||
|
||||
class Signal(GirNode):
|
||||
|
@ -478,7 +493,10 @@ class Signal(GirNode):
|
|||
args = ", ".join(
|
||||
[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
|
||||
def online_docs(self) -> T.Optional[str]:
|
||||
|
@ -890,14 +908,6 @@ class Namespace(GirNode):
|
|||
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]:
|
||||
"""Gets a type (class, interface, enum, etc.) from this namespace."""
|
||||
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
|
||||
namespace's dependencies)."""
|
||||
|
||||
if type_name in _BASIC_TYPES:
|
||||
return _BASIC_TYPES[type_name]()
|
||||
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)
|
||||
ns, name = type_name.split(".", 1)
|
||||
return self.get_containing(Repository).get_type(name, ns)
|
||||
|
||||
@property
|
||||
def online_docs(self) -> T.Optional[str]:
|
||||
|
@ -946,7 +951,7 @@ class Repository(GirNode):
|
|||
self.includes = {
|
||||
name: get_namespace(name, version) for name, version in deps
|
||||
}
|
||||
except:
|
||||
except: # pragma: no cover
|
||||
raise CompilerBugError(f"Failed to load dependencies.")
|
||||
else:
|
||||
self.includes = {}
|
||||
|
@ -954,12 +959,6 @@ class Repository(GirNode):
|
|||
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
||||
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):
|
||||
"""Finds a namespace among this namespace's dependencies."""
|
||||
if ns == self.namespace.name:
|
||||
|
|
|
@ -4,7 +4,6 @@ from .adw_breakpoint import (
|
|||
AdwBreakpointSetters,
|
||||
)
|
||||
from .adw_response_dialog import ExtAdwResponseDialog
|
||||
from .attributes import BaseAttribute
|
||||
from .binding import Binding
|
||||
from .common import *
|
||||
from .contexts import ScopeCtx, ValueTypeCtx
|
||||
|
@ -20,7 +19,7 @@ from .expression import (
|
|||
from .gobject_object import Object, ObjectContent
|
||||
from .gobject_property import Property
|
||||
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_file_filter import (
|
||||
Filters,
|
||||
|
@ -42,6 +41,7 @@ from .types import ClassName
|
|||
from .ui import UI
|
||||
from .values import (
|
||||
ArrayValue,
|
||||
ExprValue,
|
||||
Flag,
|
||||
Flags,
|
||||
IdentLiteral,
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
from ..decompiler import decompile_translatable, truthy
|
||||
from .common import *
|
||||
from .contexts import ValueTypeCtx
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .values import StringValue
|
||||
|
||||
|
@ -94,10 +93,6 @@ class ExtAdwResponseDialogResponse(AstNode):
|
|||
self.value.range.text,
|
||||
)
|
||||
|
||||
@context(ValueTypeCtx)
|
||||
def value_type(self) -> ValueTypeCtx:
|
||||
return ValueTypeCtx(StringType())
|
||||
|
||||
@validate("id")
|
||||
def unique_in_parent(self):
|
||||
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"]
|
|
@ -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."""
|
||||
|
|
|
@ -38,10 +38,6 @@ class ExprBase(AstNode):
|
|||
def type(self) -> T.Optional[GirType]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def type_complete(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def rhs(self) -> T.Optional["ExprBase"]:
|
||||
if isinstance(self.parent, Expression):
|
||||
|
@ -65,10 +61,6 @@ class Expression(ExprBase):
|
|||
def type(self) -> T.Optional[GirType]:
|
||||
return self.last.type
|
||||
|
||||
@property
|
||||
def type_complete(self) -> bool:
|
||||
return self.last.type_complete
|
||||
|
||||
|
||||
class InfixExpr(ExprBase):
|
||||
@property
|
||||
|
@ -89,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
|
||||
|
@ -99,14 +101,14 @@ class LiteralExpr(ExprBase):
|
|||
def type(self) -> T.Optional[GirType]:
|
||||
return self.literal.value.type
|
||||
|
||||
@property
|
||||
def type_complete(self) -> bool:
|
||||
from .values import IdentLiteral
|
||||
@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 isinstance(self.literal.value, IdentLiteral):
|
||||
if object := self.context[ScopeCtx].objects.get(self.literal.value.ident):
|
||||
return not object.gir_class.incomplete
|
||||
return True
|
||||
if not isinstance(self.rhs.rhs, LookupOp):
|
||||
raise CompileError('"item" can only be used for looking up properties')
|
||||
|
||||
|
||||
class LookupOp(InfixExpr):
|
||||
|
@ -211,10 +213,6 @@ class CastExpr(InfixExpr):
|
|||
def type(self) -> T.Optional[GirType]:
|
||||
return self.children[TypeName][0].gir_type
|
||||
|
||||
@property
|
||||
def type_complete(self) -> bool:
|
||||
return True
|
||||
|
||||
@validate()
|
||||
def cast_makes_sense(self):
|
||||
if self.type is None or self.lhs.type is None:
|
||||
|
@ -306,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:
|
||||
|
@ -325,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
|
||||
|
@ -339,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")
|
||||
|
@ -351,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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
@ -51,7 +50,7 @@ class Property(AstNode):
|
|||
|
||||
@property
|
||||
def document_symbol(self) -> DocumentSymbol:
|
||||
if isinstance(self.value, ObjectValue):
|
||||
if isinstance(self.value, ObjectValue) or self.value is None:
|
||||
detail = None
|
||||
else:
|
||||
detail = self.value.range.text
|
||||
|
|
|
@ -27,6 +27,7 @@ from .gtkbuilder_template import Template
|
|||
class SignalFlag(AstNode):
|
||||
grammar = AnyOf(
|
||||
UseExact("flag", "swapped"),
|
||||
UseExact("flag", "not-swapped"),
|
||||
UseExact("flag", "after"),
|
||||
)
|
||||
|
||||
|
@ -40,6 +41,27 @@ class SignalFlag(AstNode):
|
|||
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()
|
||||
def ref_docs(self):
|
||||
return get_docs_section("Syntax Signal")
|
||||
|
@ -92,9 +114,17 @@ class Signal(AstNode):
|
|||
def flags(self) -> T.List[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
|
||||
def is_swapped(self) -> bool:
|
||||
return any(x.flag == "swapped" for x in self.flags)
|
||||
def is_swapped(self) -> T.Optional[bool]:
|
||||
for flag in self.flags:
|
||||
if flag.flag == "swapped":
|
||||
return True
|
||||
elif flag.flag == "not-swapped":
|
||||
return False
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_after(self) -> bool:
|
||||
|
@ -113,16 +143,17 @@ class Signal(AstNode):
|
|||
|
||||
@property
|
||||
def document_symbol(self) -> DocumentSymbol:
|
||||
detail = self.ranges["detail_start", "detail_end"]
|
||||
return DocumentSymbol(
|
||||
self.full_name,
|
||||
SymbolKind.Event,
|
||||
self.range,
|
||||
self.group.tokens["name"].range,
|
||||
self.ranges["detail_start", "detail_end"].text,
|
||||
detail.text if detail is not None else None,
|
||||
)
|
||||
|
||||
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)
|
||||
if obj is not None:
|
||||
return LocationLink(
|
||||
|
@ -194,15 +225,16 @@ class Signal(AstNode):
|
|||
|
||||
|
||||
@decompiler("signal")
|
||||
def decompile_signal(
|
||||
ctx, gir, name, handler, swapped="false", after="false", object=None
|
||||
):
|
||||
def decompile_signal(ctx, gir, name, handler, swapped=None, after="false", object=None):
|
||||
object_name = object or ""
|
||||
name = name.replace("_", "-")
|
||||
line = f"{name} => ${handler}({object_name})"
|
||||
|
||||
if decompile.truthy(swapped):
|
||||
line += " swapped"
|
||||
elif swapped is not None:
|
||||
line += " not-swapped"
|
||||
|
||||
if decompile.truthy(after):
|
||||
line += " after"
|
||||
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
import typing as T
|
||||
|
||||
from ..decompiler import escape_quote
|
||||
from .attributes import BaseAttribute
|
||||
from .common import *
|
||||
from .contexts import ValueTypeCtx
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
|
@ -119,7 +117,7 @@ def _get_docs(gir, name):
|
|||
return gir_type.doc
|
||||
|
||||
|
||||
class A11yProperty(BaseAttribute):
|
||||
class A11yProperty(AstNode):
|
||||
grammar = Statement(
|
||||
UseIdent("name"),
|
||||
":",
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
|
||||
from .common import *
|
||||
from .contexts import ValueTypeCtx
|
||||
from .gobject_object import ObjectContent, validate_parent_type
|
||||
from .values import StringValue
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class ExtListItemFactory(AstNode):
|
|||
else:
|
||||
return self.root.gir.get_type("ListItem", "Gtk")
|
||||
|
||||
@validate("template")
|
||||
@validate("id")
|
||||
def container_is_builder_list(self):
|
||||
validate_parent_type(
|
||||
self,
|
||||
|
@ -59,7 +59,7 @@ class ExtListItemFactory(AstNode):
|
|||
"sub-templates",
|
||||
)
|
||||
|
||||
@validate("template")
|
||||
@validate("id")
|
||||
def unique_in_parent(self):
|
||||
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"
|
||||
)
|
||||
|
||||
@validate("template")
|
||||
@validate("id")
|
||||
def type_name_upgrade(self):
|
||||
if self.type_name is None:
|
||||
raise UpgradeWarning(
|
||||
|
@ -103,10 +103,7 @@ class ExtListItemFactory(AstNode):
|
|||
|
||||
@property
|
||||
def action_widgets(self):
|
||||
"""
|
||||
The sub-template shouldn't have it`s own actions this is
|
||||
just hear to satisfy XmlOutput._emit_object_or_template
|
||||
"""
|
||||
# The sub-template shouldn't have its own actions, this is just here to satisfy XmlOutput._emit_object_or_template
|
||||
return None
|
||||
|
||||
@docs("id")
|
||||
|
|
|
@ -59,14 +59,8 @@ class GtkDirective(AstNode):
|
|||
|
||||
@property
|
||||
def gir_namespace(self):
|
||||
# validate the GTK version first to make sure the more specific error
|
||||
# message is emitted
|
||||
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")
|
||||
# For better error handling, just assume it's 4.0
|
||||
return gir.get_namespace("Gtk", "4.0")
|
||||
|
||||
@docs()
|
||||
def ref_docs(self):
|
||||
|
@ -90,7 +84,7 @@ class Import(AstNode):
|
|||
|
||||
@validate("namespace", "version")
|
||||
def namespace_exists(self):
|
||||
gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
||||
gir.get_namespace(self.namespace, self.version)
|
||||
|
||||
@validate()
|
||||
def unused(self):
|
||||
|
@ -106,7 +100,7 @@ class Import(AstNode):
|
|||
@property
|
||||
def gir_namespace(self):
|
||||
try:
|
||||
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
||||
return gir.get_namespace(self.namespace, self.version)
|
||||
except CompileError:
|
||||
return None
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -57,6 +58,19 @@ class Translated(AstNode):
|
|||
f"Cannot convert translated string to {expected_type.full_name}"
|
||||
)
|
||||
|
||||
@validate("context")
|
||||
def context_double_quoted(self):
|
||||
if self.translate_context is None:
|
||||
return
|
||||
|
||||
if not str(self.group.tokens["context"]).startswith('"'):
|
||||
raise CompileWarning("gettext may not recognize single-quoted strings")
|
||||
|
||||
@validate("string")
|
||||
def string_double_quoted(self):
|
||||
if not str(self.group.tokens["string"]).startswith('"'):
|
||||
raise CompileWarning("gettext may not recognize single-quoted strings")
|
||||
|
||||
@docs()
|
||||
def ref_docs(self):
|
||||
return get_docs_section("Syntax Translated")
|
||||
|
@ -319,7 +333,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 +426,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 +500,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)
|
||||
|
||||
|
|
|
@ -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 <doc> 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")
|
||||
|
|
|
@ -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)
|
||||
|
@ -169,7 +174,7 @@ class XmlOutput(OutputFormat):
|
|||
"signal",
|
||||
name=name,
|
||||
handler=signal.handler,
|
||||
swapped=signal.is_swapped or None,
|
||||
swapped=signal.is_swapped,
|
||||
after=signal.is_after or None,
|
||||
object=(
|
||||
self._object_id(signal, signal.object_id) if signal.object_id else None
|
||||
|
@ -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:
|
||||
|
@ -366,12 +368,13 @@ class XmlOutput(OutputFormat):
|
|||
|
||||
elif isinstance(extension, ExtScaleMarks):
|
||||
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(
|
||||
"mark",
|
||||
value=mark.value,
|
||||
position=mark.position,
|
||||
**self._translated_string_attrs(mark.label and mark.label.child),
|
||||
**self._translated_string_attrs(label),
|
||||
)
|
||||
if mark.label is not None:
|
||||
xml.put_text(mark.label.string)
|
||||
|
|
|
@ -40,7 +40,9 @@ class XmlEmitter:
|
|||
self._tag_stack = []
|
||||
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.result += f"<{tag}"
|
||||
for key, val in attrs.items():
|
||||
|
|
|
@ -95,19 +95,11 @@ class ParseGroup:
|
|||
|
||||
try:
|
||||
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
|
||||
except TypeError as e:
|
||||
except TypeError: # pragma: no cover
|
||||
raise CompilerBugError(
|
||||
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:
|
||||
"""Contains the state of the parser."""
|
||||
|
@ -265,10 +257,6 @@ class ParseNode:
|
|||
"""Convenience method for err()."""
|
||||
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):
|
||||
"""ParseNode that emits a compile error if it fails to parse."""
|
||||
|
@ -290,27 +278,6 @@ class Err(ParseNode):
|
|||
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):
|
||||
"""ParseNode that emits a compile error if it parses successfully."""
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ a module in your flatpak manifest:
|
|||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
|
||||
"tag": "v0.14.0"
|
||||
"tag": "v0.16.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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 <string>
|
||||
label: bind $format_bytes(template.file-size) as <string>
|
||||
|
||||
.. _Syntax ExprValue:
|
||||
|
||||
Expression Values
|
||||
-----------------
|
||||
|
||||
.. rst-class:: grammar-block
|
||||
|
||||
ExprValue = 'expr' :ref:`Expression<Syntax Expression>`
|
||||
|
||||
Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter <https://docs.gtk.org/gtk4/class.BoolFilter.html>`_ has an ``expression`` property of type `Gtk.Expression <https://docs.gtk.org/gtk4/class.Expression.html>`_. 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;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ Properties
|
|||
|
||||
.. rst-class:: grammar-block
|
||||
|
||||
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
|
||||
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ExprValue<Syntax ExprValue>` | :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.
|
||||
|
||||
|
@ -91,7 +91,7 @@ Signal Handlers
|
|||
.. 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)* ';'
|
||||
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>`_).
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
~~~~~~~
|
||||
|
||||
|
@ -108,7 +110,6 @@ Example
|
|||
clicked => $on_button_clicked();
|
||||
}
|
||||
|
||||
|
||||
.. _Syntax Child:
|
||||
|
||||
Children
|
||||
|
|
|
@ -24,6 +24,8 @@ If you're using Meson's `i18n module <https://mesonbuild.com/i18n-module.html#i1
|
|||
|
||||
i18n.gettext('package name', preset: 'glib')
|
||||
|
||||
You must use double quotes for the translated strings in order for gettext to recognize them. Newer versions of blueprint will warn you if you use single quotes.
|
||||
|
||||
Contexts
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
project('blueprint-compiler',
|
||||
version: '0.14.0',
|
||||
version: '0.16.0',
|
||||
)
|
||||
|
||||
prefix = get_option('prefix')
|
||||
|
|
4
tests/formatting/comment_in.blp
Normal file
4
tests/formatting/comment_in.blp
Normal file
|
@ -0,0 +1,4 @@
|
|||
using Gtk 4.0;
|
||||
//comment
|
||||
// Trailing whitespace:
|
||||
//
|
4
tests/formatting/comment_out.blp
Normal file
4
tests/formatting/comment_out.blp
Normal file
|
@ -0,0 +1,4 @@
|
|||
using Gtk 4.0;
|
||||
// comment
|
||||
// Trailing whitespace:
|
||||
//
|
21
tests/formatting/lists_in.blp
Normal file
21
tests/formatting/lists_in.blp
Normal file
|
@ -0,0 +1,21 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Box {
|
||||
styles []
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a"]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a",]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a", "b"]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles ["a", "b",]
|
||||
}
|
31
tests/formatting/lists_out.blp
Normal file
31
tests/formatting/lists_out.blp
Normal file
|
@ -0,0 +1,31 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Box {
|
||||
styles []
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
||||
Box {
|
||||
styles [
|
||||
"a",
|
||||
"b",
|
||||
]
|
||||
}
|
|
@ -11,7 +11,7 @@ Overlay {
|
|||
notify::icon-name => $on_icon_name_changed(label) swapped;
|
||||
|
||||
styles [
|
||||
"destructive"
|
||||
"destructive",
|
||||
]
|
||||
}
|
||||
|
||||
|
|
5
tests/sample_errors/expr_item_not_cast.blp
Normal file
5
tests/sample_errors/expr_item_not_cast.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr item.visible;
|
||||
}
|
1
tests/sample_errors/expr_item_not_cast.err
Normal file
1
tests/sample_errors/expr_item_not_cast.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,20,4,"item" must be cast to its object type
|
5
tests/sample_errors/expr_value_assignment.blp
Normal file
5
tests/sample_errors/expr_value_assignment.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Label {
|
||||
label: expr 1;
|
||||
}
|
1
tests/sample_errors/expr_value_assignment.err
Normal file
1
tests/sample_errors/expr_value_assignment.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,10,4,Cannot convert Gtk.Expression to string
|
5
tests/sample_errors/expr_value_closure_arg.blp
Normal file
5
tests/sample_errors/expr_value_closure_arg.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr $closure(item as <Entry>) as <bool>;
|
||||
}
|
1
tests/sample_errors/expr_value_closure_arg.err
Normal file
1
tests/sample_errors/expr_value_closure_arg.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,29,4,"item" can only be used for looking up properties
|
5
tests/sample_errors/expr_value_item.blp
Normal file
5
tests/sample_errors/expr_value_item.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr item as <Label>;
|
||||
}
|
1
tests/sample_errors/expr_value_item.err
Normal file
1
tests/sample_errors/expr_value_item.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,20,4,"item" can only be used for looking up properties
|
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
|
5
tests/sample_errors/incomplete_signal.blp
Normal file
5
tests/sample_errors/incomplete_signal.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Label {
|
||||
notify::
|
||||
}
|
2
tests/sample_errors/incomplete_signal.err
Normal file
2
tests/sample_errors/incomplete_signal.err
Normal file
|
@ -0,0 +1,2 @@
|
|||
5,1,0,Expected a signal detail name
|
||||
4,9,3,Unexpected tokens
|
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/single_quoted_translated.blp
Normal file
5
tests/sample_errors/single_quoted_translated.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Label {
|
||||
label: _('Hello, World!');
|
||||
}
|
1
tests/sample_errors/single_quoted_translated.err
Normal file
1
tests/sample_errors/single_quoted_translated.err
Normal file
|
@ -0,0 +1 @@
|
|||
4,12,15,gettext may not recognize single-quoted strings
|
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
|
7
tests/sample_errors/translated_string_array.blp
Normal file
7
tests/sample_errors/translated_string_array.blp
Normal file
|
@ -0,0 +1,7 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
StringList {
|
||||
strings: [
|
||||
_("Test")
|
||||
];
|
||||
}
|
1
tests/sample_errors/translated_string_array.err
Normal file
1
tests/sample_errors/translated_string_array.err
Normal file
|
@ -0,0 +1 @@
|
|||
5,5,9,Arrays can't contain translated strings
|
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
|
9
tests/samples/bind_expr_prop.blp
Normal file
9
tests/samples/bind_expr_prop.blp
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter filter1 {
|
||||
expression: expr true;
|
||||
}
|
||||
|
||||
BoolFilter filter2 {
|
||||
expression: bind filter1.expression;
|
||||
}
|
17
tests/samples/bind_expr_prop.ui
Normal file
17
tests/samples/bind_expr_prop.ui
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?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="GtkBoolFilter" id="filter1">
|
||||
<property name="expression">
|
||||
<constant type="gboolean">true</constant>
|
||||
</property>
|
||||
</object>
|
||||
<object class="GtkBoolFilter" id="filter2">
|
||||
<property name="expression" bind-source="filter1" bind-property="expression" bind-flags="sync-create"/>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/bind_literal.blp
Normal file
5
tests/samples/bind_literal.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
Label {
|
||||
label: bind "Hello, world!";
|
||||
}
|
14
tests/samples/bind_literal.ui
Normal file
14
tests/samples/bind_literal.ui
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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="GtkLabel">
|
||||
<binding name="label">
|
||||
<constant type="gchararray">Hello, world!</constant>
|
||||
</binding>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/expr_value.blp
Normal file
5
tests/samples/expr_value.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr item as <Entry>.visible;
|
||||
}
|
14
tests/samples/expr_value.ui
Normal file
14
tests/samples/expr_value.ui
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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="GtkBoolFilter">
|
||||
<property name="expression">
|
||||
<lookup name="visible" type="GtkEntry"></lookup>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/expr_value_closure.blp
Normal file
5
tests/samples/expr_value_closure.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr $closure(item as <Entry>.visible) as <bool>;
|
||||
}
|
16
tests/samples/expr_value_closure.ui
Normal file
16
tests/samples/expr_value_closure.ui
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
DO NOT EDIT!
|
||||
This file was @generated by blueprint-compiler. Instead, edit the
|
||||
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="GtkBoolFilter">
|
||||
<property name="expression">
|
||||
<closure function="closure" type="gboolean">
|
||||
<lookup name="visible" type="GtkEntry"></lookup>
|
||||
</closure>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
5
tests/samples/expr_value_literal.blp
Normal file
5
tests/samples/expr_value_literal.blp
Normal file
|
@ -0,0 +1,5 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
BoolFilter {
|
||||
expression: expr true;
|
||||
}
|
14
tests/samples/expr_value_literal.ui
Normal file
14
tests/samples/expr_value_literal.ui
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?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="GtkBoolFilter">
|
||||
<property name="expression">
|
||||
<constant type="gboolean">true</constant>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
|
@ -4,6 +4,7 @@ Box {
|
|||
visible: bind box2.visible inverted;
|
||||
orientation: bind box2.orientation;
|
||||
spacing: bind box2.spacing no-sync-create;
|
||||
tooltip-text: bind box2.tooltip-text bidirectional;
|
||||
}
|
||||
|
||||
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="orientation" bind-source="box2" bind-property="orientation" bind-flags="sync-create"/>
|
||||
<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 class="GtkBox" id="box2">
|
||||
<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>
|
|
@ -5,6 +5,6 @@ AboutDialog about {
|
|||
|
||||
authors: [
|
||||
"Jane doe <jane-doe@email.com>",
|
||||
"Jhonny D <jd@email.com>"
|
||||
"Jhonny D <jd@email.com>",
|
||||
];
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@ using Gtk 4.0;
|
|||
Label {
|
||||
styles [
|
||||
"class-1",
|
||||
"class-2"
|
||||
"class-2",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -46,3 +46,5 @@ class TestFormatter(unittest.TestCase):
|
|||
self.assert_format_test("in2.blp", "out.blp")
|
||||
self.assert_format_test("correct1.blp", "correct1.blp")
|
||||
self.assert_format_test("string_in.blp", "string_out.blp")
|
||||
self.assert_format_test("comment_in.blp", "comment_out.blp")
|
||||
self.assert_format_test("lists_in.blp", "lists_out.blp")
|
||||
|
|
|
@ -28,6 +28,7 @@ gi.require_version("Gtk", "4.0")
|
|||
from gi.repository import Gtk
|
||||
|
||||
from blueprintcompiler import decompiler, parser, tokenizer, utils
|
||||
from blueprintcompiler.ast_utils import AstNode
|
||||
from blueprintcompiler.completions import complete
|
||||
from blueprintcompiler.errors import (
|
||||
CompileError,
|
||||
|
@ -61,11 +62,14 @@ class TestSamples(unittest.TestCase):
|
|||
except:
|
||||
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)):
|
||||
ast.get_docs(i)
|
||||
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()
|
||||
|
||||
def assert_sample(self, name, skip_run=False):
|
||||
|
@ -194,8 +198,10 @@ class TestSamples(unittest.TestCase):
|
|||
"adw_breakpoint_template",
|
||||
"expr_closure",
|
||||
"expr_closure_args",
|
||||
"expr_value_closure",
|
||||
"parseable",
|
||||
"signal",
|
||||
"signal_not_swapped",
|
||||
"template",
|
||||
"template_binding",
|
||||
"template_binding_extern",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue