Browse Source

Show method override icon in script editor gutter

Combines with the connection slot icon when both apply.
Can be clicked to jump to the method declaration (or documentation for
built-in classes).
RedMser 2 years ago
parent
commit
a9b394d5bc

+ 8 - 1
doc/classes/ScriptEditorBase.xml

@@ -35,6 +35,13 @@
 				Emitted when the user requests a specific documentation page.
 			</description>
 		</signal>
+		<signal name="go_to_method">
+			<param index="0" name="script" type="Object" />
+			<param index="1" name="method" type="String" />
+			<description>
+				Emitted when the user requests to view a specific method of a script, similar to [signal request_open_script_at_line].
+			</description>
+		</signal>
 		<signal name="name_changed">
 			<description>
 				Emitted after script validation or when the edited resource has changed.
@@ -56,7 +63,7 @@
 			<param index="0" name="script" type="Object" />
 			<param index="1" name="line" type="int" />
 			<description>
-				Emitted when the user requests a script.
+				Emitted when the user requests to view a specific line of a script, similar to [signal go_to_method].
 			</description>
 		</signal>
 		<signal name="request_save_history">

+ 1 - 0
editor/icons/MethodOverride.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 4.2333332 4.2333332" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m.49005985 3.3580432.83285685-.0000001v-.7093212c.0027125-.6681099.2054076-1.1321001 1.0021593-1.1328214h.3207573v-.79375l1.3229167 1.0648649-1.3229167 1.0518017v-.79375h-.3364788c-.2888876 0-.4514151.2436282-.4573001.5980603 0 .2833012.0000193.4455045.0000289.7134508h.79375v.4907171l-2.15577345.00147z" fill="#5fb2ff"/></svg>

+ 1 - 0
editor/icons/MethodOverrideAndSlot.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 4.2333332 4.2333332" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m.15761184 3.636193h.37155483l.004252-.7093212c.0027092-.6681099.12999225-1.1321001.92674393-1.1328214h.1273374l.0042585-.7357171 1.3186582 1.006832-1.3229167 1.0700676v-.8531081h-.1260545c-.2888876 0-.3972562.2847204-.4031411.6391525 0 .2833012.0000193.4455045.0000289.7134508h1.2412654v.4907171h-2.14198686z" fill="#5fb2ff"/><path d="m2.38125.79375h1.5875v2.6458333h-1.5875v-.5291666h1.0583333v-1.5875h-1.0583333z" fill="#5fff97"/></svg>

+ 2 - 0
editor/plugins/script_editor_plugin.cpp

@@ -227,6 +227,7 @@ void ScriptEditorBase::_bind_methods() {
 	// TODO: This signal is no use for VisualScript.
 	ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));
 	ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text")));
+	ADD_SIGNAL(MethodInfo("go_to_method", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::STRING, "method")));
 }
 
 class EditorScriptCodeCompletionCache : public ScriptCodeCompletionCache {
@@ -2380,6 +2381,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
 	se->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history));
 	se->connect("search_in_files_requested", callable_mp(this, &ScriptEditor::_on_find_in_files_requested));
 	se->connect("replace_in_files_requested", callable_mp(this, &ScriptEditor::_on_replace_in_files_requested));
+	se->connect("go_to_method", callable_mp(this, &ScriptEditor::script_goto_method));
 
 	//test for modification, maybe the script was not edited but was loaded
 

+ 86 - 11
editor/plugins/script_text_editor.cpp

@@ -956,10 +956,7 @@ void ScriptTextEditor::_update_connected_methods() {
 	CodeEdit *text_edit = code_editor->get_text_editor();
 	text_edit->set_gutter_width(connection_gutter, text_edit->get_line_height());
 	for (int i = 0; i < text_edit->get_line_count(); i++) {
-		if (text_edit->get_line_gutter_metadata(i, connection_gutter) == "") {
-			continue;
-		}
-		text_edit->set_line_gutter_metadata(i, connection_gutter, "");
+		text_edit->set_line_gutter_metadata(i, connection_gutter, Dictionary());
 		text_edit->set_line_gutter_icon(i, connection_gutter, nullptr);
 		text_edit->set_line_gutter_clickable(i, connection_gutter, false);
 	}
@@ -974,6 +971,7 @@ void ScriptTextEditor::_update_connected_methods() {
 		return;
 	}
 
+	// Add connection icons to methods.
 	Vector<Node *> nodes = _find_all_node_for_script(base, base, script);
 	HashSet<StringName> methods_found;
 	for (int i = 0; i < nodes.size(); i++) {
@@ -1002,8 +1000,11 @@ void ScriptTextEditor::_update_connected_methods() {
 				for (int j = 0; j < functions.size(); j++) {
 					String name = functions[j].get_slice(":", 0);
 					if (name == method) {
+						Dictionary line_meta;
+						line_meta["type"] = "connection";
+						line_meta["method"] = method;
 						line = functions[j].get_slice(":", 1).to_int() - 1;
-						text_edit->set_line_gutter_metadata(line, connection_gutter, method);
+						text_edit->set_line_gutter_metadata(line, connection_gutter, line_meta);
 						text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("Slot"), SNAME("EditorIcons")));
 						text_edit->set_line_gutter_clickable(line, connection_gutter, true);
 						methods_found.insert(method);
@@ -1033,6 +1034,58 @@ void ScriptTextEditor::_update_connected_methods() {
 			}
 		}
 	}
+
+	// Add override icons to methods.
+	methods_found.clear();
+	for (int i = 0; i < functions.size(); i++) {
+		StringName name = StringName(functions[i].get_slice(":", 0));
+		if (methods_found.has(name)) {
+			continue;
+		}
+
+		String found_base_class;
+		StringName base_class = script->get_instance_base_type();
+		Ref<Script> inherited_script = script->get_base_script();
+		while (!inherited_script.is_null()) {
+			if (inherited_script->has_method(name)) {
+				found_base_class = "script:" + inherited_script->get_path();
+				break;
+			}
+
+			base_class = inherited_script->get_instance_base_type();
+			inherited_script = inherited_script->get_base_script();
+		}
+
+		if (found_base_class.is_empty() && base_class) {
+			List<MethodInfo> methods;
+			ClassDB::get_method_list(base_class, &methods);
+			for (int j = 0; j < methods.size(); j++) {
+				if (methods[j].name == name) {
+					found_base_class = "builtin:" + base_class;
+					break;
+				}
+			}
+		}
+
+		if (!found_base_class.is_empty()) {
+			int line = functions[i].get_slice(":", 1).to_int() - 1;
+
+			Dictionary line_meta = text_edit->get_line_gutter_metadata(line, connection_gutter);
+			if (line_meta.is_empty()) {
+				// Add override icon to gutter.
+				line_meta["type"] = "inherits";
+				line_meta["method"] = name;
+				line_meta["base_class"] = found_base_class;
+				text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("MethodOverride"), SNAME("EditorIcons")));
+				text_edit->set_line_gutter_clickable(line, connection_gutter, true);
+			} else {
+				// If method is also connected to signal, then merge icons and keep the click behavior of the slot.
+				text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("MethodOverrideAndSlot"), SNAME("EditorIcons")));
+			}
+
+			methods_found.insert(name);
+		}
+	}
 }
 
 void ScriptTextEditor::_update_gutter_indexes() {
@@ -1054,18 +1107,40 @@ void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) {
 		return;
 	}
 
-	String method = code_editor->get_text_editor()->get_line_gutter_metadata(p_line, p_gutter);
-	if (method.is_empty()) {
+	Dictionary meta = code_editor->get_text_editor()->get_line_gutter_metadata(p_line, p_gutter);
+	String type = meta.get("type", "");
+	if (type.is_empty()) {
 		return;
 	}
 
-	Node *base = get_tree()->get_edited_scene_root();
-	if (!base) {
+	// All types currently need a method name.
+	String method = meta.get("method", "");
+	if (method.is_empty()) {
 		return;
 	}
 
-	Vector<Node *> nodes = _find_all_node_for_script(base, base, script);
-	connection_info_dialog->popup_connections(method, nodes);
+	if (type == "connection") {
+		Node *base = get_tree()->get_edited_scene_root();
+		if (!base) {
+			return;
+		}
+
+		Vector<Node *> nodes = _find_all_node_for_script(base, base, script);
+		connection_info_dialog->popup_connections(method, nodes);
+	} else if (type == "inherits") {
+		String base_class_raw = meta["base_class"];
+		PackedStringArray base_class_split = base_class_raw.split(":", true, 1);
+
+		if (base_class_split[0] == "script") {
+			// Go to function declaration.
+			Ref<Script> base_script = ResourceLoader::load(base_class_split[1]);
+			ERR_FAIL_COND(!base_script.is_valid());
+			emit_signal(SNAME("go_to_method"), base_script, method);
+		} else if (base_class_split[0] == "builtin") {
+			// Open method documentation.
+			emit_signal(SNAME("go_to_help"), "class_method:" + base_class_split[1] + ":" + method);
+		}
+	}
 }
 
 void ScriptTextEditor::_edit_option(int p_op) {