Added doc gen for structs.

This commit is contained in:
nlupugla 2024-04-06 17:58:00 -04:00
parent e4e024ab88
commit 16ccd3782f
8 changed files with 771 additions and 284 deletions

View File

@ -127,6 +127,30 @@ void DocData::property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_propert
p_property.overridden = false;
}
void DocData::struct_doc_from_structinfo(DocData::StructDoc &p_struct, const StructInfo &p_structinfo) {
p_struct.name = p_structinfo.name;
// TODO: what about description?
p_struct.properties.resize(p_structinfo.count);
for (int i = 0; i < p_structinfo.count; i++) {
PropertyDoc property_doc;
property_doc.name = p_structinfo.names[i];
Variant::Type type = p_structinfo.types[i];
if (type == Variant::OBJECT) {
property_doc.type = p_structinfo.class_names[i];
} else if (type == Variant::NIL) {
property_doc.type = "Variant";
} else {
property_doc.type = Variant::get_type_name(type);
}
if (type != Variant::OBJECT) {
property_doc.default_value = get_default_value_string(p_structinfo.default_values[i]);
}
// TODO: what about description?
p_struct.properties.write[i] = property_doc;
}
}
void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc) {
p_method.name = p_methodinfo.name;
p_method.description = p_desc;

View File

@ -698,6 +698,67 @@ public:
}
};
struct StructDoc {
String name;
String description;
Vector<PropertyDoc> properties;
bool is_deprecated = false;
bool is_experimental = false;
bool operator<(const StructDoc &p_struct) const {
return name < p_struct.name;
}
static StructDoc from_dict(const Dictionary &p_dict) {
StructDoc doc;
if (p_dict.has("name")) {
doc.name = p_dict["name"];
}
if (p_dict.has("description")) {
doc.description = p_dict["description"];
}
Array properties;
if (p_dict.has("properties")) {
properties = p_dict["properties"];
}
for (int i = 0; i < properties.size(); i++) {
doc.properties.push_back(PropertyDoc::from_dict(properties[i]));
}
if (p_dict.has("is_experimental")) {
doc.is_experimental = p_dict["is_experimental"];
}
return doc;
}
static Dictionary to_dict(const StructDoc &p_doc) {
Dictionary dict;
if (!p_doc.name.is_empty()) {
dict["name"] = p_doc.name;
}
if (!p_doc.description.is_empty()) {
dict["description"] = p_doc.description;
}
if (!p_doc.properties.is_empty()) {
Array properties;
for (int i = 0; i < p_doc.properties.size(); i++) {
properties.push_back(PropertyDoc::to_dict(p_doc.properties[i]));
}
dict["properties"] = properties;
}
dict["is_deprecated"] = p_doc.is_deprecated;
dict["is_experimental"] = p_doc.is_experimental;
return dict;
}
};
struct ClassDoc {
String name;
String inherits;
@ -713,6 +774,7 @@ public:
HashMap<String, EnumDoc> enums;
Vector<PropertyDoc> properties;
Vector<MethodDoc> annotations;
Vector<StructDoc> structs;
Vector<ThemeItemDoc> theme_properties;
bool is_deprecated = false;
String deprecated_message;
@ -818,6 +880,14 @@ public:
doc.annotations.push_back(MethodDoc::from_dict(annotations[i]));
}
Array structs;
if (p_dict.has("structs")) {
structs = p_dict["structs"];
}
for (int i = 0; i < structs.size(); i++) {
doc.structs.push_back(StructDoc::from_dict(structs[i]));
}
Array theme_properties;
if (p_dict.has("theme_properties")) {
theme_properties = p_dict["theme_properties"];
@ -947,6 +1017,13 @@ public:
dict["annotations"] = annotations;
}
if (!p_doc.structs.is_empty()) {
Array structs;
for (int i = 0; i < p_doc.structs.size(); i++) {
structs.push_back(StructDoc::to_dict(p_doc.structs[i]));
}
}
if (!p_doc.theme_properties.is_empty()) {
Array theme_properties;
for (int i = 0; i < p_doc.theme_properties.size(); i++) {
@ -982,6 +1059,7 @@ public:
static void return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo);
static void argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo);
static void property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_property, const ScriptMemberInfo &p_memberinfo);
static void struct_doc_from_structinfo(DocData::StructDoc &p_struct, const StructInfo &p_structinfo);
static void method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc);
static void constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc);
static void signal_doc_from_methodinfo(DocData::MethodDoc &p_signal, const MethodInfo &p_methodinfo, const String &p_desc);

View File

