mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
lsp: Add reference documentation on hover
For most constructs and keywords, show the relevant section of the reference documentation on hover.
This commit is contained in:
parent
b107a85947
commit
e19975e1f8
28 changed files with 326 additions and 21 deletions
|
@ -179,14 +179,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]:
|
||||||
|
|
|
@ -207,6 +207,10 @@ class AdwBreakpointSetters(AstNode):
|
||||||
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)
|
@decompiler("condition", cdata=True)
|
||||||
def decompile_condition(ctx: DecompileCtx, gir, cdata):
|
def decompile_condition(ctx: DecompileCtx, gir, cdata):
|
||||||
|
|
|
@ -138,6 +138,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],
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -55,6 +55,7 @@ from ..lsp_utils import (
|
||||||
SemanticToken,
|
SemanticToken,
|
||||||
SemanticTokenType,
|
SemanticTokenType,
|
||||||
SymbolKind,
|
SymbolKind,
|
||||||
|
get_docs_section,
|
||||||
)
|
)
|
||||||
from ..parse_tree import *
|
from ..parse_tree import *
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ class LookupOp(InfixExpr):
|
||||||
|
|
||||||
class CastExpr(InfixExpr):
|
class CastExpr(InfixExpr):
|
||||||
grammar = [
|
grammar = [
|
||||||
"as",
|
Keyword("as"),
|
||||||
AnyOf(
|
AnyOf(
|
||||||
["<", TypeName, Match(">").expected()],
|
["<", TypeName, Match(">").expected()],
|
||||||
[
|
[
|
||||||
|
@ -220,6 +220,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
|
||||||
|
@ -269,6 +273,10 @@ 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, ")"]),
|
||||||
|
|
|
@ -40,6 +40,10 @@ 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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Signal")
|
||||||
|
|
||||||
|
|
||||||
class Signal(AstNode):
|
class Signal(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
|
@ -50,7 +54,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"),
|
||||||
|
@ -184,6 +188,10 @@ class Signal(AstNode):
|
||||||
if prop is not None:
|
if prop is not None:
|
||||||
return prop.doc
|
return prop.doc
|
||||||
|
|
||||||
|
@docs("=>")
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Signal")
|
||||||
|
|
||||||
|
|
||||||
@decompiler("signal")
|
@decompiler("signal")
|
||||||
def decompile_signal(
|
def decompile_signal(
|
||||||
|
|
|
@ -225,6 +225,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],
|
||||||
|
|
|
@ -55,6 +55,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 +85,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],
|
||||||
|
|
|
@ -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
|
self.validate_unique_in_parent(
|
||||||
# the instance, hence wrapping it like this.
|
f"Duplicate {self.tokens['tag_name']} block",
|
||||||
@validate(self.tokens["tag_name"])
|
check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
|
||||||
def wrapped_validator(self):
|
)
|
||||||
self.validate_unique_in_parent(
|
|
||||||
f"Duplicate {self.tokens['tag_name']} block",
|
|
||||||
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],
|
||||||
|
|
|
@ -108,3 +108,7 @@ class ExtListItemFactory(AstNode):
|
||||||
just hear to satisfy XmlOutput._emit_object_or_template
|
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,
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -94,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],
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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), "]"]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -68,6 +68,10 @@ class GtkDirective(AstNode):
|
||||||
# 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(
|
||||||
|
@ -105,3 +109,7 @@ class Import(AstNode):
|
||||||
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
return gir.get_namespace(self.tokens["namespace"], self.tokens["version"])
|
||||||
except CompileError:
|
except CompileError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Using")
|
||||||
|
|
|
@ -124,6 +124,16 @@ 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):
|
def decompile_response_type(parent_element, child_element):
|
||||||
obj_id = None
|
obj_id = None
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from blueprintcompiler.gir import ArrayType
|
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 ScopeCtx, ValueTypeCtx
|
||||||
|
@ -56,6 +57,10 @@ class Translated(AstNode):
|
||||||
f"Cannot convert translated string to {expected_type.full_name}"
|
f"Cannot convert translated string to {expected_type.full_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@docs()
|
||||||
|
def ref_docs(self):
|
||||||
|
return get_docs_section("Syntax Translated")
|
||||||
|
|
||||||
|
|
||||||
class TypeLiteral(AstNode):
|
class TypeLiteral(AstNode):
|
||||||
grammar = [
|
grammar = [
|
||||||
|
@ -101,6 +106,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")
|
||||||
|
@ -258,6 +267,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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -200,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
|
||||||
|
|
136
docs/collect-sections.py
Executable file
136
docs/collect-sections.py
Executable file
|
@ -0,0 +1,136 @@
|
||||||
|
#!/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://jwestman.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(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
|
||||||
|
)
|
|
@ -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',
|
||||||
|
)
|
|
@ -227,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>`_, `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.
|
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
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ project('blueprint-compiler',
|
||||||
version: '0.14.0',
|
version: '0.14.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',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue