Browse Source

Code Editor: Add documentation tooltips

Danil Alexeev 7 months ago
parent
commit
80d11500b5

+ 0 - 33
core/doc_data.cpp

@@ -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);
-}

+ 7 - 13
core/doc_data.h

@@ -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

+ 29 - 7
core/object/script_language.h

@@ -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; }

+ 4 - 1
core/object/script_language_extension.cpp

@@ -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);

+ 28 - 12
core/object/script_language_extension.h

@@ -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;
 	}
 	}

+ 11 - 0
doc/classes/@GlobalScope.xml

@@ -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.

+ 13 - 0
doc/classes/CodeEdit.xml

@@ -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>

+ 5 - 0
doc/classes/ScriptExtension.xml

@@ -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>

+ 6 - 2
doc/classes/ScriptLanguageExtension.xml

@@ -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).

+ 1 - 1
editor/create_dialog.cpp

@@ -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);
 
 

+ 8 - 1
editor/doc_tools.cpp

@@ -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
+ 451 - 173
editor/editor_help.cpp


+ 25 - 8
editor/editor_help.h

@@ -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.
-		Vector<ArgumentData> arguments; // For methods and signals.
+		DocType doc_type;
+		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();
 
 

+ 3 - 13
editor/editor_inspector.cpp

@@ -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.
-						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;
-						}
+						StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());
 						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");

+ 178 - 78
editor/plugins/script_text_editor.cpp

@@ -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) {
-			case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION: {
-				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);
-				}
-			} break;
-			case ScriptLanguage::LOOKUP_RESULT_CLASS: {
-				emit_signal(SNAME("go_to_help"), "class_name:" + result.class_name);
-			} 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;
+		if (!result.class_name.is_empty() && EditorHelp::get_doc_data()->class_list.has(result.class_name)) {
+			switch (result.type) {
+				case ScriptLanguage::LOOKUP_RESULT_CLASS: {
+					emit_signal(SNAME("go_to_help"), "class_name:" + 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);
 						cname = ClassDB::get_parent_class(cname);
-					} else {
-						break;
 					}
 					}
-				}
-
-				emit_signal(SNAME("go_to_help"), "class_constant:" + result.class_name + ":" + result.class_member);
-
-			} break;
-			case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: {
-				emit_signal(SNAME("go_to_help"), "class_property:" + result.class_name + ":" + result.class_member);
-
-			} 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;
+					emit_signal(SNAME("go_to_help"), "class_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);
 						cname = ClassDB::get_parent_class(cname);
-					} else {
-						break;
 					}
 					}
-				}
-
-				emit_signal(SNAME("go_to_help"), "class_method:" + result.class_name + ":" + result.class_member);
-
-			} break;
-			case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: {
-				StringName cname = result.class_name;
-
-				while (true) {
-					if (ClassDB::has_signal(cname, result.class_member)) {
-						result.class_name = cname;
+					emit_signal(SNAME("go_to_help"), "class_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);
 						cname = ClassDB::get_parent_class(cname);
-					} else {
-						break;
 					}
 					}
-				}
-
-				emit_signal(SNAME("go_to_help"), "class_signal:" + result.class_name + ":" + result.class_member);
-
-			} break;
-			case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: {
-				StringName cname = result.class_name;
-				StringName success;
-				while (true) {
-					success = ClassDB::get_integer_constant_enum(cname, result.class_member, true);
-					if (success != StringName()) {
-						result.class_name = cname;
+					emit_signal(SNAME("go_to_help"), "class_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);
 						cname = ClassDB::get_parent_class(cname);
-					} else {
-						break;
 					}
 					}
-				}
-
-				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: {
-				emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member);
-			} break;
-			default: {
+					emit_signal(SNAME("go_to_help"), "class_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);
+					}
+					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);

+ 2 - 0
editor/plugins/script_text_editor.h

@@ -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);

+ 17 - 3
modules/gdscript/editor/gdscript_docgen.cpp

@@ -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();
+}

+ 2 - 1
modules/gdscript/editor/gdscript_docgen.h

@@ -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

+ 9 - 7
modules/gdscript/gdscript.cpp

@@ -479,23 +479,25 @@ void GDScript::_update_exports_values(HashMap<StringName, Variant> &values, List
 	}
 	}
 }
 }
 
 
-void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) {
-	if (_owner) { // Only the top-level class stores doc info
-		_owner->_add_doc(p_inner_class);
-	} else { // Remove old docs, add new
+void GDScript::_add_doc(const DocData::ClassDoc &p_doc) {
+	doc_class_name = p_doc.name;
+	if (_owner) { // Only the top-level class stores doc info.
+		_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 {

+ 4 - 4
modules/gdscript/gdscript.h

@@ -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 {
-		return docs;
-	}
+	virtual StringName get_doc_class_name() const override { return doc_class_name; }
+	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
 
 

+ 2 - 1
modules/gdscript/gdscript_analyzer.h

@@ -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);
 };
 };

+ 323 - 168
modules/gdscript/gdscript_editor.cpp

@@ -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) {
-					String name = p_symbol;
-					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();
-						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;
-					}
+				ERR_FAIL_NULL_V(base_type.class_type, ERR_BUG);
+
+				String name = p_symbol;
+				if (name == "new") {
+					name = "_init";
+				}
+
+				if (!base_type.class_type->has_member(name)) {
 					base_type = base_type.class_type->base_type;
 					base_type = base_type.class_type->base_type;
+					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;
 			} break;
 			case GDScriptParser::DataType::SCRIPT: {
 			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;
+				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;
+							}
+						}
 					}
 					}
-					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();
+					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 {
 				} else {
-					base_type.kind = GDScriptParser::DataType::UNRESOLVED;
+					base_type.kind = GDScriptParser::DataType::NATIVE;
+					base_type.builtin_type = Variant::OBJECT;
+					base_type.native_type = scr->get_instance_base_type();
 				}
 				}
 			} break;
 			} break;
 			case GDScriptParser::DataType::NATIVE: {
 			case GDScriptParser::DataType::NATIVE: {
-				StringName class_name = base_type.native_type;
-				if (!ClassDB::class_exists(class_name)) {
-					base_type.kind = GDScriptParser::DataType::UNRESOLVED;
-					break;
-				}
+				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);
-				if (parent != StringName()) {
-					base_type.native_type = parent;
+				const StringName parent_class = ClassDB::get_parent_class(class_name);
+				if (parent_class != StringName()) {
+					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 (Variant::has_constant(base_type.builtin_type, p_symbol)) {
-					r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
-					r_result.class_name = Variant::get_type_name(base_type.builtin_type);
-					r_result.class_member = p_symbol;
-					return OK;
-				}
+				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;
+					}
 
 
-				Variant v;
-				Ref<RefCounted> v_ref;
-				if (base_type.builtin_type == Variant::OBJECT) {
-					v_ref.instantiate();
-					v = v_ref;
+					if (Variant::has_constant(base_type.builtin_type, p_symbol)) {
+						r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
+						r_result.class_name = Variant::get_type_name(base_type.builtin_type);
+						r_result.class_member = p_symbol;
+						return OK;
+					}
 				} else {
 				} else {
-					Callable::CallError err;
-					Variant::construct(base_type.builtin_type, v, nullptr, 0, err);
-					if (err.error != Callable::CallError::CALL_OK) {
-						break;
+					if (Variant::has_member(base_type.builtin_type, p_symbol)) {
+						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;
 					}
 					}
 				}
 				}
 
 
-				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;
-				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;
-				}
+				return ERR_CANT_RESOLVE;
 			} 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)) {
-					GDScriptParser::EnumNode *base_enum = base_type.class_type->get_member(base_type.enum_type).m_enum;
-					for (const GDScriptParser::EnumNode::Value &value : base_enum->values) {
-						if (value.identifier && value.identifier->name == p_symbol) {
-							r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
-							r_result.class_path = base_type.script_path;
-							r_result.location = value.line;
-							Error err = OK;
-							r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
-							return err;
+				if (base_type.is_meta_type) {
+					if (base_type.enum_values.has(p_symbol)) {
+						String type_name;
+						String enum_name;
+						GDScriptDocGen::doctype_from_gdtype(GDScriptAnalyzer::type_from_metatype(base_type), type_name, enum_name);
+
+						if (CoreConstants::is_global_enum(enum_name)) {
+							r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
+							r_result.class_name = "@GlobalScope";
+							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: {
-				base_type.kind = GDScriptParser::DataType::UNRESOLVED;
+			case GDScriptParser::DataType::VARIANT: {
+				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
-		// keywords, so are not registered in GDScriptUtilityFunctions.
-		if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) {
+		// Need special checks for `assert` and `preload` as they are technically
+		// keywords, so are not registered in `GDScriptUtilityFunctions`.
+		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: {
-			is_function = true;
-			[[fallthrough]];
-		}
+		case GDScriptParser::COMPLETION_METHOD:
 		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;
-						r_result.location = suite->get_local(p_symbol).start_line;
-						return OK;
+						const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_symbol);
+
+						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,
-							// may have some success.
+							// Not a script, try find the script anyway, 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.location = 0;
+							r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
+							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.
-				HashMap<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map();
-				if (classes.has(p_symbol)) {
-					Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]];
+				if (ScriptServer::is_global_class(p_symbol)) {
+					const String scr_path = ScriptServer::get_global_class_path(p_symbol);
+					const Ref<Script> scr = ResourceLoader::load(scr_path);
+					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();
 							}
 							}
-
-							// proxy class remove the underscore.
-							if (r_result.class_name.begins_with("_")) {
-								r_result.class_name = r_result.class_name.substr(1);
-							}
 							return OK;
 							return OK;
 						}
 						}
-					} else {
-						/*
-						// Because get_integer_constant_enum and get_integer_constant don't work on @GlobalScope
-						// We cannot determine the exact nature of the identifier here
-						// Otherwise these codes would work
-						StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true);
-						if (enumName != nullptr) {
-							r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
-							r_result.class_name = "@GlobalScope";
-							r_result.class_member = enumName;
-							return OK;
-						}
-						else {
-							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;
 					}
 					}
 				}
 				}
+
+				if (CoreConstants::is_global_enum(p_symbol)) {
+					r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
+					r_result.class_name = "@GlobalScope";
+					r_result.class_member = p_symbol;
+					return OK;
+				}
+
+				if (CoreConstants::is_global_constant(p_symbol)) {
+					r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
+					r_result.class_name = "@GlobalScope";
+					r_result.class_member = p_symbol;
+					return OK;
+				}
+
+				if (Variant::has_utility_function(p_symbol)) {
+					r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD;
+					r_result.class_name = "@GlobalScope";
+					r_result.class_member = p_symbol;
+					return OK;
+				}
 			}
 			}
 		} break;
 		} break;
-		case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: {
-			is_function = true;
-			[[fallthrough]];
-		}
+		case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD:
 		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;

+ 59 - 4
modules/gdscript/gdscript_parser.cpp

@@ -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,11 +2010,44 @@ 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) {

+ 1 - 0
modules/gdscript/gdscript_parser.h

@@ -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;

+ 4 - 5
modules/gdscript/language_server/gdscript_workspace.cpp

@@ -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()) {
-							target_script_path = ret.class_path;
+						} else if (!ret.script_path.is_empty()) {
+							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) {

+ 1 - 0
modules/mono/csharp_script.h

@@ -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;

+ 56 - 0
scene/gui/code_edit.cpp

@@ -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));

+ 15 - 3
scene/gui/code_edit.h

@@ -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 = "";
-	String symbol_lookup_word = "";
-	Point2i symbol_lookup_pos;
+	/* Symbol tooltip */
+	bool symbol_tooltip_on_hover_enabled = false;
+	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();

+ 5 - 0
scene/gui/rich_text_label.cpp

@@ -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;
 }
 }
 
 

+ 5 - 0
scene/gui/rich_text_label.h

@@ -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;
 
 

Some files were not shown because too many files changed in this diff