Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/language_server/gdscript_workspace.cpp
10278 views
1
/**************************************************************************/
2
/* gdscript_workspace.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "gdscript_workspace.h"
32
33
#include "../gdscript.h"
34
#include "../gdscript_parser.h"
35
#include "gdscript_language_protocol.h"
36
37
#include "core/config/project_settings.h"
38
#include "core/object/script_language.h"
39
#include "editor/doc/doc_tools.h"
40
#include "editor/doc/editor_help.h"
41
#include "editor/editor_node.h"
42
#include "editor/file_system/editor_file_system.h"
43
#include "editor/settings/editor_settings.h"
44
#include "scene/resources/packed_scene.h"
45
46
void GDScriptWorkspace::_bind_methods() {
47
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
48
ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::didDeleteFiles);
49
ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);
50
ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script);
51
ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path);
52
ClassDB::bind_method(D_METHOD("get_file_uri", "path"), &GDScriptWorkspace::get_file_uri);
53
ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);
54
ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
55
}
56
57
void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) {
58
Ref<Script> scr = obj->get_script();
59
60
if (scr->get_language()->get_name() != "GDScript") {
61
return;
62
}
63
64
String function_signature = "func " + function;
65
String source = scr->get_source_code();
66
67
if (source.contains(function_signature)) {
68
return;
69
}
70
71
int first_class = source.find("\nclass ");
72
int start_line = 0;
73
if (first_class != -1) {
74
start_line = source.substr(0, first_class).split("\n").size();
75
} else {
76
start_line = source.split("\n").size();
77
}
78
79
String function_body = "\n\n" + function_signature + "(";
80
for (int i = 0; i < args.size(); ++i) {
81
function_body += args[i];
82
if (i < args.size() - 1) {
83
function_body += ", ";
84
}
85
}
86
function_body += ")";
87
if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) {
88
function_body += " -> void";
89
}
90
function_body += ":\n\tpass # Replace with function body.\n";
91
92
LSP::TextEdit text_edit;
93
94
if (first_class != -1) {
95
function_body += "\n\n";
96
}
97
text_edit.range.end.line = text_edit.range.start.line = start_line;
98
99
text_edit.newText = function_body;
100
101
String uri = get_file_uri(scr->get_path());
102
103
LSP::ApplyWorkspaceEditParams params;
104
params.edit.add_edit(uri, text_edit);
105
106
GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json());
107
}
108
109
void GDScriptWorkspace::didDeleteFiles(const Dictionary &p_params) {
110
Array files = p_params["files"];
111
for (int i = 0; i < files.size(); ++i) {
112
Dictionary file = files[i];
113
String uri = file["uri"];
114
String path = get_file_path(uri);
115
parse_script(path, "");
116
}
117
}
118
119
void GDScriptWorkspace::remove_cache_parser(const String &p_path) {
120
HashMap<String, ExtendGDScriptParser *>::Iterator parser = parse_results.find(p_path);
121
HashMap<String, ExtendGDScriptParser *>::Iterator scr = scripts.find(p_path);
122
if (parser && scr) {
123
if (scr->value && scr->value == parser->value) {
124
memdelete(scr->value);
125
} else {
126
memdelete(scr->value);
127
memdelete(parser->value);
128
}
129
parse_results.erase(p_path);
130
scripts.erase(p_path);
131
} else if (parser) {
132
memdelete(parser->value);
133
parse_results.erase(p_path);
134
} else if (scr) {
135
memdelete(scr->value);
136
scripts.erase(p_path);
137
}
138
}
139
140
const LSP::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const {
141
StringName class_name = p_class;
142
StringName empty;
143
144
while (class_name != empty) {
145
if (HashMap<StringName, LSP::DocumentSymbol>::ConstIterator E = native_symbols.find(class_name)) {
146
const LSP::DocumentSymbol &class_symbol = E->value;
147
148
if (p_member.is_empty()) {
149
return &class_symbol;
150
} else {
151
for (int i = 0; i < class_symbol.children.size(); i++) {
152
const LSP::DocumentSymbol &symbol = class_symbol.children[i];
153
if (symbol.name == p_member) {
154
return &symbol;
155
}
156
}
157
}
158
}
159
// Might contain pseudo classes like @GDScript that only exist in documentation.
160
if (ClassDB::class_exists(class_name)) {
161
class_name = ClassDB::get_parent_class(class_name);
162
} else {
163
break;
164
}
165
}
166
167
return nullptr;
168
}
169
170
const LSP::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const {
171
HashMap<String, ExtendGDScriptParser *>::ConstIterator S = scripts.find(p_path);
172
if (S) {
173
return &(S->value->get_symbols());
174
}
175
return nullptr;
176
}
177
178
const LSP::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const LSP::DocumentSymbol *p_parent, const String &symbol_identifier) {
179
for (int i = 0; i < p_parent->children.size(); ++i) {
180
const LSP::DocumentSymbol *parameter_symbol = &p_parent->children[i];
181
if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) {
182
return parameter_symbol;
183
}
184
}
185
186
return nullptr;
187
}
188
189
const LSP::DocumentSymbol *GDScriptWorkspace::get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const LSP::Position p_position) {
190
// Go down and pick closest `DocumentSymbol` with `p_symbol_identifier`.
191
192
const LSP::DocumentSymbol *current = &p_parser->get_symbols();
193
const LSP::DocumentSymbol *best_match = nullptr;
194
195
while (current) {
196
if (current->name == p_symbol_identifier) {
197
if (current->selectionRange.contains(p_position)) {
198
// Exact match: pos is ON symbol decl identifier.
199
return current;
200
}
201
202
best_match = current;
203
}
204
205
const LSP::DocumentSymbol *parent = current;
206
current = nullptr;
207
for (const LSP::DocumentSymbol &child : parent->children) {
208
if (child.range.contains(p_position)) {
209
current = &child;
210
break;
211
}
212
}
213
}
214
215
return best_match;
216
}
217
218
void GDScriptWorkspace::reload_all_workspace_scripts() {
219
List<String> paths;
220
list_script_files("res://", paths);
221
for (const String &path : paths) {
222
Error err;
223
String content = FileAccess::get_file_as_string(path, &err);
224
ERR_CONTINUE(err != OK);
225
err = parse_script(path, content);
226
227
if (err != OK) {
228
HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(path);
229
String err_msg = "Failed parse script " + path;
230
if (S) {
231
err_msg += "\n" + S->value->get_errors().front()->get().message;
232
}
233
ERR_CONTINUE_MSG(err != OK, err_msg);
234
}
235
}
236
}
237
238
void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) {
239
Error err;
240
Ref<DirAccess> dir = DirAccess::open(p_root_dir, &err);
241
if (OK != err) {
242
return;
243
}
244
245
// Ignore scripts in directories with a .gdignore file.
246
if (dir->file_exists(".gdignore")) {
247
return;
248
}
249
250
dir->list_dir_begin();
251
String file_name = dir->get_next();
252
while (file_name.length()) {
253
if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./") {
254
list_script_files(p_root_dir.path_join(file_name), r_files);
255
} else if (file_name.ends_with(".gd")) {
256
String script_file = p_root_dir.path_join(file_name);
257
r_files.push_back(script_file);
258
}
259
file_name = dir->get_next();
260
}
261
}
262
263
ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) {
264
HashMap<String, ExtendGDScriptParser *>::Iterator S = scripts.find(p_path);
265
if (!S) {
266
parse_local_script(p_path);
267
S = scripts.find(p_path);
268
}
269
if (S) {
270
return S->value;
271
}
272
return nullptr;
273
}
274
275
ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) {
276
HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(p_path);
277
if (!S) {
278
parse_local_script(p_path);
279
S = parse_results.find(p_path);
280
}
281
if (S) {
282
return S->value;
283
}
284
return nullptr;
285
}
286
287
#define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges())
288
289
Error GDScriptWorkspace::initialize() {
290
if (initialized) {
291
return OK;
292
}
293
294
DocTools *doc = EditorHelp::get_doc_data();
295
for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {
296
const DocData::ClassDoc &class_data = E.value;
297
const bool is_native = !class_data.is_script_doc;
298
LSP::DocumentSymbol class_symbol;
299
String class_name = E.key;
300
class_symbol.name = class_name;
301
class_symbol.native_class = class_name;
302
class_symbol.kind = LSP::SymbolKind::Class;
303
class_symbol.detail = String("<Native> class ") + class_name;
304
if (!class_data.inherits.is_empty()) {
305
class_symbol.detail += " extends " + class_data.inherits;
306
}
307
class_symbol.documentation = HANDLE_DOC(class_data.brief_description) + "\n" + HANDLE_DOC(class_data.description);
308
309
for (int i = 0; i < class_data.constants.size(); i++) {
310
const DocData::ConstantDoc &const_data = class_data.constants[i];
311
LSP::DocumentSymbol symbol;
312
symbol.name = const_data.name;
313
symbol.native_class = class_name;
314
symbol.kind = LSP::SymbolKind::Constant;
315
symbol.detail = "const " + class_name + "." + const_data.name;
316
if (const_data.enumeration.length()) {
317
symbol.detail += ": " + const_data.enumeration;
318
}
319
symbol.detail += " = " + const_data.value;
320
symbol.documentation = HANDLE_DOC(const_data.description);
321
class_symbol.children.push_back(symbol);
322
}
323
324
for (int i = 0; i < class_data.properties.size(); i++) {
325
const DocData::PropertyDoc &data = class_data.properties[i];
326
LSP::DocumentSymbol symbol;
327
symbol.name = data.name;
328
symbol.native_class = class_name;
329
symbol.kind = LSP::SymbolKind::Property;
330
symbol.detail = "var " + class_name + "." + data.name;
331
if (data.enumeration.length()) {
332
symbol.detail += ": " + data.enumeration;
333
} else {
334
symbol.detail += ": " + data.type;
335
}
336
symbol.documentation = HANDLE_DOC(data.description);
337
class_symbol.children.push_back(symbol);
338
}
339
340
for (int i = 0; i < class_data.theme_properties.size(); i++) {
341
const DocData::ThemeItemDoc &data = class_data.theme_properties[i];
342
LSP::DocumentSymbol symbol;
343
symbol.name = data.name;
344
symbol.native_class = class_name;
345
symbol.kind = LSP::SymbolKind::Property;
346
symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type;
347
symbol.documentation = HANDLE_DOC(data.description);
348
class_symbol.children.push_back(symbol);
349
}
350
351
Vector<DocData::MethodDoc> method_likes;
352
method_likes.append_array(class_data.methods);
353
method_likes.append_array(class_data.annotations);
354
const int constructors_start_idx = method_likes.size();
355
method_likes.append_array(class_data.constructors);
356
const int operator_start_idx = method_likes.size();
357
method_likes.append_array(class_data.operators);
358
const int signal_start_idx = method_likes.size();
359
method_likes.append_array(class_data.signals);
360
361
for (int i = 0; i < method_likes.size(); i++) {
362
const DocData::MethodDoc &data = method_likes[i];
363
364
LSP::DocumentSymbol symbol;
365
symbol.name = data.name;
366
symbol.native_class = class_name;
367
368
if (i >= signal_start_idx) {
369
symbol.kind = LSP::SymbolKind::Event;
370
} else if (i >= operator_start_idx) {
371
symbol.kind = LSP::SymbolKind::Operator;
372
} else if (i >= constructors_start_idx) {
373
symbol.kind = LSP::SymbolKind::Constructor;
374
} else {
375
symbol.kind = LSP::SymbolKind::Method;
376
}
377
378
String params = "";
379
bool arg_default_value_started = false;
380
for (int j = 0; j < data.arguments.size(); j++) {
381
const DocData::ArgumentDoc &arg = data.arguments[j];
382
383
LSP::DocumentSymbol symbol_arg;
384
symbol_arg.name = arg.name;
385
symbol_arg.kind = LSP::SymbolKind::Variable;
386
symbol_arg.detail = arg.type;
387
388
if (!arg_default_value_started && !arg.default_value.is_empty()) {
389
arg_default_value_started = true;
390
}
391
String arg_str = arg.name + ": " + arg.type;
392
if (arg_default_value_started) {
393
arg_str += " = " + arg.default_value;
394
}
395
if (j < data.arguments.size() - 1) {
396
arg_str += ", ";
397
}
398
params += arg_str;
399
400
symbol.children.push_back(symbol_arg);
401
}
402
if (data.qualifiers.contains("vararg")) {
403
params += params.is_empty() ? "..." : ", ...";
404
}
405
406
String return_type = data.return_type;
407
if (return_type.is_empty()) {
408
return_type = "void";
409
}
410
symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type;
411
symbol.documentation = HANDLE_DOC(data.description);
412
class_symbol.children.push_back(symbol);
413
}
414
415
native_symbols.insert(class_name, class_symbol);
416
}
417
418
reload_all_workspace_scripts();
419
420
if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
421
for (const KeyValue<StringName, LSP::DocumentSymbol> &E : native_symbols) {
422
ClassMembers members;
423
const LSP::DocumentSymbol &class_symbol = E.value;
424
for (int i = 0; i < class_symbol.children.size(); i++) {
425
const LSP::DocumentSymbol &symbol = class_symbol.children[i];
426
members.insert(symbol.name, &symbol);
427
}
428
native_members.insert(E.key, members);
429
}
430
431
// Cache member completions.
432
for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) {
433
S.value->get_member_completions();
434
}
435
}
436
437
EditorNode *editor_node = EditorNode::get_singleton();
438
editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal));
439
440
return OK;
441
}
442
443
Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) {
444
ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser);
445
Error err = parser->parse(p_content, p_path);
446
HashMap<String, ExtendGDScriptParser *>::Iterator last_parser = parse_results.find(p_path);
447
HashMap<String, ExtendGDScriptParser *>::Iterator last_script = scripts.find(p_path);
448
449
if (err == OK) {
450
remove_cache_parser(p_path);
451
parse_results[p_path] = parser;
452
scripts[p_path] = parser;
453
454
} else {
455
if (last_parser && last_script && last_parser->value != last_script->value) {
456
memdelete(last_parser->value);
457
}
458
parse_results[p_path] = parser;
459
}
460
461
publish_diagnostics(p_path);
462
463
return err;
464
}
465
466
static bool is_valid_rename_target(const LSP::DocumentSymbol *p_symbol) {
467
// Must be valid symbol.
468
if (!p_symbol) {
469
return false;
470
}
471
472
// Cannot rename builtin.
473
if (!p_symbol->native_class.is_empty()) {
474
return false;
475
}
476
477
// Source must be available.
478
if (p_symbol->script_path.is_empty()) {
479
return false;
480
}
481
482
return true;
483
}
484
485
Dictionary GDScriptWorkspace::rename(const LSP::TextDocumentPositionParams &p_doc_pos, const String &new_name) {
486
LSP::WorkspaceEdit edit;
487
488
const LSP::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);
489
if (is_valid_rename_target(reference_symbol)) {
490
Vector<LSP::Location> usages = find_all_usages(*reference_symbol);
491
for (int i = 0; i < usages.size(); ++i) {
492
LSP::Location loc = usages[i];
493
494
edit.add_change(loc.uri, loc.range.start.line, loc.range.start.character, loc.range.end.character, new_name);
495
}
496
}
497
498
return edit.to_json();
499
}
500
501
bool GDScriptWorkspace::can_rename(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::DocumentSymbol &r_symbol, LSP::Range &r_range) {
502
const LSP::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);
503
if (!is_valid_rename_target(reference_symbol)) {
504
return false;
505
}
506
507
String path = get_file_path(p_doc_pos.textDocument.uri);
508
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
509
// We only care about the range.
510
_ALLOW_DISCARD_ parser->get_identifier_under_position(p_doc_pos.position, r_range);
511
r_symbol = *reference_symbol;
512
return true;
513
}
514
515
return false;
516
}
517
518
Vector<LSP::Location> GDScriptWorkspace::find_usages_in_file(const LSP::DocumentSymbol &p_symbol, const String &p_file_path) {
519
Vector<LSP::Location> usages;
520
521
String identifier = p_symbol.name;
522
if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) {
523
const PackedStringArray &content = parser->get_lines();
524
for (int i = 0; i < content.size(); ++i) {
525
String line = content[i];
526
527
int character = line.find(identifier);
528
while (character > -1) {
529
LSP::TextDocumentPositionParams params;
530
531
LSP::TextDocumentIdentifier text_doc;
532
text_doc.uri = get_file_uri(p_file_path);
533
534
params.textDocument = text_doc;
535
params.position.line = i;
536
params.position.character = character;
537
538
const LSP::DocumentSymbol *other_symbol = resolve_symbol(params);
539
540
if (other_symbol == &p_symbol) {
541
LSP::Location loc;
542
loc.uri = text_doc.uri;
543
loc.range.start = params.position;
544
loc.range.end.line = params.position.line;
545
loc.range.end.character = params.position.character + identifier.length();
546
usages.append(loc);
547
}
548
549
character = line.find(identifier, character + 1);
550
}
551
}
552
}
553
554
return usages;
555
}
556
557
Vector<LSP::Location> GDScriptWorkspace::find_all_usages(const LSP::DocumentSymbol &p_symbol) {
558
if (p_symbol.local) {
559
// Only search in current document.
560
return find_usages_in_file(p_symbol, p_symbol.script_path);
561
}
562
// Search in all documents.
563
List<String> paths;
564
list_script_files("res://", paths);
565
566
Vector<LSP::Location> usages;
567
for (const String &path : paths) {
568
usages.append_array(find_usages_in_file(p_symbol, path));
569
}
570
return usages;
571
}
572
573
Error GDScriptWorkspace::parse_local_script(const String &p_path) {
574
Error err;
575
String content = FileAccess::get_file_as_string(p_path, &err);
576
if (err == OK) {
577
err = parse_script(p_path, content);
578
}
579
return err;
580
}
581
582
String GDScriptWorkspace::get_file_path(const String &p_uri) {
583
int port;
584
String scheme;
585
String host;
586
String encoded_path;
587
String fragment;
588
589
// Don't use the returned error, the result isn't OK for URIs that are not valid web URLs.
590
p_uri.parse_url(scheme, host, port, encoded_path, fragment);
591
592
// TODO: Make the parsing RFC-3986 compliant.
593
ERR_FAIL_COND_V_MSG(scheme != "file" && scheme != "file:" && scheme != "file://", String(), "LSP: The language server only supports the file protocol: " + p_uri);
594
595
// Treat host like authority for now and ignore the port. It's an edge case for invalid file URI's anyway.
596
ERR_FAIL_COND_V_MSG(host != "" && host != "localhost", String(), "LSP: The language server does not support nonlocal files: " + p_uri);
597
598
// If query or fragment are present, the URI is not a valid file URI as per RFC-8089.
599
// We currently don't handle the query and it will be part of the path. However,
600
// this should not be a problem for a correct file URI.
601
ERR_FAIL_COND_V_MSG(fragment != "", String(), "LSP: Received malformed file URI: " + p_uri);
602
603
String canonical_res = ProjectSettings::get_singleton()->get_resource_path();
604
String simple_path = encoded_path.uri_file_decode().simplify_path();
605
606
// First try known paths that point to res://, to reduce file system interaction.
607
bool res_adjusted = false;
608
for (const String &res_path : absolute_res_paths) {
609
if (simple_path.begins_with(res_path)) {
610
res_adjusted = true;
611
simple_path = "res://" + simple_path.substr(res_path.size());
612
break;
613
}
614
}
615
616
// Traverse the path and compare each directory with res://
617
if (!res_adjusted) {
618
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
619
620
int offset = 0;
621
while (offset <= simple_path.length()) {
622
offset = simple_path.find_char('/', offset);
623
if (offset == -1) {
624
offset = simple_path.length();
625
}
626
627
String part = simple_path.substr(0, offset);
628
629
if (!part.is_empty()) {
630
bool is_equal = dir->is_equivalent(canonical_res, part);
631
632
if (is_equal) {
633
absolute_res_paths.insert(part);
634
res_adjusted = true;
635
simple_path = "res://" + simple_path.substr(offset + 1);
636
break;
637
}
638
}
639
640
offset += 1;
641
}
642
643
// Could not resolve the path to the project.
644
if (!res_adjusted) {
645
return simple_path;
646
}
647
}
648
649
// Resolve the file inside of the project using EditorFileSystem.
650
EditorFileSystemDirectory *editor_dir;
651
int file_idx;
652
editor_dir = EditorFileSystem::get_singleton()->find_file(simple_path, &file_idx);
653
if (editor_dir) {
654
return editor_dir->get_file_path(file_idx);
655
}
656
657
return simple_path;
658
}
659
660
String GDScriptWorkspace::get_file_uri(const String &p_path) const {
661
String path = ProjectSettings::get_singleton()->globalize_path(p_path).lstrip("/");
662
LocalVector<String> encoded_parts;
663
for (const String &part : path.split("/")) {
664
encoded_parts.push_back(part.uri_encode());
665
}
666
667
// Always return file URI's with authority part (encoding drive letters with leading slash), to maintain compat with RFC-1738 which required it.
668
return "file:///" + String("/").join(encoded_parts);
669
}
670
671
void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
672
Dictionary params;
673
Array errors;
674
HashMap<String, ExtendGDScriptParser *>::ConstIterator ele = parse_results.find(p_path);
675
if (ele) {
676
const Vector<LSP::Diagnostic> &list = ele->value->get_diagnostics();
677
errors.resize(list.size());
678
for (int i = 0; i < list.size(); ++i) {
679
errors[i] = list[i].to_json();
680
}
681
}
682
params["diagnostics"] = errors;
683
params["uri"] = get_file_uri(p_path);
684
GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
685
}
686
687
void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) {
688
if (!efsd) {
689
return;
690
}
691
692
for (int i = 0; i < efsd->get_subdir_count(); i++) {
693
_get_owners(efsd->get_subdir(i), p_path, owners);
694
}
695
696
for (int i = 0; i < efsd->get_file_count(); i++) {
697
Vector<String> deps = efsd->get_file_deps(i);
698
bool found = false;
699
for (int j = 0; j < deps.size(); j++) {
700
if (deps[j] == p_path) {
701
found = true;
702
break;
703
}
704
}
705
if (!found) {
706
continue;
707
}
708
709
owners.push_back(efsd->get_file_path(i));
710
}
711
}
712
713
Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {
714
Node *owner_scene_node = nullptr;
715
List<String> owners;
716
717
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);
718
719
for (const String &owner : owners) {
720
NodePath owner_path = owner;
721
Ref<Resource> owner_res = ResourceLoader::load(String(owner_path));
722
if (Object::cast_to<PackedScene>(owner_res.ptr())) {
723
Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res));
724
owner_scene_node = owner_packed_scene->instantiate();
725
break;
726
}
727
}
728
729
return owner_scene_node;
730
}
731
732
void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) {
733
String path = get_file_path(p_params.textDocument.uri);
734
String call_hint;
735
bool forced = false;
736
737
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
738
Node *owner_scene_node = _get_owner_scene_node(path);
739
740
Array stack;
741
Node *current = nullptr;
742
if (owner_scene_node != nullptr) {
743
stack.push_back(owner_scene_node);
744
745
while (!stack.is_empty()) {
746
current = Object::cast_to<Node>(stack.pop_back());
747
Ref<GDScript> scr = current->get_script();
748
if (scr.is_valid() && GDScript::is_canonically_equal_paths(scr->get_path(), path)) {
749
break;
750
}
751
for (int i = 0; i < current->get_child_count(); ++i) {
752
stack.push_back(current->get_child(i));
753
}
754
}
755
756
Ref<GDScript> scr = current->get_script();
757
if (scr.is_null() || !GDScript::is_canonically_equal_paths(scr->get_path(), path)) {
758
current = owner_scene_node;
759
}
760
}
761
762
String code = parser->get_text_for_completion(p_params.position);
763
GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);
764
if (owner_scene_node) {
765
memdelete(owner_scene_node);
766
}
767
}
768
}
769
770
const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) {
771
const LSP::DocumentSymbol *symbol = nullptr;
772
773
String path = get_file_path(p_doc_pos.textDocument.uri);
774
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
775
String symbol_identifier = p_symbol_name;
776
Vector<String> identifier_parts = symbol_identifier.split("(");
777
if (identifier_parts.size()) {
778
symbol_identifier = identifier_parts[0];
779
}
780
781
LSP::Position pos = p_doc_pos.position;
782
if (symbol_identifier.is_empty()) {
783
LSP::Range range;
784
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
785
pos.character = range.end.character;
786
}
787
788
if (!symbol_identifier.is_empty()) {
789
if (ScriptServer::is_global_class(symbol_identifier)) {
790
String class_path = ScriptServer::get_global_class_path(symbol_identifier);
791
symbol = get_script_symbol(class_path);
792
793
} else {
794
ScriptLanguage::LookupResult ret;
795
if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].remove_chars(" \t").contains("new(")) {
796
symbol_identifier = "_init";
797
}
798
if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {
799
if (ret.location >= 0) {
800
String target_script_path = path;
801
if (ret.script.is_valid()) {
802
target_script_path = ret.script->get_path();
803
} else if (!ret.script_path.is_empty()) {
804
target_script_path = ret.script_path;
805
}
806
807
if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
808
symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier);
809
810
if (symbol) {
811
switch (symbol->kind) {
812
case LSP::SymbolKind::Function: {
813
if (symbol->name != symbol_identifier) {
814
symbol = get_parameter_symbol(symbol, symbol_identifier);
815
}
816
} break;
817
}
818
}
819
}
820
} else {
821
String member = ret.class_member;
822
if (member.is_empty() && symbol_identifier != ret.class_name) {
823
member = symbol_identifier;
824
}
825
symbol = get_native_symbol(ret.class_name, member);
826
}
827
} else {
828
symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position);
829
if (!symbol) {
830
symbol = parser->get_member_symbol(symbol_identifier);
831
}
832
}
833
}
834
}
835
}
836
837
return symbol;
838
}
839
840
void GDScriptWorkspace::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {
841
String path = get_file_path(p_doc_pos.textDocument.uri);
842
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
843
String symbol_identifier;
844
LSP::Range range;
845
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
846
847
for (const KeyValue<StringName, ClassMembers> &E : native_members) {
848
const ClassMembers &members = native_members.get(E.key);
849
if (const LSP::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
850
r_list.push_back(*symbol);
851
}
852
}
853
854
for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
855
const ExtendGDScriptParser *scr = E.value;
856
const ClassMembers &members = scr->get_members();
857
if (const LSP::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
858
r_list.push_back(*symbol);
859
}
860
861
for (const KeyValue<String, ClassMembers> &F : scr->get_inner_classes()) {
862
const ClassMembers *inner_class = &F.value;
863
if (const LSP::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) {
864
r_list.push_back(*symbol);
865
}
866
}
867
}
868
}
869
}
870
871
const LSP::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const LSP::NativeSymbolInspectParams &p_params) {
872
if (HashMap<StringName, LSP::DocumentSymbol>::Iterator E = native_symbols.find(p_params.native_class)) {
873
const LSP::DocumentSymbol &symbol = E->value;
874
if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) {
875
return &symbol;
876
}
877
878
for (int i = 0; i < symbol.children.size(); ++i) {
879
if (symbol.children[i].name == p_params.symbol_name) {
880
return &(symbol.children[i]);
881
}
882
}
883
}
884
885
return nullptr;
886
}
887
888
void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<LSP::DocumentLink> &r_list) {
889
if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) {
890
const List<LSP::DocumentLink> &links = parser->get_document_links();
891
for (const LSP::DocumentLink &E : links) {
892
r_list.push_back(E);
893
}
894
}
895
}
896
897
Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
898
Dictionary api;
899
if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) {
900
api = parser->generate_api();
901
}
902
return api;
903
}
904
905
Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::SignatureHelp &r_signature) {
906
if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) {
907
LSP::TextDocumentPositionParams text_pos;
908
text_pos.textDocument = p_doc_pos.textDocument;
909
910
if (parser->get_left_function_call(p_doc_pos.position, text_pos.position, r_signature.activeParameter) == OK) {
911
List<const LSP::DocumentSymbol *> symbols;
912
913
if (const LSP::DocumentSymbol *symbol = resolve_symbol(text_pos)) {
914
symbols.push_back(symbol);
915
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
916
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols);
917
}
918
919
for (const LSP::DocumentSymbol *const &symbol : symbols) {
920
if (symbol->kind == LSP::SymbolKind::Method || symbol->kind == LSP::SymbolKind::Function) {
921
LSP::SignatureInformation signature_info;
922
signature_info.label = symbol->detail;
923
signature_info.documentation = symbol->render();
924
925
for (int i = 0; i < symbol->children.size(); i++) {
926
const LSP::DocumentSymbol &arg = symbol->children[i];
927
LSP::ParameterInformation arg_info;
928
arg_info.label = arg.name;
929
signature_info.parameters.push_back(arg_info);
930
}
931
r_signature.signatures.push_back(signature_info);
932
break;
933
}
934
}
935
936
if (r_signature.signatures.size()) {
937
return OK;
938
}
939
}
940
}
941
return ERR_METHOD_NOT_FOUND;
942
}
943
944
GDScriptWorkspace::GDScriptWorkspace() {}
945
946
GDScriptWorkspace::~GDScriptWorkspace() {
947
HashSet<String> cached_parsers;
948
949
for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) {
950
cached_parsers.insert(E.key);
951
}
952
953
for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
954
cached_parsers.insert(E.key);
955
}
956
957
for (const String &E : cached_parsers) {
958
remove_cache_parser(E);
959
}
960
}
961
962