feat: updated godot version

This commit is contained in:
Sara Gerretsen 2026-04-04 19:38:56 +02:00
parent 0c508b0831
commit 42b028dbb5
4694 changed files with 236470 additions and 401376 deletions

View file

@ -32,11 +32,10 @@
#include "../gdscript.h"
#include "../gdscript_analyzer.h"
#include "editor/settings/editor_settings.h"
#include "gdscript_language_protocol.h"
#include "gdscript_workspace.h"
#include "editor/settings/editor_settings.h"
int get_indent_size() {
if (EditorSettings::get_singleton()) {
return EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
@ -136,12 +135,18 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.severity = LSP::DiagnosticSeverity::Error;
diagnostic.message = error.message;
diagnostic.source = "gdscript";
GodotRange godot_range(
GodotPosition(error.start_line, error.start_column),
GodotPosition(error.end_line, error.end_column));
diagnostic.range = godot_range.to_lsp(get_lines());
diagnostic.code = -1;
LSP::Range range;
LSP::Position pos;
const PackedStringArray line_array = get_lines();
int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, line_array.size() - 1);
const String &line_text = line_array[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
range.end = range.start;
range.end.character = line_text.strip_edges(false).length();
diagnostic.range = range;
diagnostics.push_back(diagnostic);
}
@ -151,12 +156,17 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.severity = LSP::DiagnosticSeverity::Warning;
diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message();
diagnostic.source = "gdscript";
GodotRange godot_range(
GodotPosition(warning.start_line, warning.start_column),
GodotPosition(warning.end_line, warning.end_column));
diagnostic.range = godot_range.to_lsp(get_lines());
diagnostic.code = warning.code;
LSP::Range range;
LSP::Position pos;
int line = LINE_NUMBER_TO_INDEX(warning.start_line);
const String &line_text = get_lines()[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
range.end = pos;
range.end.character = line_text.strip_edges(false).length();
diagnostic.range = range;
diagnostics.push_back(diagnostic);
}
}
@ -860,6 +870,30 @@ const List<LSP::DocumentLink> &ExtendGDScriptParser::get_document_links() const
return document_links;
}
const Array &ExtendGDScriptParser::get_member_completions() {
if (member_completions.is_empty()) {
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, E.key);
member_completions.push_back(item.to_json());
}
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(E.key, F.key));
member_completions.push_back(item.to_json());
}
}
}
return member_completions;
}
Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
ERR_FAIL_NULL_V(p_func, Dictionary());
Dictionary func;

View file

@ -42,6 +42,14 @@
#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column) - 1)
#endif
#ifndef SYMBOL_SEPARATOR
#define SYMBOL_SEPARATOR "::"
#endif
#ifndef JOIN_SYMBOLS
#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPARATOR + (name))
#endif
typedef HashMap<String, const LSP::DocumentSymbol *> ClassMembers;
/**
@ -126,6 +134,8 @@ class ExtendGDScriptParser : public GDScriptParser {
const LSP::DocumentSymbol *search_symbol_defined_at_line(int p_line, const LSP::DocumentSymbol &p_parent, const String &p_symbol_name = "") const;
Array member_completions;
public:
_FORCE_INLINE_ const String &get_path() const { return path; }
_FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; }

View file

@ -30,27 +30,23 @@
#include "gdscript_language_protocol.h"
#include "godot_lsp.h"
#include "core/config/project_settings.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
#include "editor/doc/doc_tools.h"
#include "editor/doc/editor_help.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/settings/editor_settings.h"
#include "modules/gdscript/language_server/godot_lsp.h"
#define LSP_CLIENT_V(m_ret_val) \
#define LSP_CLIENT_V(m_ret_val) \
ERR_FAIL_COND_V(latest_client_id == LSP_NO_CLIENT, m_ret_val); \
ERR_FAIL_COND_V(!clients.has(latest_client_id), m_ret_val); \
Ref<LSPeer> client = clients.get(latest_client_id); \
ERR_FAIL_COND_V(!clients.has(latest_client_id), m_ret_val); \
Ref<LSPeer> client = clients.get(latest_client_id); \
ERR_FAIL_COND_V(!client.is_valid(), m_ret_val);
#define LSP_CLIENT \
ERR_FAIL_COND(latest_client_id == LSP_NO_CLIENT); \
ERR_FAIL_COND(!clients.has(latest_client_id)); \
#define LSP_CLIENT \
ERR_FAIL_COND(latest_client_id == LSP_NO_CLIENT); \
ERR_FAIL_COND(!clients.has(latest_client_id)); \
Ref<LSPeer> client = clients.get(latest_client_id); \
ERR_FAIL_COND(!client.is_valid());
@ -152,9 +148,6 @@ Error GDScriptLanguageProtocol::on_client_connected() {
void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) {
clients.erase(p_client_id);
if (clients.is_empty()) {
scene_cache.clear();
}
EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR);
}
@ -178,18 +171,15 @@ String GDScriptLanguageProtocol::format_output(const String &p_text) {
}
void GDScriptLanguageProtocol::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document);
ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace);
ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled);
ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("initialize", "params"), &GDScriptLanguageProtocol::initialize);
ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized);
ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected);
ClassDB::bind_method(D_METHOD("on_client_disconnected", "client_id"), &GDScriptLanguageProtocol::on_client_disconnected);
ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected);
ClassDB::bind_method(D_METHOD("notify_client", "method", "params", "client_id"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1));
#endif // !DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled);
ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document);
ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace);
ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized);
}
Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
@ -249,6 +239,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
if (!_initialized) {
workspace->initialize();
text_document->initialize();
_initialized = true;
}
@ -279,8 +270,6 @@ void GDScriptLanguageProtocol::poll(int p_limit_usec) {
on_client_connected();
}
scene_cache.poll();
HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin();
while (E != clients.end()) {
Ref<LSPeer> peer = E->value;
@ -327,7 +316,6 @@ void GDScriptLanguageProtocol::stop() {
peer->connection->disconnect_from_host();
}
scene_cache.clear();
server->stop();
}
@ -460,8 +448,6 @@ void GDScriptLanguageProtocol::lsp_did_open(const Dictionary &p_params) {
client->managed_files[path] = document;
client->parse_script(path);
scene_cache.request_load(path);
}
void GDScriptLanguageProtocol::lsp_did_change(const Dictionary &p_params) {
@ -507,8 +493,6 @@ void GDScriptLanguageProtocol::lsp_did_close(const Dictionary &p_params) {
/// A close notification requires a previous open notification to be sent.
ERR_FAIL_COND_MSG(!was_opened, "LSP: Client is closing file without opening it.");
scene_cache.unload(path);
}
void GDScriptLanguageProtocol::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {
@ -574,7 +558,6 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
SET_DOCUMENT_METHOD(didSave);
SET_DOCUMENT_METHOD(documentSymbol);
SET_DOCUMENT_METHOD(documentHighlight);
SET_DOCUMENT_METHOD(completion);
SET_DOCUMENT_METHOD(rename);
SET_DOCUMENT_METHOD(prepareRename);

View file

@ -32,7 +32,6 @@
#include "gdscript_text_document.h"
#include "gdscript_workspace.h"
#include "scene_cache.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
@ -47,7 +46,9 @@
class GDScriptLanguageProtocol : public JSONRPC {
GDCLASS(GDScriptLanguageProtocol, JSONRPC)
#ifdef TESTS_ENABLED
friend class TestGDScriptLanguageProtocolInitializer;
#endif
private:
struct LSPeer : RefCounted {
@ -91,7 +92,6 @@ private:
static GDScriptLanguageProtocol *singleton;
HashMap<int, Ref<LSPeer>> clients;
SceneCache scene_cache;
Ref<TCPServer> server;
int latest_client_id = LSP_NO_CLIENT;
int next_client_id = 0;
@ -119,8 +119,6 @@ public:
_FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
_FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
_FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
_FORCE_INLINE_ SceneCache *get_scene_cache() { return &scene_cache; }
_FORCE_INLINE_ bool is_initialized() const { return _initialized; }
void poll(int p_limit_usec);

View file

@ -30,8 +30,6 @@
#include "gdscript_language_server.h"
#include "gdscript_language_protocol.h"
#include "core/os/os.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
@ -40,6 +38,14 @@
int GDScriptLanguageServer::port_override = -1;
GDScriptLanguageServer::GDScriptLanguageServer() {
// TODO: Move to editor_settings.cpp
_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);
_EDITOR_DEF("network/language_server/use_thread", use_thread);
_EDITOR_DEF("network/language_server/poll_limit_usec", poll_limit_usec);
set_process_internal(true);
}
@ -56,7 +62,7 @@ void GDScriptLanguageServer::_notification(int p_what) {
}
if (started && !use_thread) {
GDScriptLanguageProtocol::get_singleton()->poll(poll_limit_usec);
protocol.poll(poll_limit_usec);
}
} break;
@ -82,7 +88,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) {
GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
while (self->thread_running) {
// Poll 20 times per second
GDScriptLanguageProtocol::get_singleton()->poll(self->poll_limit_usec);
self->protocol.poll(self->poll_limit_usec);
OS::get_singleton()->delay_usec(50000);
}
}
@ -92,18 +98,15 @@ void GDScriptLanguageServer::start() {
port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port");
use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
poll_limit_usec = (int)_EDITOR_GET("network/language_server/poll_limit_usec");
const Error status = GDScriptLanguageProtocol::get_singleton()->start(port, IPAddress(host));
if (status != OK) {
EditorNode::get_log()->add_message("--- Failed to start GDScript language server on port " + itos(port) + ": " + error_names[status] + " ---", EditorLog::MSG_TYPE_EDITOR);
return;
if (protocol.start(port, IPAddress(host)) == OK) {
EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
thread.start(GDScriptLanguageServer::thread_main, this);
}
set_process_internal(!use_thread);
started = true;
}
EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
thread.start(GDScriptLanguageServer::thread_main, this);
}
set_process_internal(!use_thread);
started = true;
}
void GDScriptLanguageServer::stop() {
@ -112,7 +115,7 @@ void GDScriptLanguageServer::stop() {
thread_running = false;
thread.wait_to_finish();
}
GDScriptLanguageProtocol::get_singleton()->stop();
protocol.stop();
started = false;
EditorNode::get_log()->add_message("--- GDScript language server stopped ---", EditorLog::MSG_TYPE_EDITOR);
}

View file

@ -30,23 +30,24 @@
#pragma once
#include "gdscript_language_protocol.h"
#include "editor/plugins/editor_plugin.h"
class GDScriptLanguageServer : public EditorPlugin {
GDCLASS(GDScriptLanguageServer, EditorPlugin);
GDScriptLanguageProtocol protocol;
Thread thread;
bool thread_running = false;
// There is no notification when the editor is initialized. We need to poll till we attempted to start the server.
bool start_attempted = false;
bool started = false;
// Defaults located in editor_settings.cpp
bool use_thread = false;
String host;
int port = 0;
int poll_limit_usec = 0;
String host = "127.0.0.1";
int port = 6005;
int poll_limit_usec = 100000;
static void thread_main(void *p_userdata);
private:

View file

@ -34,38 +34,32 @@
#include "gdscript_extend_parser.h"
#include "gdscript_language_protocol.h"
#include "core/io/resource_loader.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "editor/script/script_editor_plugin.h"
#include "editor/script/script_text_editor.h"
#include "editor/settings/editor_settings.h"
#include "servers/display/display_server.h"
void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor", "symbol_id"), &GDScriptTextDocument::show_native_symbol_in_editor);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("didOpen", "params"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didClose", "params"), &GDScriptTextDocument::didClose);
ClassDB::bind_method(D_METHOD("didChange", "params"), &GDScriptTextDocument::didChange);
ClassDB::bind_method(D_METHOD("willSaveWaitUntil", "params"), &GDScriptTextDocument::willSaveWaitUntil);
ClassDB::bind_method(D_METHOD("didSave", "params"), &GDScriptTextDocument::didSave);
ClassDB::bind_method(D_METHOD("nativeSymbol", "params"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol", "params"), &GDScriptTextDocument::documentSymbol);
ClassDB::bind_method(D_METHOD("completion", "params"), &GDScriptTextDocument::completion);
ClassDB::bind_method(D_METHOD("resolve", "params"), &GDScriptTextDocument::resolve);
ClassDB::bind_method(D_METHOD("rename", "params"), &GDScriptTextDocument::rename);
ClassDB::bind_method(D_METHOD("prepareRename", "params"), &GDScriptTextDocument::prepareRename);
ClassDB::bind_method(D_METHOD("references", "params"), &GDScriptTextDocument::references);
ClassDB::bind_method(D_METHOD("foldingRange", "params"), &GDScriptTextDocument::foldingRange);
ClassDB::bind_method(D_METHOD("codeLens", "params"), &GDScriptTextDocument::codeLens);
ClassDB::bind_method(D_METHOD("documentLink", "params"), &GDScriptTextDocument::documentLink);
ClassDB::bind_method(D_METHOD("colorPresentation", "params"), &GDScriptTextDocument::colorPresentation);
ClassDB::bind_method(D_METHOD("hover", "params"), &GDScriptTextDocument::hover);
ClassDB::bind_method(D_METHOD("definition", "params"), &GDScriptTextDocument::definition);
ClassDB::bind_method(D_METHOD("declaration", "params"), &GDScriptTextDocument::declaration);
ClassDB::bind_method(D_METHOD("signatureHelp", "params"), &GDScriptTextDocument::signatureHelp);
#endif // !DISABLE_DEPRECATED
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("willSaveWaitUntil"), &GDScriptTextDocument::willSaveWaitUntil);
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("prepareRename"), &GDScriptTextDocument::prepareRename);
ClassDB::bind_method(D_METHOD("references"), &GDScriptTextDocument::references);
ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);
ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
ClassDB::bind_method(D_METHOD("colorPresentation"), &GDScriptTextDocument::colorPresentation);
ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
ClassDB::bind_method(D_METHOD("declaration"), &GDScriptTextDocument::declaration);
ClassDB::bind_method(D_METHOD("signatureHelp"), &GDScriptTextDocument::signatureHelp);
ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
}
void GDScriptTextDocument::didOpen(const Variant &p_param) {
@ -128,6 +122,21 @@ void GDScriptTextDocument::notify_client_show_symbol(const LSP::DocumentSymbol *
GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true));
}
void GDScriptTextDocument::initialize() {
if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
for (const KeyValue<StringName, ClassMembers> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members) {
const ClassMembers &members = E.value;
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(E.key), F.key);
native_member_completions.push_back(item.to_json());
}
}
}
}
Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) {
Variant ret;
@ -156,25 +165,6 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
return arr;
}
Array GDScriptTextDocument::documentHighlight(const Dictionary &p_params) {
Array arr;
LSP::TextDocumentPositionParams params;
params.load(p_params);
const LSP::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
if (symbol) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(params.textDocument.uri);
Vector<LSP::Location> usages = GDScriptLanguageProtocol::get_singleton()->get_workspace()->find_usages_in_file(*symbol, path);
for (const LSP::Location &usage : usages) {
LSP::DocumentHighlight highlight;
highlight.range = usage.range;
arr.push_back(highlight.to_json());
}
}
return arr;
}
Array GDScriptTextDocument::completion(const Dictionary &p_params) {
Array arr;
@ -299,6 +289,33 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
if (data.get_type() == Variant::DICTIONARY) {
params.load(p_params["data"]);
symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == LSP::CompletionItemKind::Method || item.kind == LSP::CompletionItemKind::Function);
} else if (data.is_string()) {
String query = data;
Vector<String> param_symbols = query.split(SYMBOL_SEPARATOR, false);
if (param_symbols.size() >= 2) {
StringName class_name = param_symbols[0];
const String &member_name = param_symbols[param_symbols.size() - 1];
String inner_class_name;
if (param_symbols.size() >= 3) {
inner_class_name = param_symbols[1];
}
if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members.getptr(class_name)) {
if (const LSP::DocumentSymbol *const *member = members->getptr(member_name)) {
symbol = *member;
}
}
if (!symbol) {
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(class_name);
if (parser) {
symbol = parser->get_member_symbol(member_name, inner_class_name);
}
}
}
}
if (symbol) {

View file

@ -44,6 +44,8 @@ protected:
Ref<FileAccess> file_checker;
Array native_member_completions;
private:
Array find_symbols(const LSP::TextDocumentPositionParams &p_location, List<const LSP::DocumentSymbol *> &r_list);
void notify_client_show_symbol(const LSP::DocumentSymbol *symbol);
@ -60,7 +62,6 @@ public:
Variant nativeSymbol(const Dictionary &p_params);
Array documentSymbol(const Dictionary &p_params);
Array documentHighlight(const Dictionary &p_params);
Array completion(const Dictionary &p_params);
Dictionary resolve(const Dictionary &p_params);
Dictionary rename(const Dictionary &p_params);
@ -75,5 +76,7 @@ public:
Variant declaration(const Dictionary &p_params);
Variant signatureHelp(const Dictionary &p_params);
void initialize();
GDScriptTextDocument();
};

View file

@ -35,26 +35,25 @@
#include "gdscript_language_protocol.h"
#include "core/config/project_settings.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "core/object/script_language.h"
#include "editor/doc/doc_tools.h"
#include "editor/doc/editor_help.h"
#include "editor/editor_node.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/settings/editor_settings.h"
#include "scene/resources/packed_scene.h"
void GDScriptWorkspace::_bind_methods() {
ClassDB::bind_method(D_METHOD("apply_new_signal", "obj", "function", "args"), &GDScriptWorkspace::apply_new_signal);
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path);
ClassDB::bind_method(D_METHOD("get_file_uri", "path"), &GDScriptWorkspace::get_file_uri);
ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);
ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("didDeleteFiles", "params"), &GDScriptWorkspace::didDeleteFiles);
ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::didDeleteFiles);
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);
ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);
ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_script);
#endif
}
@ -593,6 +592,51 @@ void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
}
void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) {
if (!efsd) {
return;
}
for (int i = 0; i < efsd->get_subdir_count(); i++) {
_get_owners(efsd->get_subdir(i), p_path, owners);
}
for (int i = 0; i < efsd->get_file_count(); i++) {
Vector<String> deps = efsd->get_file_deps(i);
bool found = false;
for (int j = 0; j < deps.size(); j++) {
if (deps[j] == p_path) {
found = true;
break;
}
}
if (!found) {
continue;
}
owners.push_back(efsd->get_file_path(i));
}
}
Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {
Node *owner_scene_node = nullptr;
List<String> owners;
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);
for (const String &owner : owners) {
NodePath owner_path = owner;
Ref<Resource> owner_res = ResourceLoader::load(String(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->instantiate();
break;
}
}
return owner_scene_node;
}
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;
@ -600,7 +644,7 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
Node *owner_scene_node = GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->get(path);
Node *owner_scene_node = _get_owner_scene_node(path);
Array stack;
Node *current = nullptr;
@ -626,6 +670,9 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
String code = parser->get_text_for_completion(p_params.position);
GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);
if (owner_scene_node) {
memdelete(owner_scene_node);
}
}
}

View file

@ -30,18 +30,22 @@
#pragma once
#include "core/error/error_macros.h"
#include "gdscript_extend_parser.h"
#include "godot_lsp.h"
#include "core/error/error_macros.h"
#include "core/variant/variant.h"
#include "editor/file_system/editor_file_system.h"
class GDScriptWorkspace : public RefCounted {
GDCLASS(GDScriptWorkspace, RefCounted);
private:
void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners);
Node *_get_owner_scene_node(String p_path);
#ifndef DISABLE_DEPRECATED
void didDeleteFiles(const Dictionary &p_params) {}
void didDeleteFiles() {}
Error parse_script(const String &p_path, const String &p_content) {
WARN_DEPRECATED;
return Error::FAILED;

View file

@ -34,15 +34,6 @@
#include "core/object/class_db.h"
#include "core/templates/list.h"
// Enable additional LSP related logging.
//#define DEBUG_LSP
#ifdef DEBUG_LSP
#define LOG_LSP(...) print_line("[ LSP -", __FILE__, ":", __LINE__, "-", __func__, "] -", ##__VA_ARGS__)
#else
#define LOG_LSP(...)
#endif
namespace LSP {
typedef String DocumentUri;
@ -1077,12 +1068,8 @@ struct CompletionItem {
dict["insertText"] = insertText;
}
if (resolved) {
if (!detail.is_empty()) {
dict["detail"] = detail;
}
if (!documentation.value.is_empty()) {
dict["documentation"] = documentation.to_json();
}
dict["detail"] = detail;
dict["documentation"] = documentation.to_json();
dict["deprecated"] = deprecated;
dict["preselect"] = preselect;
if (!sortText.is_empty()) {
@ -1298,6 +1285,48 @@ struct DocumentSymbol {
}
return markdown;
}
_FORCE_INLINE_ CompletionItem make_completion_item(bool resolved = false) const {
LSP::CompletionItem item;
item.label = name;
if (resolved) {
item.documentation = render();
}
switch (kind) {
case LSP::SymbolKind::Enum:
item.kind = LSP::CompletionItemKind::Enum;
break;
case LSP::SymbolKind::Class:
item.kind = LSP::CompletionItemKind::Class;
break;
case LSP::SymbolKind::Property:
item.kind = LSP::CompletionItemKind::Property;
break;
case LSP::SymbolKind::Method:
case LSP::SymbolKind::Function:
item.kind = LSP::CompletionItemKind::Method;
break;
case LSP::SymbolKind::Event:
item.kind = LSP::CompletionItemKind::Event;
break;
case LSP::SymbolKind::Constant:
item.kind = LSP::CompletionItemKind::Constant;
break;
case LSP::SymbolKind::Variable:
item.kind = LSP::CompletionItemKind::Variable;
break;
case LSP::SymbolKind::File:
item.kind = LSP::CompletionItemKind::File;
break;
default:
item.kind = LSP::CompletionItemKind::Text;
break;
}
return item;
}
};
struct ApplyWorkspaceEditParams {
@ -1737,7 +1766,7 @@ struct ServerCapabilities {
/**
* The server provides document highlight support.
*/
bool documentHighlightProvider = true;
bool documentHighlightProvider = false;
/**
* The server provides document symbol support.
@ -2094,26 +2123,4 @@ static String marked_documentation(const String &p_bbcode) {
}
return markdown;
}
/**
* A document highlight is a range inside a text document which deserves
* special attention. Usually a document highlight is visualized by changing
* the background color of its range.
*/
struct DocumentHighlight {
/**
* The range this highlight applies to.
*/
Range range;
_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;
dict["range"] = range.to_json();
return dict;
}
_FORCE_INLINE_ void load(const Dictionary &p_params) {
range.load(p_params["range"]);
}
};
} // namespace LSP

