Jelajahi Sumber

GDScript LSP: Implement signatureHelp
Enable smart resolve default to true as it is required for script symbol lookup

geequlim 5 tahun lalu
induk
melakukan
d35c018a7a

+ 45 - 0
modules/gdscript/language_server/gdscript_extend_parser.cpp

@@ -522,6 +522,51 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(i
 	return ret;
 }
 
+Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const {
+
+	ERR_FAIL_INDEX_V(p_position.line, lines.size(), ERR_INVALID_PARAMETER);
+
+	int bracket_stack = 0;
+	int index = 0;
+
+	bool found = false;
+	for (int l = p_position.line; l >= 0; --l) {
+		String line = lines[l];
+		int c = line.length() - 1;
+		if (l == p_position.line) {
+			c = MIN(c, p_position.character - 1);
+		}
+
+		while (c >= 0) {
+			const CharType &charactor = line[c];
+			if (charactor == ')') {
+				++bracket_stack;
+			} else if (charactor == '(') {
+				--bracket_stack;
+				if (bracket_stack < 0) {
+					found = true;
+				}
+			}
+			if (bracket_stack <= 0 && charactor == ',') {
+				++index;
+			}
+			--c;
+			if (found) {
+				r_func_pos.character = c;
+				break;
+			}
+		}
+
+		if (found) {
+			r_func_pos.line = l;
+			r_arg_index = index;
+			return OK;
+		}
+	}
+
+	return ERR_METHOD_NOT_FOUND;
+}
+
 const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
 	if (p_line <= 0) {
 		return &class_symbol;

+ 2 - 0
modules/gdscript/language_server/gdscript_extend_parser.h

@@ -83,6 +83,8 @@ public:
 	_FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
 	_FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
 
+	Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const;
+
 	String get_text_for_completion(const lsp::Position &p_cursor) const;
 	String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const;
 	String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const;

+ 1 - 1
modules/gdscript/language_server/gdscript_language_server.cpp

@@ -38,7 +38,7 @@ GDScriptLanguageServer::GDScriptLanguageServer() {
 	thread = NULL;
 	thread_exit = false;
 	_EDITOR_DEF("network/language_server/remote_port", 6008);
-	_EDITOR_DEF("network/language_server/enable_smart_resolve", false);
+	_EDITOR_DEF("network/language_server/enable_smart_resolve", true);
 	_EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false);
 }
 

+ 15 - 0
modules/gdscript/language_server/gdscript_text_document.cpp

@@ -50,6 +50,7 @@ void GDScriptTextDocument::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
 	ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
 	ClassDB::bind_method(D_METHOD("declaration"), &GDScriptTextDocument::declaration);
+	ClassDB::bind_method(D_METHOD("signatureHelp"), &GDScriptTextDocument::signatureHelp);
 	ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
 }
 
@@ -387,6 +388,20 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) {
 	return arr;
 }
 
+Variant GDScriptTextDocument::signatureHelp(const Dictionary &p_params) {
+	Variant ret;
+
+	lsp::TextDocumentPositionParams params;
+	params.load(p_params);
+
+	lsp::SignatureHelp s;
+	if (OK == GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_signature(params, s)) {
+		ret = s.to_json();
+	}
+
+	return ret;
+}
+
 GDScriptTextDocument::GDScriptTextDocument() {
 	file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
 }

+ 1 - 0
modules/gdscript/language_server/gdscript_text_document.h

@@ -67,6 +67,7 @@ public:
 	Variant hover(const Dictionary &p_params);
 	Array definition(const Dictionary &p_params);
 	Variant declaration(const Dictionary &p_params);
+	Variant signatureHelp(const Dictionary &p_params);
 
 	void initialize();
 

+ 51 - 0
modules/gdscript/language_server/gdscript_workspace.cpp

@@ -252,6 +252,12 @@ Error GDScriptWorkspace::initialize() {
 			bool arg_default_value_started = false;
 			for (int j = 0; j < data.arguments.size(); j++) {
 				const DocData::ArgumentDoc &arg = data.arguments[j];
+
+				lsp::DocumentSymbol symbol_arg;
+				symbol_arg.name = arg.name;
+				symbol_arg.kind = lsp::SymbolKind::Variable;
+				symbol_arg.detail = arg.type;
+
 				if (!arg_default_value_started && !arg.default_value.empty()) {
 					arg_default_value_started = true;
 				}
@@ -263,6 +269,8 @@ Error GDScriptWorkspace::initialize() {
 					arg_str += ", ";
 				}
 				params += arg_str;
+
+				symbol.children.push_back(symbol_arg);
 			}
 			if (data.qualifiers.find("vararg") != -1) {
 				params += params.empty() ? "..." : ", ...";
@@ -513,6 +521,49 @@ Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
 	return api;
 }
 
+Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature) {
+	if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) {
+
+		lsp::TextDocumentPositionParams text_pos;
+		text_pos.textDocument = p_doc_pos.textDocument;
+
+		if (parser->get_left_function_call(p_doc_pos.position, text_pos.position, r_signature.activeParameter) == OK) {
+
+			List<const lsp::DocumentSymbol *> symbols;
+
+			if (const lsp::DocumentSymbol *symbol = resolve_symbol(text_pos)) {
+				symbols.push_back(symbol);
+			} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+				GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols);
+			}
+
+			for (List<const lsp::DocumentSymbol *>::Element *E = symbols.front(); E; E = E->next()) {
+				const lsp::DocumentSymbol *symbol = E->get();
+				if (symbol->kind == lsp::SymbolKind::Method || symbol->kind == lsp::SymbolKind::Function) {
+
+					lsp::SignatureInformation signature_info;
+					signature_info.label = symbol->detail;
+					signature_info.documentation = symbol->render();
+
+					for (int i = 0; i < symbol->children.size(); i++) {
+						const lsp::DocumentSymbol &arg = symbol->children[i];
+						lsp::ParameterInformation arg_info;
+						arg_info.label = arg.name;
+						signature_info.parameters.push_back(arg_info);
+					}
+					r_signature.signatures.push_back(signature_info);
+					break;
+				}
+			}
+
+			if (r_signature.signatures.size()) {
+				return OK;
+			}
+		}
+	}
+	return ERR_METHOD_NOT_FOUND;
+}
+
 GDScriptWorkspace::GDScriptWorkspace() {
 	ProjectSettings::get_singleton()->get_resource_path();
 }

+ 1 - 0
modules/gdscript/language_server/gdscript_workspace.h

@@ -83,6 +83,7 @@ public:
 	const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
 	void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
 	Dictionary generate_script_api(const String &p_path);
+	Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature);
 
 	GDScriptWorkspace();
 	~GDScriptWorkspace();

+ 116 - 0
modules/gdscript/language_server/lsp.hpp

@@ -1402,6 +1402,120 @@ struct Hover {
 	}
 };
 
+/**
+ * Represents a parameter of a callable-signature. A parameter can
+ * have a label and a doc-comment.
+ */
+struct ParameterInformation {
+
+	/**
+	 * The label of this parameter information.
+	 *
+	 * Either a string or an inclusive start and exclusive end offsets within its containing
+	 * signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
+	 * string representation as `Position` and `Range` does.
+	 *
+	 * *Note*: a label of type string should be a substring of its containing signature label.
+	 * Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
+	 */
+	String label;
+
+	/**
+	 * The human-readable doc-comment of this parameter. Will be shown
+	 * in the UI but can be omitted.
+	 */
+	MarkupContent documentation;
+
+	Dictionary to_json() const {
+		Dictionary dict;
+		dict["label"] = label;
+		dict["documentation"] = documentation.to_json();
+		return dict;
+	}
+};
+
+/**
+ * Represents the signature of something callable. A signature
+ * can have a label, like a function-name, a doc-comment, and
+ * a set of parameters.
+ */
+struct SignatureInformation {
+	/**
+	 * The label of this signature. Will be shown in
+	 * the UI.
+	 */
+	String label;
+
+	/**
+	 * The human-readable doc-comment of this signature. Will be shown
+	 * in the UI but can be omitted.
+	 */
+	MarkupContent documentation;
+
+	/**
+	 * The parameters of this signature.
+	 */
+	Vector<ParameterInformation> parameters;
+
+	Dictionary to_json() const {
+		Dictionary dict;
+		dict["label"] = label;
+		dict["documentation"] = documentation.to_json();
+		Array args;
+		for (int i = 0; i < parameters.size(); i++) {
+			args.push_back(parameters[i].to_json());
+		}
+		dict["parameters"] = args;
+		return dict;
+	}
+};
+
+/**
+ * Signature help represents the signature of something
+ * callable. There can be multiple signature but only one
+ * active and only one active parameter.
+ */
+struct SignatureHelp {
+	/**
+	 * One or more signatures.
+	 */
+	Vector<SignatureInformation> signatures;
+
+	/**
+	 * The active signature. If omitted or the value lies outside the
+	 * range of `signatures` the value defaults to zero or is ignored if
+	 * `signatures.length === 0`. Whenever possible implementors should
+	 * make an active decision about the active signature and shouldn't
+	 * rely on a default value.
+	 * In future version of the protocol this property might become
+	 * mandatory to better express this.
+	 */
+	int activeSignature = 0;
+
+	/**
+	 * The active parameter of the active signature. If omitted or the value
+	 * lies outside the range of `signatures[activeSignature].parameters`
+	 * defaults to 0 if the active signature has parameters. If
+	 * the active signature has no parameters it is ignored.
+	 * In future version of the protocol this property might become
+	 * mandatory to better express the active parameter if the
+	 * active signature does have any.
+	 */
+	int activeParameter = 0;
+
+	Dictionary to_json() const {
+		Dictionary dict;
+		Array sigs;
+		for (int i = 0; i < signatures.size(); i++) {
+			sigs.push_back(signatures[i].to_json());
+		}
+		dict["signatures"] = sigs;
+		dict["activeSignature"] = activeSignature;
+		dict["activeParameter"] = activeParameter;
+		return dict;
+	}
+};
+
 struct ServerCapabilities {
 	/**
 	 * Defines how text documents are synced. Is either a detailed structure defining each notification or
@@ -1532,6 +1646,8 @@ struct ServerCapabilities {
 		Dictionary dict;
 		dict["textDocumentSync"] = (int)textDocumentSync.change;
 		dict["completionProvider"] = completionProvider.to_json();
+		signatureHelpProvider.triggerCharacters.push_back(",");
+		signatureHelpProvider.triggerCharacters.push_back("(");
 		dict["signatureHelpProvider"] = signatureHelpProvider.to_json();
 		dict["codeLensProvider"] = false; // codeLensProvider.to_json();
 		dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json();