1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-31 18:41:20 +00:00

LSP: Rework management of client owned files

This commit is contained in:
HolonProduction
2025-06-25 14:43:48 +02:00
parent ccf414ecb4
commit 4cc88e5175
11 changed files with 346 additions and 259 deletions

View File

@@ -36,6 +36,19 @@
#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) \
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(!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)); \
Ref<LSPeer> client = clients.get(latest_client_id); \
ERR_FAIL_COND(!client.is_valid());
GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = nullptr;
@@ -312,8 +325,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
}
#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
ERR_FAIL_COND_MSG(latest_client_id == LSP_NO_CLIENT, "GDScript LSP: Can't notify client as none was connected.");
p_client_id = latest_client_id;
}
ERR_FAIL_COND(!clients.has(p_client_id));
@@ -333,8 +345,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari
}
#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
ERR_FAIL_COND_MSG(latest_client_id == LSP_NO_CLIENT, "GDScript LSP: Can't notify client as none was connected.");
p_client_id = latest_client_id;
}
ERR_FAIL_COND(!clients.has(p_client_id));
@@ -356,6 +367,174 @@ bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const {
return bool(_EDITOR_GET("network/language_server/show_native_symbols_in_editor"));
}
ExtendGDScriptParser *GDScriptLanguageProtocol::LSPeer::parse_script(const String &p_path) {
remove_cached_parser(p_path);
String content;
const LSP::TextDocumentItem *document = managed_files.getptr(p_path);
if (document == nullptr) {
if (!p_path.has_extension("gd")) {
return nullptr;
}
Error err;
content = FileAccess::get_file_as_string(p_path, &err);
if (err != OK) {
return nullptr;
}
} else {
if (document->languageId != LSP::LanguageId::GDSCRIPT) {
return nullptr;
}
content = document->text;
}
ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser);
parser->parse(content, p_path);
if (document != nullptr) {
parse_results[p_path] = parser;
GDScriptLanguageProtocol::get_singleton()->get_workspace()->publish_diagnostics(p_path);
} else {
stale_parsers[p_path] = parser;
}
return parser;
}
void GDScriptLanguageProtocol::LSPeer::remove_cached_parser(const String &p_path) {
HashMap<String, ExtendGDScriptParser *>::Iterator cached = parse_results.find(p_path);
if (cached) {
memdelete(cached->value);
parse_results.remove(cached);
}
HashMap<String, ExtendGDScriptParser *>::Iterator stale = stale_parsers.find(p_path);
if (stale) {
memdelete(stale->value);
stale_parsers.remove(stale);
}
}
ExtendGDScriptParser *GDScriptLanguageProtocol::get_parse_result(const String &p_path) {
LSP_CLIENT_V(nullptr);
ExtendGDScriptParser **cached_parser = client->parse_results.getptr(p_path);
if (cached_parser == nullptr) {
return client->parse_script(p_path);
}
return *cached_parser;
}
void GDScriptLanguageProtocol::lsp_did_open(const Dictionary &p_params) {
LSP_CLIENT;
LSP::TextDocumentItem document;
document.load(p_params["textDocument"]);
// We keep track of non GDScript files that the client owns, but we are not interested in the content.
if (document.languageId != LSP::LanguageId::GDSCRIPT) {
document.text = "";
}
String path = get_workspace()->get_file_path(document.uri);
/// An open notification must not be sent more than once without a corresponding close notification send before.
ERR_FAIL_COND_MSG(client->managed_files.has(path), "LSP: Client is opening already opened file.");
client->managed_files[path] = document;
client->parse_script(path);
}
void GDScriptLanguageProtocol::lsp_did_change(const Dictionary &p_params) {
LSP_CLIENT;
LSP::TextDocumentIdentifier identifier;
identifier.load(p_params["textDocument"]);
String path = get_workspace()->get_file_path(identifier.uri);
LSP::TextDocumentItem *document = client->managed_files.getptr(path);
/// Before a client can change a text document it must claim ownership of its content using the textDocument/didOpen notification.
ERR_FAIL_COND_MSG(document == nullptr, "LSP: Client is changing file without opening it.");
if (document->languageId != LSP::LanguageId::GDSCRIPT) {
return;
}
Array contentChanges = p_params["contentChanges"];
if (contentChanges.is_empty()) {
return;
}
// We only support TextDocumentSyncKind::Full. So only the last full text is relevant.
LSP::TextDocumentContentChangeEvent event;
event.load(contentChanges.back());
document->text = event.text;
client->parse_script(path);
}
void GDScriptLanguageProtocol::lsp_did_close(const Dictionary &p_params) {
LSP_CLIENT;
LSP::TextDocumentIdentifier identifier;
identifier.load(p_params["textDocument"]);
String path = get_workspace()->get_file_path(identifier.uri);
bool was_opened = client->managed_files.erase(path);
client->remove_cached_parser(path);
/// 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.");
}
void GDScriptLanguageProtocol::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {
LSP_CLIENT;
String path = workspace->get_file_path(p_doc_pos.textDocument.uri);
const ExtendGDScriptParser *parser = get_parse_result(path);
if (!parser) {
return;
}
String symbol_identifier;
LSP::Range range;
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
for (const KeyValue<StringName, ClassMembers> &E : workspace->native_members) {
if (const LSP::DocumentSymbol *const *symbol = E.value.getptr(symbol_identifier)) {
r_list.push_back(*symbol);
}
}
for (const KeyValue<String, ExtendGDScriptParser *> &E : client->parse_results) {
const ExtendGDScriptParser *scr = E.value;
const ClassMembers &members = scr->get_members();
if (const LSP::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
r_list.push_back(*symbol);
}
for (const KeyValue<String, ClassMembers> &F : scr->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);
}
}
}
}
GDScriptLanguageProtocol::LSPeer::~LSPeer() {
while (!parse_results.is_empty()) {
remove_cached_parser(parse_results.begin()->key);
}
while (!stale_parsers.is_empty()) {
remove_cached_parser(stale_parsers.begin()->key);
}
}
// clang-format off
#define SET_DOCUMENT_METHOD(m_method) set_method(_STR(textDocument/m_method), callable_mp(text_document.ptr(), &GDScriptTextDocument::m_method))
#define SET_COMPLETION_METHOD(m_method) set_method(_STR(completionItem/m_method), callable_mp(text_document.ptr(), &GDScriptTextDocument::m_method))
@@ -392,8 +571,6 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
SET_COMPLETION_METHOD(resolve);
SET_WORKSPACE_METHOD(didDeleteFiles);
set_method("initialize", callable_mp(this, &GDScriptLanguageProtocol::initialize));
set_method("initialized", callable_mp(this, &GDScriptLanguageProtocol::initialized));
@@ -403,3 +580,6 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
#undef SET_DOCUMENT_METHOD
#undef SET_COMPLETION_METHOD
#undef SET_WORKSPACE_METHOD
#undef LSP_CLIENT
#undef LSP_CLIENT_V