mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Compare commits
83 commits
Author | SHA1 | Date | |
---|---|---|---|
|
2e42dc6848 | ||
|
a12d3f5c81 | ||
|
a83c7e936d | ||
|
3816f4fe8d | ||
|
e9d61cb6f9 | ||
|
6a77bfee0a | ||
|
f50b898e4c | ||
|
cc09f3d3bb | ||
|
f93d5d2acd | ||
|
394014429e | ||
|
a4e0c3701b | ||
|
c1fbcef6d0 | ||
|
404ae76787 | ||
|
04ef0944db | ||
|
aa13c8f5af | ||
|
29e4a56bfc | ||
|
8c6f8760f7 | ||
|
b9f58aeab5 | ||
|
55e5095fba | ||
|
f3faf4b993 | ||
|
d6f4b88d35 | ||
|
a6d57cebec | ||
|
9b9fab832b | ||
|
5b0f662478 | ||
|
e07da3c339 | ||
|
2ae41020ab | ||
|
f48b840cfa | ||
|
ac70ea7403 | ||
|
778a979714 | ||
|
6acf0fe5a0 | ||
|
a42ec3a945 | ||
|
90308b69e0 | ||
|
3bf8fc151a | ||
|
a529a61955 | ||
|
e19975e1f8 | ||
|
b107a85947 | ||
|
e5fba8f3c7 | ||
|
f6d05be10b | ||
|
94b532bc35 | ||
|
c805400a39 | ||
|
3b6dcf072d | ||
|
d7097cad01 | ||
|
8e10fcf869 | ||
|
65d4612b51 | ||
|
21d5ce86e9 | ||
|
25d9826aea | ||
|
a12ac1b976 | ||
|
078ce2f5b8 | ||
|
4d3dc92448 | ||
|
3dfce3bbe0 | ||
|
b308adc3af | ||
|
22514b96dc | ||
|
c1a82a034b | ||
|
ea4c7245be | ||
|
2dcf0c154b | ||
|
9570dceaa8 | ||
|
8d734f7bbd | ||
|
8dfa10019b | ||
|
24eed1048e | ||
|
3a712af4dd | ||
|
d0659a43c2 | ||
|
b33cc7ccd7 | ||
|
adc2be1454 | ||
|
896dd7f824 | ||
|
b76f4eef50 | ||
|
8c102cf9dc | ||
|
a075b26769 | ||
|
da5b9909fc | ||
|
f1cf70b6eb | ||
|
85630bc975 | ||
|
e44494e6e2 | ||
|
6bae860326 | ||
|
aac834e1c5 | ||
|
442fff69b6 | ||
|
25d08e56cb | ||
|
07e824d8e7 | ||
|
c502dee36b | ||
|
988e69ab25 | ||
|
84e529a4a8 | ||
|
1c8d7daea2 | ||
|
6a078ee075 | ||
|
729939ad93 | ||
|
7e4f80523d |
227 changed files with 2924 additions and 614 deletions
|
@ -3,7 +3,7 @@ stages:
|
||||||
- pages
|
- pages
|
||||||
|
|
||||||
build:
|
build:
|
||||||
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
|
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- black --check --diff ./ tests
|
- black --check --diff ./ tests
|
||||||
|
@ -19,7 +19,7 @@ build:
|
||||||
- ninja -C _build docs/en
|
- ninja -C _build docs/en
|
||||||
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
|
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
|
||||||
- cd blueprint-regression-tests
|
- cd blueprint-regression-tests
|
||||||
- git checkout 71522af3607e08143671ee0d224e65e9b9eb9f30
|
- git checkout 5f9e155c1333e84e6f683cdb26b02a5925fd8db3
|
||||||
- ./test.sh
|
- ./test.sh
|
||||||
- cd ..
|
- cd ..
|
||||||
coverage: '/TOTAL.*\s([.\d]+)%/'
|
coverage: '/TOTAL.*\s([.\d]+)%/'
|
||||||
|
@ -33,7 +33,7 @@ build:
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
|
|
||||||
fuzz:
|
fuzz:
|
||||||
image: registry.gitlab.gnome.org/jwestman/blueprint-compiler
|
image: registry.gitlab.gnome.org/gnome/blueprint-compiler
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- meson _build
|
- meson _build
|
||||||
|
|
|
@ -8,7 +8,7 @@ in the NEWS file.
|
||||||
3. Make a new commit with just these two changes. Use `Release v{version}` as the commit message. Tag the commit as `v{version}` and push the tag.
|
3. Make a new commit with just these two changes. Use `Release v{version}` as the commit message. Tag the commit as `v{version}` and push the tag.
|
||||||
4. Create a "Post-release version bump" commit.
|
4. Create a "Post-release version bump" commit.
|
||||||
5. Go to the Releases page in GitLab and create a new release from the tag.
|
5. Go to the Releases page in GitLab and create a new release from the tag.
|
||||||
6. Announce the release through relevant channels (Twitter, TWIG, etc.)
|
6. Announce the release through relevant channels (Mastodon, TWIG, etc.)
|
||||||
|
|
||||||
## Related projects
|
## Related projects
|
||||||
|
|
||||||
|
|
73
NEWS.md
73
NEWS.md
|
@ -1,3 +1,76 @@
|
||||||
|
# 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
|
||||||
|
- Added a warning for unused imports.
|
||||||
|
- Added an option to not print the diff when formatting with the CLI. (Gregor Niehl)
|
||||||
|
- Added support for building Gtk.ColumnViewRow, Gtk.ColumnViewCell, and Gtk.ListHeader widgets with Gtk.BuilderListItemFactory.
|
||||||
|
- Added support for the `after` keyword for signals. This was previously documented but not implemented. (Gregor Niehl)
|
||||||
|
- Added support for string arrays. (Diego Augusto)
|
||||||
|
- Added hover documentation for properties in lookup expressions.
|
||||||
|
- The decompiler supports action widgets, translation domains, `typeof<>` syntax, and expressions. It also supports extension syntax for Adw.Breakpoint, Gtk.BuilderListItemFactory, Gtk.ComboBoxText, Gtk.SizeGroup, and Gtk.StringList.
|
||||||
|
- Added a `decompile` subcommand to the CLI, which decompiles an XML .ui file to blueprint.
|
||||||
|
- Accessibility relations that allow multiple values are supported using list syntax. (Julian Schmidhuber)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- The decompiler sorts imports alphabetically.
|
||||||
|
- Translatable strings use `translatable="yes"` instead of `translatable="true"` for compatibility with xgettext. (Marco Köpcke)
|
||||||
|
- The first line of the documentation is shown in the completion list when using the language server. (Sonny Piers)
|
||||||
|
- Object autocomplete uses a snippet to add the braces and position the cursor inside them. (Sonny Piers)
|
||||||
|
- The carets in the CLI diagnostic output now span the whole error message up to the end of the first line, rather than just the first character.
|
||||||
|
- The decompiler emits double quotes, which are compatible with gettext.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Fixed deprecation warnings in the language server.
|
||||||
|
- The decompiler no longer duplicates translator comments on properties.
|
||||||
|
- Subtemplates no longer output a redundant `@generated` comment.
|
||||||
|
- When extension syntax from a library that is not available is used, the compiler emits an error instead of crashing.
|
||||||
|
- The language server reports semantic token positions correctly. (Szepesi Tibor)
|
||||||
|
- The decompiler no longer emits the deprecated `bind-property` syntax. (Sonny Piers)
|
||||||
|
- Fixed the tests when used as a Meson subproject. (Benoit Pierre)
|
||||||
|
- Signal autocomplete generates correct syntax. (Sonny Piers)
|
||||||
|
- The decompiler supports templates that do not specify a parent class. (Sonny Piers)
|
||||||
|
- Adw.Breakpoint setters that set a property on the template no longer cause a crash.
|
||||||
|
- Fixed type checking with templates that do not have a parent class.
|
||||||
|
- Fixed online documentation links for interfaces.
|
||||||
|
- The wording of edit suggestions is fixed for insertions and deletions.
|
||||||
|
- When an input file uses tabs instead of spaces, the diagnostic output on the CLI aligns the caret correctly.
|
||||||
|
- The decompiler emits correct syntax when a property binding refers to the template object.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- Fixed typos in "Built with Blueprint" section. (Valéry Febvre, Dexter Reed)
|
||||||
|
|
||||||
# v0.12.0
|
# v0.12.0
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
27
blueprint-compiler.doap
Normal file
27
blueprint-compiler.doap
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
|
||||||
|
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||||
|
xmlns:gnome="http://api.gnome.org/doap-extensions#"
|
||||||
|
xmlns="http://usefulinc.com/ns/doap#">
|
||||||
|
|
||||||
|
<name xml:lang="en">Blueprint</name>
|
||||||
|
<shortdesc xml:lang="en">A modern language for creating GTK interfaces</shortdesc>
|
||||||
|
<description xml:lang="en">Blueprint is a language and associated tooling for building user interfaces for GTK.</description>
|
||||||
|
<category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
|
||||||
|
<programming-language>Python</programming-language>
|
||||||
|
|
||||||
|
<homepage
|
||||||
|
rdf:resource="https://gnome.gitlab.gnome.org/blueprint-compiler/" />
|
||||||
|
<download-page
|
||||||
|
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/-/releases" />
|
||||||
|
<bug-database
|
||||||
|
rdf:resource="https://gitlab.gnome.org/GNOME/blueprint-compiler/issues" />
|
||||||
|
|
||||||
|
<maintainer>
|
||||||
|
<foaf:Person>
|
||||||
|
<foaf:name>James Westman</foaf:name>
|
||||||
|
<foaf:mbox rdf:resource="mailto:james@jwestman.net" />
|
||||||
|
<gnome:userid>jwestman</gnome:userid>
|
||||||
|
</foaf:Person>
|
||||||
|
</maintainer>
|
||||||
|
</Project>
|
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",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
}
|
|
@ -125,7 +125,7 @@ class AstNode:
|
||||||
return self.parent.root
|
return self.parent.root
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def range(self):
|
def range(self) -> Range:
|
||||||
return Range(self.group.start, self.group.end, self.group.text)
|
return Range(self.group.start, self.group.end, self.group.text)
|
||||||
|
|
||||||
def parent_by_type(self, type: T.Type[TType]) -> TType:
|
def parent_by_type(self, type: T.Type[TType]) -> TType:
|
||||||
|
@ -160,6 +160,11 @@ class AstNode:
|
||||||
yield e
|
yield e
|
||||||
if e.fatal:
|
if e.fatal:
|
||||||
return
|
return
|
||||||
|
except MultipleErrors as e:
|
||||||
|
for error in e.errors:
|
||||||
|
yield error
|
||||||
|
if error.fatal:
|
||||||
|
return
|
||||||
|
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
yield from child._get_errors()
|
yield from child._get_errors()
|
||||||
|
@ -179,14 +184,16 @@ class AstNode:
|
||||||
token = self.group.tokens.get(attr.token_name)
|
token = self.group.tokens.get(attr.token_name)
|
||||||
if token and token.start <= idx < token.end:
|
if token and token.start <= idx < token.end:
|
||||||
return getattr(self, name)
|
return getattr(self, name)
|
||||||
else:
|
|
||||||
return getattr(self, name)
|
|
||||||
|
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
if idx in child.range:
|
if idx in child.range:
|
||||||
if docs := child.get_docs(idx):
|
if docs := child.get_docs(idx):
|
||||||
return docs
|
return docs
|
||||||
|
|
||||||
|
for name, attr in self._attrs_by_type(Docs):
|
||||||
|
if not attr.token_name:
|
||||||
|
return getattr(self, name)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
|
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
|
||||||
|
@ -247,14 +254,7 @@ def validate(
|
||||||
if skip_incomplete and self.incomplete:
|
if skip_incomplete and self.incomplete:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
def fill_error(e: CompileError):
|
||||||
func(self)
|
|
||||||
except CompileError as e:
|
|
||||||
# If the node is only partially complete, then an error must
|
|
||||||
# have already been reported at the parsing stage
|
|
||||||
if self.incomplete:
|
|
||||||
return
|
|
||||||
|
|
||||||
if e.range is None:
|
if e.range is None:
|
||||||
e.range = (
|
e.range = (
|
||||||
Range.join(
|
Range.join(
|
||||||
|
@ -264,8 +264,26 @@ def validate(
|
||||||
or self.range
|
or self.range
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
func(self)
|
||||||
|
except CompileError as e:
|
||||||
|
# If the node is only partially complete, then an error must
|
||||||
|
# have already been reported at the parsing stage
|
||||||
|
if self.incomplete:
|
||||||
|
return
|
||||||
|
|
||||||
|
fill_error(e)
|
||||||
|
|
||||||
# Re-raise the exception
|
# Re-raise the exception
|
||||||
raise e
|
raise e
|
||||||
|
except MultipleErrors as e:
|
||||||
|
if self.incomplete:
|
||||||
|
return
|
||||||
|
|
||||||
|
for error in e.errors:
|
||||||
|
fill_error(error)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
inner._validator = True
|
inner._validator = True
|
||||||
return inner
|
return inner
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from . import gir, language
|
from . import annotations, gir, language
|
||||||
from .ast_utils import AstNode
|
from .ast_utils import AstNode
|
||||||
from .completions_utils import *
|
from .completions_utils import *
|
||||||
from .language.types import ClassName
|
from .language.types import ClassName
|
||||||
|
@ -72,7 +72,9 @@ def complete(
|
||||||
|
|
||||||
@completer([language.GtkDirective])
|
@completer([language.GtkDirective])
|
||||||
def using_gtk(lsp, ast_node, match_variables):
|
def using_gtk(lsp, ast_node, match_variables):
|
||||||
yield Completion("using Gtk 4.0;", CompletionItemKind.Keyword)
|
yield Completion(
|
||||||
|
"using Gtk 4.0", CompletionItemKind.Keyword, snippet="using Gtk 4.0;\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
|
@ -101,7 +103,13 @@ def object_completer(lsp, ast_node, match_variables):
|
||||||
ns = ast_node.root.gir.namespaces.get(match_variables[0])
|
ns = ast_node.root.gir.namespaces.get(match_variables[0])
|
||||||
if ns is not None:
|
if ns is not None:
|
||||||
for c in ns.classes.values():
|
for c in ns.classes.values():
|
||||||
yield Completion(c.name, CompletionItemKind.Class, docs=c.doc)
|
yield Completion(
|
||||||
|
c.name,
|
||||||
|
CompletionItemKind.Class,
|
||||||
|
snippet=f"{c.name} {{\n $0\n}}",
|
||||||
|
docs=c.doc,
|
||||||
|
detail=c.detail,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
|
@ -112,7 +120,13 @@ def gtk_object_completer(lsp, ast_node, match_variables):
|
||||||
ns = ast_node.root.gir.namespaces.get("Gtk")
|
ns = ast_node.root.gir.namespaces.get("Gtk")
|
||||||
if ns is not None:
|
if ns is not None:
|
||||||
for c in ns.classes.values():
|
for c in ns.classes.values():
|
||||||
yield Completion(c.name, CompletionItemKind.Class, docs=c.doc)
|
yield Completion(
|
||||||
|
c.name,
|
||||||
|
CompletionItemKind.Class,
|
||||||
|
snippet=f"{c.name} {{\n $0\n}}",
|
||||||
|
docs=c.doc,
|
||||||
|
detail=c.detail,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
|
@ -120,7 +134,7 @@ def gtk_object_completer(lsp, ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def property_completer(lsp, ast_node, match_variables):
|
def property_completer(lsp, ast_node, match_variables):
|
||||||
if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
|
if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
|
||||||
for prop_name, prop in ast_node.gir_class.properties.items():
|
for prop_name, prop in ast_node.gir_class.properties.items():
|
||||||
if (
|
if (
|
||||||
isinstance(prop.type, gir.BoolType)
|
isinstance(prop.type, gir.BoolType)
|
||||||
|
@ -131,13 +145,23 @@ def property_completer(lsp, ast_node, match_variables):
|
||||||
CompletionItemKind.Property,
|
CompletionItemKind.Property,
|
||||||
sort_text=f"0 {prop_name}",
|
sort_text=f"0 {prop_name}",
|
||||||
snippet=f"{prop_name}: ${{1|true,false|}};",
|
snippet=f"{prop_name}: ${{1|true,false|}};",
|
||||||
|
docs=prop.doc,
|
||||||
|
detail=prop.detail,
|
||||||
)
|
)
|
||||||
elif isinstance(prop.type, gir.StringType):
|
elif isinstance(prop.type, gir.StringType):
|
||||||
|
snippet = (
|
||||||
|
f'{prop_name}: _("$0");'
|
||||||
|
if annotations.is_property_translated(prop)
|
||||||
|
else f'{prop_name}: "$0";'
|
||||||
|
)
|
||||||
|
|
||||||
yield Completion(
|
yield Completion(
|
||||||
prop_name,
|
prop_name,
|
||||||
CompletionItemKind.Property,
|
CompletionItemKind.Property,
|
||||||
sort_text=f"0 {prop_name}",
|
sort_text=f"0 {prop_name}",
|
||||||
snippet=f'{prop_name}: "$0";',
|
snippet=snippet,
|
||||||
|
docs=prop.doc,
|
||||||
|
detail=prop.detail,
|
||||||
)
|
)
|
||||||
elif (
|
elif (
|
||||||
isinstance(prop.type, gir.Enumeration)
|
isinstance(prop.type, gir.Enumeration)
|
||||||
|
@ -150,6 +174,17 @@ def property_completer(lsp, ast_node, match_variables):
|
||||||
CompletionItemKind.Property,
|
CompletionItemKind.Property,
|
||||||
sort_text=f"0 {prop_name}",
|
sort_text=f"0 {prop_name}",
|
||||||
snippet=f"{prop_name}: ${{1|{choices}|}};",
|
snippet=f"{prop_name}: ${{1|{choices}|}};",
|
||||||
|
docs=prop.doc,
|
||||||
|
detail=prop.detail,
|
||||||
|
)
|
||||||
|
elif prop.type.full_name == "Gtk.Expression":
|
||||||
|
yield Completion(
|
||||||
|
prop_name,
|
||||||
|
CompletionItemKind.Property,
|
||||||
|
sort_text=f"0 {prop_name}",
|
||||||
|
snippet=f"{prop_name}: expr $0;",
|
||||||
|
docs=prop.doc,
|
||||||
|
detail=prop.detail,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
yield Completion(
|
yield Completion(
|
||||||
|
@ -157,18 +192,25 @@ def property_completer(lsp, ast_node, match_variables):
|
||||||
CompletionItemKind.Property,
|
CompletionItemKind.Property,
|
||||||
sort_text=f"0 {prop_name}",
|
sort_text=f"0 {prop_name}",
|
||||||
snippet=f"{prop_name}: $0;",
|
snippet=f"{prop_name}: $0;",
|
||||||
|
docs=prop.doc,
|
||||||
|
detail=prop.detail,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[language.Property, language.BaseAttribute],
|
applies_in=[language.Property, language.A11yProperty],
|
||||||
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
|
matches=[[(TokenType.IDENT, None), (TokenType.OP, ":")]],
|
||||||
)
|
)
|
||||||
def prop_value_completer(lsp, ast_node, match_variables):
|
def prop_value_completer(lsp, ast_node, match_variables):
|
||||||
if (vt := ast_node.value_type) is not None:
|
if (vt := ast_node.value_type) is not None:
|
||||||
if isinstance(vt.value_type, gir.Enumeration):
|
if isinstance(vt.value_type, gir.Enumeration):
|
||||||
for name, member in vt.value_type.members.items():
|
for name, member in vt.value_type.members.items():
|
||||||
yield Completion(name, CompletionItemKind.EnumMember, docs=member.doc)
|
yield Completion(
|
||||||
|
name,
|
||||||
|
CompletionItemKind.EnumMember,
|
||||||
|
docs=member.doc,
|
||||||
|
detail=member.detail,
|
||||||
|
)
|
||||||
|
|
||||||
elif isinstance(vt.value_type, gir.BoolType):
|
elif isinstance(vt.value_type, gir.BoolType):
|
||||||
yield Completion("true", CompletionItemKind.Constant)
|
yield Completion("true", CompletionItemKind.Constant)
|
||||||
|
@ -180,8 +222,8 @@ def prop_value_completer(lsp, ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def signal_completer(lsp, ast_node, match_variables):
|
def signal_completer(lsp, ast_node, match_variables):
|
||||||
if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType):
|
if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
|
||||||
for signal in ast_node.gir_class.signals:
|
for signal_name, signal in ast_node.gir_class.signals.items():
|
||||||
if not isinstance(ast_node.parent, language.Object):
|
if not isinstance(ast_node.parent, language.Object):
|
||||||
name = "on"
|
name = "on"
|
||||||
else:
|
else:
|
||||||
|
@ -192,10 +234,12 @@ def signal_completer(lsp, ast_node, match_variables):
|
||||||
.lower()
|
.lower()
|
||||||
)
|
)
|
||||||
yield Completion(
|
yield Completion(
|
||||||
signal,
|
signal_name,
|
||||||
CompletionItemKind.Event,
|
CompletionItemKind.Event,
|
||||||
sort_text=f"1 {signal}",
|
sort_text=f"1 {signal_name}",
|
||||||
snippet=f"{signal} => \$${{1:{name}_{signal.replace('-', '_')}}}()$0;",
|
snippet=f"{signal_name} => \\$${{1:${name}_{signal_name.replace('-', '_')}}}()$0;",
|
||||||
|
docs=signal.doc,
|
||||||
|
detail=signal.detail,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,17 +31,6 @@ new_statement_patterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def applies_to(*ast_types):
|
|
||||||
"""Decorator describing which AST nodes the completer should apply in."""
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
for c in ast_types:
|
|
||||||
c.completers.append(func)
|
|
||||||
return func
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
|
def completer(applies_in: T.List, matches: T.List = [], applies_in_subclass=None):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
def inner(prev_tokens: T.List[Token], ast_node, lsp):
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
import re
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ from .xml_reader import Element, parse, parse_string
|
||||||
__all__ = ["decompile"]
|
__all__ = ["decompile"]
|
||||||
|
|
||||||
|
|
||||||
_DECOMPILERS: T.Dict = {}
|
_DECOMPILERS: dict[str, list] = defaultdict(list)
|
||||||
_CLOSING = {
|
_CLOSING = {
|
||||||
"{": "}",
|
"{": "}",
|
||||||
"[": "]",
|
"[": "]",
|
||||||
|
@ -51,27 +51,32 @@ class LineType(Enum):
|
||||||
|
|
||||||
|
|
||||||
class DecompileCtx:
|
class DecompileCtx:
|
||||||
def __init__(self) -> None:
|
def __init__(self, parent_gir: T.Optional[GirContext] = None) -> None:
|
||||||
|
self.sub_decompiler = parent_gir is not None
|
||||||
self._result: str = ""
|
self._result: str = ""
|
||||||
self.gir = GirContext()
|
self.gir = parent_gir or GirContext()
|
||||||
self._indent: int = 0
|
|
||||||
self._blocks_need_end: T.List[str] = []
|
self._blocks_need_end: T.List[str] = []
|
||||||
self._last_line_type: LineType = LineType.NONE
|
self._last_line_type: LineType = LineType.NONE
|
||||||
self.template_class: T.Optional[str] = None
|
self._obj_type_stack: list[T.Optional[GirType]] = []
|
||||||
|
self._node_stack: list[Element] = []
|
||||||
|
|
||||||
self.gir.add_namespace(get_namespace("Gtk", "4.0"))
|
self.gir.add_namespace(get_namespace("Gtk", "4.0"))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def result(self) -> str:
|
def result(self) -> str:
|
||||||
imports = "\n".join(
|
imports = ""
|
||||||
|
|
||||||
|
if not self.sub_decompiler:
|
||||||
|
import_lines = sorted(
|
||||||
[
|
[
|
||||||
f"using {ns} {namespace.version};"
|
f"using {ns} {namespace.version};"
|
||||||
for ns, namespace in self.gir.namespaces.items()
|
for ns, namespace in self.gir.namespaces.items()
|
||||||
|
if ns != "Gtk"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
full_string = imports + "\n" + self._result
|
imports += "\n".join(["using Gtk 4.0;", *import_lines])
|
||||||
formatted_string = formatter.format(full_string)
|
|
||||||
return formatted_string
|
return formatter.format(imports + self._result)
|
||||||
|
|
||||||
def type_by_cname(self, cname: str) -> T.Optional[GirType]:
|
def type_by_cname(self, cname: str) -> T.Optional[GirType]:
|
||||||
if type := self.gir.get_type_by_cname(cname):
|
if type := self.gir.get_type_by_cname(cname):
|
||||||
|
@ -90,10 +95,66 @@ class DecompileCtx:
|
||||||
|
|
||||||
def start_block(self) -> None:
|
def start_block(self) -> None:
|
||||||
self._blocks_need_end.append("")
|
self._blocks_need_end.append("")
|
||||||
|
self._obj_type_stack.append(None)
|
||||||
|
|
||||||
def end_block(self) -> None:
|
def end_block(self) -> None:
|
||||||
if close := self._blocks_need_end.pop():
|
if close := self._blocks_need_end.pop():
|
||||||
self.print(close)
|
self.print(close)
|
||||||
|
self._obj_type_stack.pop()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_obj_type(self) -> T.Optional[GirType]:
|
||||||
|
return next((x for x in reversed(self._obj_type_stack) if x is not None), None)
|
||||||
|
|
||||||
|
def push_obj_type(self, type: T.Optional[GirType]) -> None:
|
||||||
|
self._obj_type_stack[-1] = type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_node(self) -> T.Optional[Element]:
|
||||||
|
if len(self._node_stack) == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._node_stack[-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_node(self) -> T.Optional[Element]:
|
||||||
|
if len(self._node_stack) < 2:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._node_stack[-2]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root_node(self) -> T.Optional[Element]:
|
||||||
|
if len(self._node_stack) == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._node_stack[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def template_class(self) -> T.Optional[str]:
|
||||||
|
assert self.root_node is not None
|
||||||
|
for child in self.root_node.children:
|
||||||
|
if child.tag == "template":
|
||||||
|
return child["class"]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_object(self, id: str) -> T.Optional[Element]:
|
||||||
|
assert self.root_node is not None
|
||||||
|
for child in self.root_node.children:
|
||||||
|
if child.tag == "template" and child["class"] == id:
|
||||||
|
return child
|
||||||
|
|
||||||
|
def find_in_children(node: Element) -> T.Optional[Element]:
|
||||||
|
if node.tag in ["object", "menu"] and node["id"] == id:
|
||||||
|
return node
|
||||||
|
else:
|
||||||
|
for child in node.children:
|
||||||
|
if result := find_in_children(child):
|
||||||
|
return result
|
||||||
|
return None
|
||||||
|
|
||||||
|
return find_in_children(self.root_node)
|
||||||
|
|
||||||
def end_block_with(self, text: str) -> None:
|
def end_block_with(self, text: str) -> None:
|
||||||
self._blocks_need_end[-1] = text
|
self._blocks_need_end[-1] = text
|
||||||
|
@ -105,7 +166,15 @@ class DecompileCtx:
|
||||||
if len(self._blocks_need_end):
|
if len(self._blocks_need_end):
|
||||||
self._blocks_need_end[-1] = _CLOSING[line[-1]]
|
self._blocks_need_end[-1] = _CLOSING[line[-1]]
|
||||||
|
|
||||||
def print_attribute(self, name: str, value: str, type: GirType) -> None:
|
# Converts a value from an XML element to a blueprint string
|
||||||
|
# based on the given type. Returns a tuple of translator comments
|
||||||
|
# (if any) and the decompiled syntax.
|
||||||
|
def decompile_value(
|
||||||
|
self,
|
||||||
|
value: str,
|
||||||
|
type: T.Optional[GirType],
|
||||||
|
translatable: T.Optional[T.Tuple[str, str, str]] = None,
|
||||||
|
) -> T.Tuple[str, str]:
|
||||||
def get_enum_name(value):
|
def get_enum_name(value):
|
||||||
for member in type.members.values():
|
for member in type.members.values():
|
||||||
if (
|
if (
|
||||||
|
@ -116,13 +185,18 @@ class DecompileCtx:
|
||||||
return member.name
|
return member.name
|
||||||
return value.replace("-", "_")
|
return value.replace("-", "_")
|
||||||
|
|
||||||
if type is None:
|
if translatable is not None and truthy(translatable[0]):
|
||||||
self.print(f"{name}: {escape_quote(value)};")
|
return decompile_translatable(value, *translatable)
|
||||||
|
elif type is None:
|
||||||
|
return "", f"{escape_quote(value)}"
|
||||||
elif type.assignable_to(FloatType()):
|
elif type.assignable_to(FloatType()):
|
||||||
self.print(f"{name}: {value};")
|
return "", str(value)
|
||||||
elif type.assignable_to(BoolType()):
|
elif type.assignable_to(BoolType()):
|
||||||
val = truthy(value)
|
val = truthy(value)
|
||||||
self.print(f"{name}: {'true' if val else 'false'};")
|
return "", ("true" if val else "false")
|
||||||
|
elif type.assignable_to(ArrayType(StringType())):
|
||||||
|
items = ", ".join([escape_quote(x) for x in value.split("\n")])
|
||||||
|
return "", f"[{items}]"
|
||||||
elif (
|
elif (
|
||||||
type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Pixbuf"))
|
type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Pixbuf"))
|
||||||
or type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Texture"))
|
or type.assignable_to(self.gir.namespaces["Gtk"].lookup_type("Gdk.Texture"))
|
||||||
|
@ -136,67 +210,82 @@ class DecompileCtx:
|
||||||
self.gir.namespaces["Gtk"].lookup_type("Gtk.ShortcutTrigger")
|
self.gir.namespaces["Gtk"].lookup_type("Gtk.ShortcutTrigger")
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
self.print(f"{name}: {escape_quote(value)};")
|
return "", escape_quote(value)
|
||||||
elif value == self.template_class:
|
elif value == self.template_class:
|
||||||
self.print(f"{name}: template;")
|
return "", "template"
|
||||||
elif type.assignable_to(
|
elif type.assignable_to(
|
||||||
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
|
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
|
||||||
):
|
) or isinstance(type, Interface):
|
||||||
self.print(f"{name}: {value};")
|
return "", ("null" if value == "" else value)
|
||||||
elif isinstance(type, Bitfield):
|
elif isinstance(type, Bitfield):
|
||||||
flags = [get_enum_name(flag) for flag in value.split("|")]
|
flags = [get_enum_name(flag) for flag in value.split("|")]
|
||||||
self.print(f"{name}: {' | '.join(flags)};")
|
return "", " | ".join(flags)
|
||||||
elif isinstance(type, Enumeration):
|
elif isinstance(type, Enumeration):
|
||||||
self.print(f"{name}: {get_enum_name(value)};")
|
return "", get_enum_name(value)
|
||||||
|
elif isinstance(type, TypeType):
|
||||||
|
if t := self.type_by_cname(value):
|
||||||
|
return "", f"typeof<{full_name(t)}>"
|
||||||
else:
|
else:
|
||||||
self.print(f"{name}: {escape_quote(value)};")
|
return "", f"typeof<${value}>"
|
||||||
|
else:
|
||||||
|
return "", escape_quote(value)
|
||||||
|
|
||||||
|
|
||||||
def _decompile_element(
|
def decompile_element(
|
||||||
ctx: DecompileCtx, gir: T.Optional[GirContext], xml: Element
|
ctx: DecompileCtx, gir: T.Optional[GirContext], xml: Element
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
decompiler = _DECOMPILERS.get(xml.tag)
|
decompilers = [d for d in _DECOMPILERS[xml.tag] if d._filter(ctx)]
|
||||||
if decompiler is None:
|
if len(decompilers) == 0:
|
||||||
raise UnsupportedError(f"unsupported XML tag: <{xml.tag}>")
|
raise UnsupportedError(f"unsupported XML tag: <{xml.tag}>")
|
||||||
|
|
||||||
args: T.Dict[str, T.Optional[str]] = {
|
decompiler = decompilers[0]
|
||||||
canon(name): value for name, value in xml.attrs.items()
|
|
||||||
}
|
if decompiler._element:
|
||||||
|
args = [ctx, gir, xml]
|
||||||
|
kwargs: T.Dict[str, T.Optional[str]] = {}
|
||||||
|
else:
|
||||||
|
args = [ctx, gir]
|
||||||
|
kwargs = {canon(name): value for name, value in xml.attrs.items()}
|
||||||
if decompiler._cdata:
|
if decompiler._cdata:
|
||||||
if len(xml.children):
|
if len(xml.children):
|
||||||
args["cdata"] = None
|
kwargs["cdata"] = None
|
||||||
else:
|
else:
|
||||||
args["cdata"] = xml.cdata
|
kwargs["cdata"] = xml.cdata
|
||||||
|
|
||||||
|
ctx._node_stack.append(xml)
|
||||||
ctx.start_block()
|
ctx.start_block()
|
||||||
gir = decompiler(ctx, gir, **args)
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
gir = decompiler(*args, **kwargs)
|
||||||
|
except TypeError as e:
|
||||||
|
raise UnsupportedError(tag=xml.tag)
|
||||||
|
|
||||||
|
if not decompiler._skip_children:
|
||||||
for child in xml.children:
|
for child in xml.children:
|
||||||
_decompile_element(ctx, gir, child)
|
decompile_element(ctx, gir, child)
|
||||||
|
|
||||||
ctx.end_block()
|
ctx.end_block()
|
||||||
|
ctx._node_stack.pop()
|
||||||
|
|
||||||
except UnsupportedError as e:
|
except UnsupportedError as e:
|
||||||
raise e
|
raise e
|
||||||
except TypeError as e:
|
|
||||||
raise UnsupportedError(tag=xml.tag)
|
|
||||||
|
|
||||||
|
|
||||||
def decompile(data: str) -> str:
|
def decompile(data: str) -> str:
|
||||||
ctx = DecompileCtx()
|
ctx = DecompileCtx()
|
||||||
|
|
||||||
xml = parse(data)
|
xml = parse(data)
|
||||||
_decompile_element(ctx, None, xml)
|
decompile_element(ctx, None, xml)
|
||||||
|
|
||||||
return ctx.result
|
return ctx.result
|
||||||
|
|
||||||
|
|
||||||
def decompile_string(data):
|
def decompile_string(data: str) -> str:
|
||||||
ctx = DecompileCtx()
|
ctx = DecompileCtx()
|
||||||
|
|
||||||
xml = parse_string(data)
|
xml = parse_string(data)
|
||||||
_decompile_element(ctx, None, xml)
|
decompile_element(ctx, None, xml)
|
||||||
|
|
||||||
return ctx.result
|
return ctx.result
|
||||||
|
|
||||||
|
@ -209,10 +298,10 @@ def canon(string: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def truthy(string: str) -> bool:
|
def truthy(string: str) -> bool:
|
||||||
return string.lower() in ["yes", "true", "t", "y", "1"]
|
return string is not None and string.lower() in ["yes", "true", "t", "y", "1"]
|
||||||
|
|
||||||
|
|
||||||
def full_name(gir) -> str:
|
def full_name(gir: GirType) -> str:
|
||||||
return gir.name if gir.full_name.startswith("Gtk.") else gir.full_name
|
return gir.name if gir.full_name.startswith("Gtk.") else gir.full_name
|
||||||
|
|
||||||
|
|
||||||
|
@ -223,17 +312,45 @@ def lookup_by_cname(gir, cname: str) -> T.Optional[GirType]:
|
||||||
return gir.get_containing(Repository).get_type_by_cname(cname)
|
return gir.get_containing(Repository).get_type_by_cname(cname)
|
||||||
|
|
||||||
|
|
||||||
def decompiler(tag, cdata=False):
|
def decompiler(
|
||||||
|
tag,
|
||||||
|
cdata=False,
|
||||||
|
parent_type: T.Optional[str] = None,
|
||||||
|
parent_tag: T.Optional[str] = None,
|
||||||
|
skip_children=False,
|
||||||
|
element=False,
|
||||||
|
):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func._cdata = cdata
|
func._cdata = cdata
|
||||||
_DECOMPILERS[tag] = func
|
func._skip_children = skip_children
|
||||||
|
func._element = element
|
||||||
|
|
||||||
|
def filter(ctx):
|
||||||
|
if parent_type is not None:
|
||||||
|
if (
|
||||||
|
ctx.current_obj_type is None
|
||||||
|
or ctx.current_obj_type.full_name != parent_type
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if parent_tag is not None:
|
||||||
|
if not any(x.tag == parent_tag for x in ctx._node_stack):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
func._filter = filter
|
||||||
|
|
||||||
|
_DECOMPILERS[tag].append(func)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@decompiler("interface")
|
@decompiler("interface")
|
||||||
def decompile_interface(ctx, gir):
|
def decompile_interface(ctx, gir, domain=None):
|
||||||
|
if domain is not None:
|
||||||
|
ctx.print(f"translation-domain {escape_quote(domain)};")
|
||||||
return gir
|
return gir
|
||||||
|
|
||||||
|
|
||||||
|
@ -252,9 +369,11 @@ def decompile_translatable(
|
||||||
translatable: T.Optional[str],
|
translatable: T.Optional[str],
|
||||||
context: T.Optional[str],
|
context: T.Optional[str],
|
||||||
comments: T.Optional[str],
|
comments: T.Optional[str],
|
||||||
) -> T.Tuple[T.Optional[str], str]:
|
) -> T.Tuple[str, str]:
|
||||||
if translatable is not None and truthy(translatable):
|
if translatable is not None and truthy(translatable):
|
||||||
if comments is not None:
|
if comments is None:
|
||||||
|
comments = ""
|
||||||
|
else:
|
||||||
comments = comments.replace("/*", " ").replace("*/", " ")
|
comments = comments.replace("/*", " ").replace("*/", " ")
|
||||||
comments = f"/* Translators: {comments} */"
|
comments = f"/* Translators: {comments} */"
|
||||||
|
|
||||||
|
@ -263,7 +382,7 @@ def decompile_translatable(
|
||||||
else:
|
else:
|
||||||
return comments, f"_({escape_quote(string)})"
|
return comments, f"_({escape_quote(string)})"
|
||||||
else:
|
else:
|
||||||
return comments, f"{escape_quote(string)}"
|
return "", f"{escape_quote(string)}"
|
||||||
|
|
||||||
|
|
||||||
@decompiler("property", cdata=True)
|
@decompiler("property", cdata=True)
|
||||||
|
@ -280,11 +399,8 @@ def decompile_property(
|
||||||
context=None,
|
context=None,
|
||||||
):
|
):
|
||||||
name = name.replace("_", "-")
|
name = name.replace("_", "-")
|
||||||
if comments is not None:
|
|
||||||
ctx.print(f"/* Translators: {comments} */")
|
|
||||||
|
|
||||||
if cdata is None:
|
if cdata is None:
|
||||||
ctx.print(f"{name}: ", False)
|
ctx.print(f"{name}: ")
|
||||||
ctx.end_block_with(";")
|
ctx.end_block_with(";")
|
||||||
elif bind_source:
|
elif bind_source:
|
||||||
flags = ""
|
flags = ""
|
||||||
|
@ -295,7 +411,11 @@ def decompile_property(
|
||||||
flags += " inverted"
|
flags += " inverted"
|
||||||
if "bidirectional" in bind_flags:
|
if "bidirectional" in bind_flags:
|
||||||
flags += " bidirectional"
|
flags += " bidirectional"
|
||||||
ctx.print(f"{name}: bind-property {bind_source}.{bind_property}{flags};")
|
|
||||||
|
if bind_source == ctx.template_class:
|
||||||
|
bind_source = "template"
|
||||||
|
|
||||||
|
ctx.print(f"{name}: bind {bind_source}.{bind_property}{flags};")
|
||||||
elif truthy(translatable):
|
elif truthy(translatable):
|
||||||
comments, translatable = decompile_translatable(
|
comments, translatable = decompile_translatable(
|
||||||
cdata, translatable, context, comments
|
cdata, translatable, context, comments
|
||||||
|
@ -305,8 +425,19 @@ def decompile_property(
|
||||||
ctx.print(f"{name}: {translatable};")
|
ctx.print(f"{name}: {translatable};")
|
||||||
elif gir is None or gir.properties.get(name) is None:
|
elif gir is None or gir.properties.get(name) is None:
|
||||||
ctx.print(f"{name}: {escape_quote(cdata)};")
|
ctx.print(f"{name}: {escape_quote(cdata)};")
|
||||||
|
elif (
|
||||||
|
gir.assignable_to(ctx.gir.get_class("BuilderListItemFactory", "Gtk"))
|
||||||
|
and name == "bytes"
|
||||||
|
):
|
||||||
|
sub_ctx = DecompileCtx(ctx.gir)
|
||||||
|
|
||||||
|
xml = parse_string(cdata)
|
||||||
|
decompile_element(sub_ctx, None, xml)
|
||||||
|
|
||||||
|
ctx.print(sub_ctx.result)
|
||||||
else:
|
else:
|
||||||
ctx.print_attribute(name, cdata, gir.properties.get(name).type)
|
_, string = ctx.decompile_value(cdata, gir.properties.get(name).type)
|
||||||
|
ctx.print(f"{name}: {string};")
|
||||||
return gir
|
return gir
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -93,15 +93,28 @@ class CompileError(PrintableError):
|
||||||
assert self.range is not None
|
assert self.range is not None
|
||||||
|
|
||||||
line_num, col_num = utils.idx_to_pos(self.range.start + 1, code)
|
line_num, col_num = utils.idx_to_pos(self.range.start + 1, code)
|
||||||
|
end_line_num, end_col_num = utils.idx_to_pos(self.range.end + 1, code)
|
||||||
line = code.splitlines(True)[line_num] if code != "" else ""
|
line = code.splitlines(True)[line_num] if code != "" else ""
|
||||||
|
|
||||||
# Display 1-based line numbers
|
# Display 1-based line numbers
|
||||||
line_num += 1
|
line_num += 1
|
||||||
|
end_line_num += 1
|
||||||
|
|
||||||
|
n_spaces = col_num - 1
|
||||||
|
n_carets = (
|
||||||
|
(end_col_num - col_num)
|
||||||
|
if line_num == end_line_num
|
||||||
|
else (len(line) - n_spaces - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
n_spaces += line.count("\t", 0, col_num)
|
||||||
|
n_carets += line.count("\t", col_num, col_num + n_carets)
|
||||||
|
line = line.replace("\t", " ")
|
||||||
|
|
||||||
stream.write(
|
stream.write(
|
||||||
f"""{self.color}{Colors.BOLD}{self.category}: {self.message}{Colors.CLEAR}
|
f"""{self.color}{Colors.BOLD}{self.category}: {self.message}{Colors.CLEAR}
|
||||||
at {filename} line {line_num} column {col_num}:
|
at {filename} line {line_num} column {col_num}:
|
||||||
{Colors.FAINT}{line_num :>4} |{Colors.CLEAR}{line.rstrip()}\n {Colors.FAINT}|{" "*(col_num-1)}^{Colors.CLEAR}\n"""
|
{Colors.FAINT}{line_num :>4} |{Colors.CLEAR}{line.rstrip()}\n {Colors.FAINT}|{" "*n_spaces}{"^"*n_carets}{Colors.CLEAR}\n"""
|
||||||
)
|
)
|
||||||
|
|
||||||
for hint in self.hints:
|
for hint in self.hints:
|
||||||
|
@ -113,6 +126,14 @@ at {filename} line {line_num} column {col_num}:
|
||||||
if action.edit_range is not None
|
if action.edit_range is not None
|
||||||
else self.range.text
|
else self.range.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if old == "":
|
||||||
|
stream.write(
|
||||||
|
f"suggestion: insert {Colors.GREEN}{action.replace_with}{Colors.CLEAR}\n"
|
||||||
|
)
|
||||||
|
elif action.replace_with == "":
|
||||||
|
stream.write(f"suggestion: remove {Colors.RED}{old}{Colors.CLEAR}\n")
|
||||||
|
else:
|
||||||
stream.write(
|
stream.write(
|
||||||
f"suggestion: replace {Colors.RED}{old}{Colors.CLEAR} with {Colors.GREEN}{action.replace_with}{Colors.CLEAR}\n"
|
f"suggestion: replace {Colors.RED}{old}{Colors.CLEAR} with {Colors.GREEN}{action.replace_with}{Colors.CLEAR}\n"
|
||||||
)
|
)
|
||||||
|
@ -140,6 +161,10 @@ class DeprecatedWarning(CompileWarning):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnusedWarning(CompileWarning):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UpgradeWarning(CompileWarning):
|
class UpgradeWarning(CompileWarning):
|
||||||
category = "upgrade"
|
category = "upgrade"
|
||||||
color = Colors.PURPLE
|
color = Colors.PURPLE
|
||||||
|
@ -194,7 +219,7 @@ def report_bug(): # pragma: no cover
|
||||||
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
|
f"""{Colors.BOLD}{Colors.RED}***** COMPILER BUG *****
|
||||||
The blueprint-compiler program has crashed. Please report the above stacktrace,
|
The blueprint-compiler program has crashed. Please report the above stacktrace,
|
||||||
along with the input file(s) if possible, on GitLab:
|
along with the input file(s) if possible, on GitLab:
|
||||||
{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue
|
{Colors.BOLD}{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue
|
||||||
{Colors.CLEAR}"""
|
{Colors.CLEAR}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
import re
|
import re
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from . import tokenizer, utils
|
from . import tokenizer
|
||||||
|
from .errors import CompilerBugError
|
||||||
from .tokenizer import TokenType
|
from .tokenizer import TokenType
|
||||||
|
|
||||||
OPENING_TOKENS = ("{", "[")
|
OPENING_TOKENS = ("{", "[")
|
||||||
|
@ -145,8 +146,10 @@ def format(data, tab_size=2, insert_space=True):
|
||||||
is_child_type = False
|
is_child_type = False
|
||||||
|
|
||||||
elif str_item in CLOSING_TOKENS:
|
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]
|
current_line = current_line[:-1]
|
||||||
|
if str(last_not_whitespace) != ",":
|
||||||
|
current_line += ","
|
||||||
commit_current_line()
|
commit_current_line()
|
||||||
current_line = "]"
|
current_line = "]"
|
||||||
elif str(last_not_whitespace) in OPENING_TOKENS:
|
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:
|
elif prev_line_type in require_extra_newline:
|
||||||
newlines = 2
|
newlines = 2
|
||||||
|
|
||||||
|
current_line = "\n".join(
|
||||||
|
[line.rstrip() for line in current_line.split("\n")]
|
||||||
|
)
|
||||||
commit_current_line(LineType.COMMENT, newlines_before=newlines)
|
commit_current_line(LineType.COMMENT, newlines_before=newlines)
|
||||||
|
|
||||||
else:
|
else: # pragma: no cover
|
||||||
commit_current_line()
|
raise CompilerBugError()
|
||||||
|
|
||||||
elif str_item == "(" and (
|
elif str_item == "(" and (
|
||||||
re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
|
re.match(r"^([A-Za-z_\-])+\s*\(", current_line) or watch_parentheses
|
||||||
|
|
|
@ -24,8 +24,20 @@ from functools import cached_property
|
||||||
|
|
||||||
import gi # type: ignore
|
import gi # type: ignore
|
||||||
|
|
||||||
gi.require_version("GIRepository", "2.0")
|
try:
|
||||||
from gi.repository import GIRepository # type: ignore
|
gi.require_version("GIRepository", "3.0")
|
||||||
|
from gi.repository import GIRepository # type: ignore
|
||||||
|
|
||||||
|
_repo = GIRepository.Repository()
|
||||||
|
except ValueError:
|
||||||
|
# We can remove this once we can bump the minimum dependencies
|
||||||
|
# to glib 2.80 and pygobject 3.52
|
||||||
|
# dependency('glib-2.0', version: '>= 2.80.0')
|
||||||
|
# dependency('girepository-2.0', version: '>= 2.80.0')
|
||||||
|
gi.require_version("GIRepository", "2.0")
|
||||||
|
from gi.repository import GIRepository # type: ignore
|
||||||
|
|
||||||
|
_repo = GIRepository.Repository
|
||||||
|
|
||||||
from . import typelib, xml_reader
|
from . import typelib, xml_reader
|
||||||
from .errors import CompileError, CompilerBugError
|
from .errors import CompileError, CompilerBugError
|
||||||
|
@ -42,7 +54,7 @@ def add_typelib_search_path(path: str):
|
||||||
|
|
||||||
|
|
||||||
def get_namespace(namespace: str, version: str) -> "Namespace":
|
def get_namespace(namespace: str, version: str) -> "Namespace":
|
||||||
search_paths = [*GIRepository.Repository.get_search_path(), *_user_search_paths]
|
search_paths = [*_repo.get_search_path(), *_user_search_paths]
|
||||||
|
|
||||||
filename = f"{namespace}-{version}.typelib"
|
filename = f"{namespace}-{version}.typelib"
|
||||||
|
|
||||||
|
@ -74,7 +86,7 @@ def get_available_namespaces() -> T.List[T.Tuple[str, str]]:
|
||||||
return _available_namespaces
|
return _available_namespaces
|
||||||
|
|
||||||
search_paths: list[str] = [
|
search_paths: list[str] = [
|
||||||
*GIRepository.Repository.get_search_path(),
|
*_repo.get_search_path(),
|
||||||
*_user_search_paths,
|
*_user_search_paths,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -200,6 +212,10 @@ class ArrayType(GirType):
|
||||||
def assignable_to(self, other: GirType) -> bool:
|
def assignable_to(self, other: GirType) -> bool:
|
||||||
return isinstance(other, ArrayType) and self._inner.assignable_to(other._inner)
|
return isinstance(other, ArrayType) and self._inner.assignable_to(other._inner)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inner(self) -> GirType:
|
||||||
|
return self._inner
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self._inner.name + "[]"
|
return self._inner.name + "[]"
|
||||||
|
@ -332,6 +348,17 @@ class GirNode:
|
||||||
def available_in(self) -> str:
|
def available_in(self) -> str:
|
||||||
return self.xml.get("version")
|
return self.xml.get("version")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def detail(self) -> T.Optional[str]:
|
||||||
|
try:
|
||||||
|
el = self.xml.get_elements("doc")
|
||||||
|
if len(el) == 1:
|
||||||
|
return el[0].cdata.strip().partition("\n")[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def doc(self) -> T.Optional[str]:
|
def doc(self) -> T.Optional[str]:
|
||||||
sections = []
|
sections = []
|
||||||
|
@ -440,7 +467,10 @@ class Signature(GirNode):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def return_type(self) -> GirType:
|
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(
|
return self.get_containing(Repository)._resolve_type_id(
|
||||||
self.tl.SIGNATURE_RETURN_TYPE
|
self.tl.SIGNATURE_RETURN_TYPE
|
||||||
)
|
)
|
||||||
|
@ -463,7 +493,10 @@ class Signal(GirNode):
|
||||||
args = ", ".join(
|
args = ", ".join(
|
||||||
[f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
|
[f"{a.type.full_name} {a.name}" for a in self.gir_signature.args]
|
||||||
)
|
)
|
||||||
return f"signal {self.container.full_name}::{self.name} ({args})"
|
result = f"signal {self.container.full_name}::{self.name} ({args})"
|
||||||
|
if self.gir_signature.return_type is not None:
|
||||||
|
result += f" -> {self.gir_signature.return_type.full_name}"
|
||||||
|
return result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online_docs(self) -> T.Optional[str]:
|
def online_docs(self) -> T.Optional[str]:
|
||||||
|
@ -534,7 +567,7 @@ class Interface(GirNode, GirType):
|
||||||
@property
|
@property
|
||||||
def online_docs(self) -> T.Optional[str]:
|
def online_docs(self) -> T.Optional[str]:
|
||||||
if ns := self.get_containing(Namespace).online_docs:
|
if ns := self.get_containing(Namespace).online_docs:
|
||||||
return f"{ns}interface.{self.name}.html"
|
return f"{ns}iface.{self.name}.html"
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -706,7 +739,7 @@ class TemplateType(GirType):
|
||||||
# we don't know the template type's interfaces, assume yes
|
# we don't know the template type's interfaces, assume yes
|
||||||
return True
|
return True
|
||||||
elif self.parent is None or isinstance(self.parent, ExternType):
|
elif self.parent is None or isinstance(self.parent, ExternType):
|
||||||
return isinstance(other, Class)
|
return isinstance(other, Class) or isinstance(other, ExternType)
|
||||||
else:
|
else:
|
||||||
return self.parent.assignable_to(other)
|
return self.parent.assignable_to(other)
|
||||||
|
|
||||||
|
@ -875,22 +908,22 @@ class Namespace(GirNode):
|
||||||
if isinstance(entry, Class)
|
if isinstance(entry, Class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def interfaces(self) -> T.Mapping[str, Interface]:
|
|
||||||
return {
|
|
||||||
name: entry
|
|
||||||
for name, entry in self.entries.items()
|
|
||||||
if isinstance(entry, Interface)
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_type(self, name) -> T.Optional[GirType]:
|
def get_type(self, name) -> T.Optional[GirType]:
|
||||||
"""Gets a type (class, interface, enum, etc.) from this namespace."""
|
"""Gets a type (class, interface, enum, etc.) from this namespace."""
|
||||||
return self.entries.get(name)
|
return self.entries.get(name)
|
||||||
|
|
||||||
def get_type_by_cname(self, cname: str) -> T.Optional[GirType]:
|
def get_type_by_cname(self, cname: str) -> T.Optional[GirType]:
|
||||||
"""Gets a type from this namespace by its C name."""
|
"""Gets a type from this namespace by its C name."""
|
||||||
|
for basic in _BASIC_TYPES.values():
|
||||||
|
if basic.glib_type_name == cname:
|
||||||
|
return basic()
|
||||||
|
|
||||||
for item in self.entries.values():
|
for item in self.entries.values():
|
||||||
if hasattr(item, "cname") and item.cname == cname:
|
if (
|
||||||
|
hasattr(item, "cname")
|
||||||
|
and item.cname is not None
|
||||||
|
and item.cname == cname
|
||||||
|
):
|
||||||
return item
|
return item
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -898,13 +931,8 @@ class Namespace(GirNode):
|
||||||
"""Looks up a type in the scope of this namespace (including in the
|
"""Looks up a type in the scope of this namespace (including in the
|
||||||
namespace's dependencies)."""
|
namespace's dependencies)."""
|
||||||
|
|
||||||
if type_name in _BASIC_TYPES:
|
|
||||||
return _BASIC_TYPES[type_name]()
|
|
||||||
elif "." in type_name:
|
|
||||||
ns, name = type_name.split(".", 1)
|
ns, name = type_name.split(".", 1)
|
||||||
return self.get_containing(Repository).get_type(name, ns)
|
return self.get_containing(Repository).get_type(name, ns)
|
||||||
else:
|
|
||||||
return self.get_type(type_name)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def online_docs(self) -> T.Optional[str]:
|
def online_docs(self) -> T.Optional[str]:
|
||||||
|
@ -923,7 +951,7 @@ class Repository(GirNode):
|
||||||
self.includes = {
|
self.includes = {
|
||||||
name: get_namespace(name, version) for name, version in deps
|
name: get_namespace(name, version) for name, version in deps
|
||||||
}
|
}
|
||||||
except:
|
except: # pragma: no cover
|
||||||
raise CompilerBugError(f"Failed to load dependencies.")
|
raise CompilerBugError(f"Failed to load dependencies.")
|
||||||
else:
|
else:
|
||||||
self.includes = {}
|
self.includes = {}
|
||||||
|
@ -931,12 +959,6 @@ class Repository(GirNode):
|
||||||
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
def get_type(self, name: str, ns: str) -> T.Optional[GirType]:
|
||||||
return self.lookup_namespace(ns).get_type(name)
|
return self.lookup_namespace(ns).get_type(name)
|
||||||
|
|
||||||
def get_type_by_cname(self, name: str) -> T.Optional[GirType]:
|
|
||||||
for ns in [self.namespace, *self.includes.values()]:
|
|
||||||
if type := ns.get_type_by_cname(name):
|
|
||||||
return type
|
|
||||||
return None
|
|
||||||
|
|
||||||
def lookup_namespace(self, ns: str):
|
def lookup_namespace(self, ns: str):
|
||||||
"""Finds a namespace among this namespace's dependencies."""
|
"""Finds a namespace among this namespace's dependencies."""
|
||||||
if ns == self.namespace.name:
|
if ns == self.namespace.name:
|
||||||
|
|
|
@ -71,7 +71,7 @@ def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
|
||||||
print(
|
print(
|
||||||
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
|
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
|
||||||
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
|
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
|
||||||
{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
|
{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
|
||||||
)
|
)
|
||||||
|
|
||||||
return CouldNotPort("does not compile")
|
return CouldNotPort("does not compile")
|
||||||
|
@ -136,7 +136,7 @@ def step1():
|
||||||
wrap.write(
|
wrap.write(
|
||||||
f"""[wrap-git]
|
f"""[wrap-git]
|
||||||
directory = blueprint-compiler
|
directory = blueprint-compiler
|
||||||
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
|
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
|
||||||
revision = {VERSION}
|
revision = {VERSION}
|
||||||
depth = 1
|
depth = 1
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ from .adw_breakpoint import (
|
||||||
AdwBreakpointSetters,
|
AdwBreakpointSetters,
|
||||||
)
|
)
|
||||||
from .adw_response_dialog import ExtAdwResponseDialog
|
from .adw_response_dialog import ExtAdwResponseDialog
|
||||||
from .attributes import BaseAttribute
|
|
||||||
from .binding import Binding
|
from .binding import Binding
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ScopeCtx, ValueTypeCtx
|
from .contexts import ScopeCtx, ValueTypeCtx
|
||||||
|
@ -20,7 +19,7 @@ from .expression import (
|
||||||
from .gobject_object import Object, ObjectContent
|
from .gobject_object import Object, ObjectContent
|
||||||
from .gobject_property import Property
|
from .gobject_property import Property
|
||||||
from .gobject_signal import Signal
|
from .gobject_signal import Signal
|
||||||
from .gtk_a11y import ExtAccessibility
|
from .gtk_a11y import A11yProperty, ExtAccessibility
|
||||||
from .gtk_combo_box_text import ExtComboBoxItems
|
from .gtk_combo_box_text import ExtComboBoxItems
|
||||||
from .gtk_file_filter import (
|
from .gtk_file_filter import (
|
||||||
Filters,
|
Filters,
|
||||||
|
@ -41,6 +40,8 @@ from .imports import GtkDirective, Import
|
||||||
from .types import ClassName
|
from .types import ClassName
|
||||||
from .ui import UI
|
from .ui import UI
|
||||||
from .values import (
|
from .values import (
|
||||||
|
ArrayValue,
|
||||||
|
ExprValue,
|
||||||
Flag,
|
Flag,
|
||||||
Flags,
|
Flags,
|
||||||
IdentLiteral,
|
IdentLiteral,
|
||||||
|
|
|
@ -81,8 +81,8 @@ class AdwBreakpointSetter(AstNode):
|
||||||
return self.tokens["property"]
|
return self.tokens["property"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> Value:
|
def value(self) -> T.Optional[Value]:
|
||||||
return self.children[Value][0]
|
return self.children[Value][0] if len(self.children[Value]) > 0 else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_class(self) -> T.Optional[GirType]:
|
def gir_class(self) -> T.Optional[GirType]:
|
||||||
|
@ -98,13 +98,18 @@ class AdwBreakpointSetter(AstNode):
|
||||||
and not isinstance(self.gir_class, ExternType)
|
and not isinstance(self.gir_class, ExternType)
|
||||||
and self.property_name is not None
|
and self.property_name is not None
|
||||||
):
|
):
|
||||||
assert isinstance(self.gir_class, gir.Class)
|
assert isinstance(self.gir_class, gir.Class) or isinstance(
|
||||||
|
self.gir_class, gir.TemplateType
|
||||||
|
)
|
||||||
return self.gir_class.properties.get(self.property_name)
|
return self.gir_class.properties.get(self.property_name)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def document_symbol(self) -> DocumentSymbol:
|
def document_symbol(self) -> T.Optional[DocumentSymbol]:
|
||||||
|
if self.value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
return DocumentSymbol(
|
return DocumentSymbol(
|
||||||
f"{self.object_id}.{self.property_name}",
|
f"{self.object_id}.{self.property_name}",
|
||||||
SymbolKind.Property,
|
SymbolKind.Property,
|
||||||
|
@ -113,6 +118,17 @@ class AdwBreakpointSetter(AstNode):
|
||||||
self.value.range.text,
|
self.value.range.text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_reference(self, idx: int) -> T.Optional[LocationLink]:
|
||||||
|
if idx in self.group.tokens["object"].range:
|
||||||
|
if self.object is not None:
|
||||||
|
return LocationLink(
|
||||||
|
self.group.tokens["object"].range,
|
||||||
|
self.object.range,
|
||||||
|
self.object.ranges["id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@context(ValueTypeCtx)
|
@context(ValueTypeCtx)
|
||||||
def value_type(self) -> ValueTypeCtx:
|
def value_type(self) -> ValueTypeCtx:
|
||||||
if self.gir_property is not None:
|
if self.gir_property is not None:
|
||||||
|
@ -193,3 +209,46 @@ class AdwBreakpointSetters(AstNode):
|
||||||
@validate()
|
@validate()
|
||||||
def unique(self):
|
def unique(self):
|
||||||
self.validate_unique_in_parent("Duplicate setters block")
|
self.validate_unique_in_parent("Duplicate setters block")
|
||||||
|
|
||||||
|
@docs("setters")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtAdwBreakpoint")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("condition", cdata=True)
|
||||||
|
def decompile_condition(ctx: DecompileCtx, gir, cdata):
|
||||||
|
ctx.print(f"condition({escape_quote(cdata)})")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("setter", element=True)
|
||||||
|
def decompile_setter(ctx: DecompileCtx, gir, element):
|
||||||
|
assert ctx.parent_node is not None
|
||||||
|
# only run for the first setter
|
||||||
|
for child in ctx.parent_node.children:
|
||||||
|
if child.tag == "setter":
|
||||||
|
if child != element:
|
||||||
|
# already decompiled
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
ctx.print("setters {")
|
||||||
|
for child in ctx.parent_node.children:
|
||||||
|
if child.tag == "setter":
|
||||||
|
object_id = child["object"]
|
||||||
|
property_name = child["property"]
|
||||||
|
obj = ctx.find_object(object_id)
|
||||||
|
if obj is not None:
|
||||||
|
gir_class = ctx.type_by_cname(obj["class"])
|
||||||
|
else:
|
||||||
|
gir_class = None
|
||||||
|
|
||||||
|
if object_id == ctx.template_class:
|
||||||
|
object_id = "template"
|
||||||
|
|
||||||
|
comments, string = ctx.decompile_value(
|
||||||
|
child.cdata,
|
||||||
|
gir_class,
|
||||||
|
(child["translatable"], child["context"], child["comments"]),
|
||||||
|
)
|
||||||
|
ctx.print(f"{comments} {object_id}.{property_name}: {string};")
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
|
|
||||||
from ..decompiler import decompile_translatable, truthy
|
from ..decompiler import decompile_translatable, truthy
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from .values import StringValue
|
from .values import StringValue
|
||||||
|
|
||||||
|
@ -94,10 +93,6 @@ class ExtAdwResponseDialogResponse(AstNode):
|
||||||
self.value.range.text,
|
self.value.range.text,
|
||||||
)
|
)
|
||||||
|
|
||||||
@context(ValueTypeCtx)
|
|
||||||
def value_type(self) -> ValueTypeCtx:
|
|
||||||
return ValueTypeCtx(StringType())
|
|
||||||
|
|
||||||
@validate("id")
|
@validate("id")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent(
|
self.validate_unique_in_parent(
|
||||||
|
@ -138,6 +133,10 @@ class ExtAdwResponseDialog(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate responses block")
|
self.validate_unique_in_parent("Duplicate responses block")
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtAdwMessageDialog")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -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"]
|
|
|
@ -58,6 +58,10 @@ class BindingFlag(AstNode):
|
||||||
"Only bindings with a single lookup can have flags",
|
"Only bindings with a single lookup can have flags",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Binding")
|
||||||
|
|
||||||
|
|
||||||
class Binding(AstNode):
|
class Binding(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -99,6 +103,10 @@ class Binding(AstNode):
|
||||||
actions=[CodeAction("use 'bind'", "bind")],
|
actions=[CodeAction("use 'bind'", "bind")],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs("bind")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Binding")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SimpleBinding:
|
class SimpleBinding:
|
||||||
|
@ -107,3 +115,9 @@ class SimpleBinding:
|
||||||
no_sync_create: bool = False
|
no_sync_create: bool = False
|
||||||
bidirectional: bool = False
|
bidirectional: bool = False
|
||||||
inverted: bool = False
|
inverted: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("binding")
|
||||||
|
def decompile_binding(ctx: DecompileCtx, gir: gir.GirContext, name: str):
|
||||||
|
ctx.end_block_with(";")
|
||||||
|
ctx.print(f"{name}: bind ")
|
||||||
|
|
|
@ -35,6 +35,7 @@ from ..errors import (
|
||||||
CompileWarning,
|
CompileWarning,
|
||||||
DeprecatedWarning,
|
DeprecatedWarning,
|
||||||
MultipleErrors,
|
MultipleErrors,
|
||||||
|
UnusedWarning,
|
||||||
UpgradeWarning,
|
UpgradeWarning,
|
||||||
)
|
)
|
||||||
from ..gir import (
|
from ..gir import (
|
||||||
|
@ -54,6 +55,7 @@ from ..lsp_utils import (
|
||||||
SemanticToken,
|
SemanticToken,
|
||||||
SemanticTokenType,
|
SemanticTokenType,
|
||||||
SymbolKind,
|
SymbolKind,
|
||||||
|
get_docs_section,
|
||||||
)
|
)
|
||||||
from ..parse_tree import *
|
from ..parse_tree import *
|
||||||
|
|
||||||
|
|
|
@ -79,3 +79,9 @@ class ScopeCtx:
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
if child.context[ScopeCtx] is self:
|
if child.context[ScopeCtx] is self:
|
||||||
yield from self._iter_recursive(child)
|
yield from self._iter_recursive(child)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExprValueCtx:
|
||||||
|
"""Indicates that the context is an expression literal, where the
|
||||||
|
"item" keyword may be used."""
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
from ..decompiler import decompile_element
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ScopeCtx, ValueTypeCtx
|
from .contexts import ScopeCtx, ValueTypeCtx
|
||||||
from .gtkbuilder_template import Template
|
|
||||||
from .types import TypeName
|
from .types import TypeName
|
||||||
|
|
||||||
expr = Sequence()
|
expr = Sequence()
|
||||||
|
@ -38,10 +38,6 @@ class ExprBase(AstNode):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
|
||||||
def type_complete(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rhs(self) -> T.Optional["ExprBase"]:
|
def rhs(self) -> T.Optional["ExprBase"]:
|
||||||
if isinstance(self.parent, Expression):
|
if isinstance(self.parent, Expression):
|
||||||
|
@ -65,10 +61,6 @@ class Expression(ExprBase):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.last.type
|
return self.last.type
|
||||||
|
|
||||||
@property
|
|
||||||
def type_complete(self) -> bool:
|
|
||||||
return self.last.type_complete
|
|
||||||
|
|
||||||
|
|
||||||
class InfixExpr(ExprBase):
|
class InfixExpr(ExprBase):
|
||||||
@property
|
@property
|
||||||
|
@ -89,6 +81,16 @@ class LiteralExpr(ExprBase):
|
||||||
or self.root.is_legacy_template(self.literal.value.ident)
|
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
|
@property
|
||||||
def literal(self):
|
def literal(self):
|
||||||
from .values import Literal
|
from .values import Literal
|
||||||
|
@ -99,14 +101,14 @@ class LiteralExpr(ExprBase):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.literal.value.type
|
return self.literal.value.type
|
||||||
|
|
||||||
@property
|
@validate()
|
||||||
def type_complete(self) -> bool:
|
def item_validations(self):
|
||||||
from .values import IdentLiteral
|
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 not isinstance(self.rhs.rhs, LookupOp):
|
||||||
if object := self.context[ScopeCtx].objects.get(self.literal.value.ident):
|
raise CompileError('"item" can only be used for looking up properties')
|
||||||
return not object.gir_class.incomplete
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class LookupOp(InfixExpr):
|
class LookupOp(InfixExpr):
|
||||||
|
@ -130,6 +132,17 @@ class LookupOp(InfixExpr):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@docs("property")
|
||||||
|
def property_docs(self):
|
||||||
|
if not (
|
||||||
|
isinstance(self.lhs.type, gir.Class)
|
||||||
|
or isinstance(self.lhs.type, gir.Interface)
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if property := self.lhs.type.properties.get(self.property_name):
|
||||||
|
return property.doc
|
||||||
|
|
||||||
@validate("property")
|
@validate("property")
|
||||||
def property_exists(self):
|
def property_exists(self):
|
||||||
if self.lhs.type is None:
|
if self.lhs.type is None:
|
||||||
|
@ -160,10 +173,28 @@ class LookupOp(InfixExpr):
|
||||||
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
|
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@validate("property")
|
||||||
|
def property_deprecated(self):
|
||||||
|
if self.lhs.type is None or not (
|
||||||
|
isinstance(self.lhs.type, gir.Class)
|
||||||
|
or isinstance(self.lhs.type, gir.Interface)
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
if property := self.lhs.type.properties.get(self.property_name):
|
||||||
|
if property.deprecated:
|
||||||
|
hints = []
|
||||||
|
if property.deprecated_doc:
|
||||||
|
hints.append(property.deprecated_doc)
|
||||||
|
raise DeprecatedWarning(
|
||||||
|
f"{property.signature} is deprecated",
|
||||||
|
hints=hints,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CastExpr(InfixExpr):
|
class CastExpr(InfixExpr):
|
||||||
grammar = [
|
grammar = [
|
||||||
"as",
|
Keyword("as"),
|
||||||
AnyOf(
|
AnyOf(
|
||||||
["<", TypeName, Match(">").expected()],
|
["<", TypeName, Match(">").expected()],
|
||||||
[
|
[
|
||||||
|
@ -182,10 +213,6 @@ class CastExpr(InfixExpr):
|
||||||
def type(self) -> T.Optional[GirType]:
|
def type(self) -> T.Optional[GirType]:
|
||||||
return self.children[TypeName][0].gir_type
|
return self.children[TypeName][0].gir_type
|
||||||
|
|
||||||
@property
|
|
||||||
def type_complete(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def cast_makes_sense(self):
|
def cast_makes_sense(self):
|
||||||
if self.type is None or self.lhs.type is None:
|
if self.type is None or self.lhs.type is None:
|
||||||
|
@ -209,6 +236,10 @@ class CastExpr(InfixExpr):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs("as")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax CastExpression")
|
||||||
|
|
||||||
|
|
||||||
class ClosureArg(AstNode):
|
class ClosureArg(AstNode):
|
||||||
grammar = Expression
|
grammar = Expression
|
||||||
|
@ -258,8 +289,96 @@ class ClosureExpr(ExprBase):
|
||||||
if not self.tokens["extern"]:
|
if not self.tokens["extern"]:
|
||||||
raise CompileError(f"{self.closure_name} is not a builtin function")
|
raise CompileError(f"{self.closure_name} is not a builtin function")
|
||||||
|
|
||||||
|
@docs("name")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ClosureExpression")
|
||||||
|
|
||||||
|
|
||||||
expr.children = [
|
expr.children = [
|
||||||
AnyOf(ClosureExpr, LiteralExpr, ["(", Expression, ")"]),
|
AnyOf(ClosureExpr, LiteralExpr, ["(", Expression, ")"]),
|
||||||
ZeroOrMore(AnyOf(LookupOp, CastExpr)),
|
ZeroOrMore(AnyOf(LookupOp, CastExpr)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("lookup", skip_children=True, cdata=True)
|
||||||
|
def decompile_lookup(
|
||||||
|
ctx: DecompileCtx,
|
||||||
|
gir: gir.GirContext,
|
||||||
|
cdata: str,
|
||||||
|
name: str,
|
||||||
|
type: T.Optional[str] = None,
|
||||||
|
):
|
||||||
|
if ctx.parent_node is not None and ctx.parent_node.tag == "property":
|
||||||
|
ctx.print("expr ")
|
||||||
|
|
||||||
|
if type is None:
|
||||||
|
type = ""
|
||||||
|
elif t := ctx.type_by_cname(type):
|
||||||
|
type = decompile.full_name(t)
|
||||||
|
else:
|
||||||
|
type = "$" + type
|
||||||
|
|
||||||
|
assert ctx.current_node is not None
|
||||||
|
|
||||||
|
constant = None
|
||||||
|
if len(ctx.current_node.children) == 0:
|
||||||
|
constant = cdata
|
||||||
|
elif (
|
||||||
|
len(ctx.current_node.children) == 1
|
||||||
|
and ctx.current_node.children[0].tag == "constant"
|
||||||
|
):
|
||||||
|
constant = ctx.current_node.children[0].cdata
|
||||||
|
|
||||||
|
if constant is not None:
|
||||||
|
if constant == ctx.template_class:
|
||||||
|
ctx.print("template." + name)
|
||||||
|
elif constant == "":
|
||||||
|
ctx.print(f"item as <{type}>.{name}")
|
||||||
|
else:
|
||||||
|
ctx.print(constant + "." + name)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
for child in ctx.current_node.children:
|
||||||
|
decompile.decompile_element(ctx, gir, child)
|
||||||
|
|
||||||
|
ctx.print(f" as <{type}>.{name}")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("constant", cdata=True)
|
||||||
|
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")
|
||||||
|
else:
|
||||||
|
ctx.print(cdata)
|
||||||
|
else:
|
||||||
|
_, string = ctx.decompile_value(cdata, ctx.type_by_cname(type))
|
||||||
|
ctx.print(string)
|
||||||
|
|
||||||
|
|
||||||
|
@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:
|
||||||
|
type = "$" + type
|
||||||
|
|
||||||
|
ctx.print(f"${function}(")
|
||||||
|
|
||||||
|
assert ctx.current_node is not None
|
||||||
|
for i, node in enumerate(ctx.current_node.children):
|
||||||
|
decompile_element(ctx, gir, node)
|
||||||
|
|
||||||
|
assert ctx.current_node is not None
|
||||||
|
if i < len(ctx.current_node.children) - 1:
|
||||||
|
ctx.print(", ")
|
||||||
|
|
||||||
|
ctx.end_block_with(f") as <{type}>")
|
||||||
|
|
|
@ -28,7 +28,18 @@ from .common import *
|
||||||
from .response_id import ExtResponse
|
from .response_id import ExtResponse
|
||||||
from .types import ClassName, ConcreteClassName
|
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):
|
class ObjectContent(AstNode):
|
||||||
|
@ -110,12 +121,12 @@ def validate_parent_type(node, ns: str, name: str, err_msg: str):
|
||||||
container_type = node.parent_by_type(Object).gir_class
|
container_type = node.parent_by_type(Object).gir_class
|
||||||
if container_type and not container_type.assignable_to(parent):
|
if container_type and not container_type.assignable_to(parent):
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"{container_type.full_name} is not a {parent.full_name}, so it doesn't have {err_msg}"
|
f"{container_type.full_name} is not a {ns}.{name}, so it doesn't have {err_msg}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("object")
|
@decompiler("object")
|
||||||
def decompile_object(ctx, gir, klass, id=None):
|
def decompile_object(ctx: DecompileCtx, gir, klass, id=None):
|
||||||
gir_class = ctx.type_by_cname(klass)
|
gir_class = ctx.type_by_cname(klass)
|
||||||
klass_name = (
|
klass_name = (
|
||||||
decompile.full_name(gir_class) if gir_class is not None else "$" + klass
|
decompile.full_name(gir_class) if gir_class is not None else "$" + klass
|
||||||
|
@ -124,4 +135,5 @@ def decompile_object(ctx, gir, klass, id=None):
|
||||||
ctx.print(f"{klass_name} {{")
|
ctx.print(f"{klass_name} {{")
|
||||||
else:
|
else:
|
||||||
ctx.print(f"{klass_name} {id} {{")
|
ctx.print(f"{klass_name} {id} {{")
|
||||||
|
ctx.push_obj_type(gir_class)
|
||||||
return gir_class
|
return gir_class
|
||||||
|
|
|
@ -21,19 +21,20 @@
|
||||||
from .binding import Binding
|
from .binding import Binding
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
from .contexts import ValueTypeCtx
|
||||||
from .gtkbuilder_template import Template
|
from .values import ArrayValue, ExprValue, ObjectValue, Value
|
||||||
from .values import ObjectValue, Value
|
|
||||||
|
|
||||||
|
|
||||||
class Property(AstNode):
|
class Property(AstNode):
|
||||||
grammar = Statement(UseIdent("name"), ":", AnyOf(Binding, ObjectValue, Value))
|
grammar = Statement(
|
||||||
|
UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self.tokens["name"]
|
return self.tokens["name"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> T.Union[Binding, ObjectValue, Value]:
|
def value(self) -> T.Union[Binding, ExprValue, ObjectValue, Value, ArrayValue]:
|
||||||
return self.children[0]
|
return self.children[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -49,7 +50,7 @@ class Property(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def document_symbol(self) -> DocumentSymbol:
|
def document_symbol(self) -> DocumentSymbol:
|
||||||
if isinstance(self.value, ObjectValue):
|
if isinstance(self.value, ObjectValue) or self.value is None:
|
||||||
detail = None
|
detail = None
|
||||||
else:
|
else:
|
||||||
detail = self.value.range.text
|
detail = self.value.range.text
|
||||||
|
|
|
@ -27,6 +27,7 @@ from .gtkbuilder_template import Template
|
||||||
class SignalFlag(AstNode):
|
class SignalFlag(AstNode):
|
||||||
grammar = AnyOf(
|
grammar = AnyOf(
|
||||||
UseExact("flag", "swapped"),
|
UseExact("flag", "swapped"),
|
||||||
|
UseExact("flag", "not-swapped"),
|
||||||
UseExact("flag", "after"),
|
UseExact("flag", "after"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,6 +41,31 @@ class SignalFlag(AstNode):
|
||||||
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
|
f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def swapped_exclusive(self):
|
||||||
|
if self.flag in ["swapped", "not-swapped"]:
|
||||||
|
self.validate_unique_in_parent(
|
||||||
|
"'swapped' and 'not-swapped' flags cannot be used together",
|
||||||
|
lambda x: x.flag in ["swapped", "not-swapped"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def swapped_unnecessary(self):
|
||||||
|
if self.flag == "not-swapped" and self.parent.object_id is None:
|
||||||
|
raise CompileWarning(
|
||||||
|
"'not-swapped' is the default for handlers that do not specify an object",
|
||||||
|
actions=[CodeAction("Remove 'not-swapped' flag", "")],
|
||||||
|
)
|
||||||
|
elif self.flag == "swapped" and self.parent.object_id is not None:
|
||||||
|
raise CompileWarning(
|
||||||
|
"'swapped' is the default for handlers that specify an object",
|
||||||
|
actions=[CodeAction("Remove 'swapped' flag", "")],
|
||||||
|
)
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Signal")
|
||||||
|
|
||||||
|
|
||||||
class Signal(AstNode):
|
class Signal(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
|
@ -50,7 +76,7 @@ class Signal(AstNode):
|
||||||
UseIdent("detail_name").expected("a signal detail name"),
|
UseIdent("detail_name").expected("a signal detail name"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
"=>",
|
Keyword("=>"),
|
||||||
Mark("detail_start"),
|
Mark("detail_start"),
|
||||||
Optional(["$", UseLiteral("extern", True)]),
|
Optional(["$", UseLiteral("extern", True)]),
|
||||||
UseIdent("handler").expected("the name of a function to handle the signal"),
|
UseIdent("handler").expected("the name of a function to handle the signal"),
|
||||||
|
@ -88,9 +114,17 @@ class Signal(AstNode):
|
||||||
def flags(self) -> T.List[SignalFlag]:
|
def flags(self) -> T.List[SignalFlag]:
|
||||||
return self.children[SignalFlag]
|
return self.children[SignalFlag]
|
||||||
|
|
||||||
|
# Returns True if the "swapped" flag is present, False if "not-swapped" is present, and None if neither are present.
|
||||||
|
# GtkBuilder's default if swapped is not specified is to not swap the arguments if no object is specified, and to
|
||||||
|
# swap them if an object is specified.
|
||||||
@property
|
@property
|
||||||
def is_swapped(self) -> bool:
|
def is_swapped(self) -> T.Optional[bool]:
|
||||||
return any(x.flag == "swapped" for x in self.flags)
|
for flag in self.flags:
|
||||||
|
if flag.flag == "swapped":
|
||||||
|
return True
|
||||||
|
elif flag.flag == "not-swapped":
|
||||||
|
return False
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_after(self) -> bool:
|
def is_after(self) -> bool:
|
||||||
|
@ -109,14 +143,25 @@ class Signal(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def document_symbol(self) -> DocumentSymbol:
|
def document_symbol(self) -> DocumentSymbol:
|
||||||
|
detail = self.ranges["detail_start", "detail_end"]
|
||||||
return DocumentSymbol(
|
return DocumentSymbol(
|
||||||
self.full_name,
|
self.full_name,
|
||||||
SymbolKind.Event,
|
SymbolKind.Event,
|
||||||
self.range,
|
self.range,
|
||||||
self.group.tokens["name"].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 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(
|
||||||
|
self.group.tokens["object"].range, obj.range, obj.ranges["id"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@validate("handler")
|
@validate("handler")
|
||||||
def old_extern(self):
|
def old_extern(self):
|
||||||
if not self.tokens["extern"]:
|
if not self.tokens["extern"]:
|
||||||
|
@ -164,13 +209,41 @@ class Signal(AstNode):
|
||||||
if self.gir_signal is not None:
|
if self.gir_signal is not None:
|
||||||
return self.gir_signal.doc
|
return self.gir_signal.doc
|
||||||
|
|
||||||
|
@docs("detail_name")
|
||||||
|
def detail_docs(self):
|
||||||
|
if self.name == "notify":
|
||||||
|
if self.gir_class is not None and not isinstance(
|
||||||
|
self.gir_class, ExternType
|
||||||
|
):
|
||||||
|
prop = self.gir_class.properties.get(self.tokens["detail_name"])
|
||||||
|
if prop is not None:
|
||||||
|
return prop.doc
|
||||||
|
|
||||||
|
@docs("=>")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Signal")
|
||||||
|
|
||||||
|
|
||||||
@decompiler("signal")
|
@decompiler("signal")
|
||||||
def decompile_signal(ctx, gir, name, handler, swapped="false", object=None):
|
def decompile_signal(
|
||||||
|
ctx: DecompileCtx, gir, name, handler, swapped=None, after="false", object=None
|
||||||
|
):
|
||||||
object_name = object or ""
|
object_name = object or ""
|
||||||
|
|
||||||
|
if object_name == ctx.template_class:
|
||||||
|
object_name = "template"
|
||||||
|
|
||||||
name = name.replace("_", "-")
|
name = name.replace("_", "-")
|
||||||
|
line = f"{name} => ${handler}({object_name})"
|
||||||
|
|
||||||
if decompile.truthy(swapped):
|
if decompile.truthy(swapped):
|
||||||
ctx.print(f"{name} => ${handler}({object_name}) swapped;")
|
line += " swapped"
|
||||||
else:
|
elif swapped is not None:
|
||||||
ctx.print(f"{name} => ${handler}({object_name});")
|
line += " not-swapped"
|
||||||
|
|
||||||
|
if decompile.truthy(after):
|
||||||
|
line += " after"
|
||||||
|
|
||||||
|
line += ";"
|
||||||
|
ctx.print(line)
|
||||||
return gir
|
return gir
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
from ..decompiler import escape_quote
|
import typing as T
|
||||||
from .attributes import BaseAttribute
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
from .contexts import ValueTypeCtx
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
|
@ -97,6 +97,16 @@ def get_types(gir):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
allow_duplicates = [
|
||||||
|
"controls",
|
||||||
|
"described-by",
|
||||||
|
"details",
|
||||||
|
"flow-to",
|
||||||
|
"labelled-by",
|
||||||
|
"owns",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _get_docs(gir, name):
|
def _get_docs(gir, name):
|
||||||
name = name.replace("-", "_")
|
name = name.replace("-", "_")
|
||||||
if gir_type := (
|
if gir_type := (
|
||||||
|
@ -107,11 +117,11 @@ def _get_docs(gir, name):
|
||||||
return gir_type.doc
|
return gir_type.doc
|
||||||
|
|
||||||
|
|
||||||
class A11yProperty(BaseAttribute):
|
class A11yProperty(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
Value,
|
AnyOf(Value, ["[", UseLiteral("list_form", True), Delimited(Value, ","), "]"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -132,8 +142,8 @@ class A11yProperty(BaseAttribute):
|
||||||
return self.tokens["name"].replace("_", "-")
|
return self.tokens["name"].replace("_", "-")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> Value:
|
def values(self) -> T.List[Value]:
|
||||||
return self.children[0]
|
return list(self.children)
|
||||||
|
|
||||||
@context(ValueTypeCtx)
|
@context(ValueTypeCtx)
|
||||||
def value_type(self) -> ValueTypeCtx:
|
def value_type(self) -> ValueTypeCtx:
|
||||||
|
@ -146,7 +156,7 @@ class A11yProperty(BaseAttribute):
|
||||||
SymbolKind.Field,
|
SymbolKind.Field,
|
||||||
self.range,
|
self.range,
|
||||||
self.group.tokens["name"].range,
|
self.group.tokens["name"].range,
|
||||||
self.value.range.text,
|
", ".join(v.range.text for v in self.values),
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("name")
|
@validate("name")
|
||||||
|
@ -165,6 +175,20 @@ class A11yProperty(BaseAttribute):
|
||||||
check=lambda child: child.tokens["name"] == self.tokens["name"],
|
check=lambda child: child.tokens["name"] == self.tokens["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@validate("name")
|
||||||
|
def list_only_allowed_for_subset(self):
|
||||||
|
if self.tokens["list_form"] and self.tokens["name"] not in allow_duplicates:
|
||||||
|
raise CompileError(
|
||||||
|
f"'{self.tokens['name']}' does not allow a list of values",
|
||||||
|
)
|
||||||
|
|
||||||
|
@validate("name")
|
||||||
|
def list_non_empty(self):
|
||||||
|
if len(self.values) == 0:
|
||||||
|
raise CompileError(
|
||||||
|
f"'{self.tokens['name']}' may not be empty",
|
||||||
|
)
|
||||||
|
|
||||||
@docs("name")
|
@docs("name")
|
||||||
def prop_docs(self):
|
def prop_docs(self):
|
||||||
if self.tokens["name"] in get_types(self.root.gir):
|
if self.tokens["name"] in get_types(self.root.gir):
|
||||||
|
@ -199,6 +223,10 @@ class ExtAccessibility(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate accessibility block")
|
self.validate_unique_in_parent("Duplicate accessibility block")
|
||||||
|
|
||||||
|
@docs("accessibility")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtAccessibility")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
@ -223,19 +251,37 @@ def a11y_name_completer(lsp, ast_node, match_variables):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("relation", cdata=True)
|
@decompiler("accessibility", skip_children=True, element=True)
|
||||||
def decompile_relation(ctx, gir, name, cdata):
|
def decompile_accessibility(ctx: DecompileCtx, _gir, element):
|
||||||
ctx.print_attribute(name, cdata, get_types(ctx.gir).get(name))
|
|
||||||
|
|
||||||
|
|
||||||
@decompiler("state", cdata=True)
|
|
||||||
def decompile_state(ctx, gir, name, cdata, translatable="false"):
|
|
||||||
if decompile.truthy(translatable):
|
|
||||||
ctx.print(f"{name}: _({escape_quote(cdata)});")
|
|
||||||
else:
|
|
||||||
ctx.print_attribute(name, cdata, get_types(ctx.gir).get(name))
|
|
||||||
|
|
||||||
|
|
||||||
@decompiler("accessibility")
|
|
||||||
def decompile_accessibility(ctx, gir):
|
|
||||||
ctx.print("accessibility {")
|
ctx.print("accessibility {")
|
||||||
|
already_printed = set()
|
||||||
|
types = get_types(ctx.gir)
|
||||||
|
|
||||||
|
for child in element.children:
|
||||||
|
name = child["name"]
|
||||||
|
|
||||||
|
if name in allow_duplicates:
|
||||||
|
if name in already_printed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ctx.print(f"{name}: [")
|
||||||
|
for value in element.children:
|
||||||
|
if value["name"] == name:
|
||||||
|
comments, string = ctx.decompile_value(
|
||||||
|
value.cdata,
|
||||||
|
types.get(value["name"]),
|
||||||
|
(value["translatable"], value["context"], value["comments"]),
|
||||||
|
)
|
||||||
|
ctx.print(f"{comments} {string},")
|
||||||
|
ctx.print("];")
|
||||||
|
else:
|
||||||
|
comments, string = ctx.decompile_value(
|
||||||
|
child.cdata,
|
||||||
|
types.get(child["name"]),
|
||||||
|
(child["translatable"], child["context"], child["comments"]),
|
||||||
|
)
|
||||||
|
ctx.print(f"{comments} {name}: {string};")
|
||||||
|
|
||||||
|
already_printed.add(name)
|
||||||
|
ctx.print("}")
|
||||||
|
ctx.end_block_with("")
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
|
||||||
from .gobject_object import ObjectContent, validate_parent_type
|
from .gobject_object import ObjectContent, validate_parent_type
|
||||||
from .values import StringValue
|
from .values import StringValue
|
||||||
|
|
||||||
|
@ -55,6 +54,10 @@ class Item(AstNode):
|
||||||
f"Duplicate item '{self.name}'", lambda x: x.name == self.name
|
f"Duplicate item '{self.name}'", lambda x: x.name == self.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs("name")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtComboBoxItems")
|
||||||
|
|
||||||
|
|
||||||
class ExtComboBoxItems(AstNode):
|
class ExtComboBoxItems(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -81,6 +84,10 @@ class ExtComboBoxItems(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate items block")
|
self.validate_unique_in_parent("Duplicate items block")
|
||||||
|
|
||||||
|
@docs("items")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtComboBoxItems")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
@ -89,3 +96,29 @@ class ExtComboBoxItems(AstNode):
|
||||||
)
|
)
|
||||||
def items_completer(lsp, ast_node, match_variables):
|
def items_completer(lsp, ast_node, match_variables):
|
||||||
yield Completion("items", CompletionItemKind.Snippet, snippet="items [$0]")
|
yield Completion("items", CompletionItemKind.Snippet, snippet="items [$0]")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("items", parent_type="Gtk.ComboBoxText")
|
||||||
|
def decompile_items(ctx: DecompileCtx, gir: gir.GirContext):
|
||||||
|
ctx.print("items [")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("item", parent_type="Gtk.ComboBoxText", cdata=True)
|
||||||
|
def decompile_item(
|
||||||
|
ctx: DecompileCtx,
|
||||||
|
gir: gir.GirContext,
|
||||||
|
cdata: str,
|
||||||
|
id: T.Optional[str] = None,
|
||||||
|
translatable="false",
|
||||||
|
comments=None,
|
||||||
|
context=None,
|
||||||
|
):
|
||||||
|
comments, translatable = decompile_translatable(
|
||||||
|
cdata, translatable, context, comments
|
||||||
|
)
|
||||||
|
if comments:
|
||||||
|
ctx.print(comments)
|
||||||
|
if id:
|
||||||
|
ctx.print(f"{id}: ")
|
||||||
|
ctx.print(translatable)
|
||||||
|
ctx.print(",")
|
||||||
|
|
|
@ -29,25 +29,23 @@ class Filters(AstNode):
|
||||||
self.tokens["tag_name"],
|
self.tokens["tag_name"],
|
||||||
SymbolKind.Array,
|
SymbolKind.Array,
|
||||||
self.range,
|
self.range,
|
||||||
self.group.tokens[self.tokens["tag_name"]].range,
|
self.group.tokens["tag_name"].range,
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def container_is_file_filter(self):
|
def container_is_file_filter(self):
|
||||||
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
|
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
|
||||||
|
|
||||||
@validate()
|
@validate("tag_name")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
# The token argument to validate() needs to be calculated based on
|
|
||||||
# the instance, hence wrapping it like this.
|
|
||||||
@validate(self.tokens["tag_name"])
|
|
||||||
def wrapped_validator(self):
|
|
||||||
self.validate_unique_in_parent(
|
self.validate_unique_in_parent(
|
||||||
f"Duplicate {self.tokens['tag_name']} block",
|
f"Duplicate {self.tokens['tag_name']} block",
|
||||||
check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
|
check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
wrapped_validator(self)
|
@docs("tag_name")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtFileFilter")
|
||||||
|
|
||||||
|
|
||||||
class FilterString(AstNode):
|
class FilterString(AstNode):
|
||||||
|
@ -76,8 +74,7 @@ def create_node(tag_name: str, singular: str):
|
||||||
return Group(
|
return Group(
|
||||||
Filters,
|
Filters,
|
||||||
[
|
[
|
||||||
Keyword(tag_name),
|
UseExact("tag_name", tag_name),
|
||||||
UseLiteral("tag_name", tag_name),
|
|
||||||
"[",
|
"[",
|
||||||
Delimited(
|
Delimited(
|
||||||
Group(
|
Group(
|
||||||
|
|
|
@ -83,6 +83,10 @@ class ExtLayout(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate layout block")
|
self.validate_unique_in_parent("Duplicate layout block")
|
||||||
|
|
||||||
|
@docs("layout")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtLayout")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -11,7 +11,13 @@ from .types import TypeName
|
||||||
|
|
||||||
|
|
||||||
class ExtListItemFactory(AstNode):
|
class ExtListItemFactory(AstNode):
|
||||||
grammar = [UseExact("id", "template"), Optional(TypeName), ObjectContent]
|
grammar = [
|
||||||
|
UseExact("id", "template"),
|
||||||
|
Mark("typename_start"),
|
||||||
|
Optional(TypeName),
|
||||||
|
Mark("typename_end"),
|
||||||
|
ObjectContent,
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self) -> str:
|
def id(self) -> str:
|
||||||
|
@ -39,9 +45,12 @@ class ExtListItemFactory(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
|
if self.type_name is not None:
|
||||||
|
return self.type_name.gir_type
|
||||||
|
else:
|
||||||
return self.root.gir.get_type("ListItem", "Gtk")
|
return self.root.gir.get_type("ListItem", "Gtk")
|
||||||
|
|
||||||
@validate("template")
|
@validate("id")
|
||||||
def container_is_builder_list(self):
|
def container_is_builder_list(self):
|
||||||
validate_parent_type(
|
validate_parent_type(
|
||||||
self,
|
self,
|
||||||
|
@ -50,17 +59,24 @@ class ExtListItemFactory(AstNode):
|
||||||
"sub-templates",
|
"sub-templates",
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("template")
|
@validate("id")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate template block")
|
self.validate_unique_in_parent("Duplicate template block")
|
||||||
|
|
||||||
@validate()
|
@validate("typename_start", "typename_end")
|
||||||
def type_is_list_item(self):
|
def type_is_list_item(self):
|
||||||
if self.type_name is not None:
|
if self.type_name is not None:
|
||||||
if self.type_name.glib_type_name != "GtkListItem":
|
if self.type_name.glib_type_name not in (
|
||||||
raise CompileError(f"Only Gtk.ListItem is allowed as a type here")
|
"GtkListItem",
|
||||||
|
"GtkListHeader",
|
||||||
|
"GtkColumnViewRow",
|
||||||
|
"GtkColumnViewCell",
|
||||||
|
):
|
||||||
|
raise CompileError(
|
||||||
|
f"Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here"
|
||||||
|
)
|
||||||
|
|
||||||
@validate("template")
|
@validate("id")
|
||||||
def type_name_upgrade(self):
|
def type_name_upgrade(self):
|
||||||
if self.type_name is None:
|
if self.type_name is None:
|
||||||
raise UpgradeWarning(
|
raise UpgradeWarning(
|
||||||
|
@ -87,8 +103,9 @@ class ExtListItemFactory(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def action_widgets(self):
|
def action_widgets(self):
|
||||||
"""
|
# The sub-template shouldn't have its own actions, this is just here to satisfy XmlOutput._emit_object_or_template
|
||||||
The sub-template shouldn't have it`s own actions this is
|
|
||||||
just hear to satisfy XmlOutput._emit_object_or_template
|
|
||||||
"""
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@docs("id")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtListItemFactory")
|
||||||
|
|
|
@ -70,6 +70,25 @@ class Menu(AstNode):
|
||||||
if self.id in RESERVED_IDS:
|
if self.id in RESERVED_IDS:
|
||||||
raise CompileWarning(f"{self.id} may be a confusing object ID")
|
raise CompileWarning(f"{self.id} may be a confusing object ID")
|
||||||
|
|
||||||
|
@docs("menu")
|
||||||
|
def ref_docs_menu(self):
|
||||||
|
return get_docs_section("Syntax Menu")
|
||||||
|
|
||||||
|
@docs("section")
|
||||||
|
def ref_docs_section(self):
|
||||||
|
return get_docs_section("Syntax Menu")
|
||||||
|
|
||||||
|
@docs("submenu")
|
||||||
|
def ref_docs_submenu(self):
|
||||||
|
return get_docs_section("Syntax Menu")
|
||||||
|
|
||||||
|
@docs("item")
|
||||||
|
def ref_docs_item(self):
|
||||||
|
if self.tokens["shorthand"]:
|
||||||
|
return get_docs_section("Syntax MenuItemShorthand")
|
||||||
|
else:
|
||||||
|
return get_docs_section("Syntax Menu")
|
||||||
|
|
||||||
|
|
||||||
class MenuAttribute(AstNode):
|
class MenuAttribute(AstNode):
|
||||||
tag_name = "attribute"
|
tag_name = "attribute"
|
||||||
|
@ -156,6 +175,7 @@ menu_item_shorthand = Group(
|
||||||
[
|
[
|
||||||
Keyword("item"),
|
Keyword("item"),
|
||||||
UseLiteral("tag", "item"),
|
UseLiteral("tag", "item"),
|
||||||
|
UseLiteral("shorthand", True),
|
||||||
"(",
|
"(",
|
||||||
Group(
|
Group(
|
||||||
MenuAttribute,
|
MenuAttribute,
|
||||||
|
@ -266,7 +286,7 @@ def decompile_submenu(ctx, gir, id=None):
|
||||||
ctx.print("submenu {")
|
ctx.print("submenu {")
|
||||||
|
|
||||||
|
|
||||||
@decompiler("item")
|
@decompiler("item", parent_tag="menu")
|
||||||
def decompile_item(ctx, gir, id=None):
|
def decompile_item(ctx, gir, id=None):
|
||||||
if id:
|
if id:
|
||||||
ctx.print(f"item {id} {{")
|
ctx.print(f"item {id} {{")
|
||||||
|
|
|
@ -94,6 +94,10 @@ class ExtScaleMark(AstNode):
|
||||||
did_you_mean=(self.position, positions.keys()),
|
did_you_mean=(self.position, positions.keys()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs("mark")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtScaleMarks")
|
||||||
|
|
||||||
|
|
||||||
class ExtScaleMarks(AstNode):
|
class ExtScaleMarks(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -123,6 +127,10 @@ class ExtScaleMarks(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate 'marks' block")
|
self.validate_unique_in_parent("Duplicate 'marks' block")
|
||||||
|
|
||||||
|
@docs("marks")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtScaleMarks")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -39,6 +39,12 @@ class Widget(AstNode):
|
||||||
self.group.tokens["name"].range,
|
self.group.tokens["name"].range,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_reference(self, _idx: int) -> T.Optional[LocationLink]:
|
||||||
|
if obj := self.context[ScopeCtx].objects.get(self.name):
|
||||||
|
return LocationLink(self.range, obj.range, obj.ranges["id"])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@validate("name")
|
@validate("name")
|
||||||
def obj_widget(self):
|
def obj_widget(self):
|
||||||
object = self.context[ScopeCtx].objects.get(self.tokens["name"])
|
object = self.context[ScopeCtx].objects.get(self.tokens["name"])
|
||||||
|
@ -88,6 +94,10 @@ class ExtSizeGroupWidgets(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate widgets block")
|
self.validate_unique_in_parent("Duplicate widgets block")
|
||||||
|
|
||||||
|
@docs("widgets")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtSizeGroupWidgets")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
@ -96,3 +106,13 @@ class ExtSizeGroupWidgets(AstNode):
|
||||||
)
|
)
|
||||||
def size_group_completer(lsp, ast_node, match_variables):
|
def size_group_completer(lsp, ast_node, match_variables):
|
||||||
yield Completion("widgets", CompletionItemKind.Snippet, snippet="widgets [$0]")
|
yield Completion("widgets", CompletionItemKind.Snippet, snippet="widgets [$0]")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("widgets")
|
||||||
|
def size_group_decompiler(ctx, gir: gir.GirContext):
|
||||||
|
ctx.print("widgets [")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("widget")
|
||||||
|
def widget_decompiler(ctx, gir: gir.GirContext, name: str):
|
||||||
|
ctx.print(name + ",")
|
||||||
|
|
|
@ -57,7 +57,7 @@ class ExtStringListStrings(AstNode):
|
||||||
self.group.tokens["strings"].range,
|
self.group.tokens["strings"].range,
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("items")
|
@validate("strings")
|
||||||
def container_is_string_list(self):
|
def container_is_string_list(self):
|
||||||
validate_parent_type(self, "Gtk", "StringList", "StringList items")
|
validate_parent_type(self, "Gtk", "StringList", "StringList items")
|
||||||
|
|
||||||
|
@ -65,6 +65,10 @@ class ExtStringListStrings(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate strings block")
|
self.validate_unique_in_parent("Duplicate strings block")
|
||||||
|
|
||||||
|
@docs("strings")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtStringListStrings")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
@ -73,3 +77,25 @@ class ExtStringListStrings(AstNode):
|
||||||
)
|
)
|
||||||
def strings_completer(lsp, ast_node, match_variables):
|
def strings_completer(lsp, ast_node, match_variables):
|
||||||
yield Completion("strings", CompletionItemKind.Snippet, snippet="strings [$0]")
|
yield Completion("strings", CompletionItemKind.Snippet, snippet="strings [$0]")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("items", parent_type="Gtk.StringList")
|
||||||
|
def decompile_strings(ctx: DecompileCtx, gir: gir.GirContext):
|
||||||
|
ctx.print("strings [")
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("item", cdata=True, parent_type="Gtk.StringList")
|
||||||
|
def decompile_item(
|
||||||
|
ctx: DecompileCtx,
|
||||||
|
gir: gir.GirContext,
|
||||||
|
translatable="false",
|
||||||
|
comments=None,
|
||||||
|
context=None,
|
||||||
|
cdata=None,
|
||||||
|
):
|
||||||
|
comments, translatable = decompile_translatable(
|
||||||
|
cdata, translatable, context, comments
|
||||||
|
)
|
||||||
|
if comments is not None:
|
||||||
|
ctx.print(comments)
|
||||||
|
ctx.print(translatable + ",")
|
||||||
|
|
|
@ -70,6 +70,10 @@ class ExtStyles(AstNode):
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
self.validate_unique_in_parent("Duplicate styles block")
|
self.validate_unique_in_parent("Duplicate styles block")
|
||||||
|
|
||||||
|
@docs("styles")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtStyles")
|
||||||
|
|
||||||
|
|
||||||
@completer(
|
@completer(
|
||||||
applies_in=[ObjectContent],
|
applies_in=[ObjectContent],
|
||||||
|
|
|
@ -22,7 +22,7 @@ from functools import cached_property
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .gobject_object import Object
|
from .gobject_object import Object
|
||||||
from .response_id import ExtResponse
|
from .response_id import ExtResponse, decompile_response_type
|
||||||
|
|
||||||
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [
|
ALLOWED_PARENTS: T.List[T.Tuple[str, str]] = [
|
||||||
("Gtk", "Buildable"),
|
("Gtk", "Buildable"),
|
||||||
|
@ -53,6 +53,10 @@ class ChildExtension(AstNode):
|
||||||
def child(self) -> ExtResponse:
|
def child(self) -> ExtResponse:
|
||||||
return self.children[0]
|
return self.children[0]
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ChildExtension")
|
||||||
|
|
||||||
|
|
||||||
class ChildAnnotation(AstNode):
|
class ChildAnnotation(AstNode):
|
||||||
grammar = ["[", AnyOf(ChildInternal, ChildExtension, ChildType), "]"]
|
grammar = ["[", AnyOf(ChildInternal, ChildExtension, ChildType), "]"]
|
||||||
|
@ -127,10 +131,15 @@ class Child(AstNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@decompiler("child")
|
@decompiler("child", element=True)
|
||||||
def decompile_child(ctx, gir, type=None, internal_child=None):
|
def decompile_child(ctx, gir, element):
|
||||||
if type is not None:
|
if type := element["type"]:
|
||||||
|
if type == "action":
|
||||||
|
if decompiled := decompile_response_type(ctx.parent_node, element):
|
||||||
|
ctx.print(decompiled)
|
||||||
|
return
|
||||||
|
|
||||||
ctx.print(f"[{type}]")
|
ctx.print(f"[{type}]")
|
||||||
elif internal_child is not None:
|
elif internal_child := element["internal-child"]:
|
||||||
ctx.print(f"[internal-child {internal_child}]")
|
ctx.print(f"[internal-child {internal_child}]")
|
||||||
return gir
|
return gir
|
||||||
|
|
|
@ -88,6 +88,10 @@ class Template(Object):
|
||||||
f"Only one template may be defined per file, but this file contains {len(self.parent.children[Template])}",
|
f"Only one template may be defined per file, but this file contains {len(self.parent.children[Template])}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs("id")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Template")
|
||||||
|
|
||||||
|
|
||||||
@decompiler("template")
|
@decompiler("template")
|
||||||
def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
|
def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
|
||||||
|
@ -97,8 +101,9 @@ def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
|
||||||
else:
|
else:
|
||||||
return "$" + cname
|
return "$" + cname
|
||||||
|
|
||||||
|
if parent is None:
|
||||||
|
ctx.print(f"template {class_name(klass)} {{")
|
||||||
|
else:
|
||||||
ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
|
ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
|
||||||
|
|
||||||
ctx.template_class = klass
|
|
||||||
|
|
||||||
return ctx.type_by_cname(klass) or ctx.type_by_cname(parent)
|
return ctx.type_by_cname(klass) or ctx.type_by_cname(parent)
|
||||||
|
|
|
@ -59,15 +59,13 @@ class GtkDirective(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_namespace(self):
|
def gir_namespace(self):
|
||||||
# validate the GTK version first to make sure the more specific error
|
|
||||||
# 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
|
# For better error handling, just assume it's 4.0
|
||||||
return gir.get_namespace("Gtk", "4.0")
|
return gir.get_namespace("Gtk", "4.0")
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax GtkDecl")
|
||||||
|
|
||||||
|
|
||||||
class Import(AstNode):
|
class Import(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
|
@ -86,11 +84,26 @@ class Import(AstNode):
|
||||||
|
|
||||||
@validate("namespace", "version")
|
@validate("namespace", "version")
|
||||||
def namespace_exists(self):
|
def namespace_exists(self):
|
||||||
gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
gir.get_namespace(self.namespace, self.version)
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def unused(self):
|
||||||
|
if self.namespace not in self.root.used_imports:
|
||||||
|
raise UnusedWarning(
|
||||||
|
f"Unused import: {self.namespace}",
|
||||||
|
self.range,
|
||||||
|
actions=[
|
||||||
|
CodeAction("Remove import", "", self.range.with_trailing_newline)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_namespace(self):
|
def gir_namespace(self):
|
||||||
try:
|
try:
|
||||||
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
return gir.get_namespace(self.namespace, self.version)
|
||||||
except CompileError:
|
except CompileError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Using")
|
||||||
|
|
|
@ -123,3 +123,42 @@ class ExtResponse(AstNode):
|
||||||
|
|
||||||
object = self.parent_by_type(Child).object
|
object = self.parent_by_type(Child).object
|
||||||
return object.id
|
return object.id
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax ExtResponse")
|
||||||
|
|
||||||
|
@docs("response_id")
|
||||||
|
def response_id_docs(self):
|
||||||
|
if enum := self.root.gir.get_type("ResponseType", "Gtk"):
|
||||||
|
if member := enum.members.get(self.response_id, None):
|
||||||
|
return member.doc
|
||||||
|
|
||||||
|
|
||||||
|
def decompile_response_type(parent_element, child_element):
|
||||||
|
obj_id = None
|
||||||
|
for obj in child_element.children:
|
||||||
|
if obj.tag == "object":
|
||||||
|
obj_id = obj["id"]
|
||||||
|
break
|
||||||
|
|
||||||
|
if obj_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for child in parent_element.children:
|
||||||
|
if child.tag == "action-widgets":
|
||||||
|
for action_widget in child.children:
|
||||||
|
if action_widget.cdata == obj_id:
|
||||||
|
response_id = action_widget["response"]
|
||||||
|
is_default = (
|
||||||
|
" default" if decompile.truthy(action_widget["default"]) else ""
|
||||||
|
)
|
||||||
|
return f"[action response={response_id}{is_default}]"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@decompiler("action-widgets", skip_children=True)
|
||||||
|
def decompile_action_widgets(ctx, gir):
|
||||||
|
# This is handled in the <child> decompiler and decompile_response_type above
|
||||||
|
pass
|
||||||
|
|
|
@ -29,3 +29,7 @@ class TranslationDomain(AstNode):
|
||||||
@property
|
@property
|
||||||
def domain(self):
|
def domain(self):
|
||||||
return self.tokens["domain"]
|
return self.tokens["domain"]
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax TranslationDomain")
|
||||||
|
|
|
@ -78,9 +78,10 @@ class TypeName(AstNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_ns(self):
|
def gir_ns(self) -> T.Optional[gir.Namespace]:
|
||||||
if not self.tokens["extern"]:
|
if not self.tokens["extern"]:
|
||||||
return self.root.gir.namespaces.get(self.tokens["namespace"] or "Gtk")
|
return self.root.gir.namespaces.get(self.tokens["namespace"] or "Gtk")
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_type(self) -> gir.GirType:
|
def gir_type(self) -> gir.GirType:
|
||||||
|
|
|
@ -27,6 +27,7 @@ from .gtk_menu import Menu, menu
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
from .imports import GtkDirective, Import
|
from .imports import GtkDirective, Import
|
||||||
from .translation_domain import TranslationDomain
|
from .translation_domain import TranslationDomain
|
||||||
|
from .types import TypeName
|
||||||
|
|
||||||
|
|
||||||
class UI(AstNode):
|
class UI(AstNode):
|
||||||
|
@ -121,6 +122,22 @@ class UI(AstNode):
|
||||||
Range(pos, pos, self.group.text),
|
Range(pos, pos, self.group.text),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def used_imports(self) -> T.Optional[T.Set[str]]:
|
||||||
|
def _iter_recursive(node: AstNode):
|
||||||
|
yield node
|
||||||
|
for child in node.children:
|
||||||
|
if isinstance(child, AstNode):
|
||||||
|
yield from _iter_recursive(child)
|
||||||
|
|
||||||
|
result = set()
|
||||||
|
for node in _iter_recursive(self):
|
||||||
|
if isinstance(node, TypeName):
|
||||||
|
ns = node.gir_ns
|
||||||
|
if ns is not None:
|
||||||
|
result.add(ns.name)
|
||||||
|
return result
|
||||||
|
|
||||||
@context(ScopeCtx)
|
@context(ScopeCtx)
|
||||||
def scope_ctx(self) -> ScopeCtx:
|
def scope_ctx(self) -> ScopeCtx:
|
||||||
return ScopeCtx(node=self)
|
return ScopeCtx(node=self)
|
||||||
|
|
|
@ -19,8 +19,12 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
|
from blueprintcompiler.gir import ArrayType
|
||||||
|
from blueprintcompiler.lsp_utils import SemanticToken
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ScopeCtx, ValueTypeCtx
|
from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
|
||||||
|
from .expression import Expression
|
||||||
from .gobject_object import Object
|
from .gobject_object import Object
|
||||||
from .types import TypeName
|
from .types import TypeName
|
||||||
|
|
||||||
|
@ -54,6 +58,23 @@ class Translated(AstNode):
|
||||||
f"Cannot convert translated string to {expected_type.full_name}"
|
f"Cannot convert translated string to {expected_type.full_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@validate("context")
|
||||||
|
def context_double_quoted(self):
|
||||||
|
if self.translate_context is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not str(self.group.tokens["context"]).startswith('"'):
|
||||||
|
raise CompileWarning("gettext may not recognize single-quoted strings")
|
||||||
|
|
||||||
|
@validate("string")
|
||||||
|
def string_double_quoted(self):
|
||||||
|
if not str(self.group.tokens["string"]).startswith('"'):
|
||||||
|
raise CompileWarning("gettext may not recognize single-quoted strings")
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Translated")
|
||||||
|
|
||||||
|
|
||||||
class TypeLiteral(AstNode):
|
class TypeLiteral(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -99,6 +120,10 @@ class TypeLiteral(AstNode):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax TypeLiteral")
|
||||||
|
|
||||||
|
|
||||||
class QuotedLiteral(AstNode):
|
class QuotedLiteral(AstNode):
|
||||||
grammar = UseQuoted("value")
|
grammar = UseQuoted("value")
|
||||||
|
@ -200,15 +225,22 @@ class Flag(AstNode):
|
||||||
return self.tokens["value"]
|
return self.tokens["value"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> T.Optional[int]:
|
def value(self) -> T.Optional[str]:
|
||||||
type = self.context[ValueTypeCtx].value_type
|
type = self.context[ValueTypeCtx].value_type
|
||||||
if not isinstance(type, Enumeration):
|
if not isinstance(type, Enumeration):
|
||||||
return None
|
return None
|
||||||
elif member := type.members.get(self.name):
|
elif member := type.members.get(self.name):
|
||||||
return member.value
|
return member.nick
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
|
||||||
|
yield SemanticToken(
|
||||||
|
self.group.tokens["value"].start,
|
||||||
|
self.group.tokens["value"].end,
|
||||||
|
SemanticTokenType.EnumMember,
|
||||||
|
)
|
||||||
|
|
||||||
@docs()
|
@docs()
|
||||||
def docs(self):
|
def docs(self):
|
||||||
type = self.context[ValueTypeCtx].value_type
|
type = self.context[ValueTypeCtx].value_type
|
||||||
|
@ -249,6 +281,10 @@ class Flags(AstNode):
|
||||||
if expected_type is not None and not isinstance(expected_type, gir.Bitfield):
|
if expected_type is not None and not isinstance(expected_type, gir.Bitfield):
|
||||||
raise CompileError(f"{expected_type.full_name} is not a bitfield type")
|
raise CompileError(f"{expected_type.full_name} is not a bitfield type")
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Flags")
|
||||||
|
|
||||||
|
|
||||||
class IdentLiteral(AstNode):
|
class IdentLiteral(AstNode):
|
||||||
grammar = UseIdent("value")
|
grammar = UseIdent("value")
|
||||||
|
@ -297,7 +333,12 @@ class IdentLiteral(AstNode):
|
||||||
if self.ident == "null":
|
if self.ident == "null":
|
||||||
if not self.context[ValueTypeCtx].allow_null:
|
if not self.context[ValueTypeCtx].allow_null:
|
||||||
raise CompileError("null is not permitted here")
|
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(
|
raise CompileError(
|
||||||
f"Could not find object with ID {self.ident}",
|
f"Could not find object with ID {self.ident}",
|
||||||
did_you_mean=(
|
did_you_mean=(
|
||||||
|
@ -385,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):
|
class Value(AstNode):
|
||||||
grammar = AnyOf(Translated, Flags, Literal)
|
grammar = AnyOf(Translated, Flags, Literal)
|
||||||
|
|
||||||
|
@ -395,6 +465,68 @@ class Value(AstNode):
|
||||||
return self.children[0]
|
return self.children[0]
|
||||||
|
|
||||||
|
|
||||||
|
class ArrayValue(AstNode):
|
||||||
|
grammar = ["[", Delimited(Value, ","), "]"]
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def validate_for_type(self) -> None:
|
||||||
|
expected_type = self.gir_type
|
||||||
|
if expected_type is not None and not isinstance(expected_type, gir.ArrayType):
|
||||||
|
raise CompileError(f"Cannot assign array to {expected_type.full_name}")
|
||||||
|
|
||||||
|
if expected_type is not None and not isinstance(
|
||||||
|
expected_type.inner, StringType
|
||||||
|
):
|
||||||
|
raise CompileError("Only string arrays are supported")
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def validate_invalid_newline(self) -> None:
|
||||||
|
expected_type = self.gir_type
|
||||||
|
if isinstance(expected_type, gir.ArrayType) and isinstance(
|
||||||
|
expected_type.inner, StringType
|
||||||
|
):
|
||||||
|
errors = []
|
||||||
|
for value in self.values:
|
||||||
|
if isinstance(value.child, Literal) and isinstance(
|
||||||
|
value.child.value, QuotedLiteral
|
||||||
|
):
|
||||||
|
quoted_literal = value.child.value
|
||||||
|
literal_value = quoted_literal.value
|
||||||
|
# literal_value can be None if there's an invalid escape sequence
|
||||||
|
if literal_value is not None and "\n" in literal_value:
|
||||||
|
errors.append(
|
||||||
|
CompileError(
|
||||||
|
"String literals inside arrays can't contain newlines",
|
||||||
|
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)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def values(self) -> T.List[Value]:
|
||||||
|
return self.children
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gir_type(self):
|
||||||
|
return self.parent.context[ValueTypeCtx].value_type
|
||||||
|
|
||||||
|
@context(ValueTypeCtx)
|
||||||
|
def child_value(self):
|
||||||
|
if self.gir_type is None or not isinstance(self.gir_type, ArrayType):
|
||||||
|
return ValueTypeCtx(None)
|
||||||
|
else:
|
||||||
|
return ValueTypeCtx(self.gir_type.inner)
|
||||||
|
|
||||||
|
|
||||||
class StringValue(AstNode):
|
class StringValue(AstNode):
|
||||||
grammar = AnyOf(Translated, QuotedLiteral)
|
grammar = AnyOf(Translated, QuotedLiteral)
|
||||||
|
|
||||||
|
|
|
@ -102,10 +102,10 @@ class OpenFile:
|
||||||
]
|
]
|
||||||
|
|
||||||
# convert line, column numbers to deltas
|
# convert line, column numbers to deltas
|
||||||
for i, token_list in enumerate(token_lists[1:]):
|
for a, b in zip(token_lists[-2::-1], token_lists[:0:-1]):
|
||||||
token_list[0] -= token_lists[i][0]
|
b[0] -= a[0]
|
||||||
if token_list[0] == 0:
|
if b[0] == 0:
|
||||||
token_list[1] -= token_lists[i][1]
|
b[1] -= a[1]
|
||||||
|
|
||||||
# flatten the list
|
# flatten the list
|
||||||
return [x for y in token_lists for x in y]
|
return [x for y in token_lists for x in y]
|
||||||
|
@ -118,6 +118,7 @@ class LanguageServer:
|
||||||
self.client_capabilities = {}
|
self.client_capabilities = {}
|
||||||
self.client_supports_completion_choice = False
|
self.client_supports_completion_choice = False
|
||||||
self._open_files: T.Dict[str, OpenFile] = {}
|
self._open_files: T.Dict[str, OpenFile] = {}
|
||||||
|
self._exited = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# Read <doc> tags from gir files. During normal compilation these are
|
# Read <doc> tags from gir files. During normal compilation these are
|
||||||
|
@ -125,7 +126,7 @@ class LanguageServer:
|
||||||
xml_reader.PARSE_GIR.add("doc")
|
xml_reader.PARSE_GIR.add("doc")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while not self._exited:
|
||||||
line = ""
|
line = ""
|
||||||
content_len = -1
|
content_len = -1
|
||||||
while content_len == -1 or (line != "\n" and line != "\r\n"):
|
while content_len == -1 or (line != "\n" and line != "\r\n"):
|
||||||
|
@ -149,7 +150,7 @@ class LanguageServer:
|
||||||
|
|
||||||
def _send(self, data):
|
def _send(self, data):
|
||||||
data["jsonrpc"] = "2.0"
|
data["jsonrpc"] = "2.0"
|
||||||
line = json.dumps(data, separators=(",", ":")) + "\r\n"
|
line = json.dumps(data, separators=(",", ":"))
|
||||||
printerr("output: " + line)
|
printerr("output: " + line)
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
f"Content-Length: {len(line.encode())}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{line}"
|
f"Content-Length: {len(line.encode())}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n{line}"
|
||||||
|
@ -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")
|
@command("textDocument/didOpen")
|
||||||
def didOpen(self, id, params):
|
def didOpen(self, id, params):
|
||||||
doc = params.get("textDocument")
|
doc = params.get("textDocument")
|
||||||
|
@ -472,9 +481,12 @@ class LanguageServer:
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(err, DeprecationWarning):
|
if isinstance(err, DeprecatedWarning):
|
||||||
result["tags"] = [DiagnosticTag.Deprecated]
|
result["tags"] = [DiagnosticTag.Deprecated]
|
||||||
|
|
||||||
|
if isinstance(err, UnusedWarning):
|
||||||
|
result["tags"] = [DiagnosticTag.Unnecessary]
|
||||||
|
|
||||||
if len(err.references) > 0:
|
if len(err.references) > 0:
|
||||||
result["relatedInformation"] = [
|
result["relatedInformation"] = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import typing as T
|
import typing as T
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
@ -84,6 +86,7 @@ class Completion:
|
||||||
docs: T.Optional[str] = None
|
docs: T.Optional[str] = None
|
||||||
text: T.Optional[str] = None
|
text: T.Optional[str] = None
|
||||||
snippet: T.Optional[str] = None
|
snippet: T.Optional[str] = None
|
||||||
|
detail: T.Optional[str] = None
|
||||||
|
|
||||||
def to_json(self, snippets: bool):
|
def to_json(self, snippets: bool):
|
||||||
insert_text = self.text or self.label
|
insert_text = self.text or self.label
|
||||||
|
@ -96,7 +99,8 @@ class Completion:
|
||||||
"label": self.label,
|
"label": self.label,
|
||||||
"kind": self.kind,
|
"kind": self.kind,
|
||||||
"tags": [CompletionItemTag.Deprecated] if self.deprecated else None,
|
"tags": [CompletionItemTag.Deprecated] if self.deprecated else None,
|
||||||
"detail": self.signature,
|
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemLabelDetails
|
||||||
|
"labelDetails": ({"detail": self.signature} if self.signature else None),
|
||||||
"documentation": (
|
"documentation": (
|
||||||
{
|
{
|
||||||
"kind": "markdown",
|
"kind": "markdown",
|
||||||
|
@ -109,6 +113,7 @@ class Completion:
|
||||||
"sortText": self.sort_text,
|
"sortText": self.sort_text,
|
||||||
"insertText": insert_text,
|
"insertText": insert_text,
|
||||||
"insertTextFormat": insert_text_format,
|
"insertTextFormat": insert_text_format,
|
||||||
|
"detail": self.detail if self.detail else None,
|
||||||
}
|
}
|
||||||
return {k: v for k, v in result.items() if v is not None}
|
return {k: v for k, v in result.items() if v is not None}
|
||||||
|
|
||||||
|
@ -197,3 +202,27 @@ class TextEdit:
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return {"range": self.range.to_json(), "newText": self.newText}
|
return {"range": self.range.to_json(), "newText": self.newText}
|
||||||
|
|
||||||
|
|
||||||
|
_docs_sections: T.Optional[dict[str, T.Any]] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_docs_section(section_name: str) -> T.Optional[str]:
|
||||||
|
global _docs_sections
|
||||||
|
|
||||||
|
if _docs_sections is None:
|
||||||
|
try:
|
||||||
|
with open(
|
||||||
|
os.path.join(os.path.dirname(__file__), "reference_docs.json")
|
||||||
|
) as f:
|
||||||
|
_docs_sections = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
_docs_sections = {}
|
||||||
|
|
||||||
|
if section := _docs_sections.get(section_name):
|
||||||
|
content = section["content"]
|
||||||
|
link = section["link"]
|
||||||
|
content += f"\n\n---\n\n[Online documentation]({link})"
|
||||||
|
return content
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
|
@ -25,6 +25,7 @@ import sys
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from . import formatter, interactive_port, parser, tokenizer
|
from . import formatter, interactive_port, parser, tokenizer
|
||||||
|
from .decompiler import decompile_string
|
||||||
from .errors import CompileError, CompilerBugError, PrintableError, report_bug
|
from .errors import CompileError, CompilerBugError, PrintableError, report_bug
|
||||||
from .gir import add_typelib_search_path
|
from .gir import add_typelib_search_path
|
||||||
from .lsp import LanguageServer
|
from .lsp import LanguageServer
|
||||||
|
@ -90,12 +91,28 @@ class BlueprintApp:
|
||||||
default=2,
|
default=2,
|
||||||
type=int,
|
type=int,
|
||||||
)
|
)
|
||||||
|
format.add_argument(
|
||||||
|
"-n",
|
||||||
|
"--no-diff",
|
||||||
|
help="Do not print a full diff of the changes",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
format.add_argument(
|
format.add_argument(
|
||||||
"inputs",
|
"inputs",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
metavar="filenames",
|
metavar="filenames",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
decompile = self.add_subcommand(
|
||||||
|
"decompile", "Convert .ui XML files to blueprint", self.cmd_decompile
|
||||||
|
)
|
||||||
|
decompile.add_argument("--output", dest="output", default="-")
|
||||||
|
decompile.add_argument("--typelib-path", nargs="?", action="append")
|
||||||
|
decompile.add_argument(
|
||||||
|
"input", metavar="filename", default=sys.stdin, type=argparse.FileType("r")
|
||||||
|
)
|
||||||
|
|
||||||
port = self.add_subcommand("port", "Interactive porting tool", self.cmd_port)
|
port = self.add_subcommand("port", "Interactive porting tool", self.cmd_port)
|
||||||
|
|
||||||
lsp = self.add_subcommand(
|
lsp = self.add_subcommand(
|
||||||
|
@ -221,6 +238,7 @@ class BlueprintApp:
|
||||||
file.write(formatted_str)
|
file.write(formatted_str)
|
||||||
happened = "Formatted"
|
happened = "Formatted"
|
||||||
|
|
||||||
|
if not opts.no_diff:
|
||||||
diff_lines = []
|
diff_lines = []
|
||||||
a_lines = data.splitlines(keepends=True)
|
a_lines = data.splitlines(keepends=True)
|
||||||
b_lines = formatted_str.splitlines(keepends=True)
|
b_lines = formatted_str.splitlines(keepends=True)
|
||||||
|
@ -238,6 +256,7 @@ class BlueprintApp:
|
||||||
diff_lines.append("\\ No newline at end of file\n")
|
diff_lines.append("\\ No newline at end of file\n")
|
||||||
|
|
||||||
print("".join(diff_lines))
|
print("".join(diff_lines))
|
||||||
|
|
||||||
to_print = Colors.BOLD
|
to_print = Colors.BOLD
|
||||||
if errored:
|
if errored:
|
||||||
to_print += f"{Colors.RED}Skipped {file.name}: Will not overwrite file with compile errors"
|
to_print += f"{Colors.RED}Skipped {file.name}: Will not overwrite file with compile errors"
|
||||||
|
@ -291,6 +310,24 @@ class BlueprintApp:
|
||||||
if panic:
|
if panic:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
def cmd_decompile(self, opts):
|
||||||
|
if opts.typelib_path != None:
|
||||||
|
for typelib_path in opts.typelib_path:
|
||||||
|
add_typelib_search_path(typelib_path)
|
||||||
|
|
||||||
|
data = opts.input.read()
|
||||||
|
try:
|
||||||
|
decompiled = decompile_string(data)
|
||||||
|
|
||||||
|
if opts.output == "-":
|
||||||
|
print(decompiled)
|
||||||
|
else:
|
||||||
|
with open(opts.output, "w") as file:
|
||||||
|
file.write(decompiled)
|
||||||
|
except PrintableError as e:
|
||||||
|
e.pretty_print(opts.input.name, data, stream=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def cmd_lsp(self, opts):
|
def cmd_lsp(self, opts):
|
||||||
langserv = LanguageServer()
|
langserv = LanguageServer()
|
||||||
langserv.run()
|
langserv.run()
|
||||||
|
|
|
@ -134,11 +134,25 @@ class XmlOutput(OutputFormat):
|
||||||
self._emit_expression(value.expression, xml)
|
self._emit_expression(value.expression, xml)
|
||||||
xml.end_tag()
|
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):
|
elif isinstance(value, ObjectValue):
|
||||||
xml.start_tag("property", **props)
|
xml.start_tag("property", **props)
|
||||||
self._emit_object(value.object, xml)
|
self._emit_object(value.object, xml)
|
||||||
xml.end_tag()
|
xml.end_tag()
|
||||||
|
|
||||||
|
elif isinstance(value, ArrayValue):
|
||||||
|
xml.start_tag("property", **props)
|
||||||
|
values = list(value.values)
|
||||||
|
for value in values[:-1]:
|
||||||
|
self._emit_value(value, xml)
|
||||||
|
xml.put_text("\n")
|
||||||
|
self._emit_value(values[-1], xml)
|
||||||
|
xml.end_tag()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise CompilerBugError()
|
raise CompilerBugError()
|
||||||
|
|
||||||
|
@ -150,7 +164,7 @@ class XmlOutput(OutputFormat):
|
||||||
elif isinstance(translated, QuotedLiteral):
|
elif isinstance(translated, QuotedLiteral):
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
return {"translatable": "true", "context": translated.translate_context}
|
return {"translatable": "yes", "context": translated.translate_context}
|
||||||
|
|
||||||
def _emit_signal(self, signal: Signal, xml: XmlEmitter):
|
def _emit_signal(self, signal: Signal, xml: XmlEmitter):
|
||||||
name = signal.name
|
name = signal.name
|
||||||
|
@ -160,7 +174,8 @@ class XmlOutput(OutputFormat):
|
||||||
"signal",
|
"signal",
|
||||||
name=name,
|
name=name,
|
||||||
handler=signal.handler,
|
handler=signal.handler,
|
||||||
swapped=signal.is_swapped or None,
|
swapped=signal.is_swapped,
|
||||||
|
after=signal.is_after or None,
|
||||||
object=(
|
object=(
|
||||||
self._object_id(signal, signal.object_id) if signal.object_id else None
|
self._object_id(signal, signal.object_id) if signal.object_id else None
|
||||||
),
|
),
|
||||||
|
@ -208,12 +223,6 @@ class XmlOutput(OutputFormat):
|
||||||
xml.put_text(
|
xml.put_text(
|
||||||
"|".join([str(flag.value or flag.name) for flag in value.child.flags])
|
"|".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:
|
else:
|
||||||
raise CompilerBugError()
|
raise CompilerBugError()
|
||||||
|
|
||||||
|
@ -235,6 +244,9 @@ class XmlOutput(OutputFormat):
|
||||||
raise CompilerBugError()
|
raise CompilerBugError()
|
||||||
|
|
||||||
def _emit_literal_expr(self, expr: LiteralExpr, xml: XmlEmitter):
|
def _emit_literal_expr(self, expr: LiteralExpr, xml: XmlEmitter):
|
||||||
|
if expr.is_this:
|
||||||
|
return
|
||||||
|
|
||||||
if expr.is_object:
|
if expr.is_object:
|
||||||
xml.start_tag("constant")
|
xml.start_tag("constant")
|
||||||
else:
|
else:
|
||||||
|
@ -282,8 +294,11 @@ class XmlOutput(OutputFormat):
|
||||||
def _emit_extensions(self, extension, xml: XmlEmitter):
|
def _emit_extensions(self, extension, xml: XmlEmitter):
|
||||||
if isinstance(extension, ExtAccessibility):
|
if isinstance(extension, ExtAccessibility):
|
||||||
xml.start_tag("accessibility")
|
xml.start_tag("accessibility")
|
||||||
for prop in extension.properties:
|
for property in extension.properties:
|
||||||
self._emit_attribute(prop.tag_name, "name", prop.name, prop.value, xml)
|
for val in property.values:
|
||||||
|
self._emit_attribute(
|
||||||
|
property.tag_name, "name", property.name, val, xml
|
||||||
|
)
|
||||||
xml.end_tag()
|
xml.end_tag()
|
||||||
|
|
||||||
elif isinstance(extension, AdwBreakpointCondition):
|
elif isinstance(extension, AdwBreakpointCondition):
|
||||||
|
@ -293,6 +308,9 @@ class XmlOutput(OutputFormat):
|
||||||
|
|
||||||
elif isinstance(extension, AdwBreakpointSetters):
|
elif isinstance(extension, AdwBreakpointSetters):
|
||||||
for setter in extension.setters:
|
for setter in extension.setters:
|
||||||
|
if setter.value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
attrs = {}
|
attrs = {}
|
||||||
|
|
||||||
if isinstance(setter.value.child, Translated):
|
if isinstance(setter.value.child, Translated):
|
||||||
|
@ -353,12 +371,13 @@ class XmlOutput(OutputFormat):
|
||||||
|
|
||||||
elif isinstance(extension, ExtScaleMarks):
|
elif isinstance(extension, ExtScaleMarks):
|
||||||
xml.start_tag("marks")
|
xml.start_tag("marks")
|
||||||
for mark in extension.children:
|
for mark in extension.marks:
|
||||||
|
label = mark.label.child if mark.label is not None else None
|
||||||
xml.start_tag(
|
xml.start_tag(
|
||||||
"mark",
|
"mark",
|
||||||
value=mark.value,
|
value=mark.value,
|
||||||
position=mark.position,
|
position=mark.position,
|
||||||
**self._translated_string_attrs(mark.label and mark.label.child),
|
**self._translated_string_attrs(label),
|
||||||
)
|
)
|
||||||
if mark.label is not None:
|
if mark.label is not None:
|
||||||
xml.put_text(mark.label.string)
|
xml.put_text(mark.label.string)
|
||||||
|
@ -375,9 +394,9 @@ class XmlOutput(OutputFormat):
|
||||||
xml.end_tag()
|
xml.end_tag()
|
||||||
|
|
||||||
elif isinstance(extension, ExtListItemFactory):
|
elif isinstance(extension, ExtListItemFactory):
|
||||||
child_xml = XmlEmitter()
|
child_xml = XmlEmitter(generated_notice=False)
|
||||||
child_xml.start_tag("interface")
|
child_xml.start_tag("interface")
|
||||||
child_xml.start_tag("template", **{"class": "GtkListItem"})
|
child_xml.start_tag("template", **{"class": extension.gir_class})
|
||||||
self._emit_object_or_template(extension, child_xml)
|
self._emit_object_or_template(extension, child_xml)
|
||||||
child_xml.end_tag()
|
child_xml.end_tag()
|
||||||
child_xml.end_tag()
|
child_xml.end_tag()
|
||||||
|
|
|
@ -40,7 +40,9 @@ class XmlEmitter:
|
||||||
self._tag_stack = []
|
self._tag_stack = []
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
def start_tag(self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None]):
|
def start_tag(
|
||||||
|
self, tag, **attrs: T.Union[str, GirType, ClassName, bool, None, float]
|
||||||
|
):
|
||||||
self._indent()
|
self._indent()
|
||||||
self.result += f"<{tag}"
|
self.result += f"<{tag}"
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
|
@ -71,6 +73,7 @@ class XmlEmitter:
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
def put_cdata(self, text: str):
|
def put_cdata(self, text: str):
|
||||||
|
text = text.replace("]]>", "]]]]><![CDATA[>")
|
||||||
self.result += f"<![CDATA[{text}]]>"
|
self.result += f"<![CDATA[{text}]]>"
|
||||||
self._needs_newline = False
|
self._needs_newline = False
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
""" Utilities for parsing an AST from a token stream. """
|
"""Utilities for parsing an AST from a token stream."""
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -95,19 +95,11 @@ class ParseGroup:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
|
return self.ast_type(self, children, self.keys, incomplete=self.incomplete)
|
||||||
except TypeError as e:
|
except TypeError: # pragma: no cover
|
||||||
raise CompilerBugError(
|
raise CompilerBugError(
|
||||||
f"Failed to construct ast.{self.ast_type.__name__} from ParseGroup. See the previous stacktrace."
|
f"Failed to construct ast.{self.ast_type.__name__} from ParseGroup. See the previous stacktrace."
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
result = str(self.ast_type.__name__)
|
|
||||||
result += "".join([f"\n{key}: {val}" for key, val in self.keys.items()]) + "\n"
|
|
||||||
result += "\n".join(
|
|
||||||
[str(child) for children in self.children.values() for child in children]
|
|
||||||
)
|
|
||||||
return result.replace("\n", "\n ")
|
|
||||||
|
|
||||||
|
|
||||||
class ParseContext:
|
class ParseContext:
|
||||||
"""Contains the state of the parser."""
|
"""Contains the state of the parser."""
|
||||||
|
@ -265,10 +257,6 @@ class ParseNode:
|
||||||
"""Convenience method for err()."""
|
"""Convenience method for err()."""
|
||||||
return self.err("Expected " + expect)
|
return self.err("Expected " + expect)
|
||||||
|
|
||||||
def warn(self, message) -> "Warning":
|
|
||||||
"""Causes this ParseNode to emit a warning if it parses successfully."""
|
|
||||||
return Warning(self, message)
|
|
||||||
|
|
||||||
|
|
||||||
class Err(ParseNode):
|
class Err(ParseNode):
|
||||||
"""ParseNode that emits a compile error if it fails to parse."""
|
"""ParseNode that emits a compile error if it fails to parse."""
|
||||||
|
@ -290,27 +278,6 @@ class Err(ParseNode):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Warning(ParseNode):
|
|
||||||
"""ParseNode that emits a compile warning if it parses successfully."""
|
|
||||||
|
|
||||||
def __init__(self, child, message: str):
|
|
||||||
self.child = to_parse_node(child)
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def _parse(self, ctx: ParseContext):
|
|
||||||
ctx.skip()
|
|
||||||
start_idx = ctx.index
|
|
||||||
if self.child.parse(ctx).succeeded():
|
|
||||||
start_token = ctx.tokens[start_idx]
|
|
||||||
end_token = ctx.tokens[ctx.index]
|
|
||||||
ctx.warnings.append(
|
|
||||||
CompileWarning(self.message, start_token.start, end_token.end)
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Fail(ParseNode):
|
class Fail(ParseNode):
|
||||||
"""ParseNode that emits a compile error if it parses successfully."""
|
"""ParseNode that emits a compile error if it parses successfully."""
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,9 @@ def parse(
|
||||||
original_text = tokens[0].string if len(tokens) else ""
|
original_text = tokens[0].string if len(tokens) else ""
|
||||||
ctx = ParseContext(tokens, original_text)
|
ctx = ParseContext(tokens, original_text)
|
||||||
AnyOf(UI).parse(ctx)
|
AnyOf(UI).parse(ctx)
|
||||||
ast_node = ctx.last_group.to_ast() if ctx.last_group else None
|
|
||||||
|
assert ctx.last_group is not None
|
||||||
|
ast_node = ctx.last_group.to_ast()
|
||||||
|
|
||||||
errors = [*ctx.errors, *ast_node.errors]
|
errors = [*ctx.errors, *ast_node.errors]
|
||||||
warnings = [*ctx.warnings, *ast_node.warnings]
|
warnings = [*ctx.warnings, *ast_node.warnings]
|
||||||
|
|
|
@ -127,6 +127,13 @@ class Range:
|
||||||
def text(self) -> str:
|
def text(self) -> str:
|
||||||
return self.original_text[self.start : self.end]
|
return self.original_text[self.start : self.end]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_trailing_newline(self) -> "Range":
|
||||||
|
if len(self.original_text) > self.end and self.original_text[self.end] == "\n":
|
||||||
|
return Range(self.start, self.end + 1, self.original_text)
|
||||||
|
else:
|
||||||
|
return self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def join(a: T.Optional["Range"], b: T.Optional["Range"]) -> T.Optional["Range"]:
|
def join(a: T.Optional["Range"], b: T.Optional["Range"]) -> T.Optional["Range"]:
|
||||||
if a is None:
|
if a is None:
|
||||||
|
|
|
@ -148,8 +148,8 @@ class Typelib:
|
||||||
SIGNATURE_ARGUMENTS = Field(0x8, "offset")
|
SIGNATURE_ARGUMENTS = Field(0x8, "offset")
|
||||||
|
|
||||||
ATTR_OFFSET = Field(0x0, "u32")
|
ATTR_OFFSET = Field(0x0, "u32")
|
||||||
ATTR_NAME = Field(0x0, "string")
|
ATTR_NAME = Field(0x4, "string")
|
||||||
ATTR_VALUE = Field(0x0, "string")
|
ATTR_VALUE = Field(0x8, "string")
|
||||||
|
|
||||||
TYPE_BLOB_TAG = Field(0x0, "u8", 3, 5)
|
TYPE_BLOB_TAG = Field(0x0, "u8", 3, 5)
|
||||||
TYPE_BLOB_INTERFACE = Field(0x2, "dir_entry")
|
TYPE_BLOB_INTERFACE = Field(0x2, "dir_entry")
|
||||||
|
|
|
@ -109,14 +109,14 @@ class UnescapeError(Exception):
|
||||||
|
|
||||||
def escape_quote(string: str) -> str:
|
def escape_quote(string: str) -> str:
|
||||||
return (
|
return (
|
||||||
"'"
|
'"'
|
||||||
+ (
|
+ (
|
||||||
string.replace("\\", "\\\\")
|
string.replace("\\", "\\\\")
|
||||||
.replace("'", "\\'")
|
.replace('"', '\\"')
|
||||||
.replace("\n", "\\n")
|
.replace("\n", "\\n")
|
||||||
.replace("\t", "\\t")
|
.replace("\t", "\\t")
|
||||||
)
|
)
|
||||||
+ "'"
|
+ '"'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ git clone --depth=1 https://gitlab.gnome.org/GNOME/gtk.git
|
||||||
cd gtk
|
cd gtk
|
||||||
meson setup builddir \
|
meson setup builddir \
|
||||||
--prefix=/usr \
|
--prefix=/usr \
|
||||||
-Dgtk_doc=true \
|
-Ddocumentation=true \
|
||||||
-Ddemos=false \
|
-Dbuild-demos=false \
|
||||||
-Dbuild-examples=false \
|
-Dbuild-examples=false \
|
||||||
-Dbuild-tests=false \
|
-Dbuild-tests=false \
|
||||||
-Dbuild-testsuite=false
|
-Dbuild-testsuite=false
|
||||||
|
|
139
docs/collect-sections.py
Executable file
139
docs/collect-sections.py
Executable file
|
@ -0,0 +1,139 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
__all__ = ["get_docs_section"]
|
||||||
|
|
||||||
|
DOCS_ROOT = "https://gnome.pages.gitlab.gnome.org/blueprint-compiler"
|
||||||
|
|
||||||
|
|
||||||
|
sections: dict[str, "Section"] = {}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Section:
|
||||||
|
link: str
|
||||||
|
lines: str
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"content": rst_to_md(self.lines),
|
||||||
|
"link": self.link,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_reference_docs():
|
||||||
|
for filename in Path(os.path.dirname(__file__), "reference").glob("*.rst"):
|
||||||
|
with open(filename) as f:
|
||||||
|
section_name = None
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
def close_section():
|
||||||
|
if section_name:
|
||||||
|
html_file = re.sub(r"\.rst$", ".html", filename.name)
|
||||||
|
anchor = re.sub(r"[^a-z0-9]+", "-", section_name.lower())
|
||||||
|
link = f"{DOCS_ROOT}/reference/{html_file}#{anchor}"
|
||||||
|
sections[section_name] = Section(link, lines)
|
||||||
|
|
||||||
|
for line in f:
|
||||||
|
if m := re.match(r"\.\.\s+_(.*):", line):
|
||||||
|
close_section()
|
||||||
|
section_name = m.group(1)
|
||||||
|
lines = []
|
||||||
|
else:
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
close_section()
|
||||||
|
|
||||||
|
|
||||||
|
# This isn't a comprehensive rST to markdown converter, it just needs to handle the
|
||||||
|
# small subset of rST used in the reference docs.
|
||||||
|
def rst_to_md(lines: list[str]) -> str:
|
||||||
|
result = ""
|
||||||
|
|
||||||
|
def rst_to_md_inline(line):
|
||||||
|
line = re.sub(r"``(.*?)``", r"`\1`", line)
|
||||||
|
line = re.sub(
|
||||||
|
r":ref:`(.*?)<(.*?)>`",
|
||||||
|
lambda m: f"[{m.group(1)}]({sections[m.group(2)].link})",
|
||||||
|
line,
|
||||||
|
)
|
||||||
|
line = re.sub(r"`([^`]*?) <([^`>]*?)>`_", r"[\1](\2)", line)
|
||||||
|
return line
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
n = len(lines)
|
||||||
|
heading_levels = {}
|
||||||
|
|
||||||
|
def print_block(lang: str = "", code: bool = True, strip_links: bool = False):
|
||||||
|
nonlocal result, i
|
||||||
|
block = ""
|
||||||
|
while i < n:
|
||||||
|
line = lines[i].rstrip()
|
||||||
|
if line.startswith(" "):
|
||||||
|
line = line[3:]
|
||||||
|
elif line != "":
|
||||||
|
break
|
||||||
|
|
||||||
|
if strip_links:
|
||||||
|
line = re.sub(r":ref:`(.*?)<(.*?)>`", r"\1", line)
|
||||||
|
|
||||||
|
if not code:
|
||||||
|
line = rst_to_md_inline(line)
|
||||||
|
|
||||||
|
block += line + "\n"
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if code:
|
||||||
|
result += f"```{lang}\n{block.strip()}\n```\n\n"
|
||||||
|
else:
|
||||||
|
result += block
|
||||||
|
|
||||||
|
while i < n:
|
||||||
|
line = lines[i].rstrip()
|
||||||
|
i += 1
|
||||||
|
if line == ".. rst-class:: grammar-block":
|
||||||
|
print_block("text", strip_links=True)
|
||||||
|
elif line == ".. code-block:: blueprint":
|
||||||
|
print_block("blueprint")
|
||||||
|
elif line == ".. note::":
|
||||||
|
result += "#### Note\n"
|
||||||
|
print_block(code=False)
|
||||||
|
elif m := re.match(r"\.\. image:: (.*)", line):
|
||||||
|
result += f"})\n"
|
||||||
|
elif i < n and re.match(r"^((-+)|(~+)|(\++))$", lines[i]):
|
||||||
|
level_char = lines[i][0]
|
||||||
|
if level_char not in heading_levels:
|
||||||
|
heading_levels[level_char] = max(heading_levels.values(), default=1) + 1
|
||||||
|
result += (
|
||||||
|
"#" * heading_levels[level_char] + " " + rst_to_md_inline(line) + "\n"
|
||||||
|
)
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
result += rst_to_md_inline(line) + "\n"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: collect_sections.py <output_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
outfile = sys.argv[1]
|
||||||
|
|
||||||
|
load_reference_docs()
|
||||||
|
|
||||||
|
# print the sections to a json file
|
||||||
|
with open(outfile, "w") as f:
|
||||||
|
json.dump(
|
||||||
|
{name: section.to_json() for name, section in sections.items()},
|
||||||
|
f,
|
||||||
|
indent=2,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
|
@ -16,8 +16,8 @@ a module in your flatpak manifest:
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.gnome.org/jwestman/blueprint-compiler",
|
"url": "https://gitlab.gnome.org/GNOME/blueprint-compiler",
|
||||||
"tag": "v0.12.0"
|
"tag": "v0.16.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
|
||||||
|
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
|
|
||||||
template MyAppWindow : ApplicationWindow {
|
template $MyAppWindow: ApplicationWindow {
|
||||||
default-width: 600;
|
default-width: 600;
|
||||||
default-height: 300;
|
default-height: 300;
|
||||||
title: _("Hello, Blueprint!");
|
title: _("Hello, Blueprint!");
|
||||||
|
@ -35,7 +35,7 @@ Blueprint is a markup language and compiler for GTK 4 user interfaces.
|
||||||
HeaderBar {}
|
HeaderBar {}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
label: bind MyAppWindow.main_text;
|
label: bind template.main_text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ Features
|
||||||
Links
|
Links
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- `Source code <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
|
- `Source code <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
|
||||||
- `Workbench <https://github.com/sonnyp/Workbench>`_ lets you try, preview and export Blueprint
|
- `Workbench <https://github.com/sonnyp/Workbench>`_ lets you try, preview and export Blueprint
|
||||||
- `GNOME Builder <https://developer.gnome.org/documentation/introduction/builder.html>`_ provides builtin support
|
- `GNOME Builder <https://developer.gnome.org/documentation/introduction/builder.html>`_ provides builtin support
|
||||||
- `Vim syntax highlighting plugin by thetek42 <https://github.com/thetek42/vim-blueprint-syntax>`_
|
- `Vim syntax highlighting plugin by thetek42 <https://github.com/thetek42/vim-blueprint-syntax>`_
|
||||||
|
@ -82,10 +82,12 @@ Built with Blueprint
|
||||||
- `Blurble <https://gitlab.gnome.org/World/Blurble>`_
|
- `Blurble <https://gitlab.gnome.org/World/Blurble>`_
|
||||||
- `Bottles <https://github.com/bottlesdevs/Bottles>`_
|
- `Bottles <https://github.com/bottlesdevs/Bottles>`_
|
||||||
- `Cartridges <https://github.com/kra-mo/cartridges>`_
|
- `Cartridges <https://github.com/kra-mo/cartridges>`_
|
||||||
|
- `Cassette <https://gitlab.gnome.org/Rirusha/Cassette>`_
|
||||||
- `Cavalier <https://github.com/NickvisionApps/Cavalier>`_
|
- `Cavalier <https://github.com/NickvisionApps/Cavalier>`_
|
||||||
- `Chance <https://zelikos.dev/apps/rollit>`_
|
- `Chance <https://zelikos.dev/apps/rollit>`_
|
||||||
- `Commit <https://github.com/sonnyp/Commit/>`_
|
- `Commit <https://github.com/sonnyp/Commit/>`_
|
||||||
- `Confy <https://confy.kirgroup.net/>`_
|
- `Confy <https://confy.kirgroup.net/>`_
|
||||||
|
- `Cozy <https://github.com/geigi/cozy>`_
|
||||||
- `Daikhan <https://github.com/flathub/io.gitlab.daikhan.stable>`_
|
- `Daikhan <https://github.com/flathub/io.gitlab.daikhan.stable>`_
|
||||||
- `Damask <https://gitlab.gnome.org/subpop/damask>`_
|
- `Damask <https://gitlab.gnome.org/subpop/damask>`_
|
||||||
- `Denaro <https://github.com/NickvisionApps/Denaro>`_
|
- `Denaro <https://github.com/NickvisionApps/Denaro>`_
|
||||||
|
@ -93,7 +95,7 @@ Built with Blueprint
|
||||||
- `Dev Toolbox <https://github.com/aleiepure/devtoolbox>`_
|
- `Dev Toolbox <https://github.com/aleiepure/devtoolbox>`_
|
||||||
- `Dialect <https://github.com/dialect-app/dialect>`_
|
- `Dialect <https://github.com/dialect-app/dialect>`_
|
||||||
- `Diccionario de la Lengua <https://codeberg.org/rafaelmardojai/diccionario-lengua>`_
|
- `Diccionario de la Lengua <https://codeberg.org/rafaelmardojai/diccionario-lengua>`_
|
||||||
- `Dogg <https://gitlab.gnome.org/sungsphinx/Doggo>`_
|
- `Doggo <https://gitlab.gnome.org/sungsphinx/Doggo>`_
|
||||||
- `Dosage <https://github.com/diegopvlk/Dosage>`_
|
- `Dosage <https://github.com/diegopvlk/Dosage>`_
|
||||||
- `Dynamic Wallpaper <https://github.com/dusansimic/dynamic-wallpaper>`_
|
- `Dynamic Wallpaper <https://github.com/dusansimic/dynamic-wallpaper>`_
|
||||||
- `Extension Manager <https://github.com/mjakeman/extension-manager>`_
|
- `Extension Manager <https://github.com/mjakeman/extension-manager>`_
|
||||||
|
@ -115,11 +117,10 @@ Built with Blueprint
|
||||||
- `Identity <https://gitlab.gnome.org/YaLTeR/identity>`_
|
- `Identity <https://gitlab.gnome.org/YaLTeR/identity>`_
|
||||||
- `Jogger <https://codeberg.org/baarkerlounger/jogger>`_
|
- `Jogger <https://codeberg.org/baarkerlounger/jogger>`_
|
||||||
- `Junction <https://github.com/sonnyp/Junction/>`_
|
- `Junction <https://github.com/sonnyp/Junction/>`_
|
||||||
- `Komiku <https://github.com/flathub/info.febvre.Komikku>`_
|
- `Komikku <https://codeberg.org/valos/Komikku>`_
|
||||||
- `Letterpress <https://gitlab.gnome.org/World/Letterpress>`_
|
- `Letterpress <https://gitlab.gnome.org/World/Letterpress>`_
|
||||||
- `Login Manager Settings <https://github.com/realmazharhussain/gdm-settings>`_
|
- `Login Manager Settings <https://github.com/realmazharhussain/gdm-settings>`_
|
||||||
- `Maniatic Launcher <https://github.com/santiagocezar/maniatic-launcher/>`_
|
- `Maniatic Launcher <https://github.com/santiagocezar/maniatic-launcher/>`_
|
||||||
- `Maniatic Launcher <https://github.com/santiagocezar/maniatic-launcher/>`_
|
|
||||||
- `Master Key <https://gitlab.com/guillermop/master-key/>`_
|
- `Master Key <https://gitlab.com/guillermop/master-key/>`_
|
||||||
- `Misson Center <https://github.com/flathub/io.missioncenter.MissionCenter>`_
|
- `Misson Center <https://github.com/flathub/io.missioncenter.MissionCenter>`_
|
||||||
- `NewCaw <https://github.com/CodedOre/NewCaw>`_
|
- `NewCaw <https://github.com/CodedOre/NewCaw>`_
|
||||||
|
|
|
@ -9,3 +9,11 @@ custom_target('docs',
|
||||||
)
|
)
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
custom_target('reference_docs.json',
|
||||||
|
output: 'reference_docs.json',
|
||||||
|
command: [meson.current_source_dir() / 'collect-sections.py', '@OUTPUT@'],
|
||||||
|
build_always_stale: true,
|
||||||
|
install: true,
|
||||||
|
install_dir: py.get_install_dir() / 'blueprintcompiler',
|
||||||
|
)
|
|
@ -21,7 +21,7 @@ The tokenizer encountered an unexpected sequence of characters that aren't part
|
||||||
|
|
||||||
child_not_accepted
|
child_not_accepted
|
||||||
------------------
|
------------------
|
||||||
The parent class does not have child widgets (it does not implement `Gtk.Buildable <https://docs.gtk.org/gtk4/iface.Buildable.html>`_ and is not a subclass of `Gio.ListStore <https://docs.gtk.org/gio/class.ListStore.html>`_). Some classes use properties instead of children to add widgets. Check the parent class's documentation.
|
The parent class does not have child objects (it does not implement `Gtk.Buildable <https://docs.gtk.org/gtk4/iface.Buildable.html>`_ and is not a subclass of `Gio.ListStore <https://docs.gtk.org/gio/class.ListStore.html>`_). Some classes use properties instead of children to add widgets. Check the parent class's documentation.
|
||||||
|
|
||||||
|
|
||||||
.. _Diagnostic conversion_error:
|
.. _Diagnostic conversion_error:
|
||||||
|
|
|
@ -8,7 +8,7 @@ automatically.
|
||||||
|
|
||||||
.. code-block:: blueprint
|
.. code-block:: blueprint
|
||||||
|
|
||||||
label: bind MyAppWindow.account.username;
|
label: bind template.account.username;
|
||||||
/* ^ ^ ^
|
/* ^ ^ ^
|
||||||
| creates lookup expressions that are re-evaluated when
|
| creates lookup expressions that are re-evaluated when
|
||||||
| the account's username *or* the account itself changes
|
| the account's username *or* the account itself changes
|
||||||
|
@ -42,22 +42,22 @@ Expressions are composed of property lookups and/or closures. Property lookups a
|
||||||
|
|
||||||
.. _Syntax LookupExpression:
|
.. _Syntax LookupExpression:
|
||||||
|
|
||||||
Lookup Expressions
|
Lookups
|
||||||
------------------
|
-------
|
||||||
|
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
LookupExpression = '.' <property::ref:`IDENT<Syntax IDENT>`>
|
LookupExpression = '.' <property::ref:`IDENT<Syntax IDENT>`>
|
||||||
|
|
||||||
Lookup expressions perform a GObject property lookup on the preceding expression. They are recalculated whenever the property changes, using the `notify signal <https://docs.gtk.org/gobject/signal.Object.notify.html>`_
|
Lookup expressions perform a GObject property lookup on the preceding expression. They are recalculated whenever the property changes, using the `notify signal <https://docs.gtk.org/gobject/signal.Object.notify.html>`_.
|
||||||
|
|
||||||
The type of a property expression is the type of the property it refers to.
|
The type of a property expression is the type of the property it refers to.
|
||||||
|
|
||||||
|
|
||||||
.. _Syntax ClosureExpression:
|
.. _Syntax ClosureExpression:
|
||||||
|
|
||||||
Closure Expressions
|
Closures
|
||||||
-------------------
|
--------
|
||||||
|
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
|
@ -65,23 +65,48 @@ Closure Expressions
|
||||||
|
|
||||||
Closure expressions allow you to perform additional calculations that aren't supported in blueprint by writing those calculations as application code. These application-defined functions are created in the same way as :ref:`signal handlers<Syntax Signal>`.
|
Closure expressions allow you to perform additional calculations that aren't supported in blueprint by writing those calculations as application code. These application-defined functions are created in the same way as :ref:`signal handlers<Syntax Signal>`.
|
||||||
|
|
||||||
Expressions are only reevaluated when their inputs change. Because blueprint doesn't manage a closure's application code, it can't tell what changes might affect the result. Therefore, closures must be *pure*, or deterministic. They may only calculate the result based on their immediate inputs, properties of their inputs or outside variables.
|
Expressions are only reevaluated when their inputs change. Because blueprint doesn't manage a closure's application code, it can't tell what changes might affect the result. Therefore, closures must be *pure*, or deterministic. They may only calculate the result based on their immediate inputs, not properties of their inputs or outside variables.
|
||||||
|
|
||||||
Blueprint doesn't know the closure's return type, so closure expressions must be cast to the correct return type using a :ref:`cast expression<Syntax CastExpression>`.
|
Blueprint doesn't know the closure's return type, so closure expressions must be cast to the correct return type using a :ref:`cast expression<Syntax CastExpression>`.
|
||||||
|
|
||||||
|
|
||||||
.. _Syntax CastExpression:
|
.. _Syntax CastExpression:
|
||||||
|
|
||||||
Cast Expressions
|
Casts
|
||||||
----------------
|
-----
|
||||||
|
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
CastExpression = 'as' '<' :ref:`TypeName<Syntax TypeName>` '>'
|
CastExpression = 'as' '<' :ref:`TypeName<Syntax TypeName>` '>'
|
||||||
|
|
||||||
Cast expressions allow Blueprint to know the type of an expression when it can't otherwise determine it.
|
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
|
.. code-block:: blueprint
|
||||||
|
|
||||||
// Cast the result of the closure so blueprint knows it's a string
|
// 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;
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ Properties are the main way to set values on objects, but they are limited by th
|
||||||
|
|
||||||
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() <https://docs.gtk.org/gtk4/vfunc.Buildable.custom_tag_start.html>`_ for internal details.
|
Extensions are a feature of ``Gtk.Buildable``--see `Gtk.Buildable.custom_tag_start() <https://docs.gtk.org/gtk4/vfunc.Buildable.custom_tag_start.html>`_ for internal details.
|
||||||
|
|
||||||
Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue <https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues>`_.
|
Because they aren't part of the type system, they aren't present in typelib files like properties and signals are. Therefore, if a library adds a new extension, syntax for it must be added to Blueprint manually. If there's a commonly used extension that isn't supported by Blueprint, please `file an issue <https://gitlab.gnome.org/GNOME/blueprint-compiler/-/issues>`_.
|
||||||
|
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
|
@ -37,12 +37,15 @@ Accessibility Properties
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
ExtAccessibility = 'accessibility' '{' ExtAccessibilityProp* '}'
|
ExtAccessibility = 'accessibility' '{' ExtAccessibilityProp* '}'
|
||||||
ExtAccessibilityProp = <name::ref:`IDENT<Syntax IDENT>`> ':' :ref:`Value <Syntax Value>` ';'
|
ExtAccessibilityProp = <name::ref:`IDENT<Syntax IDENT>`> ':' (:ref:`Value <Syntax Value>` | ('[' (:ref: Value <Syntax Value>),* ']') ) ';'
|
||||||
|
|
||||||
Valid in any `Gtk.Widget <https://docs.gtk.org/gtk4/class.Widget.html>`_.
|
Valid in any `Gtk.Widget <https://docs.gtk.org/gtk4/class.Widget.html>`_.
|
||||||
|
|
||||||
The ``accessibility`` block defines values relevant to accessibility software. The property names and acceptable values are described in the `Gtk.AccessibleRelation <https://docs.gtk.org/gtk4/enum.AccessibleRelation.html>`_, `Gtk.AccessibleState <https://docs.gtk.org/gtk4/enum.AccessibleState.html>`_, and `Gtk.AccessibleProperty <https://docs.gtk.org/gtk4/enum.AccessibleProperty.html>`_ enums.
|
The ``accessibility`` block defines values relevant to accessibility software. The property names and acceptable values are described in the `Gtk.AccessibleRelation <https://docs.gtk.org/gtk4/enum.AccessibleRelation.html>`_, `Gtk.AccessibleState <https://docs.gtk.org/gtk4/enum.AccessibleState.html>`_, and `Gtk.AccessibleProperty <https://docs.gtk.org/gtk4/enum.AccessibleProperty.html>`_ enums.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Relations which allow for a list of values, for example `labelled-by`, must be given as a single relation with a list of values instead of duplicating the relation like done in Gtk.Builder.
|
||||||
|
|
||||||
.. _Syntax ExtAdwBreakpoint:
|
.. _Syntax ExtAdwBreakpoint:
|
||||||
|
|
||||||
|
@ -224,7 +227,7 @@ Valid in `Gtk.BuilderListItemFactory <https://docs.gtk.org/gtk4/class.BuilderLis
|
||||||
|
|
||||||
The ``template`` block defines the template that will be used to create list items. This block is unique within Blueprint because it defines a completely separate sub-blueprint which is used to create each list item. The sub-blueprint may not reference objects in the main blueprint or vice versa.
|
The ``template`` block defines the template that will be used to create list items. This block is unique within Blueprint because it defines a completely separate sub-blueprint which is used to create each list item. The sub-blueprint may not reference objects in the main blueprint or vice versa.
|
||||||
|
|
||||||
The template type must be `Gtk.ListItem <https://docs.gtk.org/gtk4/class.ListItem.html>`_. The template object can be referenced with the ``template`` keyword.
|
The template type must be `Gtk.ListItem <https://docs.gtk.org/gtk4/class.ListItem.html>`_, `Gtk.ColumnViewRow <https://docs.gtk.org/gtk4/class.ColumnViewRow.html>`_, or `Gtk.ColumnViewCell <https://docs.gtk.org/gtk4/class.ColumnViewCell.html>`_. The template object can be referenced with the ``template`` keyword.
|
||||||
|
|
||||||
.. code-block:: blueprint
|
.. code-block:: blueprint
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ Tokens
|
||||||
IDENT
|
IDENT
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
An identifier starts with an ASCII underscore ``_`` or letter ``[A-Za-z]`` and consists of ASCII underscores, letters, digits ``[0-9]``, and dashes ``-``. Dashes are included for historical reasons, since GObject properties are traditionally kebab-case.
|
An identifier starts with an ASCII underscore ``_`` or letter ``[A-Za-z]`` and consists of ASCII underscores, letters, digits ``[0-9]``, and dashes ``-``. Dashes are included for historical reasons, since GObject properties and signals are traditionally kebab-case.
|
||||||
|
|
||||||
.. _Syntax NUMBER:
|
.. _Syntax NUMBER:
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ Properties
|
||||||
|
|
||||||
.. rst-class:: grammar-block
|
.. 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.
|
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
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
Signal = <name::ref:`IDENT<Syntax IDENT>`> ('::' <detail::ref:`IDENT<Syntax IDENT>`>)? '=>' '$' <handler::ref:`IDENT<Syntax IDENT>`> '(' <object::ref:`IDENT<Syntax IDENT>`>? ')' (SignalFlag)* ';'
|
Signal = <name::ref:`IDENT<Syntax IDENT>`> ('::' <detail::ref:`IDENT<Syntax IDENT>`>)? '=>' '$' <handler::ref:`IDENT<Syntax IDENT>`> '(' <object::ref:`IDENT<Syntax IDENT>`>? ')' (SignalFlag)* ';'
|
||||||
SignalFlag = 'after' | 'swapped'
|
SignalFlag = 'after' | 'swapped' | 'not-swapped'
|
||||||
|
|
||||||
Signals are one way to respond to user input (another is `actions <https://docs.gtk.org/gtk4/actions.html>`_, which use the `action-name property <https://docs.gtk.org/gtk4/property.Actionable.action-name.html>`_).
|
Signals are one way to respond to user input (another is `actions <https://docs.gtk.org/gtk4/actions.html>`_, which use the `action-name property <https://docs.gtk.org/gtk4/property.Actionable.action-name.html>`_).
|
||||||
|
|
||||||
|
@ -99,6 +99,8 @@ Signals provide a handle for your code to listen to events in the UI. The handle
|
||||||
|
|
||||||
Optionally, you can provide an object ID to use when connecting the signal.
|
Optionally, you can provide an object ID to use when connecting the signal.
|
||||||
|
|
||||||
|
The ``swapped`` flag is used to swap the order of the object and userdata arguments in C applications. If an object argument is specified, then this is the default behavior, so the ``not-swapped`` flag can be used to prevent the swap.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
@ -108,7 +110,6 @@ Example
|
||||||
clicked => $on_button_clicked();
|
clicked => $on_button_clicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.. _Syntax Child:
|
.. _Syntax Child:
|
||||||
|
|
||||||
Children
|
Children
|
||||||
|
|
|
@ -90,6 +90,7 @@ To reference the template object in a binding or expression, use the ``template`
|
||||||
Language Implementations
|
Language Implementations
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
- ``gtk_widget_class_set_template ()`` in C: https://docs.gtk.org/gtk4/class.Widget.html#building-composite-widgets-from-template-xml
|
- **C** ``gtk_widget_class_set_template ()``: https://docs.gtk.org/gtk4/class.Widget.html#building-composite-widgets-from-template-xml
|
||||||
- ``#[template]`` in gtk-rs: https://gtk-rs.org/gtk4-rs/stable/latest/book/composite_templates.html
|
- **gtk-rs** ``#[template]``: https://gtk-rs.org/gtk4-rs/stable/latest/book/composite_templates.html
|
||||||
- ``GObject.registerClass()`` in GJS: https://gjs.guide/guides/gtk/3/14-templates.html
|
- **GJS** ``GObject.registerClass()``: https://gjs.guide/guides/gtk/3/14-templates.html
|
||||||
|
- **PyGObject** ``@Gtk.Template``: https://pygobject.gnome.org/guide/gtk_template.html
|
||||||
|
|
|
@ -25,8 +25,7 @@ Literals
|
||||||
NumberLiteral = ( '-' | '+' )? <value::ref:`NUMBER<Syntax NUMBER>`>
|
NumberLiteral = ( '-' | '+' )? <value::ref:`NUMBER<Syntax NUMBER>`>
|
||||||
IdentLiteral = <ident::ref:`IDENT<Syntax IDENT>`>
|
IdentLiteral = <ident::ref:`IDENT<Syntax IDENT>`>
|
||||||
|
|
||||||
Literals are used to specify values for properties. They can be strings, numbers, references to objects, types, boolean values, or enum members.
|
Literals are used to specify values for properties. They can be strings, numbers, references to objects, ``null``, types, boolean values, or enum members.
|
||||||
|
|
||||||
|
|
||||||
.. _Syntax TypeLiteral:
|
.. _Syntax TypeLiteral:
|
||||||
|
|
||||||
|
@ -110,7 +109,7 @@ Bindings
|
||||||
.. rst-class:: grammar-block
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
Binding = 'bind' :ref:`Expression<Syntax Expression>` (BindingFlag)*
|
Binding = 'bind' :ref:`Expression<Syntax Expression>` (BindingFlag)*
|
||||||
BindingFlag = 'inverted' | 'bidirectional' | 'sync-create'
|
BindingFlag = 'inverted' | 'bidirectional' | 'no-sync-create'
|
||||||
|
|
||||||
Bindings keep a property updated as other properties change. They can be used to keep the UI in sync with application data, or to connect two parts of the UI.
|
Bindings keep a property updated as other properties change. They can be used to keep the UI in sync with application data, or to connect two parts of the UI.
|
||||||
|
|
||||||
|
@ -121,8 +120,8 @@ Simple Bindings
|
||||||
|
|
||||||
A binding that consists of a source object and a single lookup is called a "simple binding". These are implemented using `GObject property bindings <https://docs.gtk.org/gobject/method.Object.bind_property.html>`_ and support a few flags:
|
A binding that consists of a source object and a single lookup is called a "simple binding". These are implemented using `GObject property bindings <https://docs.gtk.org/gobject/method.Object.bind_property.html>`_ and support a few flags:
|
||||||
|
|
||||||
- ``bidirectional``: The binding is two-way, so changes to the target property will also update the source property.
|
|
||||||
- ``inverted``: For boolean properties, the target is set to the inverse of the source property.
|
- ``inverted``: For boolean properties, the target is set to the inverse of the source property.
|
||||||
|
- ``bidirectional``: The binding is two-way, so changes to the target property will also update the source property.
|
||||||
- ``no-sync-create``: Normally, when a binding is created, the target property is immediately updated with the current value of the source property. This flag disables that behavior, and the bound property will be updated the next time the source property changes.
|
- ``no-sync-create``: Normally, when a binding is created, the target property is immediately updated with the current value of the source property. This flag disables that behavior, and the bound property will be updated the next time the source property changes.
|
||||||
|
|
||||||
Complex Bindings
|
Complex Bindings
|
||||||
|
@ -138,14 +137,13 @@ Example
|
||||||
/* Use bindings to show a label when a switch
|
/* Use bindings to show a label when a switch
|
||||||
* is active, without any application code */
|
* is active, without any application code */
|
||||||
|
|
||||||
Switch advanced_feature {}
|
Switch show_label {}
|
||||||
|
|
||||||
Label warning {
|
Label {
|
||||||
visible: bind advanced_feature.active;
|
visible: bind show_label.active;
|
||||||
label: _("This is an advanced feature. Use with caution!");
|
label: _("I'm a label that's only visible when the switch is enabled!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.. _Syntax ObjectValue:
|
.. _Syntax ObjectValue:
|
||||||
|
|
||||||
Object Values
|
Object Values
|
||||||
|
@ -170,3 +168,14 @@ String Values
|
||||||
StringValue = :ref:`Translated<Syntax Translated>` | :ref:`QuotedLiteral<Syntax Literal>`
|
StringValue = :ref:`Translated<Syntax Translated>` | :ref:`QuotedLiteral<Syntax Literal>`
|
||||||
|
|
||||||
Menus, as well as some :ref:`extensions<Syntax Extension>`, have properties that can only be string literals or translated strings.
|
Menus, as well as some :ref:`extensions<Syntax Extension>`, have properties that can only be string literals or translated strings.
|
||||||
|
|
||||||
|
.. _Syntax ArrayValue:
|
||||||
|
|
||||||
|
Array Values
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. rst-class:: grammar-block
|
||||||
|
|
||||||
|
ArrayValue = '[' (:ref:`StringValue<Syntax StringValue>`),* ']'
|
||||||
|
|
||||||
|
For now, it only supports :ref:`Strings<Syntax StringValue>`. This is because Gtk.Builder only supports string arrays.
|
||||||
|
|
|
@ -8,7 +8,7 @@ Setting up Blueprint on a new or existing project
|
||||||
Using the porting tool
|
Using the porting tool
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Clone `blueprint-compiler <https://gitlab.gnome.org/jwestman/blueprint-compiler>`_
|
Clone `blueprint-compiler <https://gitlab.gnome.org/GNOME/blueprint-compiler>`_
|
||||||
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
|
from source. You can install it using ``meson _build`` and ``ninja -C _build install``,
|
||||||
or you can leave it uninstalled.
|
or you can leave it uninstalled.
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ blueprint-compiler works as a meson subproject.
|
||||||
|
|
||||||
[wrap-git]
|
[wrap-git]
|
||||||
directory = blueprint-compiler
|
directory = blueprint-compiler
|
||||||
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
|
url = https://gitlab.gnome.org/GNOME/blueprint-compiler.git
|
||||||
revision = main
|
revision = main
|
||||||
depth = 1
|
depth = 1
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ If you're using Meson's `i18n module <https://mesonbuild.com/i18n-module.html#i1
|
||||||
|
|
||||||
i18n.gettext('package name', preset: 'glib')
|
i18n.gettext('package name', preset: 'glib')
|
||||||
|
|
||||||
|
You must use double quotes for the translated strings in order for gettext to recognize them. Newer versions of blueprint will warn you if you use single quotes.
|
||||||
|
|
||||||
Contexts
|
Contexts
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
project('blueprint-compiler',
|
project('blueprint-compiler',
|
||||||
version: '0.12.0',
|
version: '0.16.0',
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('docs')
|
|
||||||
|
|
||||||
prefix = get_option('prefix')
|
prefix = get_option('prefix')
|
||||||
datadir = join_paths(prefix, get_option('datadir'))
|
datadir = join_paths(prefix, get_option('datadir'))
|
||||||
|
|
||||||
py = import('python').find_installation('python3')
|
py = import('python').find_installation('python3')
|
||||||
|
|
||||||
|
subdir('docs')
|
||||||
|
|
||||||
configure_file(
|
configure_file(
|
||||||
input: 'blueprint-compiler.pc.in',
|
input: 'blueprint-compiler.pc.in',
|
||||||
output: 'blueprint-compiler.pc',
|
output: 'blueprint-compiler.pc',
|
||||||
|
|
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;
|
notify::icon-name => $on_icon_name_changed(label) swapped;
|
||||||
|
|
||||||
styles [
|
styles [
|
||||||
"destructive"
|
"destructive",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
test('tests', py, args: ['-m', 'unittest'], workdir: meson.source_root())
|
test('tests', py, args: ['-m', 'unittest'], workdir: meson.project_source_root())
|
||||||
|
|
9
tests/sample_errors/a11y_list_empty.blp
Normal file
9
tests/sample_errors/a11y_list_empty.blp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Box {
|
||||||
|
accessibility {
|
||||||
|
label: _("Hello, world!");
|
||||||
|
labelled-by: [];
|
||||||
|
checked: true;
|
||||||
|
}
|
||||||
|
}
|
1
tests/sample_errors/a11y_list_empty.err
Normal file
1
tests/sample_errors/a11y_list_empty.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
6,5,11,'labelled-by' may not be empty
|
15
tests/sample_errors/a11y_non_list_property.blp
Normal file
15
tests/sample_errors/a11y_non_list_property.blp
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Box {
|
||||||
|
accessibility {
|
||||||
|
label: _("Hello, world!");
|
||||||
|
active-descendant: [my_label1, my_label2, my_label3];
|
||||||
|
checked: true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label my_label1 {}
|
||||||
|
|
||||||
|
Label my_label2 {}
|
||||||
|
|
||||||
|
Label my_label3 {}
|
1
tests/sample_errors/a11y_non_list_property.err
Normal file
1
tests/sample_errors/a11y_non_list_property.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
6,5,17,'active-descendant' does not allow a list of values
|
5
tests/sample_errors/array_wrong_type.blp
Normal file
5
tests/sample_errors/array_wrong_type.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: [1];
|
||||||
|
}
|
1
tests/sample_errors/array_wrong_type.err
Normal file
1
tests/sample_errors/array_wrong_type.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,12,3,Cannot assign array to string
|
6
tests/sample_errors/array_wrong_type_value.blp
Normal file
6
tests/sample_errors/array_wrong_type_value.blp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
AboutDialog about {
|
||||||
|
valign: center;
|
||||||
|
authors: [1];
|
||||||
|
}
|
1
tests/sample_errors/array_wrong_type_value.err
Normal file
1
tests/sample_errors/array_wrong_type_value.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
5,15,1,Cannot convert number to string
|
|
@ -1,10 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
using Gio 2.0;
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
use-header-bar: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Window {
|
|
||||||
keys-changed => $on_window_keys_changed();
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
4,1,6,Gtk.Dialog is deprecated
|
|
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
|
|
@ -1,3 +1,2 @@
|
||||||
3,10,12,Use type syntax here (introduced in blueprint 0.8.0)
|
3,10,12,Use type syntax here (introduced in blueprint 0.8.0)
|
||||||
8,1,6,Gtk.Dialog is deprecated
|
|
||||||
9,18,12,Use 'template' instead of the class name (introduced in 0.8.0)
|
9,18,12,Use 'template' instead of the class name (introduced in 0.8.0)
|
|
@ -1 +1 @@
|
||||||
4,3,17,Only Gtk.ListItem is allowed as a type here
|
4,11,6,Only Gtk.ListItem, Gtk.ListHeader, Gtk.ColumnViewRow, or Gtk.ColumnViewCell is allowed as a type here
|
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 {}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue