diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 8287c9c0842f..c8ff47140122 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/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; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 5afe991d2414..43dfce994bf2 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/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 &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; diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 159f2ba2d43c..9675c07c002f 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/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); } diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index b3d1b67af5f3..33ca4a5d157b 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/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); } diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index d93d82800349..b2fd0c31f9e2 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/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(); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 0661c4af26ad..1c0590cff19f 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/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 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::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(); } diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index f44153dcd087..146a5cb7c931 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/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 &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(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index ffeea70008b9..379024844e5f 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/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 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 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();