diff options
author | Geequlim <geequlim@gmail.com> | 2019-06-24 18:25:12 +0800 |
---|---|---|
committer | geequlim <geequlim@gmail.com> | 2019-08-11 13:30:15 +0800 |
commit | 76c9e4ceb73b02bd95ab0512e27229516208dc60 (patch) | |
tree | b145ae3a4a837a109943f654bf433e704b7eac74 /modules/gdscript/language_server | |
parent | fa6d6a329c93224b5454b17603284913da0472a3 (diff) |
Improved performance for completion and symbol resolvation.
Improved uri and workspace path translatation on windows platform.
The smart resolvation is much faster than builtin's in the server side.
The smart resolve mode is still disabled as default as the clients might be slow with a planty of completion items.
Diffstat (limited to 'modules/gdscript/language_server')
9 files changed, 496 insertions, 442 deletions
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 16af7cb92f..a6fbfd3779 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -80,9 +80,18 @@ void ExtendGDScriptParser::update_diagnostics() { } void ExtendGDScriptParser::update_symbols() { + + members.clear(); + const GDScriptParser::Node *head = get_parse_tree(); if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { + parse_class_symbol(gdclass, class_symbol); + + for (int i = 0; i < class_symbol.children.size(); i++) { + const lsp::DocumentSymbol &symbol = class_symbol.children[i]; + members.set(symbol.name, &symbol); + } } } @@ -448,87 +457,32 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int } const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name) const { - const GDScriptParser::Node *head = get_parse_tree(); - - if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { - - if (const Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = gdclass->constant_expressions.find(p_name)) { - return 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]; - if (m && m->name == p_name) { - return 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]; - if (m.identifier == p_name) { - return 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]; - if (m->name == p_name) { - return 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]; - if (m->name == p_name) { - return 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]; - if (m.name == p_name) { - return get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line)); - } - } + const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); + if (ptr) { + return *ptr; } return NULL; } -void ExtendGDScriptParser::dump_member_symbols(Map<String, const lsp::DocumentSymbol *> &r_symbols) { +const Array &ExtendGDScriptParser::get_member_completions() { - const GDScriptParser::Node *head = get_parse_tree(); - if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { + if (member_completions.empty()) { - for (const Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = gdclass->constant_expressions.front(); E; E = E->next()) { - get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(E->get().expression->line)); - } + const String *name = members.next(NULL); + while (name) { - 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))); - } + const lsp::DocumentSymbol *symbol = members.get(*name); + lsp::CompletionItem item = symbol->make_completion_item(false); + item.data = JOIN_SYMBOLS(path, *name); + member_completions.push_back(item.to_json()); - 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))); + name = members.next(name); } } + + return member_completions; } Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index a7e5130e2c..d20dca59cf 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -43,29 +43,36 @@ #define JOIN_SYMBOLS(p_path, name) ((p_path) + "." + (name)) #endif +typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers; + class ExtendGDScriptParser : public GDScriptParser { + String path; String code; Vector<String> lines; lsp::DocumentSymbol class_symbol; Vector<lsp::Diagnostic> diagnostics; + ClassMembers members; void update_diagnostics(); - void update_symbols(); + void update_symbols(); 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); const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const; + Array member_completions; + public: _FORCE_INLINE_ const String &get_path() const { return path; } _FORCE_INLINE_ const String &get_code() const { return code; } _FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; } _FORCE_INLINE_ const lsp::DocumentSymbol &get_symbols() const { return class_symbol; } _FORCE_INLINE_ const Vector<lsp::Diagnostic> &get_diagnostics() const { return diagnostics; } + _FORCE_INLINE_ const ClassMembers &get_members() const { return members; } 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; @@ -74,7 +81,8 @@ 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_member_symbols(Map<String, const lsp::DocumentSymbol *> &r_symbols); + + const Array &get_member_completions(); 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 7c24efe450..7fb336cc58 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -91,7 +91,27 @@ void GDScriptLanguageProtocol::_bind_methods() { Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { lsp::InitializeResult ret; - workspace.initialize(); + + String root_uri = p_params["rootUri"]; + String root = p_params["rootPath"]; + bool is_same_workspace = root == workspace.root; + is_same_workspace = root.to_lower() == workspace.root.to_lower(); +#ifdef WINDOWS_ENABLED + is_same_workspace = root.replace("\\", "/").to_lower() == workspace.root.to_lower(); +#endif + + if (root_uri.length() && is_same_workspace) { + workspace.root_uri = root_uri; + } else { + workspace.root_uri = "file://" + workspace.root; + } + + if (!_initialized) { + workspace.initialize(); + text_document.initialize(); + _initialized = true; + } + return ret.to_json(); } @@ -167,6 +187,7 @@ bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const { GDScriptLanguageProtocol::GDScriptLanguageProtocol() { server = NULL; singleton = this; + _initialized = false; set_scope("textDocument", &text_document); set_scope("completionItem", &text_document); set_scope("workspace", &workspace); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index dbe073dd07..be4a7cd47c 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -62,6 +62,8 @@ class GDScriptLanguageProtocol : public JSONRPC { String process_message(const String &p_text); String format_output(const String &p_text); + bool _initialized; + protected: static void _bind_methods(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 177f13c04c..a5211fb0f1 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -30,6 +30,8 @@ #include "gdscript_text_document.h" #include "../gdscript.h" +#include "core/os/os.h" +#include "editor/editor_settings.h" #include "gdscript_extend_parser.h" #include "gdscript_language_protocol.h" @@ -71,6 +73,33 @@ lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_ return doc; } +void GDScriptTextDocument::initialize() { + + if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + + const HashMap<StringName, ClassMembers> &native_members = GDScriptLanguageProtocol::get_singleton()->get_workspace().native_members; + + const StringName *class_ptr = native_members.next(NULL); + while (class_ptr) { + + const ClassMembers &members = native_members.get(*class_ptr); + + const String *name = members.next(NULL); + while (name) { + + const lsp::DocumentSymbol *symbol = members.get(*name); + lsp::CompletionItem item = symbol->make_completion_item(false); + item.data = JOIN_SYMBOLS(String(*class_ptr), *name); + native_member_completions.push_back(item.to_json()); + + name = members.next(name); + } + + class_ptr = native_members.next(class_ptr); + } + } +} + Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { Dictionary params = p_params["textDocument"]; String uri = params["uri"]; @@ -87,6 +116,7 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { } Array GDScriptTextDocument::completion(const Dictionary &p_params) { + Array arr; lsp::CompletionParams params; @@ -98,18 +128,16 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { if (!options.empty()) { + int i = 0; + arr.resize(options.size()); + for (const List<ScriptCodeCompletionOption>::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; @@ -142,50 +170,24 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { item.kind = lsp::CompletionItemKind::Text; break; } - arr.push_back(item.to_json()); - } + arr[i] = item.to_json(true); + i++; + } } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - for (Map<String, const lsp::DocumentSymbol *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace().flat_symbols.front(); E; E = E->next()) { - const lsp::DocumentSymbol *symbol = E->get(); - if (!symbol) continue; + arr = native_member_completions.duplicate(); - lsp::CompletionItem item; - item.label = symbol->name; - item.data = E->key(); + for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace().scripts.front(); E; E = E->next()) { - 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; + ExtendGDScriptParser *script = E->get(); + const Array &items = script->get_member_completions(); + + const int start_size = arr.size(); + arr.resize(start_size + items.size()); + for (int i = start_size; i < arr.size(); i++) { + arr[i] = items[i - start_size]; } - arr.push_back(item.to_json()); } } return arr; @@ -202,19 +204,50 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { 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); + symbol = 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<String, const lsp::DocumentSymbol *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace().flat_symbols.find(data)) { - symbol = E->get(); + 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); + StringName class_name = class_; + String member_name = query.substr(seperator_pos + 1, query.length()); + + if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace().native_members.getptr(class_name)) { + if (const lsp::DocumentSymbol *const *member = members->getptr(member_name)) { + symbol = *member; + } + } + + if (!symbol) { + if (const Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace().scripts.find(class_name)) { + symbol = E->get()->get_member_symbol(member_name); + } + } } } if (symbol) { item.documentation = symbol->render(); } + + if (item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) { + item.insertText = item.label + "("; + if (symbol && symbol->detail.find(",") == -1) { + item.insertText += ")"; + } + } else if (item.kind == lsp::CompletionItemKind::Event) { + if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) { + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + item.insertText = quote_style + item.label + quote_style; + } + } + return item.to_json(); } @@ -247,17 +280,20 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) { const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace().resolve_symbol(params); if (symbol) { + lsp::Hover hover; hover.contents = symbol->render(); return hover.to_json(); + } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { + Dictionary ret; Array contents; List<const lsp::DocumentSymbol *> list; GDScriptLanguageProtocol::get_singleton()->get_workspace().resolve_related_symbols(params, list); for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { - if (const lsp::DocumentSymbol *symbol = E->get()) { - contents.push_back(symbol->render().value); + if (const lsp::DocumentSymbol *s = E->get()) { + contents.push_back(s->render().value); } } ret["contents"] = contents; @@ -289,16 +325,13 @@ Array GDScriptTextDocument::definition(const Dictionary &p_params) { GDScriptLanguageProtocol::get_singleton()->get_workspace().resolve_related_symbols(params, list); for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { - if (const lsp::DocumentSymbol *symbol = E->get()) { + if (const lsp::DocumentSymbol *s = E->get()) { lsp::Location location; - location.uri = symbol->uri; - location.range = symbol->range; + location.uri = s->uri; + location.range = s->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()); - } + arr.push_back(location.to_json()); } } } diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 68d89c7ba4..d1e11f684c 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -47,6 +47,8 @@ protected: void sync_script_content(const String &p_path, const String &p_content); + Array native_member_completions; + private: lsp::TextDocumentItem load_document_item(const Variant &p_param); @@ -61,6 +63,8 @@ public: Variant hover(const Dictionary &p_params); Array definition(const Dictionary &p_params); + void initialize(); + GDScriptTextDocument(); virtual ~GDScriptTextDocument(); }; diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index d21f53652f..089c19e6a4 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -64,9 +64,9 @@ void GDScriptWorkspace::remove_cache_parser(const String &p_path) { const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const { StringName class_name = p_class; - StringName end_pos; + StringName empty; - while (class_name != end_pos) { + while (class_name != empty) { if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) { const lsp::DocumentSymbol &class_symbol = E->value(); @@ -159,22 +159,6 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) return NULL; } -void GDScriptWorkspace::strip_flat_symbols(const String &p_branch) { - - typedef Map<String, const lsp::DocumentSymbol *>::Element *Item; - - List<Item> 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<Item>::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(); @@ -327,13 +311,14 @@ Error GDScriptWorkspace::initialize() { } if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - // expand symbol trees to the flat symbol pool for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) { + ClassMembers members; 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); + members.set(symbol.name, &symbol); } + native_members.set(E->key(), members); } } @@ -355,12 +340,6 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont 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()); @@ -383,14 +362,16 @@ Error GDScriptWorkspace::parse_local_script(const String &p_path) { } String GDScriptWorkspace::get_file_path(const String &p_uri) const { - String path = p_uri.replace("file://", "").http_unescape(); - path = path.replace(root + "/", "res://"); - return ProjectSettings::get_singleton()->localize_path(path); + String path = p_uri; + path = path.replace(root_uri + "/", "res://"); + path = path.http_unescape(); + return path; } String GDScriptWorkspace::get_file_uri(const String &p_path) const { - String path = ProjectSettings::get_singleton()->globalize_path(p_path); - return "file://" + path; + String uri = p_path; + uri = uri.replace("res://", root_uri + "/"); + return uri; } void GDScriptWorkspace::publish_diagnostics(const String &p_path) { @@ -486,14 +467,19 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP Vector2i offset; symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); - for (Map<String, const lsp::DocumentSymbol *>::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()); - } + const StringName *class_ptr = native_members.next(NULL); + while (class_ptr) { + const ClassMembers &members = native_members.get(*class_ptr); + if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { + r_list.push_back(*symbol); + } + class_ptr = native_members.next(class_ptr); + } + + for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + const ClassMembers &members = E->get()->get_members(); + if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { + r_list.push_back(*symbol); } } } diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 2ae488b451..1ecaba6f1f 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -58,10 +58,11 @@ protected: public: String root; + String root_uri; Map<String, ExtendGDScriptParser *> scripts; Map<String, ExtendGDScriptParser *> parse_results; - Map<String, const lsp::DocumentSymbol *> flat_symbols; + HashMap<StringName, ClassMembers> native_members; public: Array symbol(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 7e98bfa279..c208d5a198 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -46,11 +46,11 @@ struct TextDocumentIdentifier { */ DocumentUri uri; - void load(const Dictionary &p_params) { + _FORCE_INLINE_ void load(const Dictionary &p_params) { uri = p_params["uri"]; } - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["uri"] = uri; return dict; @@ -78,12 +78,12 @@ struct Position { */ int character = 0; - void load(const Dictionary &p_params) { + _FORCE_INLINE_ void load(const Dictionary &p_params) { line = p_params["line"]; character = p_params["character"]; } - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["line"] = line; dict["character"] = character; @@ -107,12 +107,12 @@ struct Range { */ Position end; - void load(const Dictionary &p_params) { + _FORCE_INLINE_ void load(const Dictionary &p_params) { start.load(p_params["start"]); end.load(p_params["end"]); } - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["start"] = start.to_json(); dict["end"] = end.to_json(); @@ -127,12 +127,12 @@ struct Location { DocumentUri uri; Range range; - void load(const Dictionary &p_params) { + _FORCE_INLINE_ void load(const Dictionary &p_params) { uri = p_params["uri"]; range.load(p_params["range"]); } - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["uri"] = uri; dict["range"] = range.to_json(); @@ -186,12 +186,12 @@ struct TextDocumentPositionParams { */ Position position; - void load(const Dictionary &p_params) { + _FORCE_INLINE_ void load(const Dictionary &p_params) { textDocument.load(p_params["textDocument"]); position.load(p_params["position"]); } - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["textDocument"] = textDocument.to_json(); dict["position"] = position.to_json(); @@ -199,6 +199,54 @@ struct TextDocumentPositionParams { } }; +/** + * A textual edit applicable to a text document. + */ +struct TextEdit { + /** + * The range of the text document to be manipulated. To insert + * text into a document create a range where start === end. + */ + Range range; + + /** + * The string to be inserted. For delete operations use an + * empty string. + */ + String newText; +}; + +/** + * Represents a reference to a command. + * Provides a title which will be used to represent a command in the UI. + * Commands are identified by a string identifier. + * The recommended way to handle commands is to implement their execution on the server side if the client and server provides the corresponding capabilities. + * Alternatively the tool extension code could handle the command. The protocol currently doesn’t specify a set of well-known commands. + */ +struct Command { + /** + * Title of the command, like `save`. + */ + String title; + /** + * The identifier of the actual command handler. + */ + String command; + /** + * Arguments that the command handler should be + * invoked with. + */ + Array arguments; + + Dictionary to_json() const { + Dictionary dict; + dict["title"] = title; + dict["command"] = command; + if (arguments.size()) dict["arguments"] = arguments; + return dict; + } +}; + namespace TextDocumentSyncKind { /** * Documents should not be synced at all. @@ -674,256 +722,6 @@ struct MarkupContent { }; /** - * A symbol kind. - */ -namespace SymbolKind { -static const int File = 1; -static const int Module = 2; -static const int Namespace = 3; -static const int Package = 4; -static const int Class = 5; -static const int Method = 6; -static const int Property = 7; -static const int Field = 8; -static const int Constructor = 9; -static const int Enum = 10; -static const int Interface = 11; -static const int Function = 12; -static const int Variable = 13; -static const int Constant = 14; -static const int String = 15; -static const int Number = 16; -static const int Boolean = 17; -static const int Array = 18; -static const int Object = 19; -static const int Key = 20; -static const int Null = 21; -static const int EnumMember = 22; -static const int Struct = 23; -static const int Event = 24; -static const int Operator = 25; -static const int TypeParameter = 26; -}; // namespace SymbolKind - -/** - * Represents information about programming constructs like variables, classes, - * interfaces etc. - */ -struct SymbolInformation { - /** - * The name of this symbol. - */ - String name; - - /** - * The kind of this symbol. - */ - int kind = SymbolKind::File; - - /** - * Indicates if this symbol is deprecated. - */ - bool deprecated = false; - - /** - * The location of this symbol. The location's range is used by a tool - * to reveal the location in the editor. If the symbol is selected in the - * tool the range's start information is used to position the cursor. So - * the range usually spans more then the actual symbol's name and does - * normally include things like visibility modifiers. - * - * The range doesn't have to denote a node range in the sense of a abstract - * syntax tree. It can therefore not be used to re-construct a hierarchy of - * the symbols. - */ - Location location; - - /** - * The name of the symbol containing this symbol. This information is for - * user interface purposes (e.g. to render a qualifier in the user interface - * if necessary). It can't be used to re-infer a hierarchy for the document - * symbols. - */ - String containerName; - - Dictionary to_json() const { - Dictionary dict; - dict["name"] = name; - dict["kind"] = kind; - dict["deprecated"] = deprecated; - dict["location"] = location.to_json(); - dict["containerName"] = containerName; - return dict; - } -}; - -struct DocumentedSymbolInformation : public SymbolInformation { - /** - * A human-readable string with additional information - */ - String detail; - - /** - * A human-readable string that represents a doc-comment. - */ - String documentation; -}; - -/** - * Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be - * hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range, - * e.g. the range of an identifier. - */ -struct DocumentSymbol { - - /** - * The name of this symbol. Will be displayed in the user interface and therefore must not be - * an empty string or a string only consisting of white spaces. - */ - String name; - - /** - * More detail for this symbol, e.g the signature of a function. - */ - String detail; - - /** - * Documentation for this symbol - */ - String documentation; - - /** - * The kind of this symbol. - */ - int kind = SymbolKind::File; - - /** - * Indicates if this symbol is deprecated. - */ - bool deprecated = false; - - /** - * The range enclosing this symbol not including leading/trailing whitespace but everything else - * like comments. This information is typically used to determine if the clients cursor is - * inside the symbol to reveal in the symbol in the UI. - */ - Range range; - - /** - * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. - * Must be contained by the `range`. - */ - Range selectionRange; - - DocumentUri uri; - String script_path; - - /** - * Children of this symbol, e.g. properties of a class. - */ - Vector<DocumentSymbol> children; - - Dictionary to_json() const { - Dictionary dict; - dict["name"] = name; - dict["detail"] = detail; - dict["kind"] = kind; - dict["deprecated"] = deprecated; - dict["range"] = range.to_json(); - dict["selectionRange"] = selectionRange.to_json(); - Array arr; - arr.resize(children.size()); - for (int i = 0; i < children.size(); i++) { - arr[i] = children[i].to_json(); - } - dict["children"] = arr; - return dict; - } - - void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { - DocumentedSymbolInformation si; - if (p_join_name && !p_container.empty()) { - si.name = p_container + ">" + name; - } else { - si.name = name; - } - si.kind = kind; - si.containerName = p_container; - si.deprecated = deprecated; - si.location.uri = p_uri; - si.location.range = range; - si.detail = detail; - si.documentation = documentation; - r_list.push_back(si); - for (int i = 0; i < children.size(); i++) { - children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name); - } - } - - MarkupContent render() const { - MarkupContent markdown; - if (detail.length()) { - markdown.value = "\t" + detail + "\n\n"; - } - if (documentation.length()) { - markdown.value += documentation + "\n\n"; - } - if (script_path.length()) { - markdown.value += "Defined in [" + script_path + "](" + uri + ")"; - } - return markdown; - } -}; - -/** - * A textual edit applicable to a text document. - */ -struct TextEdit { - /** - * The range of the text document to be manipulated. To insert - * text into a document create a range where start === end. - */ - Range range; - - /** - * The string to be inserted. For delete operations use an - * empty string. - */ - String newText; -}; - -/** - * Represents a reference to a command. - * Provides a title which will be used to represent a command in the UI. - * Commands are identified by a string identifier. - * The recommended way to handle commands is to implement their execution on the server side if the client and server provides the corresponding capabilities. - * Alternatively the tool extension code could handle the command. The protocol currently doesn’t specify a set of well-known commands. - */ -struct Command { - /** - * Title of the command, like `save`. - */ - String title; - /** - * The identifier of the actual command handler. - */ - String command; - /** - * Arguments that the command handler should be - * invoked with. - */ - Array arguments; - - Dictionary to_json() const { - Dictionary dict; - dict["title"] = title; - dict["command"] = command; - if (arguments.size()) dict["arguments"] = arguments; - return dict; - } -}; - -/** * The kind of a completion entry. */ namespace CompletionItemKind { @@ -1088,20 +886,22 @@ struct CompletionItem { */ Variant data; - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json(bool minimized = false) const { Dictionary dict; dict["label"] = label; dict["kind"] = kind; - dict["detail"] = detail; - dict["documentation"] = documentation.to_json(); - dict["deprecated"] = deprecated; - dict["preselect"] = preselect; - dict["sortText"] = sortText; - dict["filterText"] = filterText; - dict["insertText"] = insertText; - if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters; - dict["command"] = command.to_json(); dict["data"] = data; + if (!minimized) { + dict["insertText"] = insertText; + dict["detail"] = detail; + dict["documentation"] = documentation.to_json(); + dict["deprecated"] = deprecated; + dict["preselect"] = preselect; + dict["sortText"] = sortText; + dict["filterText"] = filterText; + if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters; + dict["command"] = command.to_json(); + } return dict; } @@ -1145,6 +945,251 @@ struct CompletionList { }; /** + * A symbol kind. + */ +namespace SymbolKind { +static const int File = 1; +static const int Module = 2; +static const int Namespace = 3; +static const int Package = 4; +static const int Class = 5; +static const int Method = 6; +static const int Property = 7; +static const int Field = 8; +static const int Constructor = 9; +static const int Enum = 10; +static const int Interface = 11; +static const int Function = 12; +static const int Variable = 13; +static const int Constant = 14; +static const int String = 15; +static const int Number = 16; +static const int Boolean = 17; +static const int Array = 18; +static const int Object = 19; +static const int Key = 20; +static const int Null = 21; +static const int EnumMember = 22; +static const int Struct = 23; +static const int Event = 24; +static const int Operator = 25; +static const int TypeParameter = 26; +}; // namespace SymbolKind + +/** + * Represents information about programming constructs like variables, classes, + * interfaces etc. + */ +struct SymbolInformation { + /** + * The name of this symbol. + */ + String name; + + /** + * The kind of this symbol. + */ + int kind = SymbolKind::File; + + /** + * Indicates if this symbol is deprecated. + */ + bool deprecated = false; + + /** + * The location of this symbol. The location's range is used by a tool + * to reveal the location in the editor. If the symbol is selected in the + * tool the range's start information is used to position the cursor. So + * the range usually spans more then the actual symbol's name and does + * normally include things like visibility modifiers. + * + * The range doesn't have to denote a node range in the sense of a abstract + * syntax tree. It can therefore not be used to re-construct a hierarchy of + * the symbols. + */ + Location location; + + /** + * The name of the symbol containing this symbol. This information is for + * user interface purposes (e.g. to render a qualifier in the user interface + * if necessary). It can't be used to re-infer a hierarchy for the document + * symbols. + */ + String containerName; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["kind"] = kind; + dict["deprecated"] = deprecated; + dict["location"] = location.to_json(); + dict["containerName"] = containerName; + return dict; + } +}; + +struct DocumentedSymbolInformation : public SymbolInformation { + /** + * A human-readable string with additional information + */ + String detail; + + /** + * A human-readable string that represents a doc-comment. + */ + String documentation; +}; + +/** + * Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be + * hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range, + * e.g. the range of an identifier. + */ +struct DocumentSymbol { + + /** + * The name of this symbol. Will be displayed in the user interface and therefore must not be + * an empty string or a string only consisting of white spaces. + */ + String name; + + /** + * More detail for this symbol, e.g the signature of a function. + */ + String detail; + + /** + * Documentation for this symbol + */ + String documentation; + + /** + * The kind of this symbol. + */ + int kind = SymbolKind::File; + + /** + * Indicates if this symbol is deprecated. + */ + bool deprecated = false; + + /** + * The range enclosing this symbol not including leading/trailing whitespace but everything else + * like comments. This information is typically used to determine if the clients cursor is + * inside the symbol to reveal in the symbol in the UI. + */ + Range range; + + /** + * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. + * Must be contained by the `range`. + */ + Range selectionRange; + + DocumentUri uri; + String script_path; + + /** + * Children of this symbol, e.g. properties of a class. + */ + Vector<DocumentSymbol> children; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["detail"] = detail; + dict["kind"] = kind; + dict["deprecated"] = deprecated; + dict["range"] = range.to_json(); + dict["selectionRange"] = selectionRange.to_json(); + Array arr; + arr.resize(children.size()); + for (int i = 0; i < children.size(); i++) { + arr[i] = children[i].to_json(); + } + dict["children"] = arr; + return dict; + } + + void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { + DocumentedSymbolInformation si; + if (p_join_name && !p_container.empty()) { + si.name = p_container + ">" + name; + } else { + si.name = name; + } + si.kind = kind; + si.containerName = p_container; + si.deprecated = deprecated; + si.location.uri = p_uri; + si.location.range = range; + si.detail = detail; + si.documentation = documentation; + r_list.push_back(si); + for (int i = 0; i < children.size(); i++) { + children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name); + } + } + + _FORCE_INLINE_ MarkupContent render() const { + MarkupContent markdown; + if (detail.length()) { + markdown.value = "\t" + detail + "\n\n"; + } + if (documentation.length()) { + markdown.value += documentation + "\n\n"; + } + if (script_path.length()) { + markdown.value += "Defined in [" + script_path + "](" + uri + ")"; + } + return markdown; + } + + _FORCE_INLINE_ CompletionItem make_completion_item(bool with_doc = false) const { + + lsp::CompletionItem item; + item.label = name; + + if (with_doc) { + item.documentation = render(); + } + + switch (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; + } + + return item; + } +}; + +/** * Enum of known range kinds */ namespace FoldingRangeKind { @@ -1194,7 +1239,7 @@ struct FoldingRange { */ String kind = FoldingRangeKind::Region; - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["startLine"] = startLine; dict["startCharacter"] = startCharacter; @@ -1276,7 +1321,7 @@ struct Hover { */ Range range; - Dictionary to_json() const { + _FORCE_INLINE_ Dictionary to_json() const { Dictionary dict; dict["range"] = range.to_json(); dict["contents"] = contents.to_json(); @@ -1410,7 +1455,7 @@ struct ServerCapabilities { */ ExecuteCommandOptions executeCommandProvider; - Dictionary to_json() { + _FORCE_INLINE_ Dictionary to_json() { Dictionary dict; dict["textDocumentSync"] = (int)textDocumentSync.change; dict["completionProvider"] = completionProvider.to_json(); @@ -1444,7 +1489,7 @@ struct InitializeResult { */ ServerCapabilities capabilities; - Dictionary to_json() { + _FORCE_INLINE_ Dictionary to_json() { Dictionary dict; dict["capabilities"] = capabilities.to_json(); return dict; |