diff options
Diffstat (limited to 'modules/gdscript/language_server')
9 files changed, 510 insertions, 119 deletions
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index e63b6ab20e..d106b3b541 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -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(); @@ -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::PropertyStyle::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)); @@ -223,10 +221,10 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } } } 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; @@ -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; } @@ -647,7 +693,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_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 912c9a174e..b6c48468f5 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -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" @@ -130,13 +128,13 @@ Error GDScriptLanguageProtocol::on_client_connected() { peer->connection = tcp_peer; clients.set(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()); } @@ -256,7 +254,7 @@ void GDScriptLanguageProtocol::poll() { } } -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); } @@ -281,7 +279,7 @@ 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()); } @@ -295,10 +293,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..5a2dd55c46 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -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" +#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,7 +75,7 @@ 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; @@ -97,7 +103,7 @@ 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); @@ -108,4 +114,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..c47164d95b 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -30,7 +30,7 @@ #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" @@ -78,7 +78,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { 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("127.0.0.1")) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; @@ -101,7 +101,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_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 9f2373bf56..9574c765bc 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -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; @@ -151,8 +173,7 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { 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 ScriptCodeCompletionOption &option : options) { lsp::CompletionItem item; item.label = option.display; item.data = request_data; @@ -210,6 +231,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); @@ -288,8 +317,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 +345,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 +396,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); } @@ -400,10 +429,19 @@ GDScriptTextDocument::~GDScriptTextDocument() { 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 +461,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..9021c84a3f 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -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; 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); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 69cad1a335..1512b4bb89 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -42,6 +42,7 @@ #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { + 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,6 +52,16 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } +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); @@ -105,11 +116,40 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_ 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); @@ -177,7 +217,9 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { E->get()->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()); + lsp::DocumentedSymbolInformation symbol = script_symbols[i]; + symbol.location.uri = get_file_uri(symbol.location.uri); + arr.push_back(symbol.to_json()); } } } @@ -219,18 +261,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,6 +277,17 @@ 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.methods); const int signal_start_idx = methods_signals.size(); @@ -338,6 +386,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); @@ -413,7 +505,7 @@ Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) { RES 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; } } @@ -428,8 +520,31 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S 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 = 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); } @@ -466,10 +581,16 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu 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 +602,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); + } } } } @@ -546,8 +671,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 +699,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; diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 7fd8bfcf20..9496677449 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -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); @@ -52,6 +52,8 @@ protected: 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(); @@ -89,6 +91,8 @@ public: 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..9ac6c6bd4e 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -255,6 +255,62 @@ struct TextEdit { }; /** + * The edits to be applied. + */ +struct WorkspaceEdit { + /** + * Holds changes to existing resources. + */ + Map<String, Vector<TextEdit>> changes; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + + Dictionary out_changes; + for (Map<String, Vector<TextEdit>>::Element *E = changes.front(); E; E = E->next()) { + Array edits; + for (int i = 0; i < E->get().size(); ++i) { + Dictionary text_edit; + text_edit["range"] = E->get()[i].range.to_json(); + text_edit["newText"] = E->get()[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 (Map<String, Vector<TextEdit>>::Element *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 (Map<String, Vector<TextEdit>>::Element *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. @@ -486,7 +542,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 +550,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["save"] = save; + dict["save"] = save.to_json(); return dict; } }; @@ -766,7 +822,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 +844,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; @@ -1018,32 +1074,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 /** @@ -1528,6 +1584,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 +1754,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 +1845,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; |