Code Editor: Add documentation tooltips

This commit is contained in:
Danil Alexeev 2024-12-14 21:25:05 +03:00
parent 863a24ac86
commit 80d11500b5
No known key found for this signature in database
GPG key ID: 5A52F75A8679EC57
30 changed files with 1398 additions and 586 deletions

View file

@ -105,28 +105,6 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const
} }
} }
void DocData::property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_property, const ScriptMemberInfo &p_memberinfo) {
p_property.name = p_memberinfo.propinfo.name;
p_property.description = p_memberinfo.doc_string;
if (p_memberinfo.propinfo.type == Variant::OBJECT) {
p_property.type = p_memberinfo.propinfo.class_name;
} else if (p_memberinfo.propinfo.type == Variant::NIL && p_memberinfo.propinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
p_property.type = "Variant";
} else {
p_property.type = Variant::get_type_name(p_memberinfo.propinfo.type);
}
p_property.setter = p_memberinfo.setter;
p_property.getter = p_memberinfo.getter;
if (p_memberinfo.has_default_value && p_memberinfo.default_value.get_type() != Variant::OBJECT) {
p_property.default_value = get_default_value_string(p_memberinfo.default_value);
}
p_property.overridden = false;
}
void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc) { 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.name = p_methodinfo.name;
p_method.description = p_desc; p_method.description = p_desc;
@ -170,14 +148,3 @@ void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const Met
p_method.arguments.push_back(argument); p_method.arguments.push_back(argument);
} }
} }
void DocData::constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc) {
p_const.name = p_name;
p_const.value = p_value;
p_const.is_value_valid = (p_value.get_type() != Variant::OBJECT);
p_const.description = p_desc;
}
void DocData::signal_doc_from_methodinfo(DocData::MethodDoc &p_signal, const MethodInfo &p_methodinfo, const String &p_desc) {
return method_doc_from_methodinfo(p_signal, p_methodinfo, p_desc);
}

View file

@ -34,16 +34,6 @@
#include "core/io/xml_parser.h" #include "core/io/xml_parser.h"
#include "core/variant/variant.h" #include "core/variant/variant.h"
struct ScriptMemberInfo {
PropertyInfo propinfo;
String doc_string;
StringName setter;
StringName getter;
bool has_default_value = false;
Variant default_value;
};
class DocData { class DocData {
public: public:
struct ArgumentDoc { struct ArgumentDoc {
@ -276,6 +266,7 @@ public:
String name; String name;
String value; String value;
bool is_value_valid = false; bool is_value_valid = false;
String type;
String enumeration; String enumeration;
bool is_bitfield = false; bool is_bitfield = false;
String description; String description;
@ -302,6 +293,10 @@ public:
doc.is_value_valid = p_dict["is_value_valid"]; doc.is_value_valid = p_dict["is_value_valid"];
} }
if (p_dict.has("type")) {
doc.type = p_dict["type"];
}
if (p_dict.has("enumeration")) { if (p_dict.has("enumeration")) {
doc.enumeration = p_dict["enumeration"]; doc.enumeration = p_dict["enumeration"];
if (p_dict.has("is_bitfield")) { if (p_dict.has("is_bitfield")) {
@ -352,6 +347,8 @@ public:
dict["is_value_valid"] = p_doc.is_value_valid; dict["is_value_valid"] = p_doc.is_value_valid;
dict["type"] = p_doc.type;
if (!p_doc.enumeration.is_empty()) { if (!p_doc.enumeration.is_empty()) {
dict["enumeration"] = p_doc.enumeration; dict["enumeration"] = p_doc.enumeration;
dict["is_bitfield"] = p_doc.is_bitfield; dict["is_bitfield"] = p_doc.is_bitfield;
@ -981,10 +978,7 @@ public:
static void return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo); 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 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 method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc); 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);
}; };
#endif // DOC_DATA_H #endif // DOC_DATA_H

View file

@ -150,6 +150,7 @@ public:
virtual Error reload(bool p_keep_state = false) = 0; virtual Error reload(bool p_keep_state = false) = 0;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
virtual StringName get_doc_class_name() const = 0;
virtual Vector<DocData::ClassDoc> get_documentation() const = 0; virtual Vector<DocData::ClassDoc> get_documentation() const = 0;
virtual String get_class_icon_path() const = 0; virtual String get_class_icon_path() const = 0;
virtual PropertyInfo get_class_category() const; virtual PropertyInfo get_class_category() const;
@ -181,7 +182,7 @@ public:
virtual int get_member_line(const StringName &p_member) const { return -1; } virtual int get_member_line(const StringName &p_member) const { return -1; }
virtual void get_constants(HashMap<StringName, Variant> *p_constants) {} virtual void get_constants(HashMap<StringName, Variant> *p_constants) {}
virtual void get_members(HashSet<StringName> *p_constants) {} virtual void get_members(HashSet<StringName> *p_members) {}
virtual bool is_placeholder_fallback_enabled() const { return false; } virtual bool is_placeholder_fallback_enabled() const { return false; }
@ -340,25 +341,46 @@ public:
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; } virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }
enum LookupResultType { enum LookupResultType {
LOOKUP_RESULT_SCRIPT_LOCATION, LOOKUP_RESULT_SCRIPT_LOCATION, // Use if none of the options below apply.
LOOKUP_RESULT_CLASS, LOOKUP_RESULT_CLASS,
LOOKUP_RESULT_CLASS_CONSTANT, LOOKUP_RESULT_CLASS_CONSTANT,
LOOKUP_RESULT_CLASS_PROPERTY, LOOKUP_RESULT_CLASS_PROPERTY,
LOOKUP_RESULT_CLASS_METHOD, LOOKUP_RESULT_CLASS_METHOD,
LOOKUP_RESULT_CLASS_SIGNAL, LOOKUP_RESULT_CLASS_SIGNAL,
LOOKUP_RESULT_CLASS_ENUM, LOOKUP_RESULT_CLASS_ENUM,
LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE, LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE, // Deprecated.
LOOKUP_RESULT_CLASS_ANNOTATION, LOOKUP_RESULT_CLASS_ANNOTATION,
LOOKUP_RESULT_MAX LOOKUP_RESULT_LOCAL_CONSTANT,
LOOKUP_RESULT_LOCAL_VARIABLE,
LOOKUP_RESULT_MAX,
}; };
struct LookupResult { struct LookupResult {
LookupResultType type; LookupResultType type;
Ref<Script> script;
// For `CLASS_*`.
String class_name; String class_name;
String class_member; String class_member;
String class_path;
int location; // For `LOCAL_*`.
String description;
bool is_deprecated = false;
String deprecated_message;
bool is_experimental = false;
String experimental_message;
// For `LOCAL_*`.
String doc_type;
String enumeration;
bool is_bitfield = false;
// For `LOCAL_*`.
String value;
// `SCRIPT_LOCATION` and `LOCAL_*` must have, `CLASS_*` can have.
Ref<Script> script;
String script_path;
int location = -1;
}; };
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { return ERR_UNAVAILABLE; } virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { return ERR_UNAVAILABLE; }

View file

@ -51,6 +51,7 @@ void ScriptExtension::_bind_methods() {
GDVIRTUAL_BIND(_set_source_code, "code"); GDVIRTUAL_BIND(_set_source_code, "code");
GDVIRTUAL_BIND(_reload, "keep_state"); GDVIRTUAL_BIND(_reload, "keep_state");
GDVIRTUAL_BIND(_get_doc_class_name);
GDVIRTUAL_BIND(_get_documentation); GDVIRTUAL_BIND(_get_documentation);
GDVIRTUAL_BIND(_get_class_icon_path); GDVIRTUAL_BIND(_get_class_icon_path);
@ -169,8 +170,10 @@ void ScriptLanguageExtension::_bind_methods() {
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_METHOD); BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_METHOD);
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_SIGNAL); BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_SIGNAL);
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ENUM); BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ENUM);
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE); BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE); // Deprecated.
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ANNOTATION); BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ANNOTATION);
BIND_ENUM_CONSTANT(LOOKUP_RESULT_LOCAL_CONSTANT);
BIND_ENUM_CONSTANT(LOOKUP_RESULT_LOCAL_VARIABLE);
BIND_ENUM_CONSTANT(LOOKUP_RESULT_MAX); BIND_ENUM_CONSTANT(LOOKUP_RESULT_MAX);
BIND_ENUM_CONSTANT(LOCATION_LOCAL); BIND_ENUM_CONSTANT(LOCATION_LOCAL);

View file

@ -76,9 +76,16 @@ public:
EXBIND1(set_source_code, const String &) EXBIND1(set_source_code, const String &)
EXBIND1R(Error, reload, bool) EXBIND1R(Error, reload, bool)
GDVIRTUAL0RC_REQUIRED(StringName, _get_doc_class_name)
GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_documentation) GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_documentation)
GDVIRTUAL0RC(String, _get_class_icon_path) GDVIRTUAL0RC(String, _get_class_icon_path)
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
virtual StringName get_doc_class_name() const override {
StringName ret;
GDVIRTUAL_CALL(_get_doc_class_name, ret);
return ret;
}
virtual Vector<DocData::ClassDoc> get_documentation() const override { virtual Vector<DocData::ClassDoc> get_documentation() const override {
TypedArray<Dictionary> doc; TypedArray<Dictionary> doc;
GDVIRTUAL_CALL(_get_documentation, doc); GDVIRTUAL_CALL(_get_documentation, doc);
@ -454,22 +461,31 @@ public:
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) override { virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) override {
Dictionary ret; Dictionary ret;
GDVIRTUAL_CALL(_lookup_code, p_code, p_symbol, p_path, p_owner, ret); GDVIRTUAL_CALL(_lookup_code, p_code, p_symbol, p_path, p_owner, ret);
if (!ret.has("result")) {
return ERR_UNAVAILABLE; ERR_FAIL_COND_V(!ret.has("result"), ERR_UNAVAILABLE);
} const Error result = Error(int(ret["result"]));
ERR_FAIL_COND_V(!ret.has("type"), ERR_UNAVAILABLE); ERR_FAIL_COND_V(!ret.has("type"), ERR_UNAVAILABLE);
r_result.type = LookupResultType(int(ret["type"])); r_result.type = LookupResultType(int(ret["type"]));
ERR_FAIL_COND_V(!ret.has("script"), ERR_UNAVAILABLE);
r_result.script = ret["script"];
ERR_FAIL_COND_V(!ret.has("class_name"), ERR_UNAVAILABLE);
r_result.class_name = ret["class_name"];
ERR_FAIL_COND_V(!ret.has("class_path"), ERR_UNAVAILABLE);
r_result.class_path = ret["class_path"];
ERR_FAIL_COND_V(!ret.has("location"), ERR_UNAVAILABLE);
r_result.location = ret["location"];
Error result = Error(int(ret["result"])); r_result.class_name = ret.get("class_name", "");
r_result.class_member = ret.get("class_member", "");
r_result.description = ret.get("description", "");
r_result.is_deprecated = ret.get("is_deprecated", false);
r_result.deprecated_message = ret.get("deprecated_message", "");
r_result.is_deprecated = ret.get("is_deprecated", false);
r_result.experimental_message = ret.get("experimental_message", "");
r_result.doc_type = ret.get("doc_type", "");
r_result.enumeration = ret.get("enumeration", "");
r_result.is_bitfield = ret.get("is_bitfield", false);
r_result.value = ret.get("value", "");
r_result.script = ret.get("script", Ref<Script>());
r_result.script_path = ret.get("script_path", "");
r_result.location = ret.get("location", -1);
return result; return result;
} }

View file

@ -844,6 +844,7 @@
</description> </description>
</method> </method>
<method name="print" qualifiers="vararg"> <method name="print" qualifiers="vararg">
<return type="void" />
<description> <description>
Converts one or more arguments of any type to string in the best way possible and prints them to the console. Converts one or more arguments of any type to string in the best way possible and prints them to the console.
[codeblocks] [codeblocks]
@ -860,6 +861,7 @@
</description> </description>
</method> </method>
<method name="print_rich" qualifiers="vararg"> <method name="print_rich" qualifiers="vararg">
<return type="void" />
<description> <description>
Converts one or more arguments of any type to string in the best way possible and prints them to the console. Converts one or more arguments of any type to string in the best way possible and prints them to the console.
The following BBCode tags are supported: [code]b[/code], [code]i[/code], [code]u[/code], [code]s[/code], [code]indent[/code], [code]code[/code], [code]url[/code], [code]center[/code], [code]right[/code], [code]color[/code], [code]bgcolor[/code], [code]fgcolor[/code]. The following BBCode tags are supported: [code]b[/code], [code]i[/code], [code]u[/code], [code]s[/code], [code]indent[/code], [code]code[/code], [code]url[/code], [code]center[/code], [code]right[/code], [code]color[/code], [code]bgcolor[/code], [code]fgcolor[/code].
@ -879,11 +881,13 @@
</description> </description>
</method> </method>
<method name="print_verbose" qualifiers="vararg"> <method name="print_verbose" qualifiers="vararg">
<return type="void" />
<description> <description>
If verbose mode is enabled ([method OS.is_stdout_verbose] returning [code]true[/code]), converts one or more arguments of any type to string in the best way possible and prints them to the console. If verbose mode is enabled ([method OS.is_stdout_verbose] returning [code]true[/code]), converts one or more arguments of any type to string in the best way possible and prints them to the console.
</description> </description>
</method> </method>
<method name="printerr" qualifiers="vararg"> <method name="printerr" qualifiers="vararg">
<return type="void" />
<description> <description>
Prints one or more arguments to strings in the best way possible to standard error line. Prints one or more arguments to strings in the best way possible to standard error line.
[codeblocks] [codeblocks]
@ -897,6 +901,7 @@
</description> </description>
</method> </method>
<method name="printraw" qualifiers="vararg"> <method name="printraw" qualifiers="vararg">
<return type="void" />
<description> <description>
Prints one or more arguments to strings in the best way possible to the OS terminal. Unlike [method print], no newline is automatically added at the end. Prints one or more arguments to strings in the best way possible to the OS terminal. Unlike [method print], no newline is automatically added at the end.
[b]Note:[/b] The OS terminal is [i]not[/i] the same as the editor's Output dock. The output sent to the OS terminal can be seen when running Godot from a terminal. On Windows, this requires using the [code]console.exe[/code] executable. [b]Note:[/b] The OS terminal is [i]not[/i] the same as the editor's Output dock. The output sent to the OS terminal can be seen when running Godot from a terminal. On Windows, this requires using the [code]console.exe[/code] executable.
@ -917,6 +922,7 @@
</description> </description>
</method> </method>
<method name="prints" qualifiers="vararg"> <method name="prints" qualifiers="vararg">
<return type="void" />
<description> <description>
Prints one or more arguments to the console with a space between each argument. Prints one or more arguments to the console with a space between each argument.
[codeblocks] [codeblocks]
@ -930,6 +936,7 @@
</description> </description>
</method> </method>
<method name="printt" qualifiers="vararg"> <method name="printt" qualifiers="vararg">
<return type="void" />
<description> <description>
Prints one or more arguments to the console with a tab between each argument. Prints one or more arguments to the console with a tab between each argument.
[codeblocks] [codeblocks]
@ -943,6 +950,7 @@
</description> </description>
</method> </method>
<method name="push_error" qualifiers="vararg"> <method name="push_error" qualifiers="vararg">
<return type="void" />
<description> <description>
Pushes an error message to Godot's built-in debugger and to the OS terminal. Pushes an error message to Godot's built-in debugger and to the OS terminal.
[codeblocks] [codeblocks]
@ -957,6 +965,7 @@
</description> </description>
</method> </method>
<method name="push_warning" qualifiers="vararg"> <method name="push_warning" qualifiers="vararg">
<return type="void" />
<description> <description>
Pushes a warning message to Godot's built-in debugger and to the OS terminal. Pushes a warning message to Godot's built-in debugger and to the OS terminal.
[codeblocks] [codeblocks]
@ -1075,6 +1084,7 @@
</description> </description>
</method> </method>
<method name="randomize"> <method name="randomize">
<return type="void" />
<description> <description>
Randomizes the seed (or the internal state) of the random number generator. The current implementation uses a number based on the device's time. Randomizes the seed (or the internal state) of the random number generator. The current implementation uses a number based on the device's time.
[b]Note:[/b] This function is called automatically when the project is run. If you need to fix the seed to have consistent, reproducible results, use [method seed] to initialize the random number generator. [b]Note:[/b] This function is called automatically when the project is run. If you need to fix the seed to have consistent, reproducible results, use [method seed] to initialize the random number generator.
@ -1151,6 +1161,7 @@
</description> </description>
</method> </method>
<method name="seed"> <method name="seed">
<return type="void" />
<param index="0" name="base" type="int" /> <param index="0" name="base" type="int" />
<description> <description>
Sets the seed for the random number generator to [param base]. Setting the seed manually can ensure consistent, repeatable results for most random functions. Sets the seed for the random number generator to [param base]. Setting the seed manually can ensure consistent, repeatable results for most random functions.

View file

@ -587,6 +587,9 @@
<member name="symbol_lookup_on_click" type="bool" setter="set_symbol_lookup_on_click_enabled" getter="is_symbol_lookup_on_click_enabled" default="false"> <member name="symbol_lookup_on_click" type="bool" setter="set_symbol_lookup_on_click_enabled" getter="is_symbol_lookup_on_click_enabled" default="false">
Set when a validated word from [signal symbol_validate] is clicked, the [signal symbol_lookup] should be emitted. Set when a validated word from [signal symbol_validate] is clicked, the [signal symbol_lookup] should be emitted.
</member> </member>
<member name="symbol_tooltip_on_hover" type="bool" setter="set_symbol_tooltip_on_hover_enabled" getter="is_symbol_tooltip_on_hover_enabled" default="false">
Set when a word is hovered, the [signal symbol_hovered] should be emitted.
</member>
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" overrides="TextEdit" enum="Control.TextDirection" default="1" /> <member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" overrides="TextEdit" enum="Control.TextDirection" default="1" />
</members> </members>
<signals> <signals>
@ -601,6 +604,15 @@
Emitted when the user requests code completion. This signal will not be sent if [method _request_code_completion] is overridden or [member code_completion_enabled] is [code]false[/code]. Emitted when the user requests code completion. This signal will not be sent if [method _request_code_completion] is overridden or [member code_completion_enabled] is [code]false[/code].
</description> </description>
</signal> </signal>
<signal name="symbol_hovered">
<param index="0" name="symbol" type="String" />
<param index="1" name="line" type="int" />
<param index="2" name="column" type="int" />
<description>
Emitted when the user hovers over a symbol. Unlike [signal Control.mouse_entered], this signal is not emitted immediately, but when the cursor is over the symbol for [member ProjectSettings.gui/timers/tooltip_delay_sec] seconds.
[b]Note:[/b] [member symbol_tooltip_on_hover] must be [code]true[/code] for this signal to be emitted.
</description>
</signal>
<signal name="symbol_lookup"> <signal name="symbol_lookup">
<param index="0" name="symbol" type="String" /> <param index="0" name="symbol" type="String" />
<param index="1" name="line" type="int" /> <param index="1" name="line" type="int" />
@ -613,6 +625,7 @@
<param index="0" name="symbol" type="String" /> <param index="0" name="symbol" type="String" />
<description> <description>
Emitted when the user hovers over a symbol. The symbol should be validated and responded to, by calling [method set_symbol_lookup_word_as_valid]. Emitted when the user hovers over a symbol. The symbol should be validated and responded to, by calling [method set_symbol_lookup_word_as_valid].
[b]Note:[/b] [member symbol_lookup_on_click] must be [code]true[/code] for this signal to be emitted.
</description> </description>
</signal> </signal>
</signals> </signals>

View file

@ -32,6 +32,11 @@
<description> <description>
</description> </description>
</method> </method>
<method name="_get_doc_class_name" qualifiers="virtual const">
<return type="StringName" />
<description>
</description>
</method>
<method name="_get_documentation" qualifiers="virtual const"> <method name="_get_documentation" qualifiers="virtual const">
<return type="Dictionary[]" /> <return type="Dictionary[]" />
<description> <description>

View file

@ -387,11 +387,15 @@
</constant> </constant>
<constant name="LOOKUP_RESULT_CLASS_ENUM" value="6" enum="LookupResultType"> <constant name="LOOKUP_RESULT_CLASS_ENUM" value="6" enum="LookupResultType">
</constant> </constant>
<constant name="LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE" value="7" enum="LookupResultType"> <constant name="LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE" value="7" enum="LookupResultType" deprecated="">
</constant> </constant>
<constant name="LOOKUP_RESULT_CLASS_ANNOTATION" value="8" enum="LookupResultType"> <constant name="LOOKUP_RESULT_CLASS_ANNOTATION" value="8" enum="LookupResultType">
</constant> </constant>
<constant name="LOOKUP_RESULT_MAX" value="9" enum="LookupResultType"> <constant name="LOOKUP_RESULT_LOCAL_CONSTANT" value="9" enum="LookupResultType">
</constant>
<constant name="LOOKUP_RESULT_LOCAL_VARIABLE" value="10" enum="LookupResultType">
</constant>
<constant name="LOOKUP_RESULT_MAX" value="11" enum="LookupResultType">
</constant> </constant>
<constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation"> <constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation">
The option is local to the location of the code completion query - e.g. a local variable. Subsequent value of location represent options from the outer class, the exact value represent how far they are (in terms of inner classes). The option is local to the location of the code completion query - e.g. a local variable. Subsequent value of location represent options from the outer class, the exact value represent how far they are (in terms of inner classes).

View file

@ -831,7 +831,7 @@ CreateDialog::CreateDialog() {
vbc->add_margin_child(TTR("Matches:"), search_options, true); vbc->add_margin_child(TTR("Matches:"), search_options, true);
help_bit = memnew(EditorHelpBit); help_bit = memnew(EditorHelpBit);
help_bit->set_content_height_limits(64 * EDSCALE, 64 * EDSCALE); help_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested)); help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested));
vbc->add_margin_child(TTR("Description:"), help_bit); vbc->add_margin_child(TTR("Description:"), help_bit);

View file

@ -674,6 +674,7 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
constant.name = E; constant.name = E;
constant.value = itos(ClassDB::get_integer_constant(name, E)); constant.value = itos(ClassDB::get_integer_constant(name, E));
constant.is_value_valid = true; constant.is_value_valid = true;
constant.type = "int";
constant.enumeration = ClassDB::get_integer_constant_enum(name, E); constant.enumeration = ClassDB::get_integer_constant_enum(name, E);
constant.is_bitfield = ClassDB::is_enum_bitfield(name, constant.enumeration); constant.is_bitfield = ClassDB::is_enum_bitfield(name, constant.enumeration);
c.constants.push_back(constant); c.constants.push_back(constant);
@ -920,6 +921,7 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
constant.name = F; constant.name = F;
constant.value = itos(Variant::get_enum_value(Variant::Type(i), E, F)); constant.value = itos(Variant::get_enum_value(Variant::Type(i), E, F));
constant.is_value_valid = true; constant.is_value_valid = true;
constant.type = "int";
constant.enumeration = E; constant.enumeration = E;
c.constants.push_back(constant); c.constants.push_back(constant);
} }
@ -934,6 +936,7 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
Variant value = Variant::get_constant_value(Variant::Type(i), E); Variant value = Variant::get_constant_value(Variant::Type(i), E);
constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace("\n", " "); constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace("\n", " ");
constant.is_value_valid = true; constant.is_value_valid = true;
constant.type = Variant::get_type_name(value.get_type());
c.constants.push_back(constant); c.constants.push_back(constant);
} }
} }
@ -951,6 +954,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
DocData::ConstantDoc cd; DocData::ConstantDoc cd;
cd.name = CoreConstants::get_global_constant_name(i); cd.name = CoreConstants::get_global_constant_name(i);
cd.type = "int";
cd.enumeration = CoreConstants::get_global_constant_enum(i);
cd.is_bitfield = CoreConstants::is_global_constant_bitfield(i); cd.is_bitfield = CoreConstants::is_global_constant_bitfield(i);
if (!CoreConstants::get_ignore_value_in_docs(i)) { if (!CoreConstants::get_ignore_value_in_docs(i)) {
cd.value = itos(CoreConstants::get_global_constant_value(i)); cd.value = itos(CoreConstants::get_global_constant_value(i));
@ -958,7 +963,6 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
} else { } else {
cd.is_value_valid = false; cd.is_value_valid = false;
} }
cd.enumeration = CoreConstants::get_global_constant_enum(i);
c.constants.push_back(cd); c.constants.push_back(cd);
} }
@ -998,6 +1002,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
DocData::ArgumentDoc ad; DocData::ArgumentDoc ad;
DocData::argument_doc_from_arginfo(ad, pi); DocData::argument_doc_from_arginfo(ad, pi);
md.return_type = ad.type; md.return_type = ad.type;
} else {
md.return_type = "void";
} }
// Utility function's arguments. // Utility function's arguments.
@ -1077,6 +1083,7 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
cd.name = E.first; cd.name = E.first;
cd.value = E.second; cd.value = E.second;
cd.is_value_valid = true; cd.is_value_valid = true;
cd.type = Variant::get_type_name(E.second.get_type());
c.constants.push_back(cd); c.constants.push_back(cd);
} }

File diff suppressed because it is too large Load diff

View file

@ -188,7 +188,6 @@ class EditorHelp : public VBoxContainer {
void _request_help(const String &p_string); void _request_help(const String &p_string);
void _search(bool p_search_previous = false); void _search(bool p_search_previous = false);
String _fix_constant(const String &p_constant) const;
void _toggle_scripts_pressed(); void _toggle_scripts_pressed();
static int doc_generation_count; static int doc_generation_count;
@ -254,6 +253,13 @@ public:
class EditorHelpBit : public VBoxContainer { class EditorHelpBit : public VBoxContainer {
GDCLASS(EditorHelpBit, VBoxContainer); GDCLASS(EditorHelpBit, VBoxContainer);
enum SymbolHint {
SYMBOL_HINT_NONE,
SYMBOL_HINT_INHERITANCE, // [ < ParentClass[ < ...]]
SYMBOL_HINT_ASSIGNABLE, // [: Type][ = value]
SYMBOL_HINT_SIGNATURE, // (arguments)[ -> Type][ qualifiers]
};
struct DocType { struct DocType {
String type; String type;
String enumeration; String enumeration;
@ -270,23 +276,31 @@ class EditorHelpBit : public VBoxContainer {
String description; String description;
String deprecated_message; String deprecated_message;
String experimental_message; String experimental_message;
DocType doc_type; // For method return type. DocType doc_type;
Vector<ArgumentData> arguments; // For methods and signals. String value;
Vector<ArgumentData> arguments;
String qualifiers;
}; };
inline static HashMap<StringName, HelpData> doc_class_cache; inline static HashMap<StringName, HelpData> doc_class_cache;
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_enum_cache;
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_constant_cache;
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_property_cache; inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_property_cache;
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_theme_item_cache;
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_method_cache; inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_method_cache;
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_signal_cache; inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_signal_cache;
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_theme_item_cache; inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_annotation_cache;
RichTextLabel *title = nullptr; RichTextLabel *title = nullptr;
RichTextLabel *content = nullptr; RichTextLabel *content = nullptr;
bool use_class_prefix = false;
String symbol_doc_link;
String symbol_class_name; String symbol_class_name;
String symbol_type; String symbol_type;
String symbol_visible_type;
String symbol_name; String symbol_name;
SymbolHint symbol_hint = SYMBOL_HINT_NONE;
HelpData help_data; HelpData help_data;
@ -294,10 +308,13 @@ class EditorHelpBit : public VBoxContainer {
float content_max_height = 0.0; float content_max_height = 0.0;
static HelpData _get_class_help_data(const StringName &p_class_name); static HelpData _get_class_help_data(const StringName &p_class_name);
static HelpData _get_enum_help_data(const StringName &p_class_name, const StringName &p_enum_name);
static HelpData _get_constant_help_data(const StringName &p_class_name, const StringName &p_constant_name);
static HelpData _get_property_help_data(const StringName &p_class_name, const StringName &p_property_name); static HelpData _get_property_help_data(const StringName &p_class_name, const StringName &p_property_name);
static HelpData _get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name);
static HelpData _get_method_help_data(const StringName &p_class_name, const StringName &p_method_name); static HelpData _get_method_help_data(const StringName &p_class_name, const StringName &p_method_name);
static HelpData _get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name); static HelpData _get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name);
static HelpData _get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name); static HelpData _get_annotation_help_data(const StringName &p_class_name, const StringName &p_annotation_name);
void _add_type_to_title(const DocType &p_doc_type); void _add_type_to_title(const DocType &p_doc_type);
void _update_labels(); void _update_labels();
@ -315,7 +332,7 @@ public:
void set_content_height_limits(float p_min, float p_max); void set_content_height_limits(float p_min, float p_max);
void update_content_height(); void update_content_height();
EditorHelpBit(const String &p_symbol = String(), const String &p_prologue = String(), bool p_allow_selection = true); EditorHelpBit(const String &p_symbol = String(), const String &p_prologue = String(), bool p_use_class_prefix = false, bool p_allow_selection = true);
}; };
// Standard tooltips do not allow you to hover over them. // Standard tooltips do not allow you to hover over them.
@ -338,7 +355,7 @@ protected:
void _notification(int p_what); void _notification(int p_what);
public: public:
static Control *show_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue = String()); static Control *show_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue = String(), bool p_use_class_prefix = false);
void popup_under_cursor(); void popup_under_cursor();

View file

@ -3018,24 +3018,14 @@ void EditorInspector::update_tree() {
category_label = p.name; category_label = p.name;
// Use category's owner script to update some of its information. // Use category's owner script to update some of its information.
if (!EditorNode::get_editor_data().is_type_recognized(p.name) && ResourceLoader::exists(p.hint_string)) { if (!EditorNode::get_editor_data().is_type_recognized(p.name) && ResourceLoader::exists(p.hint_string, "Script")) {
Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script"); Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script");
if (scr.is_valid()) { if (scr.is_valid()) {
StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path()); doc_name = scr->get_doc_class_name();
// Update the docs reference and the label based on the script. StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());
Vector<DocData::ClassDoc> docs = scr->get_documentation();
if (!docs.is_empty()) {
// The documentation of a GDScript's main class is at the end of the array.
// Hacky because this isn't necessarily always guaranteed.
doc_name = docs[docs.size() - 1].name;
}
if (script_name != StringName()) { if (script_name != StringName()) {
category_label = script_name; category_label = script_name;
}
// Find the icon corresponding to the script.
if (script_name != StringName()) {
category_icon = EditorNode::get_singleton()->get_class_icon(script_name); category_icon = EditorNode::get_singleton()->get_class_icon(script_name);
} else { } else {
category_icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object"); category_icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object");

View file

@ -31,10 +31,12 @@
#include "script_text_editor.h" #include "script_text_editor.h"
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "core/io/json.h"
#include "core/math/expression.h" #include "core/math/expression.h"
#include "core/os/keyboard.h" #include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_command_palette.h" #include "editor/editor_command_palette.h"
#include "editor/editor_help.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/editor_string_names.h" #include "editor/editor_string_names.h"
@ -963,95 +965,88 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
} else { } else {
EditorNode::get_singleton()->load_resource(symbol); EditorNode::get_singleton()->load_resource(symbol);
} }
} else if (lc_error == OK) { } else if (lc_error == OK) {
_goto_line(p_row); _goto_line(p_row);
switch (result.type) { if (!result.class_name.is_empty() && EditorHelp::get_doc_data()->class_list.has(result.class_name)) {
case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION: { switch (result.type) {
if (result.script.is_valid()) { case ScriptLanguage::LOOKUP_RESULT_CLASS: {
emit_signal(SNAME("request_open_script_at_line"), result.script, result.location - 1); emit_signal(SNAME("go_to_help"), "class_name:" + result.class_name);
} else { } break;
emit_signal(SNAME("request_save_history")); case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: {
goto_line_centered(result.location - 1); StringName cname = result.class_name;
} while (ClassDB::class_exists(cname)) {
} break; if (ClassDB::has_integer_constant(cname, result.class_member, true)) {
case ScriptLanguage::LOOKUP_RESULT_CLASS: { result.class_name = cname;
emit_signal(SNAME("go_to_help"), "class_name:" + result.class_name); break;
} break; }
case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: {
StringName cname = result.class_name;
bool success;
while (true) {
ClassDB::get_integer_constant(cname, result.class_member, &success);
if (success) {
result.class_name = cname;
cname = ClassDB::get_parent_class(cname); cname = ClassDB::get_parent_class(cname);
} else {
break;
} }
} emit_signal(SNAME("go_to_help"), "class_constant:" + result.class_name + ":" + result.class_member);
} break;
emit_signal(SNAME("go_to_help"), "class_constant:" + result.class_name + ":" + result.class_member); case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: {
StringName cname = result.class_name;
} break; while (ClassDB::class_exists(cname)) {
case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: { if (ClassDB::has_property(cname, result.class_member, true)) {
emit_signal(SNAME("go_to_help"), "class_property:" + result.class_name + ":" + result.class_member); result.class_name = cname;
break;
} break; }
case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: {
StringName cname = result.class_name;
while (true) {
if (ClassDB::has_method(cname, result.class_member)) {
result.class_name = cname;
cname = ClassDB::get_parent_class(cname); cname = ClassDB::get_parent_class(cname);
} else {
break;
} }
} emit_signal(SNAME("go_to_help"), "class_property:" + result.class_name + ":" + result.class_member);
} break;
emit_signal(SNAME("go_to_help"), "class_method:" + result.class_name + ":" + result.class_member); case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: {
StringName cname = result.class_name;
} break; while (ClassDB::class_exists(cname)) {
case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: { if (ClassDB::has_method(cname, result.class_member, true)) {
StringName cname = result.class_name; result.class_name = cname;
break;
while (true) { }
if (ClassDB::has_signal(cname, result.class_member)) {
result.class_name = cname;
cname = ClassDB::get_parent_class(cname); cname = ClassDB::get_parent_class(cname);
} else {
break;
} }
} emit_signal(SNAME("go_to_help"), "class_method:" + result.class_name + ":" + result.class_member);
} break;
emit_signal(SNAME("go_to_help"), "class_signal:" + result.class_name + ":" + result.class_member); case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: {
StringName cname = result.class_name;
} break; while (ClassDB::class_exists(cname)) {
case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: { if (ClassDB::has_signal(cname, result.class_member, true)) {
StringName cname = result.class_name; result.class_name = cname;
StringName success; break;
while (true) { }
success = ClassDB::get_integer_constant_enum(cname, result.class_member, true);
if (success != StringName()) {
result.class_name = cname;
cname = ClassDB::get_parent_class(cname); cname = ClassDB::get_parent_class(cname);
} else {
break;
} }
} emit_signal(SNAME("go_to_help"), "class_signal:" + result.class_name + ":" + result.class_member);
} break;
emit_signal(SNAME("go_to_help"), "class_enum:" + result.class_name + ":" + result.class_member); case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: {
StringName cname = result.class_name;
} break; while (ClassDB::class_exists(cname)) {
case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: { if (ClassDB::has_enum(cname, result.class_member, true)) {
emit_signal(SNAME("go_to_help"), "class_annotation:" + result.class_name + ":" + result.class_member); result.class_name = cname;
} break; break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: { }
emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member); cname = ClassDB::get_parent_class(cname);
} break; }
default: { emit_signal(SNAME("go_to_help"), "class_enum:" + result.class_name + ":" + result.class_member);
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: {
emit_signal(SNAME("go_to_help"), "class_annotation:" + result.class_name + ":" + result.class_member);
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: { // Deprecated.
emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member);
} break;
case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION:
case ScriptLanguage::LOOKUP_RESULT_LOCAL_CONSTANT:
case ScriptLanguage::LOOKUP_RESULT_LOCAL_VARIABLE:
case ScriptLanguage::LOOKUP_RESULT_MAX: {
// Nothing to do.
} break;
}
} else if (result.location >= 0) {
if (result.script.is_valid()) {
emit_signal(SNAME("request_open_script_at_line"), result.script, result.location - 1);
} else {
emit_signal(SNAME("request_save_history"));
goto_line_centered(result.location - 1);
} }
} }
} else if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { } else if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) {
@ -1102,6 +1097,109 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
} }
} }
void ScriptTextEditor::_show_symbol_tooltip(const String &p_symbol, int p_row, int p_column) {
Node *base = get_tree()->get_edited_scene_root();
if (base) {
base = _find_node_for_script(base, base, script);
}
ScriptLanguage::LookupResult result;
const String code_text = code_editor->get_text_editor()->get_text_with_cursor_char(p_row, p_column);
const Error lc_error = script->get_language()->lookup_code(code_text, p_symbol, script->get_path(), base, result);
if (lc_error != OK) {
return;
}
String doc_symbol;
switch (result.type) {
case ScriptLanguage::LOOKUP_RESULT_CLASS: {
doc_symbol = "class|" + result.class_name + "|";
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: {
StringName cname = result.class_name;
while (ClassDB::class_exists(cname)) {
if (ClassDB::has_integer_constant(cname, result.class_member, true)) {
result.class_name = cname;
break;
}
cname = ClassDB::get_parent_class(cname);
}
doc_symbol = "constant|" + result.class_name + "|" + result.class_member;
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: {
StringName cname = result.class_name;
while (ClassDB::class_exists(cname)) {
if (ClassDB::has_property(cname, result.class_member, true)) {
result.class_name = cname;
break;
}
cname = ClassDB::get_parent_class(cname);
}
doc_symbol = "property|" + result.class_name + "|" + result.class_member;
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: {
StringName cname = result.class_name;
while (ClassDB::class_exists(cname)) {
if (ClassDB::has_method(cname, result.class_member, true)) {
result.class_name = cname;
break;
}
cname = ClassDB::get_parent_class(cname);
}
doc_symbol = "method|" + result.class_name + "|" + result.class_member;
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: {
StringName cname = result.class_name;
while (ClassDB::class_exists(cname)) {
if (ClassDB::has_signal(cname, result.class_member, true)) {
result.class_name = cname;
break;
}
cname = ClassDB::get_parent_class(cname);
}
doc_symbol = "signal|" + result.class_name + "|" + result.class_member;
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: {
StringName cname = result.class_name;
while (ClassDB::class_exists(cname)) {
if (ClassDB::has_enum(cname, result.class_member, true)) {
result.class_name = cname;
break;
}
cname = ClassDB::get_parent_class(cname);
}
doc_symbol = "enum|" + result.class_name + "|" + result.class_member;
} break;
case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: {
doc_symbol = "annotation|" + result.class_name + "|" + result.class_member;
} break;
case ScriptLanguage::LOOKUP_RESULT_LOCAL_CONSTANT:
case ScriptLanguage::LOOKUP_RESULT_LOCAL_VARIABLE: {
const String item_type = (result.type == ScriptLanguage::LOOKUP_RESULT_LOCAL_CONSTANT) ? "local_constant" : "local_variable";
Dictionary item_data;
item_data["description"] = result.description;
item_data["is_deprecated"] = result.is_deprecated;
item_data["deprecated_message"] = result.deprecated_message;
item_data["is_experimental"] = result.is_experimental;
item_data["experimental_message"] = result.experimental_message;
item_data["doc_type"] = result.doc_type;
item_data["enumeration"] = result.enumeration;
item_data["is_bitfield"] = result.is_bitfield;
item_data["value"] = result.value;
doc_symbol = item_type + "||" + p_symbol + "|" + JSON::stringify(item_data);
} break;
case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION:
case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: // Deprecated.
case ScriptLanguage::LOOKUP_RESULT_MAX: {
// Nothing to do.
} break;
}
if (!doc_symbol.is_empty()) {
EditorHelpBitTooltip::show_tooltip(code_editor->get_text_editor(), doc_symbol, String(), true);
}
}
String ScriptTextEditor::_get_absolute_path(const String &rel_path) { String ScriptTextEditor::_get_absolute_path(const String &rel_path) {
String base_path = script->get_path().get_base_dir(); String base_path = script->get_path().get_base_dir();
String path = base_path.path_join(rel_path); String path = base_path.path_join(rel_path);
@ -2235,6 +2333,7 @@ void ScriptTextEditor::_enable_code_editor() {
code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script)); code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script));
code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings)); code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings));
code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol)); code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol));
code_editor->get_text_editor()->connect("symbol_hovered", callable_mp(this, &ScriptTextEditor::_show_symbol_tooltip));
code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol)); code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol));
code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes));
code_editor->get_text_editor()->connect("gutter_removed", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); code_editor->get_text_editor()->connect("gutter_removed", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes));
@ -2411,6 +2510,7 @@ ScriptTextEditor::ScriptTextEditor() {
update_settings(); update_settings();
code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);
code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(true);
code_editor->get_text_editor()->set_context_menu_enabled(false); code_editor->get_text_editor()->set_context_menu_enabled(false);
context_menu = memnew(PopupMenu); context_menu = memnew(PopupMenu);

View file

@ -200,6 +200,8 @@ protected:
void _lookup_symbol(const String &p_symbol, int p_row, int p_column); void _lookup_symbol(const String &p_symbol, int p_row, int p_column);
void _validate_symbol(const String &p_symbol); void _validate_symbol(const String &p_symbol);
void _show_symbol_tooltip(const String &p_symbol, int p_row, int p_column);
void _convert_case(CodeTextEditor::CaseStyle p_case); void _convert_case(CodeTextEditor::CaseStyle p_case);
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);

View file

@ -289,7 +289,7 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
} }
} }
String GDScriptDocGen::_docvalue_from_expression(const GDP::ExpressionNode *p_expression) { String GDScriptDocGen::docvalue_from_expression(const GDP::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, String()); ERR_FAIL_NULL_V(p_expression, String());
if (p_expression->is_constant) { if (p_expression->is_constant) {
@ -385,6 +385,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.name = const_name; const_doc.name = const_name;
const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value); const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value);
const_doc.is_value_valid = true; const_doc.is_value_valid = true;
_doctype_from_gdtype(m_const->get_datatype(), const_doc.type, const_doc.enumeration);
const_doc.description = m_const->doc_data.description; const_doc.description = m_const->doc_data.description;
const_doc.is_deprecated = m_const->doc_data.is_deprecated; const_doc.is_deprecated = m_const->doc_data.is_deprecated;
const_doc.deprecated_message = m_const->doc_data.deprecated_message; const_doc.deprecated_message = m_const->doc_data.deprecated_message;
@ -425,7 +426,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
arg_doc.name = p->identifier->name; arg_doc.name = p->identifier->name;
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration); _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
if (p->initializer != nullptr) { if (p->initializer != nullptr) {
arg_doc.default_value = _docvalue_from_expression(p->initializer); arg_doc.default_value = docvalue_from_expression(p->initializer);
} }
method_doc.arguments.push_back(arg_doc); method_doc.arguments.push_back(arg_doc);
} }
@ -494,7 +495,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
} }
if (m_var->initializer != nullptr) { if (m_var->initializer != nullptr) {
prop_doc.default_value = _docvalue_from_expression(m_var->initializer); prop_doc.default_value = docvalue_from_expression(m_var->initializer);
} }
prop_doc.overridden = false; prop_doc.overridden = false;
@ -521,6 +522,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.name = val.identifier->name; const_doc.name = val.identifier->name;
const_doc.value = _docvalue_from_variant(val.value); const_doc.value = _docvalue_from_variant(val.value);
const_doc.is_value_valid = true; const_doc.is_value_valid = true;
const_doc.type = "int";
const_doc.enumeration = name; const_doc.enumeration = name;
const_doc.description = val.doc_data.description; const_doc.description = val.doc_data.description;
const_doc.is_deprecated = val.doc_data.is_deprecated; const_doc.is_deprecated = val.doc_data.is_deprecated;
@ -543,6 +545,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.name = name; const_doc.name = name;
const_doc.value = _docvalue_from_variant(m_enum_val.value); const_doc.value = _docvalue_from_variant(m_enum_val.value);
const_doc.is_value_valid = true; const_doc.is_value_valid = true;
const_doc.type = "int";
const_doc.enumeration = "@unnamed_enums"; const_doc.enumeration = "@unnamed_enums";
const_doc.description = m_enum_val.doc_data.description; const_doc.description = m_enum_val.doc_data.description;
const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated; const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
@ -570,3 +573,14 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
_generate_docs(p_script, p_class); _generate_docs(p_script, p_class);
singletons.clear(); singletons.clear();
} }
// This method is needed for the editor, since during autocompletion the script is not compiled, only analyzed.
void GDScriptDocGen::doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
if (E.value.is_singleton) {
singletons[E.value.path] = E.key;
}
}
_doctype_from_gdtype(p_gdtype, r_type, r_enum, p_is_return);
singletons.clear();
}

View file

@ -45,11 +45,12 @@ class GDScriptDocGen {
static String _get_class_name(const GDP::ClassNode &p_class); static String _get_class_name(const GDP::ClassNode &p_class);
static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false); static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1); static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1);
static String _docvalue_from_expression(const GDP::ExpressionNode *p_expression);
static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class); static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
public: public:
static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class); static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
static void doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
static String docvalue_from_expression(const GDP::ExpressionNode *p_expression);
}; };
#endif // GDSCRIPT_DOCGEN_H #endif // GDSCRIPT_DOCGEN_H

View file

@ -479,23 +479,25 @@ void GDScript::_update_exports_values(HashMap<StringName, Variant> &values, List
} }
} }
void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) { void GDScript::_add_doc(const DocData::ClassDoc &p_doc) {
if (_owner) { // Only the top-level class stores doc info doc_class_name = p_doc.name;
_owner->_add_doc(p_inner_class); if (_owner) { // Only the top-level class stores doc info.
} else { // Remove old docs, add new _owner->_add_doc(p_doc);
} else { // Remove old docs, add new.
for (int i = 0; i < docs.size(); i++) { for (int i = 0; i < docs.size(); i++) {
if (docs[i].name == p_inner_class.name) { if (docs[i].name == p_doc.name) {
docs.remove_at(i); docs.remove_at(i);
break; break;
} }
} }
docs.append(p_inner_class); docs.append(p_doc);
} }
} }
void GDScript::_clear_doc() { void GDScript::_clear_doc() {
docs.clear(); doc_class_name = StringName();
doc = DocData::ClassDoc(); doc = DocData::ClassDoc();
docs.clear();
} }
String GDScript::get_class_icon_path() const { String GDScript::get_class_icon_path() const {

View file

@ -157,10 +157,11 @@ private:
bool placeholder_fallback_enabled = false; bool placeholder_fallback_enabled = false;
void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames); void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames);
StringName doc_class_name;
DocData::ClassDoc doc; DocData::ClassDoc doc;
Vector<DocData::ClassDoc> docs; Vector<DocData::ClassDoc> docs;
void _add_doc(const DocData::ClassDoc &p_doc);
void _clear_doc(); void _clear_doc();
void _add_doc(const DocData::ClassDoc &p_inner_class);
#endif #endif
GDScriptFunction *implicit_initializer = nullptr; GDScriptFunction *implicit_initializer = nullptr;
@ -292,9 +293,8 @@ public:
virtual void update_exports() override; virtual void update_exports() override;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
virtual Vector<DocData::ClassDoc> get_documentation() const override { virtual StringName get_doc_class_name() const override { return doc_class_name; }
return docs; virtual Vector<DocData::ClassDoc> get_documentation() const override { return docs; }
}
virtual String get_class_icon_path() const override; virtual String get_class_icon_path() const override;
#endif // TOOLS_ENABLED #endif // TOOLS_ENABLED

View file

@ -127,7 +127,6 @@ class GDScriptAnalyzer {
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr); bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
@ -163,7 +162,9 @@ public:
Error analyze(); Error analyze();
Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable);
static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptAnalyzer(GDScriptParser *p_parser); GDScriptAnalyzer(GDScriptParser *p_parser);
}; };

View file

@ -31,12 +31,12 @@
#include "gdscript.h" #include "gdscript.h"
#include "gdscript_analyzer.h" #include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h" #include "gdscript_parser.h"
#include "gdscript_tokenizer.h" #include "gdscript_tokenizer.h"
#include "gdscript_utility_functions.h" #include "gdscript_utility_functions.h"
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
#include "editor/gdscript_docgen.h"
#include "editor/script_templates/templates.gen.h" #include "editor/script_templates/templates.gen.h"
#endif #endif
@ -3659,60 +3659,174 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, bool p_is_function, GDScriptLanguage::LookupResult &r_result) { static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, const String &p_symbol, GDScriptLanguage::LookupResult &r_result) {
GDScriptParser::DataType base_type = p_base; GDScriptParser::DataType base_type = p_base;
while (base_type.is_set()) { while (true) {
switch (base_type.kind) { switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: { case GDScriptParser::DataType::CLASS: {
if (base_type.class_type) { ERR_FAIL_NULL_V(base_type.class_type, ERR_BUG);
String name = p_symbol;
if (name == "new") { String name = p_symbol;
name = "_init"; if (name == "new") {
} name = "_init";
if (base_type.class_type->has_member(name)) { }
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
r_result.location = base_type.class_type->get_member(name).get_line(); if (!base_type.class_type->has_member(name)) {
r_result.class_path = base_type.script_path;
Error err = OK;
r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
return err;
}
base_type = base_type.class_type->base_type; base_type = base_type.class_type->base_type;
}
} break;
case GDScriptParser::DataType::SCRIPT: {
Ref<Script> scr = base_type.script_type;
if (scr.is_valid()) {
int line = scr->get_member_line(p_symbol);
if (line >= 0) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
r_result.location = line;
r_result.script = scr;
return OK;
}
Ref<Script> base_script = scr->get_base_script();
if (base_script.is_valid()) {
base_type.script_type = base_script;
} else {
base_type.kind = GDScriptParser::DataType::NATIVE;
base_type.builtin_type = Variant::OBJECT;
base_type.native_type = scr->get_instance_base_type();
}
} else {
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
}
} break;
case GDScriptParser::DataType::NATIVE: {
StringName class_name = base_type.native_type;
if (!ClassDB::class_exists(class_name)) {
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
break; break;
} }
const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(name);
switch (member.type) {
case GDScriptParser::ClassNode::Member::UNDEFINED:
case GDScriptParser::ClassNode::Member::GROUP:
return ERR_BUG;
case GDScriptParser::ClassNode::Member::CLASS: {
String type_name;
String enum_name;
GDScriptDocGen::doctype_from_gdtype(GDScriptAnalyzer::type_from_metatype(member.get_datatype()), type_name, enum_name);
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
r_result.class_name = type_name;
} break;
case GDScriptParser::ClassNode::Member::CONSTANT:
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
break;
case GDScriptParser::ClassNode::Member::FUNCTION:
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
break;
case GDScriptParser::ClassNode::Member::SIGNAL:
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL;
break;
case GDScriptParser::ClassNode::Member::VARIABLE:
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY;
break;
case GDScriptParser::ClassNode::Member::ENUM:
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
break;
case GDScriptParser::ClassNode::Member::ENUM_VALUE:
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
break;
}
if (member.type != GDScriptParser::ClassNode::Member::CLASS) {
String type_name;
String enum_name;
GDScriptDocGen::doctype_from_gdtype(GDScriptAnalyzer::type_from_metatype(base_type), type_name, enum_name);
r_result.class_name = type_name;
r_result.class_member = name;
}
Error err = OK;
r_result.script = GDScriptCache::get_shallow_script(base_type.script_path, err);
r_result.script_path = base_type.script_path;
r_result.location = member.get_line();
return err;
} break;
case GDScriptParser::DataType::SCRIPT: {
const Ref<Script> scr = base_type.script_type;
if (scr.is_null()) {
return ERR_CANT_RESOLVE;
}
String name = p_symbol;
if (name == "new") {
name = "_init";
}
const int line = scr->get_member_line(name);
if (line >= 0) {
bool found_type = false;
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
{
List<PropertyInfo> properties;
scr->get_script_property_list(&properties);
for (const PropertyInfo &property : properties) {
if (property.name == name && (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE)) {
found_type = true;
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY;
r_result.class_name = scr->get_doc_class_name();
r_result.class_member = name;
break;
}
}
}
if (!found_type) {
List<MethodInfo> methods;
scr->get_script_method_list(&methods);
for (const MethodInfo &method : methods) {
if (method.name == name) {
found_type = true;
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
r_result.class_name = scr->get_doc_class_name();
r_result.class_member = name;
break;
}
}
}
if (!found_type) {
List<MethodInfo> signals;
scr->get_script_method_list(&signals);
for (const MethodInfo &signal : signals) {
if (signal.name == name) {
found_type = true;
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL;
r_result.class_name = scr->get_doc_class_name();
r_result.class_member = name;
break;
}
}
}
if (!found_type) {
const Ref<GDScript> gds = scr;
if (gds.is_valid()) {
const Ref<GDScript> *subclass = gds->get_subclasses().getptr(name);
if (subclass != nullptr) {
found_type = true;
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
r_result.class_name = subclass->ptr()->get_doc_class_name();
}
// TODO: enums.
}
}
if (!found_type) {
HashMap<StringName, Variant> constants;
scr->get_constants(&constants);
if (constants.has(name)) {
found_type = true;
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = scr->get_doc_class_name();
r_result.class_member = name;
}
}
r_result.script = scr;
r_result.script_path = base_type.script_path;
r_result.location = line;
return OK;
}
const Ref<Script> base_script = scr->get_base_script();
if (base_script.is_valid()) {
base_type.script_type = base_script;
} else {
base_type.kind = GDScriptParser::DataType::NATIVE;
base_type.builtin_type = Variant::OBJECT;
base_type.native_type = scr->get_instance_base_type();
}
} break;
case GDScriptParser::DataType::NATIVE: {
const StringName &class_name = base_type.native_type;
ERR_FAIL_COND_V(!ClassDB::class_exists(class_name), ERR_BUG);
if (ClassDB::has_method(class_name, p_symbol, true)) { if (ClassDB::has_method(class_name, p_symbol, true)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
r_result.class_name = base_type.native_type; r_result.class_name = class_name;
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
@ -3722,7 +3836,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
for (const MethodInfo &E : virtual_methods) { for (const MethodInfo &E : virtual_methods) {
if (E.name == p_symbol) { if (E.name == p_symbol) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
r_result.class_name = base_type.native_type; r_result.class_name = class_name;
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
@ -3730,7 +3844,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
if (ClassDB::has_signal(class_name, p_symbol, true)) { if (ClassDB::has_signal(class_name, p_symbol, true)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL;
r_result.class_name = base_type.native_type; r_result.class_name = class_name;
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
@ -3740,7 +3854,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
for (const StringName &E : enums) { for (const StringName &E : enums) {
if (E == p_symbol) { if (E == p_symbol) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
r_result.class_name = base_type.native_type; r_result.class_name = class_name;
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
@ -3748,7 +3862,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
if (!String(ClassDB::get_integer_constant_enum(class_name, p_symbol, true)).is_empty()) { if (!String(ClassDB::get_integer_constant_enum(class_name, p_symbol, true)).is_empty()) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = base_type.native_type; r_result.class_name = class_name;
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
@ -3758,7 +3872,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
for (const String &E : constants) { for (const String &E : constants) {
if (E == p_symbol) { if (E == p_symbol) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = base_type.native_type; r_result.class_name = class_name;
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
@ -3772,80 +3886,98 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
} }
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY;
r_result.class_name = base_type.native_type; r_result.class_name = class_name;
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
StringName parent = ClassDB::get_parent_class(class_name); const StringName parent_class = ClassDB::get_parent_class(class_name);
if (parent != StringName()) { if (parent_class != StringName()) {
base_type.native_type = parent; base_type.native_type = parent_class;
} else { } else {
base_type.kind = GDScriptParser::DataType::UNRESOLVED; return ERR_CANT_RESOLVE;
} }
} break; } break;
case GDScriptParser::DataType::BUILTIN: { case GDScriptParser::DataType::BUILTIN: {
base_type.kind = GDScriptParser::DataType::UNRESOLVED; if (base_type.is_meta_type) {
if (Variant::has_enum(base_type.builtin_type, p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
r_result.class_name = Variant::get_type_name(base_type.builtin_type);
r_result.class_member = p_symbol;
return OK;
}
if (Variant::has_constant(base_type.builtin_type, p_symbol)) { if (Variant::has_constant(base_type.builtin_type, p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = Variant::get_type_name(base_type.builtin_type); r_result.class_name = Variant::get_type_name(base_type.builtin_type);
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
Variant v;
Ref<RefCounted> v_ref;
if (base_type.builtin_type == Variant::OBJECT) {
v_ref.instantiate();
v = v_ref;
} else { } else {
Callable::CallError err; if (Variant::has_member(base_type.builtin_type, p_symbol)) {
Variant::construct(base_type.builtin_type, v, nullptr, 0, err); r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY;
if (err.error != Callable::CallError::CALL_OK) { r_result.class_name = Variant::get_type_name(base_type.builtin_type);
break; r_result.class_member = p_symbol;
return OK;
} }
} }
if (v.has_method(p_symbol)) { if (Variant::has_builtin_method(base_type.builtin_type, p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
r_result.class_name = Variant::get_type_name(base_type.builtin_type); r_result.class_name = Variant::get_type_name(base_type.builtin_type);
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
return OK; return OK;
} }
bool valid = false; return ERR_CANT_RESOLVE;
v.get(p_symbol, &valid);
if (valid) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY;
r_result.class_name = Variant::get_type_name(base_type.builtin_type);
r_result.class_member = p_symbol;
return OK;
}
} break; } break;
case GDScriptParser::DataType::ENUM: { case GDScriptParser::DataType::ENUM: {
if (base_type.class_type && base_type.class_type->has_member(base_type.enum_type)) { if (base_type.is_meta_type) {
GDScriptParser::EnumNode *base_enum = base_type.class_type->get_member(base_type.enum_type).m_enum; if (base_type.enum_values.has(p_symbol)) {
for (const GDScriptParser::EnumNode::Value &value : base_enum->values) { String type_name;
if (value.identifier && value.identifier->name == p_symbol) { String enum_name;
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; GDScriptDocGen::doctype_from_gdtype(GDScriptAnalyzer::type_from_metatype(base_type), type_name, enum_name);
r_result.class_path = base_type.script_path;
r_result.location = value.line; if (CoreConstants::is_global_enum(enum_name)) {
Error err = OK; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); r_result.class_name = "@GlobalScope";
return err; r_result.class_member = p_symbol;
return OK;
} else {
const int dot_pos = enum_name.rfind_char('.');
if (dot_pos >= 0) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = enum_name.left(dot_pos);
r_result.class_member = p_symbol;
return OK;
}
} }
} else if (Variant::has_builtin_method(Variant::DICTIONARY, p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
r_result.class_name = "Dictionary";
r_result.class_member = p_symbol;
return OK;
} }
} else if (base_type.enum_values.has(p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = String(base_type.native_type).get_slicec('.', 0);
r_result.class_member = p_symbol;
return OK;
} }
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
return ERR_CANT_RESOLVE;
} break; } break;
default: { case GDScriptParser::DataType::VARIANT: {
base_type.kind = GDScriptParser::DataType::UNRESOLVED; if (base_type.is_meta_type) {
const String enum_name = "Variant." + p_symbol;
if (CoreConstants::is_global_enum(enum_name)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
r_result.class_name = "@GlobalScope";
r_result.class_member = enum_name;
return OK;
}
}
return ERR_CANT_RESOLVE;
} break;
case GDScriptParser::DataType::RESOLVING:
case GDScriptParser::DataType::UNRESOLVED: {
return ERR_CANT_RESOLVE;
} break; } break;
} }
} }
@ -3867,13 +3999,13 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return OK; return OK;
} }
if ("Variant" == p_symbol) { if (p_symbol == "Variant") {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
r_result.class_name = "Variant"; r_result.class_name = "Variant";
return OK; return OK;
} }
if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { if (p_symbol == "PI" || p_symbol == "TAU" || p_symbol == "INF" || p_symbol == "NAN") {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = "@GDScript"; r_result.class_name = "@GDScript";
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
@ -3888,9 +4020,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
// Allows class functions with the names like built-ins to be handled properly. // Allows class functions with the names like built-ins to be handled properly.
if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) {
// Need special checks for assert and preload as they are technically // Need special checks for `assert` and `preload` as they are technically
// keywords, so are not registered in GDScriptUtilityFunctions. // keywords, so are not registered in `GDScriptUtilityFunctions`.
if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { if (GDScriptUtilityFunctions::function_exists(p_symbol) || p_symbol == "assert" || p_symbol == "preload") {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
r_result.class_name = "@GDScript"; r_result.class_name = "@GDScript";
r_result.class_member = p_symbol; r_result.class_member = p_symbol;
@ -3952,26 +4084,16 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
switch (context.type) { switch (context.type) {
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: { case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: {
if (!Variant::has_builtin_method(context.builtin_type, StringName(p_symbol))) {
// A constant.
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = Variant::get_type_name(context.builtin_type);
r_result.class_member = p_symbol;
return OK;
}
// A method.
GDScriptParser::DataType base_type; GDScriptParser::DataType base_type;
base_type.kind = GDScriptParser::DataType::BUILTIN; base_type.kind = GDScriptParser::DataType::BUILTIN;
base_type.builtin_type = context.builtin_type; base_type.builtin_type = context.builtin_type;
if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { base_type.is_meta_type = true;
if (_lookup_symbol_from_base(base_type, p_symbol, r_result) == OK) {
return OK; return OK;
} }
} break; } break;
case GDScriptParser::COMPLETION_SUPER_METHOD: case GDScriptParser::COMPLETION_SUPER_METHOD:
case GDScriptParser::COMPLETION_METHOD: { case GDScriptParser::COMPLETION_METHOD:
is_function = true;
[[fallthrough]];
}
case GDScriptParser::COMPLETION_ASSIGN: case GDScriptParser::COMPLETION_ASSIGN:
case GDScriptParser::COMPLETION_CALL_ARGUMENTS: case GDScriptParser::COMPLETION_CALL_ARGUMENTS:
case GDScriptParser::COMPLETION_IDENTIFIER: case GDScriptParser::COMPLETION_IDENTIFIER:
@ -3993,45 +4115,96 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
const GDScriptParser::SuiteNode *suite = context.current_suite; const GDScriptParser::SuiteNode *suite = context.current_suite;
while (suite) { while (suite) {
if (suite->has_local(p_symbol)) { if (suite->has_local(p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_symbol);
r_result.location = suite->get_local(p_symbol).start_line;
return OK; switch (local.type) {
case GDScriptParser::SuiteNode::Local::UNDEFINED:
return ERR_BUG;
case GDScriptParser::SuiteNode::Local::CONSTANT:
r_result.type = ScriptLanguage::LOOKUP_RESULT_LOCAL_CONSTANT;
r_result.description = local.constant->doc_data.description;
r_result.is_deprecated = local.constant->doc_data.is_deprecated;
r_result.deprecated_message = local.constant->doc_data.deprecated_message;
r_result.is_experimental = local.constant->doc_data.is_experimental;
r_result.experimental_message = local.constant->doc_data.experimental_message;
if (local.constant->initializer != nullptr) {
r_result.value = GDScriptDocGen::docvalue_from_expression(local.constant->initializer);
}
break;
case GDScriptParser::SuiteNode::Local::VARIABLE:
r_result.type = ScriptLanguage::LOOKUP_RESULT_LOCAL_VARIABLE;
r_result.description = local.variable->doc_data.description;
r_result.is_deprecated = local.variable->doc_data.is_deprecated;
r_result.deprecated_message = local.variable->doc_data.deprecated_message;
r_result.is_experimental = local.variable->doc_data.is_experimental;
r_result.experimental_message = local.variable->doc_data.experimental_message;
if (local.variable->initializer != nullptr) {
r_result.value = GDScriptDocGen::docvalue_from_expression(local.variable->initializer);
}
break;
case GDScriptParser::SuiteNode::Local::PARAMETER:
case GDScriptParser::SuiteNode::Local::FOR_VARIABLE:
case GDScriptParser::SuiteNode::Local::PATTERN_BIND:
r_result.type = ScriptLanguage::LOOKUP_RESULT_LOCAL_VARIABLE;
break;
}
GDScriptDocGen::doctype_from_gdtype(local.get_datatype(), r_result.doc_type, r_result.enumeration);
Error err = OK;
r_result.script = GDScriptCache::get_shallow_script(base_type.script_path, err);
r_result.script_path = base_type.script_path;
r_result.location = local.start_line;
return err;
} }
suite = suite->parent_block; suite = suite->parent_block;
} }
} }
if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { if (_lookup_symbol_from_base(base_type, p_symbol, r_result) == OK) {
return OK; return OK;
} }
if (!is_function) { if (!is_function) {
// Guess in autoloads as singletons.
if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) {
const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(p_symbol); const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(p_symbol);
if (autoload.is_singleton) { if (autoload.is_singleton) {
String scr_path = autoload.path; String scr_path = autoload.path;
if (!scr_path.ends_with(".gd")) { if (!scr_path.ends_with(".gd")) {
// Not a script, try find the script anyway, // Not a script, try find the script anyway, may have some success.
// may have some success.
scr_path = scr_path.get_basename() + ".gd"; scr_path = scr_path.get_basename() + ".gd";
} }
if (FileAccess::exists(scr_path)) { if (FileAccess::exists(scr_path)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
r_result.location = 0; r_result.class_name = p_symbol;
r_result.script = ResourceLoader::load(scr_path); r_result.script = ResourceLoader::load(scr_path);
r_result.script_path = scr_path;
r_result.location = 0;
return OK; return OK;
} }
} }
} }
// Global. if (ScriptServer::is_global_class(p_symbol)) {
HashMap<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map(); const String scr_path = ScriptServer::get_global_class_path(p_symbol);
if (classes.has(p_symbol)) { const Ref<Script> scr = ResourceLoader::load(scr_path);
Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]]; if (scr.is_null()) {
return ERR_BUG;
}
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
r_result.class_name = scr->get_doc_class_name();
r_result.script = scr;
r_result.script_path = scr_path;
r_result.location = 0;
return OK;
}
const HashMap<StringName, int> &global_map = GDScriptLanguage::get_singleton()->get_global_map();
if (global_map.has(p_symbol)) {
Variant value = GDScriptLanguage::get_singleton()->get_global_array()[global_map[p_symbol]];
if (value.get_type() == Variant::OBJECT) { if (value.get_type() == Variant::OBJECT) {
Object *obj = value; const Object *obj = value;
if (obj) { if (obj) {
if (Object::cast_to<GDScriptNativeClass>(obj)) { if (Object::cast_to<GDScriptNativeClass>(obj)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
@ -4040,52 +4213,34 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
r_result.class_name = obj->get_class(); r_result.class_name = obj->get_class();
} }
return OK;
}
}
}
// proxy class remove the underscore. if (CoreConstants::is_global_enum(p_symbol)) {
if (r_result.class_name.begins_with("_")) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
r_result.class_name = r_result.class_name.substr(1); r_result.class_name = "@GlobalScope";
} r_result.class_member = p_symbol;
return OK; return OK;
} }
} else {
/* if (CoreConstants::is_global_constant(p_symbol)) {
// Because get_integer_constant_enum and get_integer_constant don't work on @GlobalScope r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
// We cannot determine the exact nature of the identifier here r_result.class_name = "@GlobalScope";
// Otherwise these codes would work r_result.class_member = p_symbol;
StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true); return OK;
if (enumName != nullptr) { }
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
r_result.class_name = "@GlobalScope"; if (Variant::has_utility_function(p_symbol)) {
r_result.class_member = enumName; r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
return OK; r_result.class_name = "@GlobalScope";
} r_result.class_member = p_symbol;
else { return OK;
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = "@GlobalScope";
r_result.class_member = p_symbol;
return OK;
}*/
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE;
r_result.class_name = "@GlobalScope";
r_result.class_member = p_symbol;
return OK;
}
} else {
List<StringName> utility_functions;
Variant::get_utility_function_list(&utility_functions);
if (utility_functions.find(p_symbol) != nullptr) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE;
r_result.class_name = "@GlobalScope";
r_result.class_member = p_symbol;
return OK;
}
} }
} }
} break; } break;
case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: { case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD:
is_function = true;
[[fallthrough]];
}
case GDScriptParser::COMPLETION_ATTRIBUTE: { case GDScriptParser::COMPLETION_ATTRIBUTE: {
if (context.node->type != GDScriptParser::Node::SUBSCRIPT) { if (context.node->type != GDScriptParser::Node::SUBSCRIPT) {
break; break;
@ -4101,7 +4256,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
break; break;
} }
if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) { if (_lookup_symbol_from_base(base.type, p_symbol, r_result) == OK) {
return OK; return OK;
} }
} break; } break;
@ -4128,14 +4283,14 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
base_type = base.type; base_type = base.type;
} }
if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { if (_lookup_symbol_from_base(base_type, p_symbol, r_result) == OK) {
return OK; return OK;
} }
} break; } break;
case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { case GDScriptParser::COMPLETION_OVERRIDE_METHOD: {
GDScriptParser::DataType base_type = context.current_class->base_type; GDScriptParser::DataType base_type = context.current_class->base_type;
if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { if (_lookup_symbol_from_base(base_type, p_symbol, r_result) == OK) {
return OK; return OK;
} }
} break; } break;
@ -4144,7 +4299,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
case GDScriptParser::COMPLETION_TYPE_NAME: { case GDScriptParser::COMPLETION_TYPE_NAME: {
GDScriptParser::DataType base_type = context.current_class->get_datatype(); GDScriptParser::DataType base_type = context.current_class->get_datatype();
if (_lookup_symbol_from_base(base_type, p_symbol, false, r_result) == OK) { if (_lookup_symbol_from_base(base_type, p_symbol, r_result) == OK) {
return OK; return OK;
} }
} break; } break;

View file

@ -736,19 +736,35 @@ void GDScriptParser::parse_program() {
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD #undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
parse_class_body(true); parse_class_body(true);
head->end_line = current.end_line;
head->end_column = current.end_column;
head->leftmost_column = MIN(head->leftmost_column, current.leftmost_column);
head->rightmost_column = MAX(head->rightmost_column, current.rightmost_column);
complete_extents(head); complete_extents(head);
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments(); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = MIN(max_script_doc_line, head->end_line);
while (line > 0) { int max_line = head->end_line;
if (!head->members.is_empty()) {
max_line = MIN(max_script_doc_line, head->members[0].get_line() - 1);
}
int line = 0;
while (line <= max_line) {
// Find the start.
if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) { if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {
// Find the end.
while (line + 1 <= max_line && comments.has(line + 1) && comments[line + 1].new_line && comments[line + 1].comment.begins_with("##")) {
line++;
}
head->doc_data = parse_class_doc_comment(line); head->doc_data = parse_class_doc_comment(line);
break; break;
} }
line--; line++;
} }
#endif // TOOLS_ENABLED #endif // TOOLS_ENABLED
if (!check(GDScriptTokenizer::Token::TK_EOF)) { if (!check(GDScriptTokenizer::Token::TK_EOF)) {
@ -1612,6 +1628,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
function->is_static = p_is_static; function->is_static = p_is_static;
SuiteNode *body = alloc_node<SuiteNode>(); SuiteNode *body = alloc_node<SuiteNode>();
SuiteNode *previous_suite = current_suite; SuiteNode *previous_suite = current_suite;
current_suite = body; current_suite = body;
@ -1620,6 +1637,11 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
parse_function_signature(function, body, "function"); parse_function_signature(function, body, "function");
current_suite = previous_suite; current_suite = previous_suite;
#ifdef TOOLS_ENABLED
function->min_local_doc_line = previous.end_line + 1;
#endif
function->body = parse_suite("function declaration", body); function->body = parse_suite("function declaration", body);
current_function = previous_function; current_function = previous_function;
@ -1988,12 +2010,45 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
} }
} }
#ifdef TOOLS_ENABLED
int doc_comment_line = 0;
if (result != nullptr) {
doc_comment_line = result->start_line - 1;
}
#endif // TOOLS_ENABLED
if (result != nullptr && !annotations.is_empty()) { if (result != nullptr && !annotations.is_empty()) {
for (AnnotationNode *&annotation : annotations) { for (AnnotationNode *&annotation : annotations) {
result->annotations.push_back(annotation); result->annotations.push_back(annotation);
#ifdef TOOLS_ENABLED
if (annotation->start_line <= doc_comment_line) {
doc_comment_line = annotation->start_line - 1;
}
#endif // TOOLS_ENABLED
} }
} }
#ifdef TOOLS_ENABLED
if (result != nullptr) {
MemberDocData doc_data;
if (has_comment(result->start_line, true)) {
// Inline doc comment.
doc_data = parse_doc_comment(result->start_line, true);
} else if (doc_comment_line >= current_function->min_local_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
// Normal doc comment.
doc_data = parse_doc_comment(doc_comment_line);
}
if (result->type == Node::CONSTANT) {
static_cast<ConstantNode *>(result)->doc_data = doc_data;
} else if (result->type == Node::VARIABLE) {
static_cast<VariableNode *>(result)->doc_data = doc_data;
}
current_function->min_local_doc_line = result->end_line + 1; // Prevent multiple locals from using the same doc comment.
}
#endif // TOOLS_ENABLED
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (unreachable && result != nullptr) { if (unreachable && result != nullptr) {
current_suite->has_unreachable_code = true; current_suite->has_unreachable_code = true;

View file

@ -862,6 +862,7 @@ public:
Vector<Variant> default_arg_values; Vector<Variant> default_arg_values;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
MemberDocData doc_data; MemberDocData doc_data;
int min_local_doc_line = 0;
#endif // TOOLS_ENABLED #endif // TOOLS_ENABLED
bool resolved_signature = false; bool resolved_signature = false;

View file

@ -699,12 +699,12 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
symbol_identifier = "_init"; symbol_identifier = "_init";
} }
if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) { if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {
if (ret.type == ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION) { if (ret.location >= 0) {
String target_script_path = path; String target_script_path = path;
if (!ret.script.is_null()) { if (ret.script.is_valid()) {
target_script_path = ret.script->get_path(); target_script_path = ret.script->get_path();
} else if (!ret.class_path.is_empty()) { } else if (!ret.script_path.is_empty()) {
target_script_path = ret.class_path; target_script_path = ret.script_path;
} }
if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
@ -720,7 +720,6 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
} }
} }
} }
} else { } else {
String member = ret.class_member; String member = ret.class_member;
if (member.is_empty() && symbol_identifier != ret.class_name) { if (member.is_empty() && symbol_identifier != ret.class_name) {

View file

@ -243,6 +243,7 @@ public:
void set_source_code(const String &p_code) override; void set_source_code(const String &p_code) override;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
virtual StringName get_doc_class_name() const override { return StringName(); } // TODO
virtual Vector<DocData::ClassDoc> get_documentation() const override { virtual Vector<DocData::ClassDoc> get_documentation() const override {
// TODO // TODO
Vector<DocData::ClassDoc> docs; Vector<DocData::ClassDoc> docs;

View file

@ -31,13 +31,27 @@
#include "code_edit.h" #include "code_edit.h"
#include "code_edit.compat.inc" #include "code_edit.compat.inc"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h" #include "core/os/keyboard.h"
#include "core/string/string_builder.h" #include "core/string/string_builder.h"
#include "core/string/ustring.h" #include "core/string/ustring.h"
#include "scene/theme/theme_db.h" #include "scene/theme/theme_db.h"
void CodeEdit::_apply_project_settings() {
symbol_tooltip_timer->set_wait_time(GLOBAL_GET("gui/timers/tooltip_delay_sec"));
}
void CodeEdit::_notification(int p_what) { void CodeEdit::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_READY: {
_apply_project_settings();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CodeEdit::_apply_project_settings));
}
#endif // TOOLS_ENABLED
} break;
case NOTIFICATION_THEME_CHANGED: { case NOTIFICATION_THEME_CHANGED: {
set_gutter_width(main_gutter, get_line_height()); set_gutter_width(main_gutter, get_line_height());
_update_line_number_gutter_width(); _update_line_number_gutter_width();
@ -262,6 +276,7 @@ void CodeEdit::_notification(int p_what) {
case NOTIFICATION_MOUSE_EXIT: { case NOTIFICATION_MOUSE_EXIT: {
queue_redraw(); queue_redraw();
symbol_tooltip_timer->stop();
} break; } break;
} }
} }
@ -438,6 +453,12 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
} }
} }
if (symbol_tooltip_on_hover_enabled) {
symbol_tooltip_pos = get_line_column_at_pos(mpos, false);
symbol_tooltip_word = get_word_at_pos(mpos);
symbol_tooltip_timer->start();
}
bool scroll_hovered = code_completion_scroll_rect.has_point(mpos); bool scroll_hovered = code_completion_scroll_rect.has_point(mpos);
if (is_code_completion_scroll_hovered != scroll_hovered) { if (is_code_completion_scroll_hovered != scroll_hovered) {
is_code_completion_scroll_hovered = scroll_hovered; is_code_completion_scroll_hovered = scroll_hovered;
@ -2408,6 +2429,26 @@ void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) {
} }
} }
/* Symbol tooltip */
void CodeEdit::set_symbol_tooltip_on_hover_enabled(bool p_enabled) {
symbol_tooltip_on_hover_enabled = p_enabled;
if (!p_enabled) {
symbol_tooltip_timer->stop();
}
}
bool CodeEdit::is_symbol_tooltip_on_hover_enabled() const {
return symbol_tooltip_on_hover_enabled;
}
void CodeEdit::_on_symbol_tooltip_timer_timeout() {
const int line = symbol_tooltip_pos.y;
const int column = symbol_tooltip_pos.x;
if (line >= 0 && column >= 0 && !symbol_tooltip_word.is_empty() && !has_selection() && !Input::get_singleton()->is_anything_pressed()) {
emit_signal(SNAME("symbol_hovered"), symbol_tooltip_word, line, column);
}
}
/* Text manipulation */ /* Text manipulation */
void CodeEdit::move_lines_up() { void CodeEdit::move_lines_up() {
begin_complex_operation(); begin_complex_operation();
@ -2771,6 +2812,10 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid); ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid);
/* Symbol tooltip */
ClassDB::bind_method(D_METHOD("set_symbol_tooltip_on_hover_enabled", "enable"), &CodeEdit::set_symbol_tooltip_on_hover_enabled);
ClassDB::bind_method(D_METHOD("is_symbol_tooltip_on_hover_enabled"), &CodeEdit::is_symbol_tooltip_on_hover_enabled);
/* Text manipulation */ /* Text manipulation */
ClassDB::bind_method(D_METHOD("move_lines_up"), &CodeEdit::move_lines_up); ClassDB::bind_method(D_METHOD("move_lines_up"), &CodeEdit::move_lines_up);
ClassDB::bind_method(D_METHOD("move_lines_down"), &CodeEdit::move_lines_down); ClassDB::bind_method(D_METHOD("move_lines_down"), &CodeEdit::move_lines_down);
@ -2780,6 +2825,7 @@ void CodeEdit::_bind_methods() {
/* Inspector */ /* Inspector */
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symbol_lookup_on_click"), "set_symbol_lookup_on_click_enabled", "is_symbol_lookup_on_click_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symbol_lookup_on_click"), "set_symbol_lookup_on_click_enabled", "is_symbol_lookup_on_click_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symbol_tooltip_on_hover"), "set_symbol_tooltip_on_hover_enabled", "is_symbol_tooltip_on_hover_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "line_length_guidelines"), "set_line_length_guidelines", "get_line_length_guidelines"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "line_length_guidelines"), "set_line_length_guidelines", "get_line_length_guidelines");
@ -2826,6 +2872,9 @@ void CodeEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "column"))); ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "column")));
ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol"))); ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol")));
/* Symbol tooltip */
ADD_SIGNAL(MethodInfo("symbol_hovered", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "column")));
/* Theme items */ /* Theme items */
/* Gutters */ /* Gutters */
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, code_folding_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, code_folding_color);
@ -3749,6 +3798,13 @@ CodeEdit::CodeEdit() {
set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_fold_gutter_draw_callback)); set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_fold_gutter_draw_callback));
gutter_idx++; gutter_idx++;
/* Symbol tooltip */
symbol_tooltip_timer = memnew(Timer);
symbol_tooltip_timer->set_wait_time(0.5); // See `_apply_project_settings()`.
symbol_tooltip_timer->set_one_shot(true);
symbol_tooltip_timer->connect("timeout", callable_mp(this, &CodeEdit::_on_symbol_tooltip_timer_timeout));
add_child(symbol_tooltip_timer, false, INTERNAL_MODE_FRONT);
connect("lines_edited_from", callable_mp(this, &CodeEdit::_lines_edited_from)); connect("lines_edited_from", callable_mp(this, &CodeEdit::_lines_edited_from));
connect("text_set", callable_mp(this, &CodeEdit::_text_set)); connect("text_set", callable_mp(this, &CodeEdit::_text_set));
connect(SceneStringName(text_changed), callable_mp(this, &CodeEdit::_text_changed)); connect(SceneStringName(text_changed), callable_mp(this, &CodeEdit::_text_changed));

