| 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 | |