diff options
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; |