View file

@ -233,10 +233,16 @@ private:
/* Symbol lookup */ /* Symbol lookup */
bool symbol_lookup_on_click_enabled = false; bool symbol_lookup_on_click_enabled = false;
Point2i symbol_lookup_pos; // Column and line.
String symbol_lookup_new_word;
String symbol_lookup_word;
String symbol_lookup_new_word = ""; /* Symbol tooltip */
String symbol_lookup_word = ""; bool symbol_tooltip_on_hover_enabled = false;
Point2i symbol_lookup_pos; Point2i symbol_tooltip_pos; // Column and line.
String symbol_tooltip_word;
Timer *symbol_tooltip_timer = nullptr;
void _on_symbol_tooltip_timer_timeout();
/* Visual */ /* Visual */
struct ThemeCache { struct ThemeCache {
@ -304,6 +310,8 @@ private:
void _text_set(); void _text_set();
void _text_changed(); void _text_changed();
void _apply_project_settings();
protected: protected:
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();
@ -497,6 +505,10 @@ public:
void set_symbol_lookup_word_as_valid(bool p_valid); void set_symbol_lookup_word_as_valid(bool p_valid);
/* Symbol tooltip */
void set_symbol_tooltip_on_hover_enabled(bool p_enabled);
bool is_symbol_tooltip_on_hover_enabled() const;
/* Text manipulation */ /* Text manipulation */
void move_lines_up(); void move_lines_up();
void move_lines_down(); void move_lines_down();

View file

@ -5882,6 +5882,11 @@ String RichTextLabel::get_selected_text() const {
for (int i = 0; i < to_line; i++) { for (int i = 0; i < to_line; i++) {
txt += _get_line_text(main, i, selection); txt += _get_line_text(main, i, selection);
} }
if (selection_modifier.is_valid()) {
txt = selection_modifier.call(txt);
}
return txt; return txt;
} }

View file

@ -539,6 +539,7 @@ private:
}; };
Selection selection; Selection selection;
Callable selection_modifier;
bool deselect_on_focus_loss_enabled = true; bool deselect_on_focus_loss_enabled = true;
bool drag_and_drop_selection_enabled = true; bool drag_and_drop_selection_enabled = true;
@ -791,6 +792,10 @@ public:
void select_all(); void select_all();
void selection_copy(); void selection_copy();
_FORCE_INLINE_ void set_selection_modifier(const Callable &p_modifier) {
selection_modifier = p_modifier;
}
void set_deselect_on_focus_loss_enabled(const bool p_enabled); void set_deselect_on_focus_loss_enabled(const bool p_enabled);
bool is_deselect_on_focus_loss_enabled() const; bool is_deselect_on_focus_loss_enabled() const;