@ -196,6 +196,37 @@
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="structs" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="struct" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="member" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="name" use="required"/>
<xs:attribute type="xs:string" name="type" use="required"/>
<xs:attribute type="xs:string" name="default" use="required"/>
<xs:attribute type="xs:string" name="enum" use="optional" />
<xs:attribute type="xs:boolean" name="is_bitfield" use="optional" />
<xs:attribute type="xs:boolean" name="is_deprecated" use="optional" />
<xs:attribute type="xs:boolean" name="is_experimental" use="optional" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="description" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="name" use="required"/>
<xs:attribute type="xs:boolean" name="is_deprecated" use="optional" />
<xs:attribute type="xs:boolean" name="is_experimental" use="optional" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="annotations" minOccurs="0">
<xs:complexType>
<xs:sequence>

View File

@ -66,6 +66,7 @@ table_columns = [
"description",
"methods",
"constants",
"structs",
"members",
"theme_items",
"signals",
@ -78,6 +79,7 @@ table_column_names = [
"Desc.",
"Methods",
"Constants",
"Structs",
"Members",
"Theme Items",
"Signals",
@ -191,6 +193,7 @@ class ClassStatus:
self.progresses: Dict[str, ClassStatusProgress] = {
"methods": ClassStatusProgress(),
"constants": ClassStatusProgress(),
"structs": ClassStatusProgress(),
"members": ClassStatusProgress(),
"theme_items": ClassStatusProgress(),
"signals": ClassStatusProgress(),
@ -239,7 +242,7 @@ class ClassStatus:
)
items_progress = ClassStatusProgress()
for k in ["methods", "constants", "members", "theme_items", "signals", "constructors", "operators"]:
for k in ["methods", "constants", "structs", "members", "theme_items", "signals", "constructors", "operators"]:
items_progress += self.progresses[k]
output[k] = self.progresses[k].to_configured_colored_string()
@ -284,7 +287,7 @@ class ClassStatus:
descr = sub_tag.find("description")
has_descr = (descr is not None) and (descr.text is not None) and len(descr.text.strip()) > 0
status.progresses[tag.tag].increment(is_deprecated or is_experimental or has_descr)
elif tag.tag in ["constants", "members", "theme_items"]:
elif tag.tag in ["constants", "structs", "members", "theme_items"]:
for sub_tag in list(tag):
if sub_tag.text is not None:
is_deprecated = "deprecated" in sub_tag.attrib
@ -327,7 +330,7 @@ for arg in sys.argv[1:]:
sys.exit(1)
if flags["i"]:
for r in ["methods", "constants", "members", "signals", "theme_items"]:
for r in ["methods", "constants", "structs", "members", "signals", "theme_items"]:
index = table_columns.index(r)
del table_column_names[index]
del table_columns[index]

View File

@ -44,6 +44,7 @@ BASE_STRINGS = [
"Theme Properties",
"Signals",
"Enumerations",
"Structs",
"Constants",
"Annotations",
"Property Descriptions",
@ -69,6 +70,7 @@ BASE_STRINGS = [
"There is currently no description for this class. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!",
"There is currently no description for this signal. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!",
"There is currently no description for this enum. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!",
"There is currently no description for this struct. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!",
"There is currently no description for this constant. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!",
"There is currently no description for this annotation. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!",
"There is currently no description for this property. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!",
@ -342,6 +344,30 @@ class State:
enum_def.values[constant_name] = constant_def
structs = class_root.find("structs")
if structs is not None:
for struct in structs:
assert struct.tag == "struct"
struct_name = struct.attrib["name"]
if struct_name in class_def.structs:
print_error(f'{class_name}.xml: Duplicate struct "{struct_name}".', self)
continue
struct_def = StructDef(struct_name)
for member in struct.findall("member"):
member_name = member.attrib["name"]
if member_name in class_def.structs:
print_error(f'{class_name}.xml: Duplicate struct "{struct_name}".', self)
continue
member_type = TypeName(member.attrib["type"])
member_default = member.attrib.get("default", None)
if member_default is not None:
member_default = f"``{member_default}``"
struct_def.members[member_name] = ParameterDef(method_name, member_type, member_default)
class_def.structs[struct_name] = struct_def
annotations = class_root.find("annotations")
if annotations is not None:
for annotation in annotations:
@ -577,6 +603,13 @@ class EnumDef(DefinitionBase):
self.is_bitfield = bitfield
class StructDef(DefinitionBase):
def __init__(self, name: str) -> None:
super().__init__("struct", name)
self.members: OrderedDict[str, ParameterDef] = OrderedDict()
class ThemeItemDef(DefinitionBase):
def __init__(
self, name: str, type_name: TypeName, data_name: str, text: Optional[str], default_value: Optional[str]
@ -598,6 +631,7 @@ class ClassDef(DefinitionBase):
self.constants: OrderedDict[str, ConstantDef] = OrderedDict()
self.enums: OrderedDict[str, EnumDef] = OrderedDict()
self.structs: OrderedDict[str, StructDef] = OrderedDict()
self.properties: OrderedDict[str, PropertyDef] = OrderedDict()
self.constructors: OrderedDict[str, List[MethodDef]] = OrderedDict()
self.methods: OrderedDict[str, List[MethodDef]] = OrderedDict()
@ -1219,6 +1253,32 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir:
f.write("\n\n")
# Struct descriptions
if len(class_def.structs) > 0:
f.write(make_separator(True))
f.write(".. rst-class:: classref-descriptions-group\n\n")
f.write(make_heading("Structs", "-"))
index = 0
for struct_def in class_def.structs.values():
if index != 0:
f.write(make_separator())
# Create struct signature and anchor point.
f.write(f".. _struct_{class_name}_{struct_def.name}:\n\n")
f.write(".. rst-class:: classref-struct\n\n")
f.write(f"struct **{struct_def.name}**:\n\n")
# Write struct members
for member_name, member in struct_def.members.items():
f.write(f".. _struct_{class_name}_{struct_def.name}_member_{member_name}:\n\n")
f.write(".. rst-class:: classref-struct-member\n\n")
f.write(f"{member.type_name.to_rst(state)} **{member_name}** = ``{member.default_value}``\n\n")
index += 1
# Annotation descriptions
if len(class_def.annotations) > 0:
f.write(make_separator(True))
@ -1512,9 +1572,13 @@ def make_type(klass: str, state: State) -> str:
def resolve_type(link_type: str) -> str:
if link_type in state.classes:
return f":ref:`{link_type}<class_{link_type}>`"
else:
print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
return f"``{link_type}``"
for class_name, class_def in state.classes.items(): # Check structs
if link_type in class_def.structs:
return f":ref:`{link_type}<struct_{class_name}_link_type>`"
print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
return f"``{link_type}``"
if klass.endswith("[]"): # Typed array, strip [] to link to contained type.
return f":ref:`Array<class_Array>`\\[{resolve_type(klass[:-len('[]')])}\\]"
@ -1558,6 +1622,27 @@ def make_enum(t: str, is_bitfield: bool, state: State) -> str:
return t
def make_struct(t: str, state: State) -> str:
p = t.find(".")
if p >= 0:
c = t[0:p]
e = t[p + 1 :]
# Variant structs live in GlobalScope but still use periods.
if c == "Variant":
c = "@GlobalScope"
e = "Variant." + e
else:
c = state.current_class
e = t
if c in state.classes and e not in state.classes[c].structs:
c = "@GlobalScope"
if c in state.classes and e in state.classes[c].structs:
return f":ref:`{e}<struct_{c}_{e}>`"
return t
def make_method_signature(
class_def: ClassDef, definition: Union[AnnotationDef, MethodDef, SignalDef], ref_type: str, state: State
) -> Tuple[str, str]:
@ -1797,6 +1882,7 @@ RESERVED_CROSSLINK_TAGS = [
"signal",
"constant",
"enum",
"struct",
"annotation",
"theme_item",
"param",
@ -2113,6 +2199,12 @@ def format_text_block(
state,
)
elif target_name in class_def.structs:
print_warning(
f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} struct in {context_name}. {code_warning_if_intended_string}',
state,
)
else:
for enum in class_def.enums.values():
if target_name in enum.values:
@ -2151,6 +2243,7 @@ def format_text_block(
or tag_state.name == "member"
or tag_state.name == "signal"
or tag_state.name == "annotation"
or tag_state.name == "struct"
or tag_state.name == "theme_item"
or tag_state.name == "constant"
):
@ -2211,6 +2304,12 @@ def format_text_block(
state,
)
elif tag_state.name == "struct" and target_name not in class_def.structs:
print_error(
f'{state.current_class}.xml: Unresolved struct reference "{link_target}" in {context_name}.',
state,
)
elif tag_state.name == "theme_item":
if target_name not in class_def.theme_items:
print_error(
@ -2268,6 +2367,11 @@ def format_text_block(
escape_pre = True
escape_post = True
elif tag_state.name == "struct":
tag_text = make_struct(link_target, state)
escape_pre = True
escape_post = True
elif tag_state.name == "param":
valid_param_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
if not valid_param_context:

View File

@ -258,6 +258,32 @@ static void merge_properties(Vector<DocData::PropertyDoc> &p_to, const Vector<Do
}
}
static void merge_structs(Vector<DocData::StructDoc> &p_to, const Vector<DocData::StructDoc> &p_from) {
// Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`.
DocData::StructDoc *to_ptrw = p_to.ptrw();
int64_t to_size = p_to.size();
SearchArray<DocData::StructDoc> search_array;
for (const DocData::StructDoc &from : p_from) {
int64_t found = search_array.bisect(to_ptrw, to_size, from, true);
if (found >= to_size) {
continue;
}
DocData::StructDoc &to = to_ptrw[found];
// Check found entry on name and data type.
if (to.name == from.name) {
to.description = from.description;
to.is_deprecated = from.is_deprecated;
to.is_experimental = from.is_experimental;
merge_properties(to.properties, from.properties);
}
}
}
static void merge_theme_properties(Vector<DocData::ThemeItemDoc> &p_to, const Vector<DocData::ThemeItemDoc> &p_from) {
// Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`.
DocData::ThemeItemDoc *to_ptrw = p_to.ptrw();
@ -345,6 +371,8 @@ void DocTools::merge_from(const DocTools &p_data) {
merge_properties(c.properties, cf.properties);
merge_structs(c.structs, cf.structs);
merge_theme_properties(c.theme_properties, cf.theme_properties);
merge_operators(c.operators, cf.operators);
@ -679,6 +707,15 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
c.constants.push_back(constant);
}
List<StructInfo> struct_list;
ClassDB::get_struct_list(name, &struct_list, true);
for (const StructInfo &E : struct_list) {
DocData::StructDoc struct_doc;
DocData::struct_doc_from_structinfo(struct_doc, E);
c.structs.push_back(struct_doc);
}
// Theme items.
{
List<ThemeDB::ThemeItemBind> theme_items;
@ -1114,85 +1151,83 @@ static Error _parse_methods(Ref<XMLParser> &parser, Vector<DocData::MethodDoc> &
String element = section.substr(0, section.length() - 1);
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
if (parser->get_node_name() == element) {
DocData::MethodDoc method;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
method.name = parser->get_named_attribute_value("name");
if (parser->has_attribute("qualifiers")) {
method.qualifiers = parser->get_named_attribute_value("qualifiers");
}
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == section) {
break;
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
ERR_FAIL_COND_V_MSG(parser->get_node_name() != element, ERR_FILE_CORRUPT, "Invalid tag in doc file: " + parser->get_node_name() + ", expected " + element + ".");
DocData::MethodDoc method;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
method.name = parser->get_named_attribute_value("name");
if (parser->has_attribute("qualifiers")) {
method.qualifiers = parser->get_named_attribute_value("qualifiers");
}
#ifndef DISABLE_DEPRECATED
if (parser->has_attribute("is_deprecated")) {
method.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
method.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
if (parser->has_attribute("is_deprecated")) {
method.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
method.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
#endif
if (parser->has_attribute("deprecated")) {
method.is_deprecated = true;
method.deprecated_message = parser->get_named_attribute_value("deprecated");
if (parser->has_attribute("deprecated")) {
method.is_deprecated = true;
method.deprecated_message = parser->get_named_attribute_value("deprecated");
}
if (parser->has_attribute("experimental")) {
method.is_experimental = true;
method.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
method.keywords = parser->get_named_attribute_value("keywords");
}
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == element) {
break;
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
String name = parser->get_node_name();
if (name == "return") {
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
method.return_type = parser->get_named_attribute_value("type");
if (parser->has_attribute("enum")) {
method.return_enum = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
method.return_is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
if (parser->has_attribute("experimental")) {
method.is_experimental = true;
method.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
method.keywords = parser->get_named_attribute_value("keywords");
}
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
String name = parser->get_node_name();
if (name == "return") {
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
method.return_type = parser->get_named_attribute_value("type");
if (parser->has_attribute("enum")) {
method.return_enum = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
method.return_is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
} else if (name == "returns_error") {
ERR_FAIL_COND_V(!parser->has_attribute("number"), ERR_FILE_CORRUPT);
method.errors_returned.push_back(parser->get_named_attribute_value("number").to_int());
} else if (name == "param") {
DocData::ArgumentDoc argument;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
argument.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
argument.type = parser->get_named_attribute_value("type");
if (parser->has_attribute("enum")) {
argument.enumeration = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
argument.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
method.arguments.push_back(argument);
} else if (name == "description") {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
method.description = parser->get_node_data();
}
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == element) {
break;
} else if (name == "returns_error") {
ERR_FAIL_COND_V(!parser->has_attribute("number"), ERR_FILE_CORRUPT);
method.errors_returned.push_back(parser->get_named_attribute_value("number").to_int());
} else if (name == "param") {
DocData::ArgumentDoc argument;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
argument.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
argument.type = parser->get_named_attribute_value("type");
if (parser->has_attribute("enum")) {
argument.enumeration = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
argument.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
methods.push_back(method);
method.arguments.push_back(argument);
} else {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + parser->get_node_name() + ", expected " + element + ".");
} else if (name == "description") {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
method.description = parser->get_node_data();
}
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == section) {
break;
}
methods.push_back(method);
}
return OK;
@ -1269,7 +1304,7 @@ Error DocTools::_load(Ref<XMLParser> parser) {
ERR_FAIL_COND_V(parser->get_node_name() != "class", ERR_FILE_CORRUPT);
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
String name = parser->get_named_attribute_value("name");
const String name = parser->get_named_attribute_value("name");
class_list[name] = DocData::ClassDoc();
DocData::ClassDoc &c = class_list[name];
@ -1302,216 +1337,286 @@ Error DocTools::_load(Ref<XMLParser> parser) {
}
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
String name2 = parser->get_node_name();
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "class") {
break; // End of <class>.
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
const String tag = parser->get_node_name();
if (name2 == "brief_description") {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
c.brief_description = parser->get_node_data();
}
} else if (name2 == "description") {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
c.description = parser->get_node_data();
}
} else if (name2 == "tutorials") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
String name3 = parser->get_node_name();
if (name3 == "link") {
DocData::TutorialDoc tutorial;
if (parser->has_attribute("title")) {
tutorial.title = parser->get_named_attribute_value("title");
}
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
tutorial.link = parser->get_node_data().strip_edges();
c.tutorials.push_back(tutorial);
}
} else {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + ".");
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "tutorials") {
break; // End of <tutorials>.
}
}
} else if (name2 == "constructors") {
Error err2 = _parse_methods(parser, c.constructors);
ERR_FAIL_COND_V(err2, err2);
} else if (name2 == "methods") {
Error err2 = _parse_methods(parser, c.methods);
ERR_FAIL_COND_V(err2, err2);
} else if (name2 == "operators") {
Error err2 = _parse_methods(parser, c.operators);
ERR_FAIL_COND_V(err2, err2);
} else if (name2 == "signals") {
Error err2 = _parse_methods(parser, c.signals);
ERR_FAIL_COND_V(err2, err2);
} else if (name2 == "annotations") {
Error err2 = _parse_methods(parser, c.annotations);
ERR_FAIL_COND_V(err2, err2);
} else if (name2 == "members") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
String name3 = parser->get_node_name();
if (name3 == "member") {
DocData::PropertyDoc prop2;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
prop2.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
prop2.type = parser->get_named_attribute_value("type");
if (parser->has_attribute("setter")) {
prop2.setter = parser->get_named_attribute_value("setter");
}
if (parser->has_attribute("getter")) {
prop2.getter = parser->get_named_attribute_value("getter");
}
if (parser->has_attribute("enum")) {
prop2.enumeration = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
prop2.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
#ifndef DISABLE_DEPRECATED
if (parser->has_attribute("is_deprecated")) {
prop2.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
prop2.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
#endif
if (parser->has_attribute("deprecated")) {
prop2.is_deprecated = true;
prop2.deprecated_message = parser->get_named_attribute_value("deprecated");
}
if (parser->has_attribute("experimental")) {
prop2.is_experimental = true;
prop2.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
prop2.keywords = parser->get_named_attribute_value("keywords");
}
if (!parser->is_empty()) {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
prop2.description = parser->get_node_data();
}
}
c.properties.push_back(prop2);
} else {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + ".");
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "members") {
break; // End of <members>.
}
}
} else if (name2 == "theme_items") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
String name3 = parser->get_node_name();
if (name3 == "theme_item") {
DocData::ThemeItemDoc prop2;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
prop2.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
prop2.type = parser->get_named_attribute_value("type");
ERR_FAIL_COND_V(!parser->has_attribute("data_type"), ERR_FILE_CORRUPT);
prop2.data_type = parser->get_named_attribute_value("data_type");
if (parser->has_attribute("deprecated")) {
prop2.is_deprecated = true;
prop2.deprecated_message = parser->get_named_attribute_value("deprecated");
}
if (parser->has_attribute("experimental")) {
prop2.is_experimental = true;
prop2.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
prop2.keywords = parser->get_named_attribute_value("keywords");
}
if (!parser->is_empty()) {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
prop2.description = parser->get_node_data();
}
}
c.theme_properties.push_back(prop2);
} else {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + ".");
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "theme_items") {
break; // End of <theme_items>.
}
}
} else if (name2 == "constants") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
String name3 = parser->get_node_name();
if (name3 == "constant") {
DocData::ConstantDoc constant2;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
constant2.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT);
constant2.value = parser->get_named_attribute_value("value");
constant2.is_value_valid = true;
if (parser->has_attribute("enum")) {
constant2.enumeration = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
constant2.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
#ifndef DISABLE_DEPRECATED
if (parser->has_attribute("is_deprecated")) {
constant2.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
constant2.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
#endif
if (parser->has_attribute("deprecated")) {
constant2.is_deprecated = true;
constant2.deprecated_message = parser->get_named_attribute_value("deprecated");
}
if (parser->has_attribute("experimental")) {
constant2.is_experimental = true;
constant2.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
constant2.keywords = parser->get_named_attribute_value("keywords");
}
if (!parser->is_empty()) {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
constant2.description = parser->get_node_data();
}
}
c.constants.push_back(constant2);
} else {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + ".");
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "constants") {
break; // End of <constants>.
}
}
} else {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name2 + ".");
if (tag == "brief_description") {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
c.brief_description = parser->get_node_data();
}
} else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "class") {
break; // End of <class>.
} else if (tag == "description") {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
c.description = parser->get_node_data();
}
} else if (tag == "tutorials") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "tutorials") {
break; // End of <tutorials>.
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
const String link_tag = parser->get_node_name();
ERR_FAIL_COND_V_MSG(link_tag != "link", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + link_tag + ", expected link.");
DocData::TutorialDoc tutorial;
if (parser->has_attribute("title")) {
tutorial.title = parser->get_named_attribute_value("title");
}
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
tutorial.link = parser->get_node_data().strip_edges();
c.tutorials.push_back(tutorial);
}
}
} else if (tag == "constructors") {
const Error err2 = _parse_methods(parser, c.constructors);
ERR_FAIL_COND_V(err2, err2);
} else if (tag == "methods") {
const Error err2 = _parse_methods(parser, c.methods);
ERR_FAIL_COND_V(err2, err2);
} else if (tag == "operators") {
const Error err2 = _parse_methods(parser, c.operators);
ERR_FAIL_COND_V(err2, err2);
} else if (tag == "signals") {
const Error err2 = _parse_methods(parser, c.signals);
ERR_FAIL_COND_V(err2, err2);
} else if (tag == "annotations") {
const Error err2 = _parse_methods(parser, c.annotations);
ERR_FAIL_COND_V(err2, err2);
} else if (tag == "members") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "members") {
break; // End of <members>.
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
String member_tag = parser->get_node_name();
ERR_FAIL_COND_V_MSG(member_tag != "member", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + member_tag + ", expected member.");
DocData::PropertyDoc property_doc;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
property_doc.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
property_doc.type = parser->get_named_attribute_value("type");
if (parser->has_attribute("setter")) {
property_doc.setter = parser->get_named_attribute_value("setter");
}
if (parser->has_attribute("getter")) {
property_doc.getter = parser->get_named_attribute_value("getter");
}
if (parser->has_attribute("enum")) {
property_doc.enumeration = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
property_doc.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
#ifndef DISABLE_DEPRECATED
if (parser->has_attribute("is_deprecated")) {
property_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
property_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
#endif
if (parser->has_attribute("deprecated")) {
property_doc.is_deprecated = true;
property_doc.deprecated_message = parser->get_named_attribute_value("deprecated");
}
if (parser->has_attribute("experimental")) {
property_doc.is_experimental = true;
property_doc.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
property_doc.keywords = parser->get_named_attribute_value("keywords");
}
if (!parser->is_empty()) {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
property_doc.description = parser->get_node_data();
}
}
c.properties.push_back(property_doc);
}
} else if (tag == "structs") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "structs") {
break; // End of <structs>.
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
const String struct_tag = parser->get_node_name();
ERR_FAIL_COND_V_MSG(struct_tag != "struct", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + struct_tag + ", expected struct.");
DocData::StructDoc struct_doc;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
struct_doc.name = parser->get_named_attribute_value("name");
if (parser->has_attribute("is_deprecated")) {
struct_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
struct_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "struct") {
break; // End of <struct>
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
const String member_tag = parser->get_node_name();
ERR_FAIL_COND_V_MSG(member_tag != "member", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + member_tag + ", expected member.");
DocData::PropertyDoc member_doc;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
member_doc.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
member_doc.type = parser->get_named_attribute_value("type");
ERR_FAIL_COND_V(!parser->has_attribute("default"), ERR_FILE_CORRUPT);
member_doc.default_value = parser->get_named_attribute_value("default");
if (parser->has_attribute("enum")) {
member_doc.enumeration = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
member_doc.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
if (parser->has_attribute("is_deprecated")) {
member_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
member_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
// TODO: figure out description
// if (!parser->is_empty()) {
// parser->read();
// if (parser->get_node_type() == XMLParser::NODE_TEXT) {
// member_doc.description = parser->get_node_data().strip_edges();
// }
// }
struct_doc.properties.push_back(member_doc);
}
// if (!parser->is_empty()) {
// parser->read();
// if (parser->get_node_type() == XMLParser::NODE_TEXT) {
// struct_doc.description = parser->get_node_data().strip_edges();
// }
// }
c.structs.push_back(struct_doc);
}
} else if (tag == "theme_items") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "theme_items") {
break; // End of <theme_items>.
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
const String theme_item_tag = parser->get_node_name();
ERR_FAIL_COND_V_MSG(theme_item_tag != "theme_item", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + theme_item_tag + ", expected theme_item.");
DocData::ThemeItemDoc theme_item_doc;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
theme_item_doc.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT);
theme_item_doc.type = parser->get_named_attribute_value("type");
ERR_FAIL_COND_V(!parser->has_attribute("data_type"), ERR_FILE_CORRUPT);
theme_item_doc.data_type = parser->get_named_attribute_value("data_type");
if (parser->has_attribute("deprecated")) {
theme_item_doc.is_deprecated = true;
theme_item_doc.deprecated_message = parser->get_named_attribute_value("deprecated");
}
if (parser->has_attribute("experimental")) {
theme_item_doc.is_experimental = true;
theme_item_doc.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
theme_item_doc.keywords = parser->get_named_attribute_value("keywords");
}
if (!parser->is_empty()) {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
theme_item_doc.description = parser->get_node_data();
}
}
c.theme_properties.push_back(theme_item_doc);
}
} else if (tag == "constants") {
while (parser->read() == OK) {
if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "constants") {
break; // End of <constants>.
}
if (parser->get_node_type() != XMLParser::NODE_ELEMENT) {
continue;
}
const String constant_tag = parser->get_node_name();
ERR_FAIL_COND_V_MSG(constant_tag != "constant", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + constant_tag + ", expected constant.");
DocData::ConstantDoc constant_doc;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
constant_doc.name = parser->get_named_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT);
constant_doc.value = parser->get_named_attribute_value("value");
constant_doc.is_value_valid = true;
if (parser->has_attribute("enum")) {
constant_doc.enumeration = parser->get_named_attribute_value("enum");
if (parser->has_attribute("is_bitfield")) {
constant_doc.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true";
}
}
#ifndef DISABLE_DEPRECATED
if (parser->has_attribute("is_deprecated")) {
constant_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true";
}
if (parser->has_attribute("is_experimental")) {
constant_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true";
}
#endif
if (parser->has_attribute("deprecated")) {
constant_doc.is_deprecated = true;
constant_doc.deprecated_message = parser->get_named_attribute_value("deprecated");
}
if (parser->has_attribute("experimental")) {
constant_doc.is_experimental = true;
constant_doc.experimental_message = parser->get_named_attribute_value("experimental");
}
if (parser->has_attribute("keywords")) {
constant_doc.keywords = parser->get_named_attribute_value("keywords");
}
if (!parser->is_empty()) {
parser->read();
if (parser->get_node_type() == XMLParser::NODE_TEXT) {
constant_doc.description = parser->get_node_data();
}
}
c.constants.push_back(constant_doc);
}
} else {
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + tag + ".");
}
}
@ -1743,6 +1848,53 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap<String,
_write_method_doc(f, "annotation", c.annotations);
if (!c.structs.is_empty()) {
_write_string(f, 1, "<structs>");
for (int i = 0; i < c.structs.size(); i++) {
const DocData::StructDoc &s = c.structs[i];
String struct_header = "<struct name=\"" + s.name.xml_escape(true) + "\"";
if (s.is_deprecated) {
struct_header += " is_deprecated=\"true\"";
}
if (s.is_experimental) {
struct_header += " is_experimental=\"true\"";
}
struct_header += ">";
_write_string(f, 2, struct_header);
if (!s.description.is_empty()) {
_write_string(f, 3, "<description>" + s.description.xml_escape() + "</description>");
}
for (int j = 0; j < s.properties.size(); j++) {
const DocData::PropertyDoc &m = s.properties[j];
String member_line = "<member name=\"" + m.name.xml_escape(true) + "\" type=\"" + m.type.xml_escape(true) + "\" default=\"" + m.default_value.xml_escape(true) + "\"";
if (!m.description.is_empty()) {
member_line += " description=\"" + m.description.xml_escape() + "\"";
}
if (!m.enumeration.is_empty()) {
member_line += " enum=\"" + m.enumeration.xml_escape() + "\"";
}
if (m.is_bitfield) {
member_line += " is_bitfield=\"true\"";
}
if (m.is_deprecated) {
member_line += " is_deprecated=\"true\"";
}
if (m.is_experimental) {
member_line += " is_experimental=\"true\"";
}
member_line += "/>";
_write_string(f, 3, member_line);
}
_write_string(f, 2, "</struct>");
}
_write_string(f, 1, "</structs>");
}
if (!c.theme_properties.is_empty()) {
_write_string(f, 1, "<theme_items>");
for (int i = 0; i < c.theme_properties.size(); i++) {

View File

@ -282,6 +282,9 @@ void EditorHelp::_class_desc_select(const String &p_select) {
} else if (tag == "theme_item") {
topic = "class_theme_item";
table = &theme_property_line;
} else if (tag == "struct") {
topic = "struct";
table = &struct_line;
} else {
return;
}
@ -1640,6 +1643,92 @@ void EditorHelp::_update_doc() {
}
}
// Structs
if (!cd.structs.is_empty()) {
class_desc->add_newline();
class_desc->add_newline();
section_line.push_back(Pair<String, int>(TTR("Structs"), class_desc->get_paragraph_count() - 2));
_push_title_font();
class_desc->add_text(TTR("Structs"));
_pop_title_font();
for (const DocData::StructDoc &struct_item : cd.structs) {
class_desc->add_newline();
class_desc->add_newline();
struct_line[struct_item.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
class_desc->push_indent(1);
_push_code_font();
// Struct name.
class_desc->push_color(theme_cache.headline_color);
class_desc->add_text(struct_item.name);
class_desc->pop(); // color
_pop_code_font();
// Struct description.
class_desc->push_indent(1);
_push_normal_font();
class_desc->push_color(theme_cache.comment_color);
const String descr = DTR(struct_item.description).strip_edges();
if (!descr.is_empty()) {
_add_text(descr);
} else {
class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
class_desc->add_text(" ");
class_desc->push_color(theme_cache.comment_color);
if (cd.is_script_doc) {
class_desc->add_text(TTR("There is currently no description for this struct."));
} else {
class_desc->append_text(TTR("There is currently no description for this struct. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text));
}
class_desc->pop(); // color
}
class_desc->pop(); // color
_pop_normal_font();
class_desc->pop(); // indent
// Struct members.
for (const DocData::PropertyDoc &member : struct_item.properties) {
class_desc->add_newline();
class_desc->push_indent(1);
_push_code_font();
// Member type and name.
_add_bulletpoint();
_add_type(member.type);
class_desc->add_text(" " + member.name);
// Member default value.
if (!member.default_value.is_empty()) {
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text(" [" + TTR("default:") + " ");
class_desc->pop(); // color
class_desc->push_color(theme_cache.value_color);
class_desc->add_text(_fix_constant(member.default_value));
class_desc->pop(); // color
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text("]");
class_desc->pop(); // color
}
_pop_code_font();
class_desc->pop(); // indent
}
class_desc->pop(); // indent for struct members
class_desc->add_newline();
}
class_desc->add_newline();
}
// Constants and enums
if (!cd.constants.is_empty()) {
HashMap<String, Vector<DocData::ConstantDoc>> enums;
@ -2345,6 +2434,10 @@ void EditorHelp::_help_callback(const String &p_topic) {
if (enum_line.has(name)) {
line = enum_line[name];
}
} else if (what == "class_struct") {
if (struct_line.has(name)) {
line = struct_line[name];
}
} else if (what == "class_theme_item") {
if (theme_property_line.has(name)) {
line = theme_property_line[name];

View File

@ -110,6 +110,8 @@ class EditorHelp : public VBoxContainer {
HashMap<String, int> annotation_line;
HashMap<String, int> enum_line;
HashMap<String, HashMap<String, int>> enum_values_line;
HashMap<String, int> struct_line;
HashMap<String, HashMap<String, int>> struct_members_line;
int description_line = 0;
RichTextLabel *class_desc = nullptr;