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

Merge pull request #105236 from HolonProduction/lsp-sync

LSP: Rework management of client owned files
This commit is contained in:
Thaddeus Crews
2025-12-16 20:32:57 -06:00
10 changed files with 345 additions and 259 deletions

View File

@@ -344,8 +344,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
if (res.is_valid() && !res->get_path().is_empty()) {
value_text = "preload(\"" + res->get_path() + "\")";
if (symbol.documentation.is_empty()) {
if (HashMap<String, ExtendGDScriptParser *>::Iterator S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
symbol.documentation = S->value->class_symbol.documentation;
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(res->get_path());
if (parser) {
symbol.documentation = parser->class_symbol.documentation;
}
}
} else {
@@ -1047,18 +1048,17 @@ Dictionary ExtendGDScriptParser::generate_api() const {
return api;
}
Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
void ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
path = p_path;
lines = p_code.split("\n");
Error err = GDScriptParser::parse(p_code, p_path, false);
parse_result = GDScriptParser::parse(p_code, p_path, false);
GDScriptAnalyzer analyzer(this);
if (err == OK) {
err = analyzer.analyze();
if (parse_result == OK) {
parse_result = analyzer.analyze();
}
update_diagnostics();
update_symbols();
update_document_links(p_code);
return err;
}

View File

@@ -143,6 +143,7 @@ public:
_FORCE_INLINE_ const Vector<LSP::Diagnostic> &get_diagnostics() const { return diagnostics; }
_FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
_FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
Error parse_result;
Error get_left_function_call(const LSP::Position &p_position, LSP::Position &r_func_pos, int &r_arg_index) const;
@@ -166,5 +167,5 @@ public:
const Array &get_member_completions();
Dictionary generate_api() const;
Error parse(const String &p_code, const String &p_path);
void parse(const String &p_code, const String &p_path);
};

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

View File

@@ -41,9 +41,15 @@
#define LSP_MAX_BUFFER_SIZE 4194304
#define LSP_MAX_CLIENTS 8
#define LSP_NO_CLIENT -1
class GDScriptLanguageProtocol : public JSONRPC {
GDCLASS(GDScriptLanguageProtocol, JSONRPC)
#ifdef TESTS_ENABLED
friend class TestGDScriptLanguageProtocolInitializer;
#endif
private:
struct LSPeer : RefCounted {
Ref<StreamPeerTCP> connection;
@@ -58,6 +64,24 @@ private:
Error handle_data();
Error send_data();
/**
* Tracks all files that the client claimed, however for files deemed not relevant
* to the server the `text` might not be persisted.
*/
HashMap<String, LSP::TextDocumentItem> managed_files;
HashMap<String, ExtendGDScriptParser *> parse_results;
void remove_cached_parser(const String &p_path);
ExtendGDScriptParser *parse_script(const String &p_path);
~LSPeer();
private:
// We can't cache parsers for scripts not managed by the editor since we have
// no way to invalidate the cache. We still need to keep track of those parsers
// to clean them up properly.
HashMap<String, ExtendGDScriptParser *> stale_parsers;
};
enum LSPErrorCode {
@@ -69,7 +93,7 @@ private:
HashMap<int, Ref<LSPeer>> clients;
Ref<TCPServer> server;
int latest_client_id = 0;
int latest_client_id = LSP_NO_CLIENT;
int next_client_id = 0;
int next_server_id = 0;
@@ -107,5 +131,27 @@ public:
bool is_smart_resolve_enabled() const;
bool is_goto_native_symbols_enabled() const;
// Text Document Synchronization
void lsp_did_open(const Dictionary &p_params);
void lsp_did_change(const Dictionary &p_params);
void lsp_did_close(const Dictionary &p_params);
/**
* Returns a list of symbols that might be related to the document position.
*
* The result fulfills no semantic guarantees, nor is it guaranteed to be complete.
* Should only be used for "smart resolve".
*/
void resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list);
/**
* Returns parse results for the given path, using the cache if available.
* If no such file exists, or the file is not a GDScript file a `nullptr` is returned.
*/
ExtendGDScriptParser *get_parse_result(const String &p_path);
GDScriptLanguageProtocol();
~GDScriptLanguageProtocol() {
clients.clear();
}
};

View File

@@ -63,29 +63,21 @@ void GDScriptTextDocument::_bind_methods() {
}
void GDScriptTextDocument::didOpen(const Variant &p_param) {
LSP::TextDocumentItem doc = load_document_item(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.
GDScriptLanguageProtocol::get_singleton()->lsp_did_open(p_param);
}
void GDScriptTextDocument::didChange(const Variant &p_param) {
LSP::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
Array contentChanges = dict["contentChanges"];
for (int i = 0; i < contentChanges.size(); ++i) {
LSP::TextDocumentContentChangeEvent evt;
evt.load(contentChanges[i]);
doc.text = evt.text;
}
sync_script_content(doc.uri, doc.text);
GDScriptLanguageProtocol::get_singleton()->lsp_did_change(p_param);
}
void GDScriptTextDocument::didClose(const Variant &p_param) {
GDScriptLanguageProtocol::get_singleton()->lsp_did_close(p_param);
}
void GDScriptTextDocument::willSaveWaitUntil(const Variant &p_param) {
LSP::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
LSP::TextDocumentIdentifier doc;
doc.load(dict["textDocument"]);
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
Ref<Script> scr = ResourceLoader::load(path);
@@ -95,12 +87,11 @@ void GDScriptTextDocument::willSaveWaitUntil(const Variant &p_param) {
}
void GDScriptTextDocument::didSave(const Variant &p_param) {
LSP::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
LSP::TextDocumentIdentifier doc;
doc.load(dict["textDocument"]);
String text = dict["text"];
sync_script_content(doc.uri, text);
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
Ref<GDScript> scr = ResourceLoader::load(path);
if (scr.is_valid() && (scr->load_source_code(path) == OK)) {
@@ -126,13 +117,6 @@ void GDScriptTextDocument::reload_script(Ref<GDScript> p_to_reload_script) {
ScriptEditor::get_singleton()->trigger_live_script_reload(p_to_reload_script->get_path());
}
LSP::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
LSP::TextDocumentItem doc;
Dictionary params = p_param;
doc.load(params["textDocument"]);
return doc;
}
void GDScriptTextDocument::notify_client_show_symbol(const LSP::DocumentSymbol *symbol) {
ERR_FAIL_NULL(symbol);
GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true));
@@ -172,8 +156,10 @@ 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 (HashMap<String, ExtendGDScriptParser *>::ConstIterator parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) {
LSP::DocumentSymbol symbol = parser->value->get_symbols();
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
LSP::DocumentSymbol symbol = parser->get_symbols();
arr.push_back(symbol.to_json(true));
}
return arr;
@@ -324,8 +310,9 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
}
if (!symbol) {
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);
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(class_name);
if (parser) {
symbol = parser->get_member_symbol(member_name, inner_class_name);
}
}
}
@@ -396,7 +383,7 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
Dictionary ret;
Array contents;
List<const LSP::DocumentSymbol *> list;
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list);
GDScriptLanguageProtocol::get_singleton()->resolve_related_symbols(params, list);
for (const LSP::DocumentSymbol *&E : list) {
if (const LSP::DocumentSymbol *s = E) {
contents.push_back(s->render().value);
@@ -473,11 +460,6 @@ GDScriptTextDocument::GDScriptTextDocument() {
file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
}
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);
}
void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
callable_mp(ScriptEditor::get_singleton(), &ScriptEditor::goto_help).call_deferred(p_symbol_id);
@@ -500,7 +482,7 @@ Array GDScriptTextDocument::find_symbols(const LSP::TextDocumentPositionParams &
r_list.push_back(symbol);
} 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);
GDScriptLanguageProtocol::get_singleton()->resolve_related_symbols(p_location, list);
for (const LSP::DocumentSymbol *&E : list) {
if (const LSP::DocumentSymbol *s = E) {
if (!s->uri.is_empty()) {

View File

@@ -48,7 +48,6 @@ protected:
private:
Array find_symbols(const LSP::TextDocumentPositionParams &p_location, List<const LSP::DocumentSymbol *> &r_list);
LSP::TextDocumentItem load_document_item(const Variant &p_param);
void notify_client_show_symbol(const LSP::DocumentSymbol *symbol);
public:
@@ -59,7 +58,6 @@ public:
void didSave(const Variant &p_param);
void reload_script(Ref<GDScript> p_to_reload_script);
void sync_script_content(const String &p_path, const String &p_content);
void show_native_symbol_in_editor(const String &p_symbol_id);
Variant nativeSymbol(const Dictionary &p_params);

View File

@@ -45,13 +45,16 @@
void GDScriptWorkspace::_bind_methods() {
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
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("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"), &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_script);
#endif
}
void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) {
@@ -106,37 +109,6 @@ void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStr
GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json());
}
void GDScriptWorkspace::didDeleteFiles(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) {
HashMap<String, ExtendGDScriptParser *>::Iterator parser = parse_results.find(p_path);
HashMap<String, ExtendGDScriptParser *>::Iterator scr = scripts.find(p_path);
if (parser && scr) {
if (scr->value && scr->value == parser->value) {
memdelete(scr->value);
} else {
memdelete(scr->value);
memdelete(parser->value);
}
parse_results.erase(p_path);
scripts.erase(p_path);
} else if (parser) {
memdelete(parser->value);
parse_results.erase(p_path);
} else if (scr) {
memdelete(scr->value);
scripts.erase(p_path);
}
}
const LSP::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const {
StringName class_name = p_class;
StringName empty;
@@ -168,9 +140,9 @@ const LSP::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_
}
const LSP::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const {
HashMap<String, ExtendGDScriptParser *>::ConstIterator S = scripts.find(p_path);
if (S) {
return &(S->value->get_symbols());
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(p_path);
if (parser) {
return &(parser->get_symbols());
}
return nullptr;
}
@@ -219,18 +191,13 @@ void GDScriptWorkspace::reload_all_workspace_scripts() {
List<String> paths;
list_script_files("res://", paths);
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) {
HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(path);
String err_msg = "Failed parse script " + path;
if (S) {
err_msg += "\n" + S->value->get_errors().front()->get().message;
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser == nullptr || parser->parse_result != OK) {
String err_msg = "LSP: Failed to parse script: " + path;
if (parser) {
err_msg += "\n" + parser->get_errors().front()->get().message;
}
ERR_CONTINUE_MSG(err != OK, err_msg);
ERR_PRINT(err_msg);
}
}
}
@@ -260,30 +227,6 @@ void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String>
}
}
ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &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->value;
}
return nullptr;
}
ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &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->value;
}
return nullptr;
}
#define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges())
Error GDScriptWorkspace::initialize() {
@@ -427,11 +370,6 @@ Error GDScriptWorkspace::initialize() {
}
native_members.insert(E.key, members);
}
// Cache member completions.
for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) {
S.value->get_member_completions();
}
}
EditorNode *editor_node = EditorNode::get_singleton();
@@ -440,29 +378,6 @@ Error GDScriptWorkspace::initialize() {
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);
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);
parse_results[p_path] = parser;
scripts[p_path] = parser;
} else {
if (last_parser && last_script && last_parser->value != last_script->value) {
memdelete(last_parser->value);
}
parse_results[p_path] = parser;
}
publish_diagnostics(p_path);
return err;
}
static bool is_valid_rename_target(const LSP::DocumentSymbol *p_symbol) {
// Must be valid symbol.
if (!p_symbol) {
@@ -505,8 +420,8 @@ bool GDScriptWorkspace::can_rename(const LSP::TextDocumentPositionParams &p_doc_
}
String path = get_file_path(p_doc_pos.textDocument.uri);
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
// We only care about the range.
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
_ALLOW_DISCARD_ parser->get_identifier_under_position(p_doc_pos.position, r_range);
r_symbol = *reference_symbol;
return true;
@@ -519,7 +434,8 @@ Vector<LSP::Location> GDScriptWorkspace::find_usages_in_file(const LSP::Document
Vector<LSP::Location> usages;
String identifier = p_symbol.name;
if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) {
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(p_file_path);
if (parser) {
const PackedStringArray &content = parser->get_lines();
for (int i = 0; i < content.size(); ++i) {
String line = content[i];
@@ -570,15 +486,6 @@ Vector<LSP::Location> GDScriptWorkspace::find_all_usages(const LSP::DocumentSymb
return usages;
}
Error GDScriptWorkspace::parse_local_script(const String &p_path) {
Error err;
String content = FileAccess::get_file_as_string(p_path, &err);
if (err == OK) {
err = parse_script(p_path, content);
}
return err;
}
String GDScriptWorkspace::get_file_path(const String &p_uri) {
int port;
String scheme;
@@ -671,9 +578,10 @@ String GDScriptWorkspace::get_file_uri(const String &p_path) const {
void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
Dictionary params;
Array errors;
HashMap<String, ExtendGDScriptParser *>::ConstIterator ele = parse_results.find(p_path);
if (ele) {
const Vector<LSP::Diagnostic> &list = ele->value->get_diagnostics();
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(p_path);
if (parser) {
const Vector<LSP::Diagnostic> &list = parser->get_diagnostics();
errors.resize(list.size());
for (int i = 0; i < list.size(); ++i) {
errors[i] = list[i].to_json();
@@ -734,7 +642,8 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
String call_hint;
bool forced = false;
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
Node *owner_scene_node = _get_owner_scene_node(path);
Array stack;
@@ -771,7 +680,9 @@ const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocu
const LSP::DocumentSymbol *symbol = nullptr;
String path = get_file_path(p_doc_pos.textDocument.uri);
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
if (parser) {
String symbol_identifier = p_symbol_name;
if (symbol_identifier.get_slice_count("(") > 0) {
symbol_identifier = symbol_identifier.get_slicec('(', 0);
@@ -803,7 +714,8 @@ const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocu
target_script_path = ret.script_path;
}
if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
const ExtendGDScriptParser *target_parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(target_script_path);
if (target_parser) {
symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier);
if (symbol) {
@@ -836,37 +748,6 @@ const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocu
return symbol;
}
void GDScriptWorkspace::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {
String path = get_file_path(p_doc_pos.textDocument.uri);
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
String symbol_identifier;
LSP::Range range;
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
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);
}
}
for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
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);
}
}
}
}
}
const LSP::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const LSP::NativeSymbolInspectParams &p_params) {
if (HashMap<StringName, LSP::DocumentSymbol>::Iterator E = native_symbols.find(p_params.native_class)) {
const LSP::DocumentSymbol &symbol = E->value;
@@ -885,7 +766,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 ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(get_file_path(p_uri));
if (parser && parser->parse_result == Error::OK) {
const List<LSP::DocumentLink> &links = parser->get_document_links();
for (const LSP::DocumentLink &E : links) {
r_list.push_back(E);
@@ -895,14 +777,17 @@ void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<LSP::Do
Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
Dictionary api;
if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) {
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(p_path);
if (parser) {
api = parser->generate_api();
}
return api;
}
Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::SignatureHelp &r_signature) {
if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) {
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(get_file_path(p_doc_pos.textDocument.uri));
if (parser) {
LSP::TextDocumentPositionParams text_pos;
text_pos.textDocument = p_doc_pos.textDocument;
@@ -912,7 +797,7 @@ Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams
if (const LSP::DocumentSymbol *symbol = resolve_symbol(text_pos)) {
symbols.push_back(symbol);
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols);
GDScriptLanguageProtocol::get_singleton()->resolve_related_symbols(text_pos, symbols);
}
for (const LSP::DocumentSymbol *const &symbol : symbols) {
@@ -942,18 +827,4 @@ Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams
GDScriptWorkspace::GDScriptWorkspace() {}
GDScriptWorkspace::~GDScriptWorkspace() {
HashSet<String> cached_parsers;
for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) {
cached_parsers.insert(E.key);
}
for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
cached_parsers.insert(E.key);
}
for (const String &E : cached_parsers) {
remove_cache_parser(E);
}
}
GDScriptWorkspace::~GDScriptWorkspace() {}

View File

@@ -30,7 +30,7 @@
#pragma once
#include "../gdscript_parser.h"
#include "core/error/error_macros.h"
#include "gdscript_extend_parser.h"
#include "godot_lsp.h"
@@ -44,9 +44,20 @@ 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() {}
Error parse_script(const String &p_path, const String &p_content) {
WARN_DEPRECATED;
return Error::FAILED;
}
Error parse_local_script(const String &p_path) {
WARN_DEPRECATED;
return Error::FAILED;
}
#endif // DISABLE_DEPRECATED
protected:
static void _bind_methods();
void remove_cache_parser(const String &p_path);
bool initialized = false;
HashMap<StringName, LSP::DocumentSymbol> native_symbols;
@@ -60,9 +71,6 @@ protected:
void reload_all_workspace_scripts();
ExtendGDScriptParser *get_parse_successed_script(const String &p_path);
ExtendGDScriptParser *get_parse_result(const String &p_path);
void list_script_files(const String &p_root_dir, List<String> &r_files);
void apply_new_signal(Object *obj, String function, PackedStringArray args);
@@ -71,16 +79,11 @@ public:
String root;
String root_uri;
HashMap<String, ExtendGDScriptParser *> scripts;
HashMap<String, ExtendGDScriptParser *> parse_results;
HashMap<StringName, ClassMembers> native_members;
public:
Error initialize();
Error parse_script(const String &p_path, const String &p_content);
Error parse_local_script(const String &p_path);
String get_file_path(const String &p_uri);
String get_file_uri(const String &p_path) const;
@@ -88,12 +91,11 @@ public:
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_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 didDeleteFiles(const Dictionary &p_params);
Dictionary rename(const LSP::TextDocumentPositionParams &p_doc_pos, const String &new_name);
bool can_rename(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::DocumentSymbol &r_symbol, LSP::Range &r_range);
Vector<LSP::Location> find_usages_in_file(const LSP::DocumentSymbol &p_symbol, const String &p_file_path);

View File

@@ -663,6 +663,11 @@ struct DocumentOnTypeFormattingOptions {
}
};
enum class LanguageId {
GDSCRIPT,
OTHER,
};
struct TextDocumentItem {
/**
* The text document's URI.
@@ -672,7 +677,7 @@ struct TextDocumentItem {
/**
* The text document's language identifier.
*/
String languageId;
LanguageId languageId;
/**
* The version number of this document (it will increase after each
@@ -687,18 +692,17 @@ struct TextDocumentItem {
void load(const Dictionary &p_dict) {
uri = p_dict["uri"];
languageId = p_dict.get("languageId", "");
version = p_dict.get("version", 0);
text = p_dict.get("text", "");
}
version = p_dict["version"];
text = p_dict["text"];
Dictionary to_json() const {
Dictionary dict;
dict["uri"] = uri;
dict["languageId"] = languageId;
dict["version"] = version;
dict["text"] = text;
return dict;
// Clients should use "gdscript" as language id, but we can't enforce it. The Rider integration
// in particular uses "gd" at the time of writing. We normalize the id to make it easier to work with.
String rawLanguageId = p_dict["languageId"];
if (rawLanguageId == "gdscript" || rawLanguageId == "gd") {
languageId = LanguageId::GDSCRIPT;
} else {
languageId = LanguageId::OTHER;
}
}
};
@@ -1702,16 +1706,8 @@ struct FileOperations {
* 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;
}
};

View File

@@ -54,6 +54,17 @@
#include "thirdparty/doctest/doctest.h"
class TestGDScriptLanguageProtocolInitializer {
public:
static void setup_client() {
GDScriptLanguageProtocol *proto = GDScriptLanguageProtocol::get_singleton();
Ref<GDScriptLanguageProtocol::LSPeer> peer = memnew(GDScriptLanguageProtocol::LSPeer);
proto->clients.insert(proto->next_client_id, peer);
proto->latest_client_id = proto->next_client_id;
proto->next_client_id++;
}
};
template <>
struct doctest::StringMaker<LSP::Position> {
static doctest::String convert(const LSP::Position &p_val) {
@@ -96,6 +107,7 @@ GDScriptLanguageProtocol *initialize(const String &p_root) {
init_language(absolute_root);
GDScriptLanguageProtocol *proto = memnew(GDScriptLanguageProtocol);
TestGDScriptLanguageProtocolInitializer::setup_client();
Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
workspace->root = absolute_root;
@@ -502,8 +514,7 @@ func f():
for (const String &path : paths) {
assert_no_errors_in(path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_local_script(path);
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_results[path];
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
REQUIRE(parser);
LSP::DocumentSymbol cls = parser->get_symbols();
@@ -515,8 +526,7 @@ func f():
SUBCASE("Documentation is correctly set") {
String path = "res://lsp/doc_comments.gd";
assert_no_errors_in(path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_local_script(path);
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_results[path];
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
REQUIRE(parser);
LSP::DocumentSymbol cls = parser->get_symbols();
REQUIRE(cls.documentation.contains("brief"));