LSP: Rework management of client owned files
This commit is contained in:
@@ -52,6 +52,7 @@ for name, path in env.module_list.items():
|
|||||||
|
|
||||||
# Generate header to be included in `tests/test_main.cpp` to run module-specific tests.
|
# Generate header to be included in `tests/test_main.cpp` to run module-specific tests.
|
||||||
if env["tests"]:
|
if env["tests"]:
|
||||||
|
env.Append(CPPDEFINES=["TESTS_ENABLED"])
|
||||||
env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_builders.modules_tests_builder))
|
env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_builders.modules_tests_builder))
|
||||||
|
|
||||||
# libmodules.a with only register_module_types.
|
# libmodules.a with only register_module_types.
|
||||||
|
|||||||
@@ -344,8 +344,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
|
|||||||
if (res.is_valid() && !res->get_path().is_empty()) {
|
if (res.is_valid() && !res->get_path().is_empty()) {
|
||||||
value_text = "preload(\"" + res->get_path() + "\")";
|
value_text = "preload(\"" + res->get_path() + "\")";
|
||||||
if (symbol.documentation.is_empty()) {
|
if (symbol.documentation.is_empty()) {
|
||||||
if (HashMap<String, ExtendGDScriptParser *>::Iterator S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
|
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(res->get_path());
|
||||||
symbol.documentation = S->value->class_symbol.documentation;
|
if (parser) {
|
||||||
|
symbol.documentation = parser->class_symbol.documentation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1038,18 +1039,17 @@ Dictionary ExtendGDScriptParser::generate_api() const {
|
|||||||
return api;
|
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;
|
path = p_path;
|
||||||
lines = p_code.split("\n");
|
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);
|
GDScriptAnalyzer analyzer(this);
|
||||||
|
|
||||||
if (err == OK) {
|
if (parse_result == OK) {
|
||||||
err = analyzer.analyze();
|
parse_result = analyzer.analyze();
|
||||||
}
|
}
|
||||||
update_diagnostics();
|
update_diagnostics();
|
||||||
update_symbols();
|
update_symbols();
|
||||||
update_document_links(p_code);
|
update_document_links(p_code);
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ public:
|
|||||||
_FORCE_INLINE_ const Vector<LSP::Diagnostic> &get_diagnostics() const { return diagnostics; }
|
_FORCE_INLINE_ const Vector<LSP::Diagnostic> &get_diagnostics() const { return diagnostics; }
|
||||||
_FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
|
_FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
|
||||||
_FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
|
_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;
|
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();
|
const Array &get_member_completions();
|
||||||
Dictionary generate_api() const;
|
Dictionary generate_api() const;
|
||||||
|
|
||||||
Error parse(const String &p_code, const String &p_path);
|
void parse(const String &p_code, const String &p_path);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,19 @@
|
|||||||
#include "editor/editor_log.h"
|
#include "editor/editor_log.h"
|
||||||
#include "editor/editor_node.h"
|
#include "editor/editor_node.h"
|
||||||
#include "editor/settings/editor_settings.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;
|
GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = nullptr;
|
||||||
|
|
||||||
@@ -312,8 +325,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (p_client_id == -1) {
|
if (p_client_id == -1) {
|
||||||
ERR_FAIL_COND_MSG(latest_client_id == -1,
|
ERR_FAIL_COND_MSG(latest_client_id == LSP_NO_CLIENT, "GDScript LSP: Can't notify client as none was connected.");
|
||||||
"GDScript LSP: Can't notify client as none was connected.");
|
|
||||||
p_client_id = latest_client_id;
|
p_client_id = latest_client_id;
|
||||||
}
|
}
|
||||||
ERR_FAIL_COND(!clients.has(p_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
|
#endif
|
||||||
if (p_client_id == -1) {
|
if (p_client_id == -1) {
|
||||||
ERR_FAIL_COND_MSG(latest_client_id == -1,
|
ERR_FAIL_COND_MSG(latest_client_id == LSP_NO_CLIENT, "GDScript LSP: Can't notify client as none was connected.");
|
||||||
"GDScript LSP: Can't notify client as none was connected.");
|
|
||||||
p_client_id = latest_client_id;
|
p_client_id = latest_client_id;
|
||||||
}
|
}
|
||||||
ERR_FAIL_COND(!clients.has(p_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"));
|
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
|
// 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_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))
|
#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_COMPLETION_METHOD(resolve);
|
||||||
|
|
||||||
SET_WORKSPACE_METHOD(didDeleteFiles);
|
|
||||||
|
|
||||||
set_method("initialize", callable_mp(this, &GDScriptLanguageProtocol::initialize));
|
set_method("initialize", callable_mp(this, &GDScriptLanguageProtocol::initialize));
|
||||||
set_method("initialized", callable_mp(this, &GDScriptLanguageProtocol::initialized));
|
set_method("initialized", callable_mp(this, &GDScriptLanguageProtocol::initialized));
|
||||||
|
|
||||||
@@ -403,3 +580,6 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
|
|||||||
#undef SET_DOCUMENT_METHOD
|
#undef SET_DOCUMENT_METHOD
|
||||||
#undef SET_COMPLETION_METHOD
|
#undef SET_COMPLETION_METHOD
|
||||||
#undef SET_WORKSPACE_METHOD
|
#undef SET_WORKSPACE_METHOD
|
||||||
|
|
||||||
|
#undef LSP_CLIENT
|
||||||
|
#undef LSP_CLIENT_V
|
||||||
|
|||||||
@@ -41,9 +41,15 @@
|
|||||||
#define LSP_MAX_BUFFER_SIZE 4194304
|
#define LSP_MAX_BUFFER_SIZE 4194304
|
||||||
#define LSP_MAX_CLIENTS 8
|
#define LSP_MAX_CLIENTS 8
|
||||||
|
|
||||||
|
#define LSP_NO_CLIENT -1
|
||||||
|
|
||||||
class GDScriptLanguageProtocol : public JSONRPC {
|
class GDScriptLanguageProtocol : public JSONRPC {
|
||||||
GDCLASS(GDScriptLanguageProtocol, JSONRPC)
|
GDCLASS(GDScriptLanguageProtocol, JSONRPC)
|
||||||
|
|
||||||
|
#ifdef TESTS_ENABLED
|
||||||
|
friend class TestGDScriptLanguageProtocolInitializer;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LSPeer : RefCounted {
|
struct LSPeer : RefCounted {
|
||||||
Ref<StreamPeerTCP> connection;
|
Ref<StreamPeerTCP> connection;
|
||||||
@@ -58,6 +64,24 @@ private:
|
|||||||
|
|
||||||
Error handle_data();
|
Error handle_data();
|
||||||
Error send_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 {
|
enum LSPErrorCode {
|
||||||
@@ -69,7 +93,7 @@ private:
|
|||||||
|
|
||||||
HashMap<int, Ref<LSPeer>> clients;
|
HashMap<int, Ref<LSPeer>> clients;
|
||||||
Ref<TCPServer> server;
|
Ref<TCPServer> server;
|
||||||
int latest_client_id = 0;
|
int latest_client_id = LSP_NO_CLIENT;
|
||||||
int next_client_id = 0;
|
int next_client_id = 0;
|
||||||
|
|
||||||
int next_server_id = 0;
|
int next_server_id = 0;
|
||||||
@@ -107,5 +131,27 @@ public:
|
|||||||
bool is_smart_resolve_enabled() const;
|
bool is_smart_resolve_enabled() const;
|
||||||
bool is_goto_native_symbols_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();
|
||||||
|
~GDScriptLanguageProtocol() {
|
||||||
|
clients.clear();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,29 +63,21 @@ void GDScriptTextDocument::_bind_methods() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptTextDocument::didOpen(const Variant &p_param) {
|
void GDScriptTextDocument::didOpen(const Variant &p_param) {
|
||||||
LSP::TextDocumentItem doc = load_document_item(p_param);
|
GDScriptLanguageProtocol::get_singleton()->lsp_did_open(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) {
|
void GDScriptTextDocument::didChange(const Variant &p_param) {
|
||||||
LSP::TextDocumentItem doc = load_document_item(p_param);
|
GDScriptLanguageProtocol::get_singleton()->lsp_did_change(p_param);
|
||||||
Dictionary dict = p_param;
|
}
|
||||||
Array contentChanges = dict["contentChanges"];
|
|
||||||
for (int i = 0; i < contentChanges.size(); ++i) {
|
void GDScriptTextDocument::didClose(const Variant &p_param) {
|
||||||
LSP::TextDocumentContentChangeEvent evt;
|
GDScriptLanguageProtocol::get_singleton()->lsp_did_close(p_param);
|
||||||
evt.load(contentChanges[i]);
|
|
||||||
doc.text = evt.text;
|
|
||||||
}
|
|
||||||
sync_script_content(doc.uri, doc.text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptTextDocument::willSaveWaitUntil(const Variant &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);
|
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
|
||||||
Ref<Script> scr = ResourceLoader::load(path);
|
Ref<Script> scr = ResourceLoader::load(path);
|
||||||
@@ -95,12 +87,11 @@ void GDScriptTextDocument::willSaveWaitUntil(const Variant &p_param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptTextDocument::didSave(const Variant &p_param) {
|
void GDScriptTextDocument::didSave(const Variant &p_param) {
|
||||||
LSP::TextDocumentItem doc = load_document_item(p_param);
|
|
||||||
Dictionary dict = p_param;
|
Dictionary dict = p_param;
|
||||||
|
LSP::TextDocumentIdentifier doc;
|
||||||
|
doc.load(dict["textDocument"]);
|
||||||
String text = dict["text"];
|
String text = dict["text"];
|
||||||
|
|
||||||
sync_script_content(doc.uri, text);
|
|
||||||
|
|
||||||
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
|
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
|
||||||
Ref<GDScript> scr = ResourceLoader::load(path);
|
Ref<GDScript> scr = ResourceLoader::load(path);
|
||||||
if (scr.is_valid() && (scr->load_source_code(path) == OK)) {
|
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());
|
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) {
|
void GDScriptTextDocument::notify_client_show_symbol(const LSP::DocumentSymbol *symbol) {
|
||||||
ERR_FAIL_NULL(symbol);
|
ERR_FAIL_NULL(symbol);
|
||||||
GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true));
|
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 uri = params["uri"];
|
||||||
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri);
|
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri);
|
||||||
Array arr;
|
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));
|
arr.push_back(symbol.to_json(true));
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
@@ -324,8 +310,9 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
if (HashMap<String, ExtendGDScriptParser *>::ConstIterator E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(class_name)) {
|
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(class_name);
|
||||||
symbol = E->value->get_member_symbol(member_name, inner_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;
|
Dictionary ret;
|
||||||
Array contents;
|
Array contents;
|
||||||
List<const LSP::DocumentSymbol *> list;
|
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) {
|
for (const LSP::DocumentSymbol *&E : list) {
|
||||||
if (const LSP::DocumentSymbol *s = E) {
|
if (const LSP::DocumentSymbol *s = E) {
|
||||||
contents.push_back(s->render().value);
|
contents.push_back(s->render().value);
|
||||||
@@ -473,11 +460,6 @@ GDScriptTextDocument::GDScriptTextDocument() {
|
|||||||
file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
|
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) {
|
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);
|
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);
|
r_list.push_back(symbol);
|
||||||
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
|
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
|
||||||
List<const LSP::DocumentSymbol *> list;
|
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) {
|
for (const LSP::DocumentSymbol *&E : list) {
|
||||||
if (const LSP::DocumentSymbol *s = E) {
|
if (const LSP::DocumentSymbol *s = E) {
|
||||||
if (!s->uri.is_empty()) {
|
if (!s->uri.is_empty()) {
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Array find_symbols(const LSP::TextDocumentPositionParams &p_location, List<const LSP::DocumentSymbol *> &r_list);
|
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);
|
void notify_client_show_symbol(const LSP::DocumentSymbol *symbol);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -59,7 +58,6 @@ public:
|
|||||||
void didSave(const Variant &p_param);
|
void didSave(const Variant &p_param);
|
||||||
|
|
||||||
void reload_script(Ref<GDScript> p_to_reload_script);
|
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);
|
void show_native_symbol_in_editor(const String &p_symbol_id);
|
||||||
|
|
||||||
Variant nativeSymbol(const Dictionary &p_params);
|
Variant nativeSymbol(const Dictionary &p_params);
|
||||||
|
|||||||
@@ -45,13 +45,16 @@
|
|||||||
|
|
||||||
void GDScriptWorkspace::_bind_methods() {
|
void GDScriptWorkspace::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
|
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_path", "uri"), &GDScriptWorkspace::get_file_path);
|
||||||
ClassDB::bind_method(D_METHOD("get_file_uri", "path"), &GDScriptWorkspace::get_file_uri);
|
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("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);
|
||||||
ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
|
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) {
|
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());
|
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 {
|
const LSP::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const {
|
||||||
StringName class_name = p_class;
|
StringName class_name = p_class;
|
||||||
StringName empty;
|
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 {
|
const LSP::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const {
|
||||||
HashMap<String, ExtendGDScriptParser *>::ConstIterator S = scripts.find(p_path);
|
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(p_path);
|
||||||
if (S) {
|
if (parser) {
|
||||||
return &(S->value->get_symbols());
|
return &(parser->get_symbols());
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -219,18 +191,13 @@ void GDScriptWorkspace::reload_all_workspace_scripts() {
|
|||||||
List<String> paths;
|
List<String> paths;
|
||||||
list_script_files("res://", paths);
|
list_script_files("res://", paths);
|
||||||
for (const String &path : paths) {
|
for (const String &path : paths) {
|
||||||
Error err;
|
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
|
||||||
String content = FileAccess::get_file_as_string(path, &err);
|
if (parser == nullptr || parser->parse_result != OK) {
|
||||||
ERR_CONTINUE(err != OK);
|
String err_msg = "LSP: Failed to parse script: " + path;
|
||||||
err = parse_script(path, content);
|
if (parser) {
|
||||||
|
err_msg += "\n" + parser->get_errors().front()->get().message;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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())
|
#define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges())
|
||||||
|
|
||||||
Error GDScriptWorkspace::initialize() {
|
Error GDScriptWorkspace::initialize() {
|
||||||
@@ -427,11 +370,6 @@ Error GDScriptWorkspace::initialize() {
|
|||||||
}
|
}
|
||||||
native_members.insert(E.key, members);
|
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();
|
EditorNode *editor_node = EditorNode::get_singleton();
|
||||||
@@ -440,29 +378,6 @@ Error GDScriptWorkspace::initialize() {
|
|||||||
return OK;
|
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) {
|
static bool is_valid_rename_target(const LSP::DocumentSymbol *p_symbol) {
|
||||||
// Must be valid symbol.
|
// Must be valid symbol.
|
||||||
if (!p_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);
|
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);
|
||||||
// We only care about the range.
|
if (parser) {
|
||||||
_ALLOW_DISCARD_ parser->get_identifier_under_position(p_doc_pos.position, r_range);
|
_ALLOW_DISCARD_ parser->get_identifier_under_position(p_doc_pos.position, r_range);
|
||||||
r_symbol = *reference_symbol;
|
r_symbol = *reference_symbol;
|
||||||
return true;
|
return true;
|
||||||
@@ -519,7 +434,8 @@ Vector<LSP::Location> GDScriptWorkspace::find_usages_in_file(const LSP::Document
|
|||||||
Vector<LSP::Location> usages;
|
Vector<LSP::Location> usages;
|
||||||
|
|
||||||
String identifier = p_symbol.name;
|
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();
|
const PackedStringArray &content = parser->get_lines();
|
||||||
for (int i = 0; i < content.size(); ++i) {
|
for (int i = 0; i < content.size(); ++i) {
|
||||||
String line = content[i];
|
String line = content[i];
|
||||||
@@ -570,15 +486,6 @@ Vector<LSP::Location> GDScriptWorkspace::find_all_usages(const LSP::DocumentSymb
|
|||||||
return usages;
|
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) {
|
String GDScriptWorkspace::get_file_path(const String &p_uri) {
|
||||||
int port;
|
int port;
|
||||||
String scheme;
|
String scheme;
|
||||||
@@ -671,9 +578,10 @@ String GDScriptWorkspace::get_file_uri(const String &p_path) const {
|
|||||||
void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
|
void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
|
||||||
Dictionary params;
|
Dictionary params;
|
||||||
Array errors;
|
Array errors;
|
||||||
HashMap<String, ExtendGDScriptParser *>::ConstIterator ele = parse_results.find(p_path);
|
|
||||||
if (ele) {
|
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(p_path);
|
||||||
const Vector<LSP::Diagnostic> &list = ele->value->get_diagnostics();
|
if (parser) {
|
||||||
|
const Vector<LSP::Diagnostic> &list = parser->get_diagnostics();
|
||||||
errors.resize(list.size());
|
errors.resize(list.size());
|
||||||
for (int i = 0; i < list.size(); ++i) {
|
for (int i = 0; i < list.size(); ++i) {
|
||||||
errors[i] = list[i].to_json();
|
errors[i] = list[i].to_json();
|
||||||
@@ -734,7 +642,8 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
|
|||||||
String call_hint;
|
String call_hint;
|
||||||
bool forced = false;
|
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);
|
Node *owner_scene_node = _get_owner_scene_node(path);
|
||||||
|
|
||||||
Array stack;
|
Array stack;
|
||||||
@@ -771,7 +680,9 @@ const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocu
|
|||||||
const LSP::DocumentSymbol *symbol = nullptr;
|
const LSP::DocumentSymbol *symbol = nullptr;
|
||||||
|
|
||||||
String path = get_file_path(p_doc_pos.textDocument.uri);
|
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;
|
String symbol_identifier = p_symbol_name;
|
||||||
if (symbol_identifier.get_slice_count("(") > 0) {
|
if (symbol_identifier.get_slice_count("(") > 0) {
|
||||||
symbol_identifier = symbol_identifier.get_slicec('(', 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;
|
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);
|
symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier);
|
||||||
|
|
||||||
if (symbol) {
|
if (symbol) {
|
||||||
@@ -836,37 +748,6 @@ const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocu
|
|||||||
return symbol;
|
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) {
|
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)) {
|
if (HashMap<StringName, LSP::DocumentSymbol>::Iterator E = native_symbols.find(p_params.native_class)) {
|
||||||
const LSP::DocumentSymbol &symbol = E->value;
|
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) {
|
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();
|
const List<LSP::DocumentLink> &links = parser->get_document_links();
|
||||||
for (const LSP::DocumentLink &E : links) {
|
for (const LSP::DocumentLink &E : links) {
|
||||||
r_list.push_back(E);
|
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 GDScriptWorkspace::generate_script_api(const String &p_path) {
|
||||||
Dictionary api;
|
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();
|
api = parser->generate_api();
|
||||||
}
|
}
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::SignatureHelp &r_signature) {
|
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;
|
LSP::TextDocumentPositionParams text_pos;
|
||||||
text_pos.textDocument = p_doc_pos.textDocument;
|
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)) {
|
if (const LSP::DocumentSymbol *symbol = resolve_symbol(text_pos)) {
|
||||||
symbols.push_back(symbol);
|
symbols.push_back(symbol);
|
||||||
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
|
} 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) {
|
for (const LSP::DocumentSymbol *const &symbol : symbols) {
|
||||||
@@ -942,18 +827,4 @@ Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams
|
|||||||
|
|
||||||
GDScriptWorkspace::GDScriptWorkspace() {}
|
GDScriptWorkspace::GDScriptWorkspace() {}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../gdscript_parser.h"
|
#include "core/error/error_macros.h"
|
||||||
#include "gdscript_extend_parser.h"
|
#include "gdscript_extend_parser.h"
|
||||||
#include "godot_lsp.h"
|
#include "godot_lsp.h"
|
||||||
|
|
||||||
@@ -44,9 +44,20 @@ private:
|
|||||||
void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners);
|
void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners);
|
||||||
Node *_get_owner_scene_node(String p_path);
|
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:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
void remove_cache_parser(const String &p_path);
|
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
HashMap<StringName, LSP::DocumentSymbol> native_symbols;
|
HashMap<StringName, LSP::DocumentSymbol> native_symbols;
|
||||||
|
|
||||||
@@ -60,9 +71,6 @@ protected:
|
|||||||
|
|
||||||
void reload_all_workspace_scripts();
|
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 list_script_files(const String &p_root_dir, List<String> &r_files);
|
||||||
|
|
||||||
void apply_new_signal(Object *obj, String function, PackedStringArray args);
|
void apply_new_signal(Object *obj, String function, PackedStringArray args);
|
||||||
@@ -71,16 +79,11 @@ public:
|
|||||||
String root;
|
String root;
|
||||||
String root_uri;
|
String root_uri;
|
||||||
|
|
||||||
HashMap<String, ExtendGDScriptParser *> scripts;
|
|
||||||
HashMap<String, ExtendGDScriptParser *> parse_results;
|
|
||||||
HashMap<StringName, ClassMembers> native_members;
|
HashMap<StringName, ClassMembers> native_members;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Error initialize();
|
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_path(const String &p_uri);
|
||||||
String get_file_uri(const String &p_path) const;
|
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);
|
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);
|
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);
|
const LSP::DocumentSymbol *resolve_native_symbol(const LSP::NativeSymbolInspectParams &p_params);
|
||||||
void resolve_document_links(const String &p_uri, List<LSP::DocumentLink> &r_list);
|
void resolve_document_links(const String &p_uri, List<LSP::DocumentLink> &r_list);
|
||||||
Dictionary generate_script_api(const String &p_path);
|
Dictionary generate_script_api(const String &p_path);
|
||||||
Error resolve_signature(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::SignatureHelp &r_signature);
|
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);
|
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);
|
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);
|
Vector<LSP::Location> find_usages_in_file(const LSP::DocumentSymbol &p_symbol, const String &p_file_path);
|
||||||
|
|||||||
@@ -663,6 +663,11 @@ struct DocumentOnTypeFormattingOptions {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class LanguageId {
|
||||||
|
GDSCRIPT,
|
||||||
|
OTHER,
|
||||||
|
};
|
||||||
|
|
||||||
struct TextDocumentItem {
|
struct TextDocumentItem {
|
||||||
/**
|
/**
|
||||||
* The text document's URI.
|
* The text document's URI.
|
||||||
@@ -672,7 +677,7 @@ struct TextDocumentItem {
|
|||||||
/**
|
/**
|
||||||
* The text document's language identifier.
|
* The text document's language identifier.
|
||||||
*/
|
*/
|
||||||
String languageId;
|
LanguageId languageId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version number of this document (it will increase after each
|
* The version number of this document (it will increase after each
|
||||||
@@ -687,18 +692,17 @@ struct TextDocumentItem {
|
|||||||
|
|
||||||
void load(const Dictionary &p_dict) {
|
void load(const Dictionary &p_dict) {
|
||||||
uri = p_dict["uri"];
|
uri = p_dict["uri"];
|
||||||
languageId = p_dict.get("languageId", "");
|
version = p_dict["version"];
|
||||||
version = p_dict.get("version", 0);
|
text = p_dict["text"];
|
||||||
text = p_dict.get("text", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary to_json() const {
|
// Clients should use "gdscript" as language id, but we can't enforce it. The Rider integration
|
||||||
Dictionary dict;
|
// in particular uses "gd" at the time of writing. We normalize the id to make it easier to work with.
|
||||||
dict["uri"] = uri;
|
String rawLanguageId = p_dict["languageId"];
|
||||||
dict["languageId"] = languageId;
|
if (rawLanguageId == "gdscript" || rawLanguageId == "gd") {
|
||||||
dict["version"] = version;
|
languageId = LanguageId::GDSCRIPT;
|
||||||
dict["text"] = text;
|
} else {
|
||||||
return dict;
|
languageId = LanguageId::OTHER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1702,16 +1706,8 @@ struct FileOperations {
|
|||||||
* Workspace specific server capabilities
|
* Workspace specific server capabilities
|
||||||
*/
|
*/
|
||||||
struct Workspace {
|
struct Workspace {
|
||||||
/**
|
|
||||||
* The server is interested in file notifications/requests.
|
|
||||||
*/
|
|
||||||
FileOperations fileOperations;
|
|
||||||
|
|
||||||
Dictionary to_json() const {
|
Dictionary to_json() const {
|
||||||
Dictionary dict;
|
Dictionary dict;
|
||||||
|
|
||||||
dict["fileOperations"] = fileOperations.to_json();
|
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,6 +54,17 @@
|
|||||||
|
|
||||||
#include "thirdparty/doctest/doctest.h"
|
#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 <>
|
template <>
|
||||||
struct doctest::StringMaker<LSP::Position> {
|
struct doctest::StringMaker<LSP::Position> {
|
||||||
static doctest::String convert(const LSP::Position &p_val) {
|
static doctest::String convert(const LSP::Position &p_val) {
|
||||||
@@ -96,6 +107,7 @@ GDScriptLanguageProtocol *initialize(const String &p_root) {
|
|||||||
init_language(absolute_root);
|
init_language(absolute_root);
|
||||||
|
|
||||||
GDScriptLanguageProtocol *proto = memnew(GDScriptLanguageProtocol);
|
GDScriptLanguageProtocol *proto = memnew(GDScriptLanguageProtocol);
|
||||||
|
TestGDScriptLanguageProtocolInitializer::setup_client();
|
||||||
|
|
||||||
Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
|
Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace();
|
||||||
workspace->root = absolute_root;
|
workspace->root = absolute_root;
|
||||||
@@ -502,8 +514,7 @@ func f():
|
|||||||
|
|
||||||
for (const String &path : paths) {
|
for (const String &path : paths) {
|
||||||
assert_no_errors_in(path);
|
assert_no_errors_in(path);
|
||||||
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_local_script(path);
|
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
|
||||||
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_results[path];
|
|
||||||
REQUIRE(parser);
|
REQUIRE(parser);
|
||||||
LSP::DocumentSymbol cls = parser->get_symbols();
|
LSP::DocumentSymbol cls = parser->get_symbols();
|
||||||
|
|
||||||
@@ -515,8 +526,7 @@ func f():
|
|||||||
SUBCASE("Documentation is correctly set") {
|
SUBCASE("Documentation is correctly set") {
|
||||||
String path = "res://lsp/doc_comments.gd";
|
String path = "res://lsp/doc_comments.gd";
|
||||||
assert_no_errors_in(path);
|
assert_no_errors_in(path);
|
||||||
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_local_script(path);
|
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
|
||||||
ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_results[path];
|
|
||||||
REQUIRE(parser);
|
REQUIRE(parser);
|
||||||
LSP::DocumentSymbol cls = parser->get_symbols();
|
LSP::DocumentSymbol cls = parser->get_symbols();
|
||||||
REQUIRE(cls.documentation.contains("brief"));
|
REQUIRE(cls.documentation.contains("brief"));
|
||||||
|
|||||||
Reference in New Issue
Block a user