View file

@ -1,186 +0,0 @@
/**************************************************************************/
/* scene_cache.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "scene_cache.h"
#include "godot_lsp.h"
#include "core/io/resource_loader.h"
#include "editor/file_system/editor_file_system.h"
#include "scene/resources/packed_scene.h"
void SceneCache::_get_owner_paths(EditorFileSystemDirectory *p_dir, const String &p_script_path, LocalVector<String> &r_owner_paths) {
if (!p_dir) {
return;
}
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
_get_owner_paths(p_dir->get_subdir(i), p_script_path, r_owner_paths);
}
for (int i = 0; i < p_dir->get_file_count(); i++) {
if (p_dir->get_file_deps(i).has(p_script_path)) {
r_owner_paths.push_back(p_dir->get_file_path(i));
}
}
}
void SceneCache::_finalize_scene_load() {
ERR_FAIL_COND(current_loaded_owner.is_empty() || script_path_queue.is_empty());
Ref<PackedScene> scene_res = ResourceLoader::load_threaded_get(current_loaded_owner);
if (scene_res.is_valid()) {
cache[script_path_queue[0]] = scene_res->instantiate();
} else {
cache[script_path_queue[0]] = nullptr;
}
LOG_LSP("Scene cached for script:", script_path_queue[0]);
LOG_LSP("pending_script_queue length:", script_path_queue.size() - 1);
script_path_queue.remove_at(0);
current_loaded_owner = String();
}
void SceneCache::poll() {
if (current_loaded_owner.is_empty()) {
// No load ongoing, start the next one.
if (EditorFileSystem::get_singleton()->is_scanning() || script_path_queue.is_empty()) {
return;
}
LocalVector<String> owners;
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), script_path_queue[0], owners);
for (const String &owner : owners) {
if (ResourceLoader::load_threaded_request(owner) == Error::OK) {
current_loaded_owner = owner;
LOG_LSP("Scene load started for:", current_loaded_owner);
break;
}
}
if (current_loaded_owner.is_empty()) {
cache[script_path_queue[0]] = nullptr;
LOG_LSP("No scene found for script:", script_path_queue[0]);
script_path_queue.remove_at(0);
LOG_LSP("pending_script_queue length:", script_path_queue.size());
}
} else {
ERR_FAIL_COND(script_path_queue.is_empty());
// There is an ongoing load. Check the status.
ResourceLoader::ThreadLoadStatus status = ResourceLoader::load_threaded_get_status(current_loaded_owner);
if (status == ResourceLoader::THREAD_LOAD_IN_PROGRESS) {
return;
}
if (status == ResourceLoader::THREAD_LOAD_LOADED) {
_finalize_scene_load();
} else {
LOG_LSP("Scene load failure for:", current_loaded_owner);
cache[script_path_queue[0]] = nullptr;
script_path_queue.remove_at(0);
current_loaded_owner = String();
}
}
}
Node *SceneCache::get(const String &p_script_path) {
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
_finalize_scene_load();
} else {
script_path_queue.erase(p_script_path);
}
if (Node **entry = cache.getptr(p_script_path)) {
return *entry;
}
// Fallback to blocking load. This could happen if the open request was only recently sent.
// TODO: This could also happen when multiple clients are connected.
LocalVector<String> owners;
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), p_script_path, owners);
for (const String &owner : owners) {
Ref<PackedScene> scene = ResourceLoader::load(owner);
if (scene.is_valid()) {
Node *instance = scene->instantiate();
cache[p_script_path] = instance;
return instance;
}
}
cache[p_script_path] = nullptr;
return nullptr;
}
void SceneCache::request_load(const String &p_script_path) {
if (!cache.has(p_script_path) && !script_path_queue.has(p_script_path)) {
script_path_queue.push_back(p_script_path);
LOG_LSP("Scene load requested for:", p_script_path);
LOG_LSP("pending_script_queue length:", script_path_queue.size());
}
}
void SceneCache::unload(const String &p_script_path) {
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
script_path_queue.remove_at(0);
current_loaded_owner = String();
} else {
script_path_queue.erase(p_script_path);
}
if (!cache.has(p_script_path)) {
return;
}
memdelete_notnull(cache[p_script_path]);
cache.erase(p_script_path);
LOG_LSP("Cache cleared for path:", p_script_path);
}
void SceneCache::clear() {
if (!current_loaded_owner.is_empty()) {
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
current_loaded_owner = String();
}
script_path_queue.clear();
for (const KeyValue<String, Node *> &E : cache) {
memdelete_notnull(E.value);
}
cache.clear();
LOG_LSP("Cache cleared.");
}

View file

@ -1,65 +0,0 @@
/**************************************************************************/
/* scene_cache.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/templates/local_vector.h"
class Node;
class EditorFileSystemDirectory;
class PackedScene;
/**
* Used to load and cache scene instances for LSP autocompletion.
*
* This implementation is not thread safe.
*/
class SceneCache {
// Always contains the path to the scene which is currently loaded via the `ResourceLoader`.
// If this is not empty, `script_path_queue` must have at least one element.
String current_loaded_owner;
LocalVector<String> script_path_queue;
HashMap<String, Node *> cache;
void _get_owner_paths(EditorFileSystemDirectory *p_dir, const String &p_script_path, LocalVector<String> &r_owner_paths);
void _finalize_scene_load();
public:
void poll();
void clear();
void request_load(const String &p_script_path);
void unload(const String &p_script_path);
Node *get(const String &p_script_path);
};