diff options
Diffstat (limited to 'modules/gdscript/language_server')
11 files changed, 835 insertions, 356 deletions
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index e63b6ab20e..d3c5fed95a 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,7 +32,6 @@ #include "../gdscript.h" #include "../gdscript_analyzer.h" -#include "core/io/json.h" #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" @@ -40,8 +39,7 @@ void ExtendGDScriptParser::update_diagnostics() { diagnostics.clear(); const List<ParserError> &errors = get_errors(); - for (const List<ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { - const ParserError &error = E->get(); + for (const ParserError &error : errors) { lsp::Diagnostic diagnostic; diagnostic.severity = lsp::DiagnosticSeverity::Error; diagnostic.message = error.message; @@ -49,8 +47,9 @@ void ExtendGDScriptParser::update_diagnostics() { diagnostic.code = -1; lsp::Range range; lsp::Position pos; - int line = LINE_NUMBER_TO_INDEX(error.line); - const String &line_text = get_lines()[line]; + const PackedStringArray lines = get_lines(); + int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, lines.size() - 1); + const String &line_text = lines[line]; pos.line = line; pos.character = line_text.length() - line_text.strip_edges(true, false).length(); range.start = pos; @@ -61,8 +60,7 @@ void ExtendGDScriptParser::update_diagnostics() { } const List<GDScriptWarning> &warnings = get_warnings(); - for (const List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { - const GDScriptWarning &warning = E->get(); + for (const GDScriptWarning &warning : warnings) { lsp::Diagnostic diagnostic; diagnostic.severity = lsp::DiagnosticSeverity::Warning; diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message(); @@ -91,16 +89,16 @@ void ExtendGDScriptParser::update_symbols() { for (int i = 0; i < class_symbol.children.size(); i++) { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; - members.set(symbol.name, &symbol); + members.insert(symbol.name, &symbol); // cache level one inner classes if (symbol.kind == lsp::SymbolKind::Class) { ClassMembers inner_class; for (int j = 0; j < symbol.children.size(); j++) { const lsp::DocumentSymbol &s = symbol.children[j]; - inner_class.set(s.name, &s); + inner_class.insert(s.name, &s); } - inner_classes.set(symbol.name, inner_class); + inner_classes.insert(symbol.name, inner_class); } } } @@ -110,7 +108,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { document_links.clear(); GDScriptTokenizer tokenizer; - FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES); + Ref<FileAccess> fs = FileAccess::create(FileAccess::ACCESS_RESOURCES); tokenizer.set_source_code(p_code); while (true) { GDScriptTokenizer::Token token = tokenizer.scan(); @@ -152,9 +150,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } r_symbol.kind = lsp::SymbolKind::Class; r_symbol.deprecated = false; - r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->start_line); - r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_class->start_column); - r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line); + r_symbol.range.start.line = p_class->start_line; + r_symbol.range.start.character = p_class->start_column; + r_symbol.range.end.line = lines.size(); r_symbol.selectionRange.start.line = r_symbol.range.start.line; r_symbol.detail = "class " + r_symbol.name; bool is_root_class = &r_symbol == &class_symbol; @@ -167,7 +165,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p case ClassNode::Member::VARIABLE: { lsp::DocumentSymbol symbol; symbol.name = m.variable->identifier->name; - symbol.kind = lsp::SymbolKind::Variable; + symbol.kind = m.variable->property == VariableNode::PROP_NONE ? lsp::SymbolKind::Variable : lsp::SymbolKind::Property; symbol.deprecated = false; symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line); symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column); @@ -182,7 +180,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += ": " + m.get_datatype().to_string(); } if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) { - symbol.detail += " = " + JSON::print(m.variable->initializer->reduced_value); + symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string(); } symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); @@ -214,19 +212,19 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p const Variant &default_value = m.constant->initializer->reduced_value; String value_text; if (default_value.get_type() == Variant::OBJECT) { - RES res = default_value; + Ref<Resource> res = default_value; if (res.is_valid() && !res->get_path().is_empty()) { value_text = "preload(\"" + res->get_path() + "\")"; if (symbol.documentation.is_empty()) { - if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { - symbol.documentation = S->get()->class_symbol.documentation; + if (HashMap<String, ExtendGDScriptParser *>::Iterator S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { + symbol.documentation = S->value->class_symbol.documentation; } } } else { - value_text = JSON::print(default_value); + value_text = default_value.to_json_string(); } } else { - value_text = JSON::print(default_value); + value_text = default_value.to_json_string(); } if (!value_text.is_empty()) { symbol.detail += " = " + value_text; @@ -271,7 +269,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p if (j > 0) { symbol.detail += ", "; } - symbol.detail += m.signal->parameters[i]->identifier->name; + symbol.detail += m.signal->parameters[j]->identifier->name; } symbol.detail += ")"; @@ -319,7 +317,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN const String uri = get_uri(); r_symbol.name = p_func->identifier->name; - r_symbol.kind = lsp::SymbolKind::Function; + r_symbol.kind = p_func->is_static ? lsp::SymbolKind::Function : lsp::SymbolKind::Method; r_symbol.detail = "func " + String(p_func->identifier->name) + "("; r_symbol.deprecated = false; r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line); @@ -352,8 +350,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN parameters += ": " + parameter->get_datatype().to_string(); } if (parameter->default_value != nullptr) { - String value = JSON::print(parameter->default_value->reduced_value); - parameters += " = " + value; + parameters += " = " + parameter->default_value->reduced_value.to_json_string(); } } r_symbol.detail += parameters + ")"; @@ -361,24 +358,73 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN r_symbol.detail += " -> " + p_func->get_datatype().to_string(); } - for (int i = 0; i < p_func->body->locals.size(); i++) { - const SuiteNode::Local &local = p_func->body->locals[i]; - lsp::DocumentSymbol symbol; - symbol.name = local.name; - symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column); - symbol.uri = uri; - symbol.script_path = path; - symbol.detail = SuiteNode::Local::CONSTANT ? "const " : "var "; - symbol.detail += symbol.name; - if (local.get_datatype().is_hard_type()) { - symbol.detail += ": " + local.get_datatype().to_string(); + List<GDScriptParser::SuiteNode *> function_nodes; + + List<GDScriptParser::Node *> node_stack; + node_stack.push_back(p_func->body); + + while (!node_stack.is_empty()) { + GDScriptParser::Node *node = node_stack[0]; + node_stack.pop_front(); + + switch (node->type) { + case GDScriptParser::TypeNode::IF: { + GDScriptParser::IfNode *if_node = (GDScriptParser::IfNode *)node; + node_stack.push_back(if_node->true_block); + if (if_node->false_block) { + node_stack.push_back(if_node->false_block); + } + } break; + + case GDScriptParser::TypeNode::FOR: { + GDScriptParser::ForNode *for_node = (GDScriptParser::ForNode *)node; + node_stack.push_back(for_node->loop); + } break; + + case GDScriptParser::TypeNode::WHILE: { + GDScriptParser::WhileNode *while_node = (GDScriptParser::WhileNode *)node; + node_stack.push_back(while_node->loop); + } break; + + case GDScriptParser::TypeNode::MATCH_BRANCH: { + GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node; + node_stack.push_back(match_node->block); + } break; + + case GDScriptParser::TypeNode::SUITE: { + GDScriptParser::SuiteNode *suite_node = (GDScriptParser::SuiteNode *)node; + function_nodes.push_back(suite_node); + for (int i = 0; i < suite_node->statements.size(); ++i) { + node_stack.push_back(suite_node->statements[i]); + } + } break; + + default: + continue; + } + } + + for (List<GDScriptParser::SuiteNode *>::Element *N = function_nodes.front(); N; N = N->next()) { + const GDScriptParser::SuiteNode *suite_node = N->get(); + for (int i = 0; i < suite_node->locals.size(); i++) { + const SuiteNode::Local &local = suite_node->locals[i]; + lsp::DocumentSymbol symbol; + symbol.name = local.name; + symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column); + symbol.uri = uri; + symbol.script_path = path; + symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var "; + symbol.detail += symbol.name; + if (local.get_datatype().is_hard_type()) { + symbol.detail += ": " + local.get_datatype().to_string(); + } + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line)); + r_symbol.children.push_back(symbol); } - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line)); - r_symbol.children.push_back(symbol); } } @@ -419,8 +465,8 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { } String doc; - for (List<String>::Element *E = doc_lines.front(); E; E = E->next()) { - doc += E->get() + "\n"; + for (const String &E : doc_lines) { + doc += E + "\n"; } return doc; } @@ -445,7 +491,7 @@ String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_curs return longthing; } -String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const { +String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_required) const { String longthing; int len = lines.size(); for (int i = 0; i < len; i++) { @@ -467,7 +513,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c longthing += first_part; longthing += String::chr(0xFFFF); //not unicode, represents the cursor - if (p_func_requred) { + if (p_func_required) { longthing += "("; // tell the parser this is a function call } longthing += last_part; @@ -486,13 +532,16 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); String line = lines[p_position.line]; + if (line.is_empty()) { + return ""; + } ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); int start_pos = p_position.character; for (int c = p_position.character; c >= 0; c--) { start_pos = c; char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } @@ -501,7 +550,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } @@ -612,30 +661,22 @@ const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const const Array &ExtendGDScriptParser::get_member_completions() { if (member_completions.is_empty()) { - const String *name = members.next(nullptr); - while (name) { - const lsp::DocumentSymbol *symbol = members.get(*name); + for (const KeyValue<String, const lsp::DocumentSymbol *> &E : members) { + const lsp::DocumentSymbol *symbol = E.value; lsp::CompletionItem item = symbol->make_completion_item(); - item.data = JOIN_SYMBOLS(path, *name); + item.data = JOIN_SYMBOLS(path, E.key); member_completions.push_back(item.to_json()); - - name = members.next(name); } - const String *_class = inner_classes.next(nullptr); - while (_class) { - const ClassMembers *inner_class = inner_classes.getptr(*_class); - const String *member_name = inner_class->next(nullptr); - while (member_name) { - const lsp::DocumentSymbol *symbol = inner_class->get(*member_name); + for (const KeyValue<String, ClassMembers> &E : inner_classes) { + const ClassMembers *inner_class = &E.value; + + for (const KeyValue<String, const lsp::DocumentSymbol *> &F : *inner_class) { + const lsp::DocumentSymbol *symbol = F.value; lsp::CompletionItem item = symbol->make_completion_item(); - item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(*_class, *member_name)); + item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(E.key, F.key)); member_completions.push_back(item.to_json()); - - member_name = inner_class->next(member_name); } - - _class = inner_classes.next(_class); } } @@ -647,7 +688,9 @@ Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::Functio ERR_FAIL_NULL_V(p_func, func); func["name"] = p_func->identifier->name; func["return_type"] = p_func->get_datatype().to_string(); - func["rpc_mode"] = p_func->rpc_mode; + func["rpc_mode"] = p_func->rpc_config.rpc_mode; + func["rpc_transfer_mode"] = p_func->rpc_config.transfer_mode; + func["rpc_transfer_channel"] = p_func->rpc_config.channel; Array parameters; for (int i = 0; i < p_func->parameters.size(); i++) { Dictionary arg; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 28b9b3c82a..99b0bf45d0 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -85,7 +85,7 @@ public: Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const; String get_text_for_completion(const lsp::Position &p_cursor) const; - String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const; + String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const; String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const; String get_uri() const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 912c9a174e..7460f8edff 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,8 +31,6 @@ #include "gdscript_language_protocol.h" #include "core/config/project_settings.h" -#include "core/io/json.h" -#include "core/os/copymem.h" #include "editor/doc_tools.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -117,7 +115,7 @@ Error GDScriptLanguageProtocol::LSPeer::send_data() { // Response sent if (res_sent >= c_res.size() - 1) { res_sent = 0; - res_queue.remove(0); + res_queue.remove_at(0); } } return OK; @@ -128,15 +126,15 @@ Error GDScriptLanguageProtocol::on_client_connected() { ERR_FAIL_COND_V_MSG(clients.size() >= LSP_MAX_CLIENTS, FAILED, "Max client limits reached"); Ref<LSPeer> peer = memnew(LSPeer); peer->connection = tcp_peer; - clients.set(next_client_id, peer); + clients.insert(next_client_id, peer); next_client_id++; - EditorNode::get_log()->add_message("Connection Taken", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("[LSP] Connection Taken", EditorLog::MSG_TYPE_EDITOR); return OK; } void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) { clients.erase(p_client_id); - EditorNode::get_log()->add_message("Disconnected", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR); } String GDScriptLanguageProtocol::process_message(const String &p_text) { @@ -195,7 +193,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id)); Ref<LSPeer> peer = clients.get(latest_client_id); if (peer != nullptr) { - String msg = JSON::print(request); + String msg = Variant(request).to_json_string(); msg = format_output(msg); (*peer)->res_queue.push_back(msg.utf8()); } @@ -214,11 +212,11 @@ void GDScriptLanguageProtocol::initialized(const Variant &p_params) { lsp::GodotCapabilities capabilities; DocTools *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { lsp::GodotNativeClassInfo gdclass; - gdclass.name = E->get().name; - gdclass.class_doc = &(E->get()); - if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E->get().name))) { + gdclass.name = E.value.name; + gdclass.class_doc = &(E.value); + if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E.value.name))) { gdclass.class_info = ptr; } capabilities.native_classes.push_back(gdclass); @@ -231,39 +229,43 @@ void GDScriptLanguageProtocol::poll() { if (server->is_connection_available()) { on_client_connected(); } - const int *id = nullptr; - while ((id = clients.next(id))) { - Ref<LSPeer> peer = clients.get(*id); + + HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin(); + while (E != clients.end()) { + Ref<LSPeer> peer = E->value; StreamPeerTCP::Status status = peer->connection->get_status(); if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) { - on_client_disconnected(*id); - id = nullptr; + on_client_disconnected(E->key); + E = clients.begin(); + continue; } else { if (peer->connection->get_available_bytes() > 0) { - latest_client_id = *id; + latest_client_id = E->key; Error err = peer->handle_data(); if (err != OK && err != ERR_BUSY) { - on_client_disconnected(*id); - id = nullptr; + on_client_disconnected(E->key); + E = clients.begin(); + continue; } } Error err = peer->send_data(); if (err != OK && err != ERR_BUSY) { - on_client_disconnected(*id); - id = nullptr; + on_client_disconnected(E->key); + E = clients.begin(); + continue; } } + ++E; } } -Error GDScriptLanguageProtocol::start(int p_port, const IP_Address &p_bind_ip) { +Error GDScriptLanguageProtocol::start(int p_port, const IPAddress &p_bind_ip) { return server->listen(p_port, p_bind_ip); } void GDScriptLanguageProtocol::stop() { - const int *id = nullptr; - while ((id = clients.next(id))) { - Ref<LSPeer> peer = clients.get(*id); + for (const KeyValue<int, Ref<LSPeer>> &E : clients) { + Ref<LSPeer> peer = clients.get(E.key); peer->connection->disconnect_from_host(); } @@ -281,7 +283,24 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia ERR_FAIL_COND(peer == nullptr); Dictionary message = make_notification(p_method, p_params); - String msg = JSON::print(message); + String msg = Variant(message).to_json_string(); + msg = format_output(msg); + peer->res_queue.push_back(msg.utf8()); +} + +void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) { + if (p_client_id == -1) { + ERR_FAIL_COND_MSG(latest_client_id == -1, + "GDScript LSP: Can't notify client as none was connected."); + p_client_id = latest_client_id; + } + ERR_FAIL_COND(!clients.has(p_client_id)); + Ref<LSPeer> peer = clients.get(p_client_id); + ERR_FAIL_COND(peer == nullptr); + + Dictionary message = make_request(p_method, p_params, next_server_id); + next_server_id++; + String msg = Variant(message).to_json_string(); msg = format_output(msg); peer->res_queue.push_back(msg.utf8()); } @@ -295,10 +314,10 @@ bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const { } GDScriptLanguageProtocol::GDScriptLanguageProtocol() { - server.instance(); + server.instantiate(); singleton = this; - workspace.instance(); - text_document.instance(); + workspace.instantiate(); + text_document.instantiate(); set_scope("textDocument", text_document.ptr()); set_scope("completionItem", text_document.ptr()); set_scope("workspace", workspace.ptr()); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index 8b08ae0655..0fed8597f9 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GDSCRIPT_PROTOCAL_SERVER_H -#define GDSCRIPT_PROTOCAL_SERVER_H +#ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H +#define GDSCRIPT_LANGUAGE_PROTOCOL_H #include "core/io/stream_peer.h" #include "core/io/stream_peer_tcp.h" @@ -37,7 +37,13 @@ #include "gdscript_text_document.h" #include "gdscript_workspace.h" #include "lsp.hpp" + +#include "modules/modules_enabled.gen.h" // For jsonrpc. +#ifdef MODULE_JSONRPC_ENABLED #include "modules/jsonrpc/jsonrpc.h" +#else +#error "Can't build GDScript LSP without JSONRPC module." +#endif #define LSP_MAX_BUFFER_SIZE 4194304 #define LSP_MAX_CLIENTS 8 @@ -46,7 +52,7 @@ class GDScriptLanguageProtocol : public JSONRPC { GDCLASS(GDScriptLanguageProtocol, JSONRPC) private: - struct LSPeer : Reference { + struct LSPeer : RefCounted { Ref<StreamPeerTCP> connection; uint8_t req_buf[LSP_MAX_BUFFER_SIZE]; @@ -69,10 +75,12 @@ private: static GDScriptLanguageProtocol *singleton; HashMap<int, Ref<LSPeer>> clients; - Ref<TCP_Server> server; + Ref<TCPServer> server; int latest_client_id = 0; int next_client_id = 0; + int next_server_id = 0; + Ref<GDScriptTextDocument> text_document; Ref<GDScriptWorkspace> workspace; @@ -97,10 +105,11 @@ public: _FORCE_INLINE_ bool is_initialized() const { return _initialized; } void poll(); - Error start(int p_port, const IP_Address &p_bind_ip); + Error start(int p_port, const IPAddress &p_bind_ip); void stop(); void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); + void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); bool is_smart_resolve_enabled() const; bool is_goto_native_symbols_enabled() const; @@ -108,4 +117,4 @@ public: GDScriptLanguageProtocol(); }; -#endif +#endif // GDSCRIPT_LANGUAGE_PROTOCOL_H diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 98ada9de4d..14337e87da 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,12 +30,13 @@ #include "gdscript_language_server.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "core/os/os.h" #include "editor/editor_log.h" #include "editor/editor_node.h" GDScriptLanguageServer::GDScriptLanguageServer() { + _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); _EDITOR_DEF("network/language_server/enable_smart_resolve", true); _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false); @@ -44,21 +45,25 @@ GDScriptLanguageServer::GDScriptLanguageServer() { void GDScriptLanguageServer::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { start(); - break; - case NOTIFICATION_EXIT_TREE: + } break; + + case NOTIFICATION_EXIT_TREE: { stop(); - break; + } break; + case NOTIFICATION_INTERNAL_PROCESS: { if (started && !use_thread) { protocol.poll(); } } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + String host = String(_EDITOR_GET("network/language_server/remote_host")); int port = (int)_EDITOR_GET("network/language_server/remote_port"); bool use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (port != this->port || use_thread != this->use_thread) { + if (host != this->host || port != this->port || use_thread != this->use_thread) { this->stop(); this->start(); } @@ -76,9 +81,10 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { } void GDScriptLanguageServer::start() { + host = String(_EDITOR_GET("network/language_server/remote_host")); port = (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (protocol.start(port, IP_Address("127.0.0.1")) == OK) { + if (protocol.start(port, IPAddress(host)) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; @@ -101,7 +107,7 @@ void GDScriptLanguageServer::stop() { } void register_lsp_types() { - ClassDB::register_class<GDScriptLanguageProtocol>(); - ClassDB::register_class<GDScriptTextDocument>(); - ClassDB::register_class<GDScriptWorkspace>(); + GDREGISTER_CLASS(GDScriptLanguageProtocol); + GDREGISTER_CLASS(GDScriptTextDocument); + GDREGISTER_CLASS(GDScriptWorkspace); } diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 29c5ddd70e..8de72fc9c9 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -44,15 +44,14 @@ class GDScriptLanguageServer : public EditorPlugin { bool thread_running = false; bool started = false; bool use_thread = false; - int port = 6008; + String host = "127.0.0.1"; + int port = 6005; static void thread_main(void *p_userdata); private: void _notification(int p_what); - void _iteration(); public: - Error parse_script_file(const String &p_path); GDScriptLanguageServer(); void start(); void stop(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 9f2373bf56..5ad9680ea0 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -40,11 +40,14 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); + ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose); ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); + ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave); ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol); ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol); ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion); ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); + ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename); ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange); ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens); ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink); @@ -61,6 +64,11 @@ void GDScriptTextDocument::didOpen(const Variant &p_param) { sync_script_content(doc.uri, doc.text); } +void GDScriptTextDocument::didClose(const Variant &p_param) { + // Left empty on purpose. Godot does nothing special on closing a document, + // but it satisfies LSP clients that require didClose be implemented. +} + void GDScriptTextDocument::didChange(const Variant &p_param) { lsp::TextDocumentItem doc = load_document_item(p_param); Dictionary dict = p_param; @@ -73,6 +81,20 @@ void GDScriptTextDocument::didChange(const Variant &p_param) { sync_script_content(doc.uri, doc.text); } +void GDScriptTextDocument::didSave(const Variant &p_param) { + lsp::TextDocumentItem doc = load_document_item(p_param); + Dictionary dict = p_param; + String text = dict["text"]; + + sync_script_content(doc.uri, text); + + /*String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri); + + Ref<GDScript> script = ResourceLoader::load(path); + script->load_source_code(path); + script->reload(true);*/ +} + lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) { lsp::TextDocumentItem doc; Dictionary params = p_param; @@ -87,23 +109,15 @@ void GDScriptTextDocument::notify_client_show_symbol(const lsp::DocumentSymbol * void GDScriptTextDocument::initialize() { if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - const HashMap<StringName, ClassMembers> &native_members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members; + for (const KeyValue<StringName, ClassMembers> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members) { + const ClassMembers &members = E.value; - const StringName *class_ptr = native_members.next(nullptr); - while (class_ptr) { - const ClassMembers &members = native_members.get(*class_ptr); - - const String *name = members.next(nullptr); - while (name) { - const lsp::DocumentSymbol *symbol = members.get(*name); + for (const KeyValue<String, const lsp::DocumentSymbol *> &F : members) { + const lsp::DocumentSymbol *symbol = members.get(F.key); lsp::CompletionItem item = symbol->make_completion_item(); - item.data = JOIN_SYMBOLS(String(*class_ptr), *name); + item.data = JOIN_SYMBOLS(String(E.key), F.key); native_member_completions.push_back(item.to_json()); - - name = members.next(name); } - - class_ptr = native_members.next(class_ptr); } } } @@ -127,9 +141,9 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { String uri = params["uri"]; String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri); Array arr; - if (const Map<String, ExtendGDScriptParser *>::Element *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) { + if (HashMap<String, ExtendGDScriptParser *>::ConstIterator parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) { Vector<lsp::DocumentedSymbolInformation> list; - parser->get()->get_symbols().symbol_tree_as_list(uri, list); + parser->value->get_symbols().symbol_tree_as_list(uri, list); for (int i = 0; i < list.size(); i++) { arr.push_back(list[i].to_json()); } @@ -144,50 +158,52 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { params.load(p_params); Dictionary request_data = params.to_json(); - List<ScriptCodeCompletionOption> options; + List<ScriptLanguage::CodeCompletionOption> options; GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options); if (!options.is_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(); + for (const ScriptLanguage::CodeCompletionOption &option : options) { lsp::CompletionItem item; item.label = option.display; item.data = request_data; + item.insertText = option.insert_text; switch (option.kind) { - case ScriptCodeCompletionOption::KIND_ENUM: + case ScriptLanguage::CODE_COMPLETION_KIND_ENUM: item.kind = lsp::CompletionItemKind::Enum; break; - case ScriptCodeCompletionOption::KIND_CLASS: + case ScriptLanguage::CODE_COMPLETION_KIND_CLASS: item.kind = lsp::CompletionItemKind::Class; break; - case ScriptCodeCompletionOption::KIND_MEMBER: + case ScriptLanguage::CODE_COMPLETION_KIND_MEMBER: item.kind = lsp::CompletionItemKind::Property; break; - case ScriptCodeCompletionOption::KIND_FUNCTION: + case ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION: item.kind = lsp::CompletionItemKind::Method; break; - case ScriptCodeCompletionOption::KIND_SIGNAL: + case ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL: item.kind = lsp::CompletionItemKind::Event; break; - case ScriptCodeCompletionOption::KIND_CONSTANT: + case ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT: item.kind = lsp::CompletionItemKind::Constant; break; - case ScriptCodeCompletionOption::KIND_VARIABLE: + case ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE: item.kind = lsp::CompletionItemKind::Variable; break; - case ScriptCodeCompletionOption::KIND_FILE_PATH: + case ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH: item.kind = lsp::CompletionItemKind::File; break; - case ScriptCodeCompletionOption::KIND_NODE_PATH: + case ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH: item.kind = lsp::CompletionItemKind::Snippet; break; - case ScriptCodeCompletionOption::KIND_PLAIN_TEXT: + case ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT: item.kind = lsp::CompletionItemKind::Text; break; + default: { + } } arr[i] = item.to_json(); @@ -196,8 +212,8 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { arr = native_member_completions.duplicate(); - for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) { - ExtendGDScriptParser *script = E->get(); + for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) { + ExtendGDScriptParser *script = E.value; const Array &items = script->get_member_completions(); const int start_size = arr.size(); @@ -210,6 +226,14 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { return arr; } +Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) { + lsp::TextDocumentPositionParams params; + params.load(p_params); + String new_name = p_params["newName"]; + + return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name); +} + Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { lsp::CompletionItem item; item.load(p_params); @@ -244,8 +268,8 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } 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, inner_class_name); + if (HashMap<String, ExtendGDScriptParser *>::ConstIterator E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(class_name)) { + symbol = E->value->get_member_symbol(member_name, inner_class_name); } } } @@ -255,15 +279,10 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { item.documentation = symbol->render(); } - if ((item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) && !item.label.ends_with("):")) { - item.insertText = item.label + "("; - if (symbol && symbol->children.is_empty()) { - item.insertText += ")"; - } - } else if (item.kind == lsp::CompletionItemKind::Event) { + 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; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; + item.insertText = item.label.quote(quote_style); } } @@ -288,8 +307,8 @@ Array GDScriptTextDocument::documentLink(const Dictionary &p_params) { List<lsp::DocumentLink> links; GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links); - for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { - ret.push_back(E->get().to_json()); + for (const lsp::DocumentLink &E : links) { + ret.push_back(E.to_json()); } return ret; } @@ -316,8 +335,8 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) { 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 *s = E->get()) { + for (const lsp::DocumentSymbol *&E : list) { + if (const lsp::DocumentSymbol *s = E) { contents.push_back(s->render().value); } } @@ -367,7 +386,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) { id = "class_global:" + symbol->native_class + ":" + symbol->name; break; } - call_deferred("show_native_symbol_in_editor", id); + call_deferred(SNAME("show_native_symbol_in_editor"), id); } else { notify_client_show_symbol(symbol); } @@ -393,17 +412,22 @@ GDScriptTextDocument::GDScriptTextDocument() { file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES); } -GDScriptTextDocument::~GDScriptTextDocument() { - memdelete(file_checker); -} - void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); + + EditorFileSystem::get_singleton()->update_file(path); + Error error; + Ref<GDScript> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error); + if (error == OK) { + if (script->load_source_code(path) == OK) { + script->reload(true); + } + } } void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { - ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id); + ScriptEditor::get_singleton()->call_deferred(SNAME("_help_class_goto"), p_symbol_id); DisplayServer::get_singleton()->window_move_to_foreground(); } @@ -423,8 +447,8 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { List<const lsp::DocumentSymbol *> list; GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(p_location, list); - for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { - if (const lsp::DocumentSymbol *s = E->get()) { + for (const lsp::DocumentSymbol *&E : list) { + if (const lsp::DocumentSymbol *s = E) { if (!s->uri.is_empty()) { lsp::Location location; location.uri = s->uri; diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 792e601bc1..9732765f34 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,19 +31,21 @@ #ifndef GDSCRIPT_TEXT_DOCUMENT_H #define GDSCRIPT_TEXT_DOCUMENT_H -#include "core/object/reference.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" #include "lsp.hpp" -class GDScriptTextDocument : public Reference { - GDCLASS(GDScriptTextDocument, Reference) +class GDScriptTextDocument : public RefCounted { + GDCLASS(GDScriptTextDocument, RefCounted) protected: static void _bind_methods(); - FileAccess *file_checker; + Ref<FileAccess> file_checker; void didOpen(const Variant &p_param); + void didClose(const Variant &p_param); void didChange(const Variant &p_param); + void didSave(const Variant &p_param); void sync_script_content(const String &p_path, const String &p_content); void show_native_symbol_in_editor(const String &p_symbol_id); @@ -60,6 +62,7 @@ public: Array documentSymbol(const Dictionary &p_params); Array completion(const Dictionary &p_params); Dictionary resolve(const Dictionary &p_params); + Dictionary rename(const Dictionary &p_params); Array foldingRange(const Dictionary &p_params); Array codeLens(const Dictionary &p_params); Array documentLink(const Dictionary &p_params); @@ -72,7 +75,6 @@ public: void initialize(); GDScriptTextDocument(); - virtual ~GDScriptTextDocument(); }; #endif diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 69cad1a335..8d484a43b3 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -42,6 +42,8 @@ #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { + ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal); + ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files); ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol); ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script); ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script); @@ -51,23 +53,85 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } +void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) { + Ref<Script> script = obj->get_script(); + + if (script->get_language()->get_name() != "GDScript") { + return; + } + + String function_signature = "func " + function; + String source = script->get_source_code(); + + if (source.contains(function_signature)) { + return; + } + + int first_class = source.find("\nclass "); + int start_line = 0; + if (first_class != -1) { + start_line = source.substr(0, first_class).split("\n").size(); + } else { + start_line = source.split("\n").size(); + } + + String function_body = "\n\n" + function_signature + "("; + for (int i = 0; i < args.size(); ++i) { + function_body += args[i]; + if (i < args.size() - 1) { + function_body += ", "; + } + } + function_body += ")"; + if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) { + function_body += " -> void"; + } + function_body += ":\n\tpass # Replace with function body.\n"; + + lsp::TextEdit text_edit; + + if (first_class != -1) { + function_body += "\n\n"; + } + text_edit.range.end.line = text_edit.range.start.line = start_line; + + text_edit.newText = function_body; + + String uri = get_file_uri(script->get_path()); + + lsp::ApplyWorkspaceEditParams params; + params.edit.add_edit(uri, text_edit); + + GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json()); +} + +void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) { + Array files = p_params["files"]; + for (int i = 0; i < files.size(); ++i) { + Dictionary file = files[i]; + String uri = file["uri"]; + String path = get_file_path(uri); + parse_script(path, ""); + } +} + void GDScriptWorkspace::remove_cache_parser(const String &p_path) { - Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path); - Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path); + HashMap<String, ExtendGDScriptParser *>::Iterator parser = parse_results.find(p_path); + HashMap<String, ExtendGDScriptParser *>::Iterator script = scripts.find(p_path); if (parser && script) { - if (script->get() && script->get() == parser->get()) { - memdelete(script->get()); + if (script->value && script->value == parser->value) { + memdelete(script->value); } else { - memdelete(script->get()); - memdelete(parser->get()); + memdelete(script->value); + memdelete(parser->value); } parse_results.erase(p_path); scripts.erase(p_path); } else if (parser) { - memdelete(parser->get()); + memdelete(parser->value); parse_results.erase(p_path); } else if (script) { - memdelete(script->get()); + memdelete(script->value); scripts.erase(p_path); } } @@ -77,8 +141,8 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_ StringName empty; while (class_name != empty) { - if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) { - const lsp::DocumentSymbol &class_symbol = E->value(); + if (HashMap<StringName, lsp::DocumentSymbol>::ConstIterator E = native_symbols.find(class_name)) { + const lsp::DocumentSymbol &class_symbol = E->value; if (p_member.is_empty()) { return &class_symbol; @@ -98,28 +162,57 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_ } const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const { - const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path); + HashMap<String, ExtendGDScriptParser *>::ConstIterator S = scripts.find(p_path); if (S) { - return &(S->get()->get_symbols()); + return &(S->value->get_symbols()); + } + return nullptr; +} + +const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier) { + for (int i = 0; i < p_parent->children.size(); ++i) { + const lsp::DocumentSymbol *parameter_symbol = &p_parent->children[i]; + if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) { + return parameter_symbol; + } + } + + return nullptr; +} + +const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier) { + const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols(); + + for (int i = 0; i < class_symbol->children.size(); ++i) { + if (class_symbol->children[i].kind == lsp::SymbolKind::Function || class_symbol->children[i].kind == lsp::SymbolKind::Class) { + const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + + for (int l = 0; l < function_symbol->children.size(); ++l) { + const lsp::DocumentSymbol *local = &function_symbol->children[l]; + if (!local->detail.is_empty() && local->name == p_symbol_identifier) { + return local; + } + } + } } + return nullptr; } void GDScriptWorkspace::reload_all_workspace_scripts() { List<String> paths; list_script_files("res://", paths); - for (List<String>::Element *E = paths.front(); E; E = E->next()) { - const String &path = E->get(); + for (const String &path : paths) { Error err; String content = FileAccess::get_file_as_string(path, &err); ERR_CONTINUE(err != OK); err = parse_script(path, content); if (err != OK) { - Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path); + HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(path); String err_msg = "Failed parse script " + path; if (S) { - err_msg += "\n" + S->get()->get_errors()[0].message; + err_msg += "\n" + S->value->get_errors()[0].message; } ERR_CONTINUE_MSG(err != OK, err_msg); } @@ -128,7 +221,7 @@ void GDScriptWorkspace::reload_all_workspace_scripts() { void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) { Error err; - DirAccessRef dir = DirAccess::open(p_root_dir, &err); + Ref<DirAccess> dir = DirAccess::open(p_root_dir, &err); if (OK == err) { dir->list_dir_begin(); String file_name = dir->get_next(); @@ -145,25 +238,25 @@ void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> } ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) { - const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path); + HashMap<String, ExtendGDScriptParser *>::Iterator S = scripts.find(p_path); if (!S) { parse_local_script(p_path); S = scripts.find(p_path); } if (S) { - return S->get(); + return S->value; } return nullptr; } ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) { - const Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(p_path); + HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(p_path); if (!S) { parse_local_script(p_path); S = parse_results.find(p_path); } if (S) { - return S->get(); + return S->value; } return nullptr; } @@ -172,12 +265,14 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { String query = p_params["query"]; Array arr; if (!query.is_empty()) { - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { Vector<lsp::DocumentedSymbolInformation> script_symbols; - E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); + E.value->get_symbols().symbol_tree_as_list(E.key, script_symbols); for (int i = 0; i < script_symbols.size(); ++i) { - if (query.is_subsequence_ofi(script_symbols[i].name)) { - arr.push_back(script_symbols[i].to_json()); + if (query.is_subsequence_ofn(script_symbols[i].name)) { + lsp::DocumentedSymbolInformation symbol = script_symbols[i]; + symbol.location.uri = get_file_uri(symbol.location.uri); + arr.push_back(symbol.to_json()); } } } @@ -191,10 +286,10 @@ Error GDScriptWorkspace::initialize() { } DocTools *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { - const DocData::ClassDoc &class_data = E->value(); + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { + const DocData::ClassDoc &class_data = E.value; lsp::DocumentSymbol class_symbol; - String class_name = E->key(); + String class_name = E.key; class_symbol.name = class_name; class_symbol.native_class = class_name; class_symbol.kind = lsp::SymbolKind::Class; @@ -219,18 +314,13 @@ Error GDScriptWorkspace::initialize() { class_symbol.children.push_back(symbol); } - Vector<DocData::PropertyDoc> properties; - properties.append_array(class_data.properties); - const int theme_prop_start_idx = properties.size(); - properties.append_array(class_data.theme_properties); - for (int i = 0; i < class_data.properties.size(); i++) { const DocData::PropertyDoc &data = class_data.properties[i]; lsp::DocumentSymbol symbol; symbol.name = data.name; symbol.native_class = class_name; symbol.kind = lsp::SymbolKind::Property; - symbol.detail = String(i >= theme_prop_start_idx ? "<Theme> var" : "var") + " " + class_name + "." + data.name; + symbol.detail = "var " + class_name + "." + data.name; if (data.enumeration.length()) { symbol.detail += ": " + data.enumeration; } else { @@ -240,8 +330,21 @@ Error GDScriptWorkspace::initialize() { class_symbol.children.push_back(symbol); } + for (int i = 0; i < class_data.theme_properties.size(); i++) { + const DocData::ThemeItemDoc &data = class_data.theme_properties[i]; + lsp::DocumentSymbol symbol; + symbol.name = data.name; + symbol.native_class = class_name; + symbol.kind = lsp::SymbolKind::Property; + symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type; + symbol.documentation = data.description; + class_symbol.children.push_back(symbol); + } + Vector<DocData::MethodDoc> methods_signals; + methods_signals.append_array(class_data.constructors); methods_signals.append_array(class_data.methods); + methods_signals.append_array(class_data.operators); const int signal_start_idx = methods_signals.size(); methods_signals.append_array(class_data.signals); @@ -277,7 +380,7 @@ Error GDScriptWorkspace::initialize() { symbol.children.push_back(symbol_arg); } - if (data.qualifiers.find("vararg") != -1) { + if (data.qualifiers.contains("vararg")) { params += params.is_empty() ? "..." : ", ..."; } @@ -296,30 +399,33 @@ Error GDScriptWorkspace::initialize() { reload_all_workspace_scripts(); if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) { + for (const KeyValue<StringName, lsp::DocumentSymbol> &E : native_symbols) { ClassMembers members; - const lsp::DocumentSymbol &class_symbol = E->get(); + const lsp::DocumentSymbol &class_symbol = E.value; for (int i = 0; i < class_symbol.children.size(); i++) { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; - members.set(symbol.name, &symbol); + members.insert(symbol.name, &symbol); } - native_members.set(E->key(), members); + native_members.insert(E.key, members); } // cache member completions - for (Map<String, ExtendGDScriptParser *>::Element *S = scripts.front(); S; S = S->next()) { - S->get()->get_member_completions(); + for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) { + S.value->get_member_completions(); } } + EditorNode *editor_node = EditorNode::get_singleton(); + editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal)); + 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<String, ExtendGDScriptParser *>::Element *last_parser = parse_results.find(p_path); - Map<String, ExtendGDScriptParser *>::Element *last_script = scripts.find(p_path); + HashMap<String, ExtendGDScriptParser *>::Iterator last_parser = parse_results.find(p_path); + HashMap<String, ExtendGDScriptParser *>::Iterator last_script = scripts.find(p_path); if (err == OK) { remove_cache_parser(p_path); @@ -327,8 +433,8 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont scripts[p_path] = parser; } else { - if (last_parser && last_script && last_parser->get() != last_script->get()) { - memdelete(last_parser->get()); + if (last_parser && last_script && last_parser->value != last_script->value) { + memdelete(last_parser->value); } parse_results[p_path] = parser; } @@ -338,6 +444,50 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont return err; } +Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { + Error err; + String path = get_file_path(p_doc_pos.textDocument.uri); + + lsp::WorkspaceEdit edit; + + List<String> paths; + list_script_files("res://", paths); + + const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); + if (reference_symbol) { + String identifier = reference_symbol->name; + + for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { + PackedStringArray content = FileAccess::get_file_as_string(PE->get(), &err).split("\n"); + for (int i = 0; i < content.size(); ++i) { + String line = content[i]; + + int character = line.find(identifier); + while (character > -1) { + lsp::TextDocumentPositionParams params; + + lsp::TextDocumentIdentifier text_doc; + text_doc.uri = get_file_uri(PE->get()); + + params.textDocument = text_doc; + params.position.line = i; + params.position.character = character; + + const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + + if (other_symbol == reference_symbol) { + edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name); + } + + character = line.find(identifier, character + 1); + } + } + } + } + + return edit.to_json(); +} + Error GDScriptWorkspace::parse_local_script(const String &p_path) { Error err; String content = FileAccess::get_file_as_string(p_path, &err); @@ -363,9 +513,9 @@ String GDScriptWorkspace::get_file_uri(const String &p_path) const { void GDScriptWorkspace::publish_diagnostics(const String &p_path) { Dictionary params; Array errors; - const Map<String, ExtendGDScriptParser *>::Element *ele = parse_results.find(p_path); + HashMap<String, ExtendGDScriptParser *>::ConstIterator ele = parse_results.find(p_path); if (ele) { - const Vector<lsp::Diagnostic> &list = ele->get()->get_diagnostics(); + const Vector<lsp::Diagnostic> &list = ele->value->get_diagnostics(); errors.resize(list.size()); for (int i = 0; i < list.size(); ++i) { errors[i] = list[i].to_json(); @@ -410,10 +560,10 @@ Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) { for (int i = 0; i < owners.size(); i++) { NodePath owner_path = owners[i]; - RES owner_res = ResourceLoader::load(owner_path); + Ref<Resource> owner_res = ResourceLoader::load(owner_path); if (Object::cast_to<PackedScene>(owner_res.ptr())) { Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res)); - owner_scene_node = owner_packed_scene->instance(); + owner_scene_node = owner_packed_scene->instantiate(); break; } } @@ -421,22 +571,45 @@ Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) { return owner_scene_node; } -void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options) { +void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) { String path = get_file_path(p_params.textDocument.uri); String call_hint; bool forced = false; if (const ExtendGDScriptParser *parser = get_parse_result(path)) { Node *owner_scene_node = _get_owner_scene_node(path); + + Array stack; + Node *current = nullptr; + if (owner_scene_node != nullptr) { + stack.push_back(owner_scene_node); + + while (!stack.is_empty()) { + current = Object::cast_to<Node>(stack.pop_back()); + Ref<GDScript> script = current->get_script(); + if (script.is_valid() && script->get_path() == path) { + break; + } + for (int i = 0; i < current->get_child_count(); ++i) { + stack.push_back(current->get_child(i)); + } + } + + Ref<GDScript> script = current->get_script(); + if (!script.is_valid() || script->get_path() != path) { + current = owner_scene_node; + } + } + String code = parser->get_text_for_completion(p_params.position); - GDScriptLanguage::get_singleton()->complete_code(code, path, owner_scene_node, r_options, forced, call_hint); + GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint); if (owner_scene_node) { memdelete(owner_scene_node); } } } -const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) { +const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) { const lsp::DocumentSymbol *symbol = nullptr; String path = get_file_path(p_doc_pos.textDocument.uri); @@ -461,15 +634,24 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } else { ScriptLanguage::LookupResult ret; - if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, nullptr, ret)) { - if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) { + if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" ", "").replace("\t", "").find("new(") > -1) { + symbol_identifier = "_init"; + } + if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) { + if (ret.type == ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION) { String target_script_path = path; if (!ret.script.is_null()) { target_script_path = ret.script->get_path(); + } else if (!ret.class_path.is_empty()) { + target_script_path = ret.class_path; } if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); + + if (symbol && symbol->kind == lsp::SymbolKind::Function && symbol->name != symbol_identifier) { + symbol = get_parameter_symbol(symbol, symbol_identifier); + } } } else { @@ -481,6 +663,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } } else { symbol = parser->get_member_symbol(symbol_identifier); + + if (!symbol) { + symbol = get_local_symbol(parser, symbol_identifier); + } } } } @@ -496,39 +682,33 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP Vector2i offset; symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); - const StringName *class_ptr = native_members.next(nullptr); - while (class_ptr) { - const ClassMembers &members = native_members.get(*class_ptr); + for (const KeyValue<StringName, ClassMembers> &E : native_members) { + const ClassMembers &members = native_members.get(E.key); 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 ExtendGDScriptParser *script = E->get(); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + const ExtendGDScriptParser *script = E.value; const ClassMembers &members = script->get_members(); if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { r_list.push_back(*symbol); } - const HashMap<String, ClassMembers> &inner_classes = script->get_inner_classes(); - const String *_class = inner_classes.next(nullptr); - while (_class) { - const ClassMembers *inner_class = inner_classes.getptr(*_class); + for (const KeyValue<String, ClassMembers> &F : script->get_inner_classes()) { + const ClassMembers *inner_class = &F.value; if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) { r_list.push_back(*symbol); } - - _class = inner_classes.next(_class); } } } } const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) { - if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) { - const lsp::DocumentSymbol &symbol = E->get(); + if (HashMap<StringName, lsp::DocumentSymbol>::Iterator E = native_symbols.find(p_params.native_class)) { + const lsp::DocumentSymbol &symbol = E->value; if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) { return &symbol; } @@ -546,8 +726,8 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::N void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) { if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) { const List<lsp::DocumentLink> &links = parser->get_document_links(); - for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { - r_list.push_back(E->get()); + for (const lsp::DocumentLink &E : links) { + r_list.push_back(E); } } } @@ -574,8 +754,7 @@ Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols); } - for (List<const lsp::DocumentSymbol *>::Element *E = symbols.front(); E; E = E->next()) { - const lsp::DocumentSymbol *symbol = E->get(); + for (const lsp::DocumentSymbol *const &symbol : symbols) { if (symbol->kind == lsp::SymbolKind::Method || symbol->kind == lsp::SymbolKind::Function) { lsp::SignatureInformation signature_info; signature_info.label = symbol->detail; @@ -605,17 +784,17 @@ GDScriptWorkspace::GDScriptWorkspace() { } GDScriptWorkspace::~GDScriptWorkspace() { - Set<String> cached_parsers; + HashSet<String> cached_parsers; - for (Map<String, ExtendGDScriptParser *>::Element *E = parse_results.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) { + cached_parsers.insert(E.key); } - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + cached_parsers.insert(E.key); } - for (Set<String>::Element *E = cached_parsers.front(); E; E = E->next()) { - remove_cache_parser(E->get()); + for (const String &E : cached_parsers) { + remove_cache_parser(E); } } diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 7fd8bfcf20..7bff5db81f 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,8 +37,8 @@ #include "gdscript_extend_parser.h" #include "lsp.hpp" -class GDScriptWorkspace : public Reference { - GDCLASS(GDScriptWorkspace, Reference); +class GDScriptWorkspace : public RefCounted { + GDCLASS(GDScriptWorkspace, RefCounted); private: void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners); @@ -48,10 +48,12 @@ protected: static void _bind_methods(); void remove_cache_parser(const String &p_path); bool initialized = false; - Map<StringName, lsp::DocumentSymbol> native_symbols; + HashMap<StringName, lsp::DocumentSymbol> native_symbols; const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const; const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const; + const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier); + const lsp::DocumentSymbol *get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier); void reload_all_workspace_scripts(); @@ -60,12 +62,14 @@ protected: void list_script_files(const String &p_root_dir, List<String> &r_files); + void apply_new_signal(Object *obj, String function, PackedStringArray args); + public: String root; String root_uri; - Map<String, ExtendGDScriptParser *> scripts; - Map<String, ExtendGDScriptParser *> parse_results; + HashMap<String, ExtendGDScriptParser *> scripts; + HashMap<String, ExtendGDScriptParser *> parse_results; HashMap<StringName, ClassMembers> native_members; public: @@ -81,14 +85,16 @@ public: String get_file_uri(const String &p_path) const; void publish_diagnostics(const String &p_path); - void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options); + void completion(const lsp::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options); - const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); + const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_required = false); void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list); const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params); void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list); Dictionary generate_script_api(const String &p_path); Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature); + void did_delete_files(const Dictionary &p_params); + Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name); GDScriptWorkspace(); ~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 6635098be2..1c9349097f 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -255,6 +255,72 @@ struct TextEdit { }; /** + * The edits to be applied. + */ +struct WorkspaceEdit { + /** + * Holds changes to existing resources. + */ + HashMap<String, Vector<TextEdit>> changes; + + _FORCE_INLINE_ void add_edit(const String &uri, const TextEdit &edit) { + if (changes.has(uri)) { + changes[uri].push_back(edit); + } else { + Vector<TextEdit> edits; + edits.push_back(edit); + changes[uri] = edits; + } + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + + Dictionary out_changes; + for (const KeyValue<String, Vector<TextEdit>> &E : changes) { + Array edits; + for (int i = 0; i < E.value.size(); ++i) { + Dictionary text_edit; + text_edit["range"] = E.value[i].range.to_json(); + text_edit["newText"] = E.value[i].newText; + edits.push_back(text_edit); + } + out_changes[E.key] = edits; + } + dict["changes"] = out_changes; + + return dict; + } + + _FORCE_INLINE_ void add_change(const String &uri, const int &line, const int &start_character, const int &end_character, const String &new_text) { + if (HashMap<String, Vector<TextEdit>>::Iterator E = changes.find(uri)) { + Vector<TextEdit> edit_list = E->value; + for (int i = 0; i < edit_list.size(); ++i) { + TextEdit edit = edit_list[i]; + if (edit.range.start.character == start_character) { + return; + } + } + } + + TextEdit new_edit; + new_edit.newText = new_text; + new_edit.range.start.line = line; + new_edit.range.start.character = start_character; + new_edit.range.end.line = line; + new_edit.range.end.character = end_character; + + if (HashMap<String, Vector<TextEdit>>::Iterator E = changes.find(uri)) { + E->value.push_back(new_edit); + } else { + Vector<TextEdit> edit_list; + edit_list.push_back(new_edit); + changes.insert(uri, edit_list); + } + } +}; + +/** * 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. @@ -292,21 +358,21 @@ struct Command { namespace TextDocumentSyncKind { /** - * Documents should not be synced at all. - */ + * Documents should not be synced at all. + */ static const int None = 0; /** - * Documents are synced by always sending the full content - * of the document. - */ + * Documents are synced by always sending the full content + * of the document. + */ static const int Full = 1; /** - * Documents are synced by sending the full content on open. - * After that only incremental updates to the document are - * send. - */ + * Documents are synced by sending the full content on open. + * After that only incremental updates to the document are + * send. + */ static const int Incremental = 2; }; // namespace TextDocumentSyncKind @@ -486,7 +552,7 @@ struct TextDocumentSyncOptions { * If present save notifications are sent to the server. If omitted the notification should not be * sent. */ - bool save = false; + SaveOptions save; Dictionary to_json() { Dictionary dict; @@ -494,7 +560,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["save"] = save; + dict["save"] = save.to_json(); return dict; } }; @@ -601,20 +667,20 @@ struct TextDocumentContentChangeEvent { // Use namespace instead of enumeration to follow the LSP specifications namespace DiagnosticSeverity { /** - * Reports an error. - */ + * Reports an error. + */ static const int Error = 1; /** - * Reports a warning. - */ + * Reports a warning. + */ static const int Warning = 2; /** - * Reports an information. - */ + * Reports an information. + */ static const int Information = 3; /** - * Reports a hint. - */ + * Reports a hint. + */ static const int Hint = 4; }; // namespace DiagnosticSeverity @@ -766,7 +832,7 @@ struct MarkupContent { // Use namespace instead of enumeration to follow the LSP specifications // lsp::EnumName::EnumValue is OK but lsp::EnumValue is not -// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc. +// And here C++ compilers are unhappy with our enumeration name like Color, File, RefCounted etc. /** * The kind of a completion entry. */ @@ -788,7 +854,7 @@ static const int Keyword = 14; static const int Snippet = 15; static const int Color = 16; static const int File = 17; -static const int Reference = 18; +static const int RefCounted = 18; static const int Folder = 19; static const int EnumMember = 20; static const int Constant = 21; @@ -805,18 +871,18 @@ static const int TypeParameter = 25; */ namespace InsertTextFormat { /** - * The primary text to be inserted is treated as a plain string. - */ + * The primary text to be inserted is treated as a plain string. + */ static const int PlainText = 1; /** - * The primary text to be inserted is treated as a snippet. - * - * A snippet can define tab stops and placeholders with `$1`, `$2` - * and `${3:foo}`. `$0` defines the final tab stop, it defaults to - * the end of the snippet. Placeholders with equal identifiers are linked, - * that is typing in one will update others too. - */ + * The primary text to be inserted is treated as a snippet. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ static const int Snippet = 2; }; // namespace InsertTextFormat @@ -938,8 +1004,8 @@ struct CompletionItem { dict["label"] = label; dict["kind"] = kind; dict["data"] = data; + dict["insertText"] = insertText; if (resolved) { - dict["insertText"] = insertText; dict["detail"] = detail; dict["documentation"] = documentation.to_json(); dict["deprecated"] = deprecated; @@ -1018,32 +1084,32 @@ struct CompletionList { * A symbol kind. */ namespace SymbolKind { -static const int File = 0; -static const int Module = 1; -static const int Namespace = 2; -static const int Package = 3; -static const int Class = 4; -static const int Method = 5; -static const int Property = 6; -static const int Field = 7; -static const int Constructor = 8; -static const int Enum = 9; -static const int Interface = 10; -static const int Function = 11; -static const int Variable = 12; -static const int Constant = 13; -static const int String = 14; -static const int Number = 15; -static const int Boolean = 16; -static const int Array = 17; -static const int Object = 18; -static const int Key = 19; -static const int Null = 20; -static const int EnumMember = 21; -static const int Struct = 22; -static const int Event = 23; -static const int Operator = 24; -static const int TypeParameter = 25; +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 /** @@ -1266,6 +1332,18 @@ struct DocumentSymbol { } }; +struct ApplyWorkspaceEditParams { + WorkspaceEdit edit; + + Dictionary to_json() { + Dictionary dict; + + dict["edit"] = edit.to_json(); + + return dict; + } +}; + struct NativeSymbolInspectParams { String native_class; String symbol_name; @@ -1281,16 +1359,16 @@ struct NativeSymbolInspectParams { */ namespace FoldingRangeKind { /** - * Folding range for a comment - */ + * Folding range for a comment + */ static const String Comment = "comment"; /** - * Folding range for a imports or includes - */ + * Folding range for a imports or includes + */ static const String Imports = "imports"; /** - * Folding range for a region (e.g. `#region`) - */ + * Folding range for a region (e.g. `#region`) + */ static const String Region = "region"; } // namespace FoldingRangeKind @@ -1341,20 +1419,20 @@ struct FoldingRange { */ namespace CompletionTriggerKind { /** - * Completion was triggered by typing an identifier (24x7 code - * complete), manual invocation (e.g Ctrl+Space) or via API. - */ + * Completion was triggered by typing an identifier (24x7 code + * complete), manual invocation (e.g Ctrl+Space) or via API. + */ static const int Invoked = 1; /** - * Completion was triggered by a trigger character specified by - * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. - */ + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + */ static const int TriggerCharacter = 2; /** - * Completion was re-triggered as the current completion list is incomplete. - */ + * Completion was re-triggered as the current completion list is incomplete. + */ static const int TriggerForIncompleteCompletions = 3; } // namespace CompletionTriggerKind @@ -1363,8 +1441,8 @@ static const int TriggerForIncompleteCompletions = 3; */ struct CompletionContext { /** - * How the completion was triggered. - */ + * How the completion was triggered. + */ int triggerKind = CompletionTriggerKind::TriggerCharacter; /** @@ -1528,6 +1606,114 @@ struct SignatureHelp { } }; +/** + * A pattern to describe in which file operation requests or notifications + * the server is interested in. + */ +struct FileOperationPattern { + /** + * The glob pattern to match. + */ + String glob = "**/*.gd"; + + /** + * Whether to match `file`s or `folder`s with this pattern. + * + * Matches both if undefined. + */ + String matches = "file"; + + Dictionary to_json() const { + Dictionary dict; + + dict["glob"] = glob; + dict["matches"] = matches; + + return dict; + } +}; + +/** + * A filter to describe in which file operation requests or notifications + * the server is interested in. + */ +struct FileOperationFilter { + /** + * The actual file operation pattern. + */ + FileOperationPattern pattern; + + Dictionary to_json() const { + Dictionary dict; + + dict["pattern"] = pattern.to_json(); + + return dict; + } +}; + +/** + * The options to register for file operations. + */ +struct FileOperationRegistrationOptions { + /** + * The actual filters. + */ + Vector<FileOperationFilter> filters; + + FileOperationRegistrationOptions() { + filters.push_back(FileOperationFilter()); + } + + Dictionary to_json() const { + Dictionary dict; + + Array filts; + for (int i = 0; i < filters.size(); i++) { + filts.push_back(filters[i].to_json()); + } + dict["filters"] = filts; + + return dict; + } +}; + +/** + * The server is interested in file notifications/requests. + */ +struct FileOperations { + /** + * The server is interested in receiving didDeleteFiles file notifications. + */ + FileOperationRegistrationOptions didDelete; + + Dictionary to_json() const { + Dictionary dict; + + dict["didDelete"] = didDelete.to_json(); + + return dict; + } +}; + +/** + * Workspace specific server capabilities + */ +struct Workspace { + /** + * The server is interested in file notifications/requests. + */ + FileOperations fileOperations; + + Dictionary to_json() const { + Dictionary dict; + + dict["fileOperations"] = fileOperations.to_json(); + + return dict; + } +}; + struct ServerCapabilities { /** * Defines how text documents are synced. Is either a detailed structure defining each notification or @@ -1590,6 +1776,11 @@ struct ServerCapabilities { bool workspaceSymbolProvider = true; /** + * The server supports workspace folder. + */ + Workspace workspace; + + /** * The server provides code actions. The `CodeActionOptions` return type is only * valid if the client signals code action literal support via the property * `textDocument.codeAction.codeActionLiteralSupport`. @@ -1676,6 +1867,7 @@ struct ServerCapabilities { dict["documentHighlightProvider"] = documentHighlightProvider; dict["documentSymbolProvider"] = documentSymbolProvider; dict["workspaceSymbolProvider"] = workspaceSymbolProvider; + dict["workspace"] = workspace.to_json(); dict["codeActionProvider"] = codeActionProvider; dict["documentFormattingProvider"] = documentFormattingProvider; dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider; @@ -1714,7 +1906,7 @@ struct GodotNativeClassInfo { struct GodotCapabilities { /** * Native class list - */ + */ List<GodotNativeClassInfo> native_classes; Dictionary to_json() { @@ -1748,7 +1940,7 @@ static String marked_documentation(const String &p_bbcode) { line = "\t" + line.substr(code_block_indent, line.length()); } - if (in_code_block && line.find("[/codeblock]") != -1) { + if (in_code_block && line.contains("[/codeblock]")) { line = "\n"; in_code_block = false; } |