From fa6d6a329c93224b5454b17603284913da0472a3 Mon Sep 17 00:00:00 2001 From: geequlim Date: Sun, 23 Jun 2019 21:10:28 +0800 Subject: [PATCH] Add optional smart resolve sulotion The smart resolvaion can guess most symbols but it might be slow so disabled by default users can turn on it in the editor setting --- .../gdscript_extend_parser.cpp | 43 +++- .../language_server/gdscript_extend_parser.h | 7 +- .../gdscript_language_protocol.cpp | 5 + .../gdscript_language_protocol.h | 2 + .../gdscript_language_server.cpp | 1 + .../gdscript_text_document.cpp | 184 +++++++++++++----- .../language_server/gdscript_workspace.cpp | 71 ++++++- .../language_server/gdscript_workspace.h | 12 +- 8 files changed, 260 insertions(+), 65 deletions(-) diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 9ec93a813e2..16af7cb92ff 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -123,7 +123,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) { symbol.detail += ": " + m.data_type.to_string(); } - symbol.detail += " = " + String(m.default_value); + if (m.default_value.get_type() != Variant::NIL) { + symbol.detail += " = " + JSON::print(m.default_value); + } symbol.documentation = parse_documentation(line); symbol.uri = uri; @@ -493,12 +495,39 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String return NULL; } -void ExtendGDScriptParser::dump_symbols(HashMap &r_symbols) { - Vector list; - class_symbol.symbol_tree_as_list(path, list, path, true); - for (int i = 0; i < list.size(); i++) { - const lsp::DocumentedSymbolInformation &symbol = list[i]; - r_symbols.set(symbol.name, symbol); +void ExtendGDScriptParser::dump_member_symbols(Map &r_symbols) { + + const GDScriptParser::Node *head = get_parse_tree(); + if (const GDScriptParser::ClassNode *gdclass = dynamic_cast(head)) { + + for (const Map::Element *E = gdclass->constant_expressions.front(); E; E = E->next()) { + get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(E->get().expression->line)); + } + + for (int i = 0; i < gdclass->subclasses.size(); i++) { + const ClassNode *m = gdclass->subclasses[i]; + r_symbols.insert(JOIN_SYMBOLS(path, m->name), get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m->line))); + } + + for (int i = 0; i < gdclass->variables.size(); i++) { + const GDScriptParser::ClassNode::Member &m = gdclass->variables[i]; + r_symbols.insert(JOIN_SYMBOLS(path, m.identifier), get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))); + } + + for (int i = 0; i < gdclass->functions.size(); i++) { + const GDScriptParser::FunctionNode *m = gdclass->functions[i]; + r_symbols.insert(JOIN_SYMBOLS(path, m->name), get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m->line))); + } + + for (int i = 0; i < gdclass->static_functions.size(); i++) { + const GDScriptParser::FunctionNode *m = gdclass->static_functions[i]; + r_symbols.insert(JOIN_SYMBOLS(path, m->name), get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m->line))); + } + + for (int i = 0; i < gdclass->_signals.size(); i++) { + const GDScriptParser::ClassNode::Signal &m = gdclass->_signals[i]; + r_symbols.insert(JOIN_SYMBOLS(path, m.name), get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))); + } } } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index a1a2e2c31f2..a7e5130e2cf 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -39,6 +39,10 @@ #define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) #endif +#ifndef JOIN_SYMBOLS +#define JOIN_SYMBOLS(p_path, name) ((p_path) + "." + (name)) +#endif + class ExtendGDScriptParser : public GDScriptParser { String path; String code; @@ -70,8 +74,7 @@ public: const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const; const lsp::DocumentSymbol *get_member_symbol(const String &p_name) const; - - void dump_symbols(HashMap &r_symbols); + void dump_member_symbols(Map &r_symbols); Error parse(const String &p_code, const String &p_path); }; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 7f74e68a947..7c24efe4508 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -32,6 +32,7 @@ #include "core/io/json.h" #include "core/os/copymem.h" #include "core/project_settings.h" +#include "editor/editor_node.h" GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = NULL; @@ -159,6 +160,10 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length()); } +bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const { + return bool(_EDITOR_GET("network/language_server/enable_smart_resolve")); +} + GDScriptLanguageProtocol::GDScriptLanguageProtocol() { server = NULL; singleton = this; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index c6495250c19..dbe073dd074 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -79,6 +79,8 @@ public: void notify_all_clients(const String &p_method, const Variant &p_params = Variant()); void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client = -1); + bool is_smart_resolve_enabled() const; + GDScriptLanguageProtocol(); ~GDScriptLanguageProtocol(); }; diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 63ced28ddd9..893bfd5f983 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -37,6 +37,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); } void GDScriptLanguageServer::_notification(int p_what) { diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 7e5d2a512ac..177f13c04ce 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -68,7 +68,6 @@ lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_ lsp::TextDocumentItem doc; Dictionary params = p_param; doc.load(params["textDocument"]); - print_line(doc.text); return doc; } @@ -97,62 +96,122 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { List options; GDScriptLanguageProtocol::get_singleton()->get_workspace().completion(params, &options); - for (const List::Element *E = options.front(); E; E = E->next()) { - const ScriptCodeCompletionOption &option = E->get(); - lsp::CompletionItem item; - item.label = option.display; - item.insertText = option.insert_text; - item.data = request_data; + if (!options.empty()) { - if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "'" || params.context.triggerCharacter == "\"") && (option.insert_text.begins_with("'") || option.insert_text.begins_with("\""))) { - item.insertText = option.insert_text.substr(1, option.insert_text.length() - 2); + for (const List::Element *E = options.front(); E; E = E->next()) { + + const ScriptCodeCompletionOption &option = E->get(); + lsp::CompletionItem item; + item.label = option.display; + item.insertText = option.insert_text; + item.data = request_data; + + if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "'" || params.context.triggerCharacter == "\"") && (option.insert_text.begins_with("'") || option.insert_text.begins_with("\""))) { + item.insertText = option.insert_text.substr(1, option.insert_text.length() - 2); + } + + switch (option.kind) { + case ScriptCodeCompletionOption::KIND_ENUM: + item.kind = lsp::CompletionItemKind::Enum; + break; + case ScriptCodeCompletionOption::KIND_CLASS: + item.kind = lsp::CompletionItemKind::Class; + break; + case ScriptCodeCompletionOption::KIND_MEMBER: + item.kind = lsp::CompletionItemKind::Property; + break; + case ScriptCodeCompletionOption::KIND_FUNCTION: + item.kind = lsp::CompletionItemKind::Method; + break; + case ScriptCodeCompletionOption::KIND_SIGNAL: + item.kind = lsp::CompletionItemKind::Event; + break; + case ScriptCodeCompletionOption::KIND_CONSTANT: + item.kind = lsp::CompletionItemKind::Constant; + break; + case ScriptCodeCompletionOption::KIND_VARIABLE: + item.kind = lsp::CompletionItemKind::Variable; + break; + case ScriptCodeCompletionOption::KIND_FILE_PATH: + item.kind = lsp::CompletionItemKind::File; + break; + case ScriptCodeCompletionOption::KIND_NODE_PATH: + item.kind = lsp::CompletionItemKind::Snippet; + break; + case ScriptCodeCompletionOption::KIND_PLAIN_TEXT: + item.kind = lsp::CompletionItemKind::Text; + break; + } + arr.push_back(item.to_json()); } - switch (option.kind) { - case ScriptCodeCompletionOption::KIND_ENUM: - item.kind = lsp::CompletionItemKind::Enum; - break; - case ScriptCodeCompletionOption::KIND_CLASS: - item.kind = lsp::CompletionItemKind::Class; - break; - case ScriptCodeCompletionOption::KIND_MEMBER: - item.kind = lsp::CompletionItemKind::Property; - break; - case ScriptCodeCompletionOption::KIND_FUNCTION: - item.kind = lsp::CompletionItemKind::Method; - break; - case ScriptCodeCompletionOption::KIND_SIGNAL: - item.kind = lsp::CompletionItemKind::Event; - break; - case ScriptCodeCompletionOption::KIND_CONSTANT: - item.kind = lsp::CompletionItemKind::Constant; - break; - case ScriptCodeCompletionOption::KIND_VARIABLE: - item.kind = lsp::CompletionItemKind::Variable; - break; - case ScriptCodeCompletionOption::KIND_FILE_PATH: - item.kind = lsp::CompletionItemKind::File; - break; - case ScriptCodeCompletionOption::KIND_NODE_PATH: - item.kind = lsp::CompletionItemKind::Snippet; - break; - case ScriptCodeCompletionOption::KIND_PLAIN_TEXT: - item.kind = lsp::CompletionItemKind::Text; - break; - } + } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - arr.push_back(item.to_json()); + for (Map::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace().flat_symbols.front(); E; E = E->next()) { + const lsp::DocumentSymbol *symbol = E->get(); + if (!symbol) continue; + + lsp::CompletionItem item; + item.label = symbol->name; + item.data = E->key(); + + switch (symbol->kind) { + case lsp::SymbolKind::Enum: + item.kind = lsp::CompletionItemKind::Enum; + break; + case lsp::SymbolKind::Class: + item.kind = lsp::CompletionItemKind::Class; + break; + case lsp::SymbolKind::Property: + item.kind = lsp::CompletionItemKind::Property; + break; + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Function: + item.kind = lsp::CompletionItemKind::Method; + break; + case lsp::SymbolKind::Event: + item.kind = lsp::CompletionItemKind::Event; + break; + case lsp::SymbolKind::Constant: + item.kind = lsp::CompletionItemKind::Constant; + break; + case lsp::SymbolKind::Variable: + item.kind = lsp::CompletionItemKind::Variable; + break; + case lsp::SymbolKind::File: + item.kind = lsp::CompletionItemKind::File; + break; + default: + item.kind = lsp::CompletionItemKind::Text; + break; + } + arr.push_back(item.to_json()); + } } - return arr; } Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { + lsp::CompletionItem item; item.load(p_params); + lsp::CompletionParams params; - params.load(p_params["data"]); - const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace().resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function); + Variant data = p_params["data"]; + + const lsp::DocumentSymbol *symbol = NULL; + + if (data.get_type() == Variant::DICTIONARY) { + params.load(p_params["data"]); + GDScriptLanguageProtocol::get_singleton()->get_workspace().resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function); + + } else if (data.get_type() == Variant::STRING) { + + if (Map::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace().flat_symbols.find(data)) { + symbol = E->get(); + } + } + if (symbol) { item.documentation = symbol->render(); } @@ -182,7 +241,6 @@ Array GDScriptTextDocument::colorPresentation(const Dictionary &p_params) { } Variant GDScriptTextDocument::hover(const Dictionary &p_params) { - Variant ret; lsp::TextDocumentPositionParams params; params.load(p_params); @@ -191,10 +249,22 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) { if (symbol) { lsp::Hover hover; hover.contents = symbol->render(); - ret = hover.to_json(); + return hover.to_json(); + } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + Dictionary ret; + Array contents; + List list; + GDScriptLanguageProtocol::get_singleton()->get_workspace().resolve_related_symbols(params, list); + for (List::Element *E = list.front(); E; E = E->next()) { + if (const lsp::DocumentSymbol *symbol = E->get()) { + contents.push_back(symbol->render().value); + } + } + ret["contents"] = contents; + return ret; } - return ret; + return Variant(); } Array GDScriptTextDocument::definition(const Dictionary &p_params) { @@ -213,6 +283,24 @@ Array GDScriptTextDocument::definition(const Dictionary &p_params) { if (file_checker->file_exists(path)) { arr.push_back(location.to_json()); } + } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + + List list; + GDScriptLanguageProtocol::get_singleton()->get_workspace().resolve_related_symbols(params, list); + for (List::Element *E = list.front(); E; E = E->next()) { + + if (const lsp::DocumentSymbol *symbol = E->get()) { + + lsp::Location location; + location.uri = symbol->uri; + location.range = symbol->range; + + const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace().get_file_path(symbol->uri); + if (file_checker->file_exists(path)) { + arr.push_back(location.to_json()); + } + } + } } return arr; diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 068930002b6..d21f53652f0 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -148,13 +148,10 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String } ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) { - const Map::Element *S = scripts.find(p_path); + const Map::Element *S = parse_results.find(p_path); if (!S) { + parse_local_script(p_path); S = parse_results.find(p_path); - if (!S) { - parse_local_script(p_path); - S = scripts.find(p_path); - } } if (S) { return S->get(); @@ -162,6 +159,22 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) return NULL; } +void GDScriptWorkspace::strip_flat_symbols(const String &p_branch) { + + typedef Map::Element *Item; + + List removal_items; + for (Item E = flat_symbols.front(); E; E = E->next()) { + if (E->key().begins_with(p_branch)) { + removal_items.push_back(E); + } + } + + for (List::Element *E = removal_items.front(); E; E = E->next()) { + flat_symbols.erase(E->get()); + } +} + String GDScriptWorkspace::marked_documentation(const String &p_bbcode) { String markdown = p_bbcode.strip_edges(); @@ -313,21 +326,41 @@ Error GDScriptWorkspace::initialize() { native_symbols.insert(class_name, class_symbol); } + if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + // expand symbol trees to the flat symbol pool + for (Map::Element *E = native_symbols.front(); E; E = E->next()) { + const lsp::DocumentSymbol &class_symbol = E->get(); + for (int i = 0; i < class_symbol.children.size(); i++) { + const lsp::DocumentSymbol &symbol = class_symbol.children[i]; + flat_symbols.insert(JOIN_SYMBOLS(class_symbol.name, symbol.name), &symbol); + } + } + } + reload_all_workspace_scripts(); return OK; } Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) { + ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser); Error err = parser->parse(p_content, p_path); Map::Element *last_parser = parse_results.find(p_path); Map::Element *last_script = scripts.find(p_path); if (err == OK) { + remove_cache_parser(p_path); parse_results[p_path] = parser; scripts[p_path] = parser; + + if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + // update flat symbol pool + strip_flat_symbols(p_path); + parser->dump_member_symbols(flat_symbols); + } + } else { if (last_parser && last_script && last_parser->get() != last_script->get()) { memdelete(last_parser->get()); @@ -377,11 +410,13 @@ void GDScriptWorkspace::publish_diagnostics(const String &p_path) { } void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List *r_options) { + String path = get_file_path(p_params.textDocument.uri); String call_hint; bool forced = false; - if (Map::Element *E = parse_results.find(path)) { - String code = E->get()->get_text_for_completion(p_params.position); + + if (const ExtendGDScriptParser *parser = get_parse_result(path)) { + String code = parser->get_text_for_completion(p_params.position); GDScriptLanguage::get_singleton()->complete_code(code, path, NULL, r_options, forced, call_hint); } } @@ -442,6 +477,28 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu return symbol; } +void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List &r_list) { + + String path = get_file_path(p_doc_pos.textDocument.uri); + if (const ExtendGDScriptParser *parser = get_parse_result(path)) { + + String symbol_identifier; + Vector2i offset; + symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); + + for (Map::Element *E = flat_symbols.front(); E; E = E->next()) { + String id = E->key(); + int idx = id.find_last("."); + if (idx >= 0 && idx < id.length() - 1) { + String name = id.substr(idx + 1, id.length()); + if (name == symbol_identifier) { + r_list.push_back(E->get()); + } + } + } + } +} + 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 ebefd125873..2ae488b4510 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -50,27 +50,37 @@ protected: void reload_all_workspace_scripts(); - void list_script_files(const String &p_root_dir, List &r_files); ExtendGDScriptParser *get_parse_successed_script(const String &p_path); ExtendGDScriptParser *get_parse_result(const String &p_path); + void strip_flat_symbols(const String &p_branch); + void list_script_files(const String &p_root_dir, List &r_files); + public: String root; + Map scripts; Map parse_results; + Map flat_symbols; public: Array symbol(const Dictionary &p_params); public: Error initialize(); + Error parse_script(const String &p_path, const String &p_content); Error parse_local_script(const String &p_path); + String get_file_path(const String &p_uri) const; String get_file_uri(const String &p_path) const; + void publish_diagnostics(const String &p_path); void completion(const lsp::CompletionParams &p_params, List *r_options); + const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); + void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List &r_list); + static String marked_documentation(const String &p_bbcode); GDScriptWorkspace();