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_tools.h" |
40 | #include "editor/editor_file_system.h" |
41 | #include "editor/editor_help.h" |
42 | #include "editor/editor_node.h" |
43 | #include "editor/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::did_delete_files); |
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::did_delete_files(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 | class_name = ClassDB::get_parent_class(class_name); |
160 | } |
161 | |
162 | return nullptr; |
163 | } |
164 | |
165 | const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const { |
166 | HashMap<String, ExtendGDScriptParser *>::ConstIterator S = scripts.find(p_path); |
167 | if (S) { |
168 | return &(S->value->get_symbols()); |
169 | } |
170 | return nullptr; |
171 | } |
172 | |
173 | const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier) { |
174 | for (int i = 0; i < p_parent->children.size(); ++i) { |
175 | const lsp::DocumentSymbol *parameter_symbol = &p_parent->children[i]; |
176 | if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) { |
177 | return parameter_symbol; |
178 | } |
179 | } |
180 | |
181 | return nullptr; |
182 | } |
183 | |
184 | const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position) { |
185 | // Go down and pick closest `DocumentSymbol` with `p_symbol_identifier`. |
186 | |
187 | const lsp::DocumentSymbol *current = &p_parser->get_symbols(); |
188 | const lsp::DocumentSymbol *best_match = nullptr; |
189 | |
190 | while (current) { |
191 | if (current->name == p_symbol_identifier) { |
192 | if (current->selectionRange.contains(p_position)) { |
193 | // Exact match: pos is ON symbol decl identifier. |
194 | return current; |
195 | } |
196 | |
197 | best_match = current; |
198 | } |
199 | |
200 | const lsp::DocumentSymbol *parent = current; |
201 | current = nullptr; |
202 | for (const lsp::DocumentSymbol &child : parent->children) { |
203 | if (child.range.contains(p_position)) { |
204 | current = &child; |
205 | break; |
206 | } |
207 | } |
208 | } |
209 | |
210 | return best_match; |
211 | } |
212 | |
213 | void GDScriptWorkspace::reload_all_workspace_scripts() { |
214 | List<String> paths; |
215 | list_script_files("res://" , paths); |
216 | for (const String &path : paths) { |
217 | Error err; |
218 | String content = FileAccess::get_file_as_string(path, &err); |
219 | ERR_CONTINUE(err != OK); |
220 | err = parse_script(path, content); |
221 | |
222 | if (err != OK) { |
223 | HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(path); |
224 | String err_msg = "Failed parse script " + path; |
225 | if (S) { |
226 | err_msg += "\n" + S->value->get_errors()[0].message; |
227 | } |
228 | ERR_CONTINUE_MSG(err != OK, err_msg); |
229 | } |
230 | } |
231 | } |
232 | |
233 | void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) { |
234 | Error err; |
235 | Ref<DirAccess> dir = DirAccess::open(p_root_dir, &err); |
236 | if (OK == err) { |
237 | dir->list_dir_begin(); |
238 | String file_name = dir->get_next(); |
239 | while (file_name.length()) { |
240 | if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./" ) { |
241 | list_script_files(p_root_dir.path_join(file_name), r_files); |
242 | } else if (file_name.ends_with(".gd" )) { |
243 | String script_file = p_root_dir.path_join(file_name); |
244 | r_files.push_back(script_file); |
245 | } |
246 | file_name = dir->get_next(); |
247 | } |
248 | } |
249 | } |
250 | |
251 | ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) { |
252 | HashMap<String, ExtendGDScriptParser *>::Iterator S = scripts.find(p_path); |
253 | if (!S) { |
254 | parse_local_script(p_path); |
255 | S = scripts.find(p_path); |
256 | } |
257 | if (S) { |
258 | return S->value; |
259 | } |
260 | return nullptr; |
261 | } |
262 | |
263 | ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) { |
264 | HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(p_path); |
265 | if (!S) { |
266 | parse_local_script(p_path); |
267 | S = parse_results.find(p_path); |
268 | } |
269 | if (S) { |
270 | return S->value; |
271 | } |
272 | return nullptr; |
273 | } |
274 | |
275 | Error GDScriptWorkspace::initialize() { |
276 | if (initialized) { |
277 | return OK; |
278 | } |
279 | |
280 | DocTools *doc = EditorHelp::get_doc_data(); |
281 | for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { |
282 | const DocData::ClassDoc &class_data = E.value; |
283 | lsp::DocumentSymbol class_symbol; |
284 | String class_name = E.key; |
285 | class_symbol.name = class_name; |
286 | class_symbol.native_class = class_name; |
287 | class_symbol.kind = lsp::SymbolKind::Class; |
288 | class_symbol.detail = String("<Native> class " ) + class_name; |
289 | if (!class_data.inherits.is_empty()) { |
290 | class_symbol.detail += " extends " + class_data.inherits; |
291 | } |
292 | class_symbol.documentation = class_data.brief_description + "\n" + class_data.description; |
293 | |
294 | for (int i = 0; i < class_data.constants.size(); i++) { |
295 | const DocData::ConstantDoc &const_data = class_data.constants[i]; |
296 | lsp::DocumentSymbol symbol; |
297 | symbol.name = const_data.name; |
298 | symbol.native_class = class_name; |
299 | symbol.kind = lsp::SymbolKind::Constant; |
300 | symbol.detail = "const " + class_name + "." + const_data.name; |
301 | if (const_data.enumeration.length()) { |
302 | symbol.detail += ": " + const_data.enumeration; |
303 | } |
304 | symbol.detail += " = " + const_data.value; |
305 | symbol.documentation = const_data.description; |
306 | class_symbol.children.push_back(symbol); |
307 | } |
308 | |
309 | for (int i = 0; i < class_data.properties.size(); i++) { |
310 | const DocData::PropertyDoc &data = class_data.properties[i]; |
311 | lsp::DocumentSymbol symbol; |
312 | symbol.name = data.name; |
313 | symbol.native_class = class_name; |
314 | symbol.kind = lsp::SymbolKind::Property; |
315 | symbol.detail = "var " + class_name + "." + data.name; |
316 | if (data.enumeration.length()) { |
317 | symbol.detail += ": " + data.enumeration; |
318 | } else { |
319 | symbol.detail += ": " + data.type; |
320 | } |
321 | symbol.documentation = data.description; |
322 | class_symbol.children.push_back(symbol); |
323 | } |
324 | |
325 | for (int i = 0; i < class_data.theme_properties.size(); i++) { |
326 | const DocData::ThemeItemDoc &data = class_data.theme_properties[i]; |
327 | lsp::DocumentSymbol symbol; |
328 | symbol.name = data.name; |
329 | symbol.native_class = class_name; |
330 | symbol.kind = lsp::SymbolKind::Property; |
331 | symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type; |
332 | symbol.documentation = data.description; |
333 | class_symbol.children.push_back(symbol); |
334 | } |
335 | |
336 | Vector<DocData::MethodDoc> methods_signals; |
337 | methods_signals.append_array(class_data.constructors); |
338 | methods_signals.append_array(class_data.methods); |
339 | methods_signals.append_array(class_data.operators); |
340 | const int signal_start_idx = methods_signals.size(); |
341 | methods_signals.append_array(class_data.signals); |
342 | |
343 | for (int i = 0; i < methods_signals.size(); i++) { |
344 | const DocData::MethodDoc &data = methods_signals[i]; |
345 | |
346 | lsp::DocumentSymbol symbol; |
347 | symbol.name = data.name; |
348 | symbol.native_class = class_name; |
349 | symbol.kind = i >= signal_start_idx ? lsp::SymbolKind::Event : lsp::SymbolKind::Method; |
350 | |
351 | String params = "" ; |
352 | bool arg_default_value_started = false; |
353 | for (int j = 0; j < data.arguments.size(); j++) { |
354 | const DocData::ArgumentDoc &arg = data.arguments[j]; |
355 | |
356 | lsp::DocumentSymbol symbol_arg; |
357 | symbol_arg.name = arg.name; |
358 | symbol_arg.kind = lsp::SymbolKind::Variable; |
359 | symbol_arg.detail = arg.type; |
360 | |
361 | if (!arg_default_value_started && !arg.default_value.is_empty()) { |
362 | arg_default_value_started = true; |
363 | } |
364 | String arg_str = arg.name + ": " + arg.type; |
365 | if (arg_default_value_started) { |
366 | arg_str += " = " + arg.default_value; |
367 | } |
368 | if (j < data.arguments.size() - 1) { |
369 | arg_str += ", " ; |
370 | } |
371 | params += arg_str; |
372 | |
373 | symbol.children.push_back(symbol_arg); |
374 | } |
375 | if (data.qualifiers.contains("vararg" )) { |
376 | params += params.is_empty() ? "..." : ", ..." ; |
377 | } |
378 | |
379 | String return_type = data.return_type; |
380 | if (return_type.is_empty()) { |
381 | return_type = "void" ; |
382 | } |
383 | symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type; |
384 | symbol.documentation = data.description; |
385 | class_symbol.children.push_back(symbol); |
386 | } |
387 | |
388 | native_symbols.insert(class_name, class_symbol); |
389 | } |
390 | |
391 | reload_all_workspace_scripts(); |
392 | |
393 | if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { |
394 | for (const KeyValue<StringName, lsp::DocumentSymbol> &E : native_symbols) { |
395 | ClassMembers members; |
396 | const lsp::DocumentSymbol &class_symbol = E.value; |
397 | for (int i = 0; i < class_symbol.children.size(); i++) { |
398 | const lsp::DocumentSymbol &symbol = class_symbol.children[i]; |
399 | members.insert(symbol.name, &symbol); |
400 | } |
401 | native_members.insert(E.key, members); |
402 | } |
403 | |
404 | // Cache member completions. |
405 | for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) { |
406 | S.value->get_member_completions(); |
407 | } |
408 | } |
409 | |
410 | EditorNode *editor_node = EditorNode::get_singleton(); |
411 | editor_node->connect("script_add_function_request" , callable_mp(this, &GDScriptWorkspace::apply_new_signal)); |
412 | |
413 | return OK; |
414 | } |
415 | |
416 | Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) { |
417 | ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser); |
418 | Error err = parser->parse(p_content, p_path); |
419 | HashMap<String, ExtendGDScriptParser *>::Iterator last_parser = parse_results.find(p_path); |
420 | HashMap<String, ExtendGDScriptParser *>::Iterator last_script = scripts.find(p_path); |
421 | |
422 | if (err == OK) { |
423 | remove_cache_parser(p_path); |
424 | parse_results[p_path] = parser; |
425 | scripts[p_path] = parser; |
426 | |
427 | } else { |
428 | if (last_parser && last_script && last_parser->value != last_script->value) { |
429 | memdelete(last_parser->value); |
430 | } |
431 | parse_results[p_path] = parser; |
432 | } |
433 | |
434 | publish_diagnostics(p_path); |
435 | |
436 | return err; |
437 | } |
438 | |
439 | static bool is_valid_rename_target(const lsp::DocumentSymbol *p_symbol) { |
440 | // Must be valid symbol. |
441 | if (!p_symbol) { |
442 | return false; |
443 | } |
444 | |
445 | // Cannot rename builtin. |
446 | if (!p_symbol->native_class.is_empty()) { |
447 | return false; |
448 | } |
449 | |
450 | // Source must be available. |
451 | if (p_symbol->script_path.is_empty()) { |
452 | return false; |
453 | } |
454 | |
455 | return true; |
456 | } |
457 | |
458 | Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { |
459 | lsp::WorkspaceEdit edit; |
460 | |
461 | const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); |
462 | if (is_valid_rename_target(reference_symbol)) { |
463 | Vector<lsp::Location> usages = find_all_usages(*reference_symbol); |
464 | for (int i = 0; i < usages.size(); ++i) { |
465 | lsp::Location loc = usages[i]; |
466 | |
467 | edit.add_change(loc.uri, loc.range.start.line, loc.range.start.character, loc.range.end.character, new_name); |
468 | } |
469 | } |
470 | |
471 | return edit.to_json(); |
472 | } |
473 | |
474 | bool GDScriptWorkspace::can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range) { |
475 | const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); |
476 | if (!is_valid_rename_target(reference_symbol)) { |
477 | return false; |
478 | } |
479 | |
480 | String path = get_file_path(p_doc_pos.textDocument.uri); |
481 | if (const ExtendGDScriptParser *parser = get_parse_result(path)) { |
482 | parser->get_identifier_under_position(p_doc_pos.position, r_range); |
483 | r_symbol = *reference_symbol; |
484 | return true; |
485 | } |
486 | |
487 | return false; |
488 | } |
489 | |
490 | Vector<lsp::Location> GDScriptWorkspace::find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path) { |
491 | Vector<lsp::Location> usages; |
492 | |
493 | String identifier = p_symbol.name; |
494 | if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) { |
495 | const PackedStringArray &content = parser->get_lines(); |
496 | for (int i = 0; i < content.size(); ++i) { |
497 | String line = content[i]; |
498 | |
499 | int character = line.find(identifier); |
500 | while (character > -1) { |
501 | lsp::TextDocumentPositionParams params; |
502 | |
503 | lsp::TextDocumentIdentifier text_doc; |
504 | text_doc.uri = get_file_uri(p_file_path); |
505 | |
506 | params.textDocument = text_doc; |
507 | params.position.line = i; |
508 | params.position.character = character; |
509 | |
510 | const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); |
511 | |
512 | if (other_symbol == &p_symbol) { |
513 | lsp::Location loc; |
514 | loc.uri = text_doc.uri; |
515 | loc.range.start = params.position; |
516 | loc.range.end.line = params.position.line; |
517 | loc.range.end.character = params.position.character + identifier.length(); |
518 | usages.append(loc); |
519 | } |
520 | |
521 | character = line.find(identifier, character + 1); |
522 | } |
523 | } |
524 | } |
525 | |
526 | return usages; |
527 | } |
528 | |
529 | Vector<lsp::Location> GDScriptWorkspace::find_all_usages(const lsp::DocumentSymbol &p_symbol) { |
530 | if (p_symbol.local) { |
531 | // Only search in current document. |
532 | return find_usages_in_file(p_symbol, p_symbol.script_path); |
533 | } |
534 | // Search in all documents. |
535 | List<String> paths; |
536 | list_script_files("res://" , paths); |
537 | |
538 | Vector<lsp::Location> usages; |
539 | for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { |
540 | usages.append_array(find_usages_in_file(p_symbol, PE->get())); |
541 | } |
542 | return usages; |
543 | } |
544 | |
545 | Error GDScriptWorkspace::parse_local_script(const String &p_path) { |
546 | Error err; |
547 | String content = FileAccess::get_file_as_string(p_path, &err); |
548 | if (err == OK) { |
549 | err = parse_script(p_path, content); |
550 | } |
551 | return err; |
552 | } |
553 | |
554 | String GDScriptWorkspace::get_file_path(const String &p_uri) const { |
555 | String path = p_uri.uri_decode(); |
556 | String base_uri = root_uri.uri_decode(); |
557 | path = path.replacen(base_uri + "/" , "res://" ); |
558 | return path; |
559 | } |
560 | |
561 | String GDScriptWorkspace::get_file_uri(const String &p_path) const { |
562 | String uri = p_path; |
563 | uri = uri.replace("res://" , root_uri + "/" ); |
564 | return uri; |
565 | } |
566 | |
567 | void GDScriptWorkspace::publish_diagnostics(const String &p_path) { |
568 | Dictionary params; |
569 | Array errors; |
570 | HashMap<String, ExtendGDScriptParser *>::ConstIterator ele = parse_results.find(p_path); |
571 | if (ele) { |
572 | const Vector<lsp::Diagnostic> &list = ele->value->get_diagnostics(); |
573 | errors.resize(list.size()); |
574 | for (int i = 0; i < list.size(); ++i) { |
575 | errors[i] = list[i].to_json(); |
576 | } |
577 | } |
578 | params["diagnostics" ] = errors; |
579 | params["uri" ] = get_file_uri(p_path); |
580 | GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics" , params); |
581 | } |
582 | |
583 | void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) { |
584 | if (!efsd) { |
585 | return; |
586 | } |
587 | |
588 | for (int i = 0; i < efsd->get_subdir_count(); i++) { |
589 | _get_owners(efsd->get_subdir(i), p_path, owners); |
590 | } |
591 | |
592 | for (int i = 0; i < efsd->get_file_count(); i++) { |
593 | Vector<String> deps = efsd->get_file_deps(i); |
594 | bool found = false; |
595 | for (int j = 0; j < deps.size(); j++) { |
596 | if (deps[j] == p_path) { |
597 | found = true; |
598 | break; |
599 | } |
600 | } |
601 | if (!found) { |
602 | continue; |
603 | } |
604 | |
605 | owners.push_back(efsd->get_file_path(i)); |
606 | } |
607 | } |
608 | |
609 | Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) { |
610 | Node *owner_scene_node = nullptr; |
611 | List<String> owners; |
612 | |
613 | _get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners); |
614 | |
615 | for (int i = 0; i < owners.size(); i++) { |
616 | NodePath owner_path = owners[i]; |
617 | Ref<Resource> owner_res = ResourceLoader::load(owner_path); |
618 | if (Object::cast_to<PackedScene>(owner_res.ptr())) { |
619 | Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res)); |
620 | owner_scene_node = owner_packed_scene->instantiate(); |
621 | break; |
622 | } |
623 | } |
624 | |
625 | return owner_scene_node; |
626 | } |
627 | |
628 | void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) { |
629 | String path = get_file_path(p_params.textDocument.uri); |
630 | String call_hint; |
631 | bool forced = false; |
632 | |
633 | if (const ExtendGDScriptParser *parser = get_parse_result(path)) { |
634 | Node *owner_scene_node = _get_owner_scene_node(path); |
635 | |
636 | Array stack; |
637 | Node *current = nullptr; |
638 | if (owner_scene_node != nullptr) { |
639 | stack.push_back(owner_scene_node); |
640 | |
641 | while (!stack.is_empty()) { |
642 | current = Object::cast_to<Node>(stack.pop_back()); |
643 | Ref<GDScript> scr = current->get_script(); |
644 | if (scr.is_valid() && scr->get_path() == path) { |
645 | break; |
646 | } |
647 | for (int i = 0; i < current->get_child_count(); ++i) { |
648 | stack.push_back(current->get_child(i)); |
649 | } |
650 | } |
651 | |
652 | Ref<GDScript> scr = current->get_script(); |
653 | if (!scr.is_valid() || scr->get_path() != path) { |
654 | current = owner_scene_node; |
655 | } |
656 | } |
657 | |
658 | String code = parser->get_text_for_completion(p_params.position); |
659 | GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint); |
660 | if (owner_scene_node) { |
661 | memdelete(owner_scene_node); |
662 | } |
663 | } |
664 | } |
665 | |
666 | const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) { |
667 | const lsp::DocumentSymbol *symbol = nullptr; |
668 | |
669 | String path = get_file_path(p_doc_pos.textDocument.uri); |
670 | if (const ExtendGDScriptParser *parser = get_parse_result(path)) { |
671 | String symbol_identifier = p_symbol_name; |
672 | Vector<String> identifier_parts = symbol_identifier.split("(" ); |
673 | if (identifier_parts.size()) { |
674 | symbol_identifier = identifier_parts[0]; |
675 | } |
676 | |
677 | lsp::Position pos = p_doc_pos.position; |
678 | if (symbol_identifier.is_empty()) { |
679 | lsp::Range range; |
680 | symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range); |
681 | pos.character = range.end.character; |
682 | } |
683 | |
684 | if (!symbol_identifier.is_empty()) { |
685 | if (ScriptServer::is_global_class(symbol_identifier)) { |
686 | String class_path = ScriptServer::get_global_class_path(symbol_identifier); |
687 | symbol = get_script_symbol(class_path); |
688 | |
689 | } else { |
690 | ScriptLanguage::LookupResult ret; |
691 | if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" " , "" ).replace("\t" , "" ).find("new(" ) > -1) { |
692 | symbol_identifier = "_init" ; |
693 | } |
694 | if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) { |
695 | if (ret.type == ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION) { |
696 | String target_script_path = path; |
697 | if (!ret.script.is_null()) { |
698 | target_script_path = ret.script->get_path(); |
699 | } else if (!ret.class_path.is_empty()) { |
700 | target_script_path = ret.class_path; |
701 | } |
702 | |
703 | if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { |
704 | symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier); |
705 | |
706 | if (symbol) { |
707 | switch (symbol->kind) { |
708 | case lsp::SymbolKind::Function: { |
709 | if (symbol->name != symbol_identifier) { |
710 | symbol = get_parameter_symbol(symbol, symbol_identifier); |
711 | } |
712 | } break; |
713 | } |
714 | } |
715 | } |
716 | |
717 | } else { |
718 | String member = ret.class_member; |
719 | if (member.is_empty() && symbol_identifier != ret.class_name) { |
720 | member = symbol_identifier; |
721 | } |
722 | symbol = get_native_symbol(ret.class_name, member); |
723 | } |
724 | } else { |
725 | symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position); |
726 | if (!symbol) { |
727 | symbol = parser->get_member_symbol(symbol_identifier); |
728 | } |
729 | } |
730 | } |
731 | } |
732 | } |
733 | |
734 | return symbol; |
735 | } |
736 | |
737 | void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list) { |
738 | String path = get_file_path(p_doc_pos.textDocument.uri); |
739 | if (const ExtendGDScriptParser *parser = get_parse_result(path)) { |
740 | String symbol_identifier; |
741 | lsp::Range range; |
742 | symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range); |
743 | |
744 | for (const KeyValue<StringName, ClassMembers> &E : native_members) { |
745 | const ClassMembers &members = native_members.get(E.key); |
746 | if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { |
747 | r_list.push_back(*symbol); |
748 | } |
749 | } |
750 | |
751 | for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { |
752 | const ExtendGDScriptParser *scr = E.value; |
753 | const ClassMembers &members = scr->get_members(); |
754 | if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { |
755 | r_list.push_back(*symbol); |
756 | } |
757 | |
758 | for (const KeyValue<String, ClassMembers> &F : scr->get_inner_classes()) { |
759 | const ClassMembers *inner_class = &F.value; |
760 | if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) { |
761 | r_list.push_back(*symbol); |
762 | } |
763 | } |
764 | } |
765 | } |
766 | } |
767 | |
768 | const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) { |
769 | if (HashMap<StringName, lsp::DocumentSymbol>::Iterator E = native_symbols.find(p_params.native_class)) { |
770 | const lsp::DocumentSymbol &symbol = E->value; |
771 | if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) { |
772 | return &symbol; |
773 | } |
774 | |
775 | for (int i = 0; i < symbol.children.size(); ++i) { |
776 | if (symbol.children[i].name == p_params.symbol_name) { |
777 | return &(symbol.children[i]); |
778 | } |
779 | } |
780 | } |
781 | |
782 | return nullptr; |
783 | } |
784 | |
785 | void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) { |
786 | if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) { |
787 | const List<lsp::DocumentLink> &links = parser->get_document_links(); |
788 | for (const lsp::DocumentLink &E : links) { |
789 | r_list.push_back(E); |
790 | } |
791 | } |
792 | } |
793 | |
794 | Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) { |
795 | Dictionary api; |
796 | if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) { |
797 | api = parser->generate_api(); |
798 | } |
799 | return api; |
800 | } |
801 | |
802 | Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature) { |
803 | if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) { |
804 | lsp::TextDocumentPositionParams text_pos; |
805 | text_pos.textDocument = p_doc_pos.textDocument; |
806 | |
807 | if (parser->get_left_function_call(p_doc_pos.position, text_pos.position, r_signature.activeParameter) == OK) { |
808 | List<const lsp::DocumentSymbol *> symbols; |
809 | |
810 | if (const lsp::DocumentSymbol *symbol = resolve_symbol(text_pos)) { |
811 | symbols.push_back(symbol); |
812 | } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { |
813 | GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols); |
814 | } |
815 | |
816 | for (const lsp::DocumentSymbol *const &symbol : symbols) { |
817 | if (symbol->kind == lsp::SymbolKind::Method || symbol->kind == lsp::SymbolKind::Function) { |
818 | lsp::SignatureInformation signature_info; |
819 | signature_info.label = symbol->detail; |
820 | signature_info.documentation = symbol->render(); |
821 | |
822 | for (int i = 0; i < symbol->children.size(); i++) { |
823 | const lsp::DocumentSymbol &arg = symbol->children[i]; |
824 | lsp::ParameterInformation arg_info; |
825 | arg_info.label = arg.name; |
826 | signature_info.parameters.push_back(arg_info); |
827 | } |
828 | r_signature.signatures.push_back(signature_info); |
829 | break; |
830 | } |
831 | } |
832 | |
833 | if (r_signature.signatures.size()) { |
834 | return OK; |
835 | } |
836 | } |
837 | } |
838 | return ERR_METHOD_NOT_FOUND; |
839 | } |
840 | |
841 | GDScriptWorkspace::GDScriptWorkspace() { |
842 | ProjectSettings::get_singleton()->get_resource_path(); |
843 | } |
844 | |
845 | GDScriptWorkspace::~GDScriptWorkspace() { |
846 | HashSet<String> cached_parsers; |
847 | |
848 | for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) { |
849 | cached_parsers.insert(E.key); |
850 | } |
851 | |
852 | for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { |
853 | cached_parsers.insert(E.key); |
854 | } |
855 | |
856 | for (const String &E : cached_parsers) { |
857 | remove_cache_parser(E); |
858 | } |
859 | } |
860 | |