diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index a6fbfd3779c..16f4324da82 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -91,6 +91,16 @@ void ExtendGDScriptParser::update_symbols() { for (int i = 0; i < class_symbol.children.size(); i++) { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; members.set(symbol.name, &symbol); + + // cache level one inner classes + if (symbol.kind == lsp::SymbolKind::Class) { + ClassMembers inner_class; + for (int j = 0; j < symbol.children.size(); j++) { + const lsp::DocumentSymbol &s = symbol.children[j]; + inner_class.set(s.name, &s); + } + inner_classes.set(symbol.name, inner_class); + } } } } @@ -112,7 +122,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line); r_symbol.selectionRange.start.line = r_symbol.range.start.line; r_symbol.detail = "class " + r_symbol.name; - r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_class->line)); + bool is_root_class = &r_symbol == &class_symbol; + r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); for (int i = 0; i < p_class->variables.size(); ++i) { @@ -192,7 +203,26 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) { symbol.detail += ": " + c.type.to_string(); } - symbol.detail += " = " + String(node->value); + + String value_text; + if (node->value.get_type() == Variant::OBJECT) { + RES res = node->value; + if (res.is_valid() && !res->get_path().empty()) { + value_text = "preload(\"" + res->get_path() + "\")"; + if (symbol.documentation.empty()) { + if (Map::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace().scripts.find(res->get_path())) { + symbol.documentation = S->get()->class_symbol.documentation; + } + } + } else { + value_text = JSON::print(node->value); + } + } else { + value_text = JSON::print(node->value); + } + if (!value_text.empty()) { + symbol.detail += " = " + value_text; + } r_symbol.children.push_back(symbol); } @@ -296,29 +326,43 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN } } -String ExtendGDScriptParser::parse_documentation(int p_line) { +String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { ERR_FAIL_INDEX_V(p_line, lines.size(), String()); List doc_lines; - // inline comment - String inline_comment = lines[p_line]; - int comment_start = inline_comment.find("#"); - if (comment_start != -1) { - inline_comment = inline_comment.substr(comment_start, inline_comment.length()); - if (inline_comment.length() > 1) { - doc_lines.push_back(inline_comment.substr(1, inline_comment.length())); + if (!p_docs_down) { // inline comment + String inline_comment = lines[p_line]; + int comment_start = inline_comment.find("#"); + if (comment_start != -1) { + inline_comment = inline_comment.substr(comment_start, inline_comment.length()); + if (inline_comment.length() > 1) { + doc_lines.push_back(inline_comment.substr(1, inline_comment.length())); + } } } - // upper line comments - for (int i = p_line - 1; i >= 0; --i) { + int step = p_docs_down ? 1 : -1; + int start_line = p_docs_down ? p_line : p_line - 1; + for (int i = start_line; true; i += step) { + + if (i < 0 || i >= lines.size()) break; + String line_comment = lines[i].strip_edges(true, false); if (line_comment.begins_with("#")) { if (line_comment.length() > 1) { - doc_lines.push_front(line_comment.substr(1, line_comment.length())); + line_comment = line_comment.substr(1, line_comment.length()); + if (p_docs_down) { + doc_lines.push_back(line_comment); + } else { + doc_lines.push_front(line_comment); + } } else { - doc_lines.push_front(""); + if (p_docs_down) { + doc_lines.push_back(""); + } else { + doc_lines.push_front(""); + } } } else { break; @@ -456,11 +500,20 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int return search_symbol_defined_at_line(p_line, class_symbol); } -const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name) const { +const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const { - const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); - if (ptr) { - return *ptr; + if (p_subclass.empty()) { + const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); + if (ptr) { + return *ptr; + } + } else { + if (const ClassMembers *_class = inner_classes.getptr(p_subclass)) { + const lsp::DocumentSymbol *const *ptr = _class->getptr(p_name); + if (ptr) { + return *ptr; + } + } } return NULL; @@ -474,12 +527,29 @@ const Array &ExtendGDScriptParser::get_member_completions() { while (name) { const lsp::DocumentSymbol *symbol = members.get(*name); - lsp::CompletionItem item = symbol->make_completion_item(false); + lsp::CompletionItem item = symbol->make_completion_item(); item.data = JOIN_SYMBOLS(path, *name); member_completions.push_back(item.to_json()); name = members.next(name); } + + const String *_class = inner_classes.next(NULL); + while (_class) { + + const ClassMembers *inner_class = inner_classes.getptr(*_class); + const String *member_name = inner_class->next(NULL); + while (member_name) { + const lsp::DocumentSymbol *symbol = inner_class->get(*member_name); + lsp::CompletionItem item = symbol->make_completion_item(); + item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(*_class, *member_name)); + member_completions.push_back(item.to_json()); + + member_name = inner_class->next(member_name); + } + + _class = inner_classes.next(_class); + } } return member_completions; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index d20dca59cff..3710b92993c 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -39,8 +39,12 @@ #define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) #endif +#ifndef SYMBOL_SEPERATOR +#define SYMBOL_SEPERATOR "::" +#endif + #ifndef JOIN_SYMBOLS -#define JOIN_SYMBOLS(p_path, name) ((p_path) + "." + (name)) +#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPERATOR + (name)) #endif typedef HashMap ClassMembers; @@ -54,6 +58,7 @@ class ExtendGDScriptParser : public GDScriptParser { lsp::DocumentSymbol class_symbol; Vector diagnostics; ClassMembers members; + HashMap inner_classes; void update_diagnostics(); @@ -61,7 +66,7 @@ class ExtendGDScriptParser : public GDScriptParser { void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol); void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol); - String parse_documentation(int p_line); + String parse_documentation(int p_line, bool p_docs_down = false); const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const; Array member_completions; @@ -73,6 +78,7 @@ public: _FORCE_INLINE_ const lsp::DocumentSymbol &get_symbols() const { return class_symbol; } _FORCE_INLINE_ const Vector &get_diagnostics() const { return diagnostics; } _FORCE_INLINE_ const ClassMembers &get_members() const { return members; } + _FORCE_INLINE_ const HashMap &get_inner_classes() const { return inner_classes; } 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; @@ -80,7 +86,7 @@ public: String get_uri() const; const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const; - const lsp::DocumentSymbol *get_member_symbol(const String &p_name) const; + const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const; const Array &get_member_completions(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index a5211fb0f19..cb42e316442 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -88,7 +88,7 @@ void GDScriptTextDocument::initialize() { while (name) { const lsp::DocumentSymbol *symbol = members.get(*name); - lsp::CompletionItem item = symbol->make_completion_item(false); + lsp::CompletionItem item = symbol->make_completion_item(); item.data = JOIN_SYMBOLS(String(*class_ptr), *name); native_member_completions.push_back(item.to_json()); @@ -171,7 +171,7 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { break; } - arr[i] = item.to_json(true); + arr[i] = item.to_json(); i++; } } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { @@ -211,12 +211,18 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } else if (data.get_type() == Variant::STRING) { String query = data; - int seperator_pos = query.find_last("."); - if (seperator_pos >= 0 && seperator_pos < query.length() - 1) { - String class_ = query.substr(0, seperator_pos); + Vector param_symbols = query.split(SYMBOL_SEPERATOR, false); + + if (param_symbols.size() >= 2) { + + String class_ = param_symbols[0]; StringName class_name = class_; - String member_name = query.substr(seperator_pos + 1, query.length()); + String member_name = param_symbols[param_symbols.size() - 1]; + String inner_class_name; + if (param_symbols.size() >= 3) { + inner_class_name = param_symbols[1]; + } if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace().native_members.getptr(class_name)) { if (const lsp::DocumentSymbol *const *member = members->getptr(member_name)) { @@ -226,7 +232,7 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { if (!symbol) { if (const Map::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace().scripts.find(class_name)) { - symbol = E->get()->get_member_symbol(member_name); + symbol = E->get()->get_member_symbol(member_name, inner_class_name); } } } @@ -248,7 +254,7 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } } - return item.to_json(); + return item.to_json(true); } Array GDScriptTextDocument::foldingRange(const Dictionary &p_params) { diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 089c19e6a45..ec95ea57650 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -170,14 +170,6 @@ String GDScriptWorkspace::marked_documentation(const String &p_bbcode) { markdown = ""; for (int i = 0; i < lines.size(); i++) { String line = lines[i]; - line = line.replace("[code]", "`"); - line = line.replace("[/code]", "`"); - line = line.replace("[i]", "*"); - line = line.replace("[/i]", "*"); - line = line.replace("[b]", "**"); - line = line.replace("[/b]", "**"); - line = line.replace("[u]", "__"); - line = line.replace("[/u]", "__"); int block_start = line.find("[codeblock]"); if (block_start != -1) { code_block_indent = block_start; @@ -186,14 +178,31 @@ String GDScriptWorkspace::marked_documentation(const String &p_bbcode) { line = "\n"; } else if (in_code_block) { line = "\t" + line.substr(code_block_indent, line.length()); - } else { - line = line.strip_edges(); } + if (in_code_block && line.find("[/codeblock]") != -1) { line = "'''\n"; line = "\n"; in_code_block = false; } + + if (!in_code_block) { + line = line.strip_edges(); + line = line.replace("[code]", "`"); + line = line.replace("[/code]", "`"); + line = line.replace("[i]", "*"); + line = line.replace("[/i]", "*"); + line = line.replace("[b]", "**"); + line = line.replace("[/b]", "**"); + line = line.replace("[u]", "__"); + line = line.replace("[/u]", "__"); + line = line.replace("[method ", "`"); + line = line.replace("[member ", "`"); + line = line.replace("[signal ", "`"); + line = line.replace("[", "`"); + line = line.replace("]", "`"); + } + if (!in_code_block && i < lines.size() - 1) { line += "\n"; } @@ -310,6 +319,8 @@ Error GDScriptWorkspace::initialize() { native_symbols.insert(class_name, class_symbol); } + reload_all_workspace_scripts(); + if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { for (Map::Element *E = native_symbols.front(); E; E = E->next()) { ClassMembers members; @@ -320,9 +331,12 @@ Error GDScriptWorkspace::initialize() { } native_members.set(E->key(), members); } - } - reload_all_workspace_scripts(); + // cache member completions + for (Map::Element *S = scripts.front(); S; S = S->next()) { + S->get()->get_member_completions(); + } + } return OK; } @@ -477,10 +491,23 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP } for (Map::Element *E = scripts.front(); E; E = E->next()) { - const ClassMembers &members = E->get()->get_members(); + const ExtendGDScriptParser *script = E->get(); + const ClassMembers &members = script->get_members(); if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { r_list.push_back(*symbol); } + + const HashMap &inner_classes = script->get_inner_classes(); + const String *_class = inner_classes.next(NULL); + while (_class) { + + const ClassMembers *inner_class = inner_classes.getptr(*_class); + if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) { + r_list.push_back(*symbol); + } + + _class = inner_classes.next(_class); + } } } } diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index c208d5a1984..065b8b65e83 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -886,12 +886,12 @@ struct CompletionItem { */ Variant data; - _FORCE_INLINE_ Dictionary to_json(bool minimized = false) const { + _FORCE_INLINE_ Dictionary to_json(bool resolved = false) const { Dictionary dict; dict["label"] = label; dict["kind"] = kind; dict["data"] = data; - if (!minimized) { + if (resolved) { dict["insertText"] = insertText; dict["detail"] = detail; dict["documentation"] = documentation.to_json(); @@ -1145,12 +1145,12 @@ struct DocumentSymbol { return markdown; } - _FORCE_INLINE_ CompletionItem make_completion_item(bool with_doc = false) const { + _FORCE_INLINE_ CompletionItem make_completion_item(bool resolved = false) const { lsp::CompletionItem item; item.label = name; - if (with_doc) { + if (resolved) { item.documentation = render(); }