Path: blob/master/modules/gdscript/language_server/gdscript_workspace.cpp
10278 views
/**************************************************************************/1/* gdscript_workspace.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "gdscript_workspace.h"3132#include "../gdscript.h"33#include "../gdscript_parser.h"34#include "gdscript_language_protocol.h"3536#include "core/config/project_settings.h"37#include "core/object/script_language.h"38#include "editor/doc/doc_tools.h"39#include "editor/doc/editor_help.h"40#include "editor/editor_node.h"41#include "editor/file_system/editor_file_system.h"42#include "editor/settings/editor_settings.h"43#include "scene/resources/packed_scene.h"4445void GDScriptWorkspace::_bind_methods() {46ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);47ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::didDeleteFiles);48ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);49ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script);50ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path);51ClassDB::bind_method(D_METHOD("get_file_uri", "path"), &GDScriptWorkspace::get_file_uri);52ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);53ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);54}5556void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) {57Ref<Script> scr = obj->get_script();5859if (scr->get_language()->get_name() != "GDScript") {60return;61}6263String function_signature = "func " + function;64String source = scr->get_source_code();6566if (source.contains(function_signature)) {67return;68}6970int first_class = source.find("\nclass ");71int start_line = 0;72if (first_class != -1) {73start_line = source.substr(0, first_class).split("\n").size();74} else {75start_line = source.split("\n").size();76}7778String function_body = "\n\n" + function_signature + "(";79for (int i = 0; i < args.size(); ++i) {80function_body += args[i];81if (i < args.size() - 1) {82function_body += ", ";83}84}85function_body += ")";86if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) {87function_body += " -> void";88}89function_body += ":\n\tpass # Replace with function body.\n";9091LSP::TextEdit text_edit;9293if (first_class != -1) {94function_body += "\n\n";95}96text_edit.range.end.line = text_edit.range.start.line = start_line;9798text_edit.newText = function_body;99100String uri = get_file_uri(scr->get_path());101102LSP::ApplyWorkspaceEditParams params;103params.edit.add_edit(uri, text_edit);104105GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json());106}107108void GDScriptWorkspace::didDeleteFiles(const Dictionary &p_params) {109Array files = p_params["files"];110for (int i = 0; i < files.size(); ++i) {111Dictionary file = files[i];112String uri = file["uri"];113String path = get_file_path(uri);114parse_script(path, "");115}116}117118void GDScriptWorkspace::remove_cache_parser(const String &p_path) {119HashMap<String, ExtendGDScriptParser *>::Iterator parser = parse_results.find(p_path);120HashMap<String, ExtendGDScriptParser *>::Iterator scr = scripts.find(p_path);121if (parser && scr) {122if (scr->value && scr->value == parser->value) {123memdelete(scr->value);124} else {125memdelete(scr->value);126memdelete(parser->value);127}128parse_results.erase(p_path);129scripts.erase(p_path);130} else if (parser) {131memdelete(parser->value);132parse_results.erase(p_path);133} else if (scr) {134memdelete(scr->value);135scripts.erase(p_path);136}137}138139const LSP::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const {140StringName class_name = p_class;141StringName empty;142143while (class_name != empty) {144if (HashMap<StringName, LSP::DocumentSymbol>::ConstIterator E = native_symbols.find(class_name)) {145const LSP::DocumentSymbol &class_symbol = E->value;146147if (p_member.is_empty()) {148return &class_symbol;149} else {150for (int i = 0; i < class_symbol.children.size(); i++) {151const LSP::DocumentSymbol &symbol = class_symbol.children[i];152if (symbol.name == p_member) {153return &symbol;154}155}156}157}158// Might contain pseudo classes like @GDScript that only exist in documentation.159if (ClassDB::class_exists(class_name)) {160class_name = ClassDB::get_parent_class(class_name);161} else {162break;163}164}165166return nullptr;167}168169const LSP::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const {170HashMap<String, ExtendGDScriptParser *>::ConstIterator S = scripts.find(p_path);171if (S) {172return &(S->value->get_symbols());173}174return nullptr;175}176177const LSP::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const LSP::DocumentSymbol *p_parent, const String &symbol_identifier) {178for (int i = 0; i < p_parent->children.size(); ++i) {179const LSP::DocumentSymbol *parameter_symbol = &p_parent->children[i];180if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) {181return parameter_symbol;182}183}184185return nullptr;186}187188const LSP::DocumentSymbol *GDScriptWorkspace::get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const LSP::Position p_position) {189// Go down and pick closest `DocumentSymbol` with `p_symbol_identifier`.190191const LSP::DocumentSymbol *current = &p_parser->get_symbols();192const LSP::DocumentSymbol *best_match = nullptr;193194while (current) {195if (current->name == p_symbol_identifier) {196if (current->selectionRange.contains(p_position)) {197// Exact match: pos is ON symbol decl identifier.198return current;199}200201best_match = current;202}203204const LSP::DocumentSymbol *parent = current;205current = nullptr;206for (const LSP::DocumentSymbol &child : parent->children) {207if (child.range.contains(p_position)) {208current = &child;209break;210}211}212}213214return best_match;215}216217void GDScriptWorkspace::reload_all_workspace_scripts() {218List<String> paths;219list_script_files("res://", paths);220for (const String &path : paths) {221Error err;222String content = FileAccess::get_file_as_string(path, &err);223ERR_CONTINUE(err != OK);224err = parse_script(path, content);225226if (err != OK) {227HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(path);228String err_msg = "Failed parse script " + path;229if (S) {230err_msg += "\n" + S->value->get_errors().front()->get().message;231}232ERR_CONTINUE_MSG(err != OK, err_msg);233}234}235}236237void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) {238Error err;239Ref<DirAccess> dir = DirAccess::open(p_root_dir, &err);240if (OK != err) {241return;242}243244// Ignore scripts in directories with a .gdignore file.245if (dir->file_exists(".gdignore")) {246return;247}248249dir->list_dir_begin();250String file_name = dir->get_next();251while (file_name.length()) {252if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./") {253list_script_files(p_root_dir.path_join(file_name), r_files);254} else if (file_name.ends_with(".gd")) {255String script_file = p_root_dir.path_join(file_name);256r_files.push_back(script_file);257}258file_name = dir->get_next();259}260}261262ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) {263HashMap<String, ExtendGDScriptParser *>::Iterator S = scripts.find(p_path);264if (!S) {265parse_local_script(p_path);266S = scripts.find(p_path);267}268if (S) {269return S->value;270}271return nullptr;272}273274ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) {275HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(p_path);276if (!S) {277parse_local_script(p_path);278S = parse_results.find(p_path);279}280if (S) {281return S->value;282}283return nullptr;284}285286#define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges())287288Error GDScriptWorkspace::initialize() {289if (initialized) {290return OK;291}292293DocTools *doc = EditorHelp::get_doc_data();294for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {295const DocData::ClassDoc &class_data = E.value;296const bool is_native = !class_data.is_script_doc;297LSP::DocumentSymbol class_symbol;298String class_name = E.key;299class_symbol.name = class_name;300class_symbol.native_class = class_name;301class_symbol.kind = LSP::SymbolKind::Class;302class_symbol.detail = String("<Native> class ") + class_name;303if (!class_data.inherits.is_empty()) {304class_symbol.detail += " extends " + class_data.inherits;305}306class_symbol.documentation = HANDLE_DOC(class_data.brief_description) + "\n" + HANDLE_DOC(class_data.description);307308for (int i = 0; i < class_data.constants.size(); i++) {309const DocData::ConstantDoc &const_data = class_data.constants[i];310LSP::DocumentSymbol symbol;311symbol.name = const_data.name;312symbol.native_class = class_name;313symbol.kind = LSP::SymbolKind::Constant;314symbol.detail = "const " + class_name + "." + const_data.name;315if (const_data.enumeration.length()) {316symbol.detail += ": " + const_data.enumeration;317}318symbol.detail += " = " + const_data.value;319symbol.documentation = HANDLE_DOC(const_data.description);320class_symbol.children.push_back(symbol);321}322323for (int i = 0; i < class_data.properties.size(); i++) {324const DocData::PropertyDoc &data = class_data.properties[i];325LSP::DocumentSymbol symbol;326symbol.name = data.name;327symbol.native_class = class_name;328symbol.kind = LSP::SymbolKind::Property;329symbol.detail = "var " + class_name + "." + data.name;330if (data.enumeration.length()) {331symbol.detail += ": " + data.enumeration;332} else {333symbol.detail += ": " + data.type;334}335symbol.documentation = HANDLE_DOC(data.description);336class_symbol.children.push_back(symbol);337}338339for (int i = 0; i < class_data.theme_properties.size(); i++) {340const DocData::ThemeItemDoc &data = class_data.theme_properties[i];341LSP::DocumentSymbol symbol;342symbol.name = data.name;343symbol.native_class = class_name;344symbol.kind = LSP::SymbolKind::Property;345symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type;346symbol.documentation = HANDLE_DOC(data.description);347class_symbol.children.push_back(symbol);348}349350Vector<DocData::MethodDoc> method_likes;351method_likes.append_array(class_data.methods);352method_likes.append_array(class_data.annotations);353const int constructors_start_idx = method_likes.size();354method_likes.append_array(class_data.constructors);355const int operator_start_idx = method_likes.size();356method_likes.append_array(class_data.operators);357const int signal_start_idx = method_likes.size();358method_likes.append_array(class_data.signals);359360for (int i = 0; i < method_likes.size(); i++) {361const DocData::MethodDoc &data = method_likes[i];362363LSP::DocumentSymbol symbol;364symbol.name = data.name;365symbol.native_class = class_name;366367if (i >= signal_start_idx) {368symbol.kind = LSP::SymbolKind::Event;369} else if (i >= operator_start_idx) {370symbol.kind = LSP::SymbolKind::Operator;371} else if (i >= constructors_start_idx) {372symbol.kind = LSP::SymbolKind::Constructor;373} else {374symbol.kind = LSP::SymbolKind::Method;375}376377String params = "";378bool arg_default_value_started = false;379for (int j = 0; j < data.arguments.size(); j++) {380const DocData::ArgumentDoc &arg = data.arguments[j];381382LSP::DocumentSymbol symbol_arg;383symbol_arg.name = arg.name;384symbol_arg.kind = LSP::SymbolKind::Variable;385symbol_arg.detail = arg.type;386387if (!arg_default_value_started && !arg.default_value.is_empty()) {388arg_default_value_started = true;389}390String arg_str = arg.name + ": " + arg.type;391if (arg_default_value_started) {392arg_str += " = " + arg.default_value;393}394if (j < data.arguments.size() - 1) {395arg_str += ", ";396}397params += arg_str;398399symbol.children.push_back(symbol_arg);400}401if (data.qualifiers.contains("vararg")) {402params += params.is_empty() ? "..." : ", ...";403}404405String return_type = data.return_type;406if (return_type.is_empty()) {407return_type = "void";408}409symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type;410symbol.documentation = HANDLE_DOC(data.description);411class_symbol.children.push_back(symbol);412}413414native_symbols.insert(class_name, class_symbol);415}416417reload_all_workspace_scripts();418419if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {420for (const KeyValue<StringName, LSP::DocumentSymbol> &E : native_symbols) {421ClassMembers members;422const LSP::DocumentSymbol &class_symbol = E.value;423for (int i = 0; i < class_symbol.children.size(); i++) {424const LSP::DocumentSymbol &symbol = class_symbol.children[i];425members.insert(symbol.name, &symbol);426}427native_members.insert(E.key, members);428}429430// Cache member completions.431for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) {432S.value->get_member_completions();433}434}435436EditorNode *editor_node = EditorNode::get_singleton();437editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal));438439return OK;440}441442Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) {443ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser);444Error err = parser->parse(p_content, p_path);445HashMap<String, ExtendGDScriptParser *>::Iterator last_parser = parse_results.find(p_path);446HashMap<String, ExtendGDScriptParser *>::Iterator last_script = scripts.find(p_path);447448if (err == OK) {449remove_cache_parser(p_path);450parse_results[p_path] = parser;451scripts[p_path] = parser;452453} else {454if (last_parser && last_script && last_parser->value != last_script->value) {455memdelete(last_parser->value);456}457parse_results[p_path] = parser;458}459460publish_diagnostics(p_path);461462return err;463}464465static bool is_valid_rename_target(const LSP::DocumentSymbol *p_symbol) {466// Must be valid symbol.467if (!p_symbol) {468return false;469}470471// Cannot rename builtin.472if (!p_symbol->native_class.is_empty()) {473return false;474}475476// Source must be available.477if (p_symbol->script_path.is_empty()) {478return false;479}480481return true;482}483484Dictionary GDScriptWorkspace::rename(const LSP::TextDocumentPositionParams &p_doc_pos, const String &new_name) {485LSP::WorkspaceEdit edit;486487const LSP::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);488if (is_valid_rename_target(reference_symbol)) {489Vector<LSP::Location> usages = find_all_usages(*reference_symbol);490for (int i = 0; i < usages.size(); ++i) {491LSP::Location loc = usages[i];492493edit.add_change(loc.uri, loc.range.start.line, loc.range.start.character, loc.range.end.character, new_name);494}495}496497return edit.to_json();498}499500bool GDScriptWorkspace::can_rename(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::DocumentSymbol &r_symbol, LSP::Range &r_range) {501const LSP::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);502if (!is_valid_rename_target(reference_symbol)) {503return false;504}505506String path = get_file_path(p_doc_pos.textDocument.uri);507if (const ExtendGDScriptParser *parser = get_parse_result(path)) {508// We only care about the range.509_ALLOW_DISCARD_ parser->get_identifier_under_position(p_doc_pos.position, r_range);510r_symbol = *reference_symbol;511return true;512}513514return false;515}516517Vector<LSP::Location> GDScriptWorkspace::find_usages_in_file(const LSP::DocumentSymbol &p_symbol, const String &p_file_path) {518Vector<LSP::Location> usages;519520String identifier = p_symbol.name;521if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) {522const PackedStringArray &content = parser->get_lines();523for (int i = 0; i < content.size(); ++i) {524String line = content[i];525526int character = line.find(identifier);527while (character > -1) {528LSP::TextDocumentPositionParams params;529530LSP::TextDocumentIdentifier text_doc;531text_doc.uri = get_file_uri(p_file_path);532533params.textDocument = text_doc;534params.position.line = i;535params.position.character = character;536537const LSP::DocumentSymbol *other_symbol = resolve_symbol(params);538539if (other_symbol == &p_symbol) {540LSP::Location loc;541loc.uri = text_doc.uri;542loc.range.start = params.position;543loc.range.end.line = params.position.line;544loc.range.end.character = params.position.character + identifier.length();545usages.append(loc);546}547548character = line.find(identifier, character + 1);549}550}551}552553return usages;554}555556Vector<LSP::Location> GDScriptWorkspace::find_all_usages(const LSP::DocumentSymbol &p_symbol) {557if (p_symbol.local) {558// Only search in current document.559return find_usages_in_file(p_symbol, p_symbol.script_path);560}561// Search in all documents.562List<String> paths;563list_script_files("res://", paths);564565Vector<LSP::Location> usages;566for (const String &path : paths) {567usages.append_array(find_usages_in_file(p_symbol, path));568}569return usages;570}571572Error GDScriptWorkspace::parse_local_script(const String &p_path) {573Error err;574String content = FileAccess::get_file_as_string(p_path, &err);575if (err == OK) {576err = parse_script(p_path, content);577}578return err;579}580581String GDScriptWorkspace::get_file_path(const String &p_uri) {582int port;583String scheme;584String host;585String encoded_path;586String fragment;587588// Don't use the returned error, the result isn't OK for URIs that are not valid web URLs.589p_uri.parse_url(scheme, host, port, encoded_path, fragment);590591// TODO: Make the parsing RFC-3986 compliant.592ERR_FAIL_COND_V_MSG(scheme != "file" && scheme != "file:" && scheme != "file://", String(), "LSP: The language server only supports the file protocol: " + p_uri);593594// Treat host like authority for now and ignore the port. It's an edge case for invalid file URI's anyway.595ERR_FAIL_COND_V_MSG(host != "" && host != "localhost", String(), "LSP: The language server does not support nonlocal files: " + p_uri);596597// If query or fragment are present, the URI is not a valid file URI as per RFC-8089.598// We currently don't handle the query and it will be part of the path. However,599// this should not be a problem for a correct file URI.600ERR_FAIL_COND_V_MSG(fragment != "", String(), "LSP: Received malformed file URI: " + p_uri);601602String canonical_res = ProjectSettings::get_singleton()->get_resource_path();603String simple_path = encoded_path.uri_file_decode().simplify_path();604605// First try known paths that point to res://, to reduce file system interaction.606bool res_adjusted = false;607for (const String &res_path : absolute_res_paths) {608if (simple_path.begins_with(res_path)) {609res_adjusted = true;610simple_path = "res://" + simple_path.substr(res_path.size());611break;612}613}614615// Traverse the path and compare each directory with res://616if (!res_adjusted) {617Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);618619int offset = 0;620while (offset <= simple_path.length()) {621offset = simple_path.find_char('/', offset);622if (offset == -1) {623offset = simple_path.length();624}625626String part = simple_path.substr(0, offset);627628if (!part.is_empty()) {629bool is_equal = dir->is_equivalent(canonical_res, part);630631if (is_equal) {632absolute_res_paths.insert(part);633res_adjusted = true;634simple_path = "res://" + simple_path.substr(offset + 1);635break;636}637}638639offset += 1;640}641642// Could not resolve the path to the project.643if (!res_adjusted) {644return simple_path;645}646}647648// Resolve the file inside of the project using EditorFileSystem.649EditorFileSystemDirectory *editor_dir;650int file_idx;651editor_dir = EditorFileSystem::get_singleton()->find_file(simple_path, &file_idx);652if (editor_dir) {653return editor_dir->get_file_path(file_idx);654}655656return simple_path;657}658659String GDScriptWorkspace::get_file_uri(const String &p_path) const {660String path = ProjectSettings::get_singleton()->globalize_path(p_path).lstrip("/");661LocalVector<String> encoded_parts;662for (const String &part : path.split("/")) {663encoded_parts.push_back(part.uri_encode());664}665666// Always return file URI's with authority part (encoding drive letters with leading slash), to maintain compat with RFC-1738 which required it.667return "file:///" + String("/").join(encoded_parts);668}669670void GDScriptWorkspace::publish_diagnostics(const String &p_path) {671Dictionary params;672Array errors;673HashMap<String, ExtendGDScriptParser *>::ConstIterator ele = parse_results.find(p_path);674if (ele) {675const Vector<LSP::Diagnostic> &list = ele->value->get_diagnostics();676errors.resize(list.size());677for (int i = 0; i < list.size(); ++i) {678errors[i] = list[i].to_json();679}680}681params["diagnostics"] = errors;682params["uri"] = get_file_uri(p_path);683GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);684}685686void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) {687if (!efsd) {688return;689}690691for (int i = 0; i < efsd->get_subdir_count(); i++) {692_get_owners(efsd->get_subdir(i), p_path, owners);693}694695for (int i = 0; i < efsd->get_file_count(); i++) {696Vector<String> deps = efsd->get_file_deps(i);697bool found = false;698for (int j = 0; j < deps.size(); j++) {699if (deps[j] == p_path) {700found = true;701break;702}703}704if (!found) {705continue;706}707708owners.push_back(efsd->get_file_path(i));709}710}711712Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {713Node *owner_scene_node = nullptr;714List<String> owners;715716_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);717718for (const String &owner : owners) {719NodePath owner_path = owner;720Ref<Resource> owner_res = ResourceLoader::load(String(owner_path));721if (Object::cast_to<PackedScene>(owner_res.ptr())) {722Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res));723owner_scene_node = owner_packed_scene->instantiate();724break;725}726}727728return owner_scene_node;729}730731void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) {732String path = get_file_path(p_params.textDocument.uri);733String call_hint;734bool forced = false;735736if (const ExtendGDScriptParser *parser = get_parse_result(path)) {737Node *owner_scene_node = _get_owner_scene_node(path);738739Array stack;740Node *current = nullptr;741if (owner_scene_node != nullptr) {742stack.push_back(owner_scene_node);743744while (!stack.is_empty()) {745current = Object::cast_to<Node>(stack.pop_back());746Ref<GDScript> scr = current->get_script();747if (scr.is_valid() && GDScript::is_canonically_equal_paths(scr->get_path(), path)) {748break;749}750for (int i = 0; i < current->get_child_count(); ++i) {751stack.push_back(current->get_child(i));752}753}754755Ref<GDScript> scr = current->get_script();756if (scr.is_null() || !GDScript::is_canonically_equal_paths(scr->get_path(), path)) {757current = owner_scene_node;758}759}760761String code = parser->get_text_for_completion(p_params.position);762GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);763if (owner_scene_node) {764memdelete(owner_scene_node);765}766}767}768769const LSP::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const LSP::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) {770const LSP::DocumentSymbol *symbol = nullptr;771772String path = get_file_path(p_doc_pos.textDocument.uri);773if (const ExtendGDScriptParser *parser = get_parse_result(path)) {774String symbol_identifier = p_symbol_name;775Vector<String> identifier_parts = symbol_identifier.split("(");776if (identifier_parts.size()) {777symbol_identifier = identifier_parts[0];778}779780LSP::Position pos = p_doc_pos.position;781if (symbol_identifier.is_empty()) {782LSP::Range range;783symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);784pos.character = range.end.character;785}786787if (!symbol_identifier.is_empty()) {788if (ScriptServer::is_global_class(symbol_identifier)) {789String class_path = ScriptServer::get_global_class_path(symbol_identifier);790symbol = get_script_symbol(class_path);791792} else {793ScriptLanguage::LookupResult ret;794if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].remove_chars(" \t").contains("new(")) {795symbol_identifier = "_init";796}797if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {798if (ret.location >= 0) {799String target_script_path = path;800if (ret.script.is_valid()) {801target_script_path = ret.script->get_path();802} else if (!ret.script_path.is_empty()) {803target_script_path = ret.script_path;804}805806if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {807symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier);808809if (symbol) {810switch (symbol->kind) {811case LSP::SymbolKind::Function: {812if (symbol->name != symbol_identifier) {813symbol = get_parameter_symbol(symbol, symbol_identifier);814}815} break;816}817}818}819} else {820String member = ret.class_member;821if (member.is_empty() && symbol_identifier != ret.class_name) {822member = symbol_identifier;823}824symbol = get_native_symbol(ret.class_name, member);825}826} else {827symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position);828if (!symbol) {829symbol = parser->get_member_symbol(symbol_identifier);830}831}832}833}834}835836return symbol;837}838839void GDScriptWorkspace::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {840String path = get_file_path(p_doc_pos.textDocument.uri);841if (const ExtendGDScriptParser *parser = get_parse_result(path)) {842String symbol_identifier;843LSP::Range range;844symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);845846for (const KeyValue<StringName, ClassMembers> &E : native_members) {847const ClassMembers &members = native_members.get(E.key);848if (const LSP::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {849r_list.push_back(*symbol);850}851}852853for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {854const ExtendGDScriptParser *scr = E.value;855const ClassMembers &members = scr->get_members();856if (const LSP::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {857r_list.push_back(*symbol);858}859860for (const KeyValue<String, ClassMembers> &F : scr->get_inner_classes()) {861const ClassMembers *inner_class = &F.value;862if (const LSP::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) {863r_list.push_back(*symbol);864}865}866}867}868}869870const LSP::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const LSP::NativeSymbolInspectParams &p_params) {871if (HashMap<StringName, LSP::DocumentSymbol>::Iterator E = native_symbols.find(p_params.native_class)) {872const LSP::DocumentSymbol &symbol = E->value;873if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) {874return &symbol;875}876877for (int i = 0; i < symbol.children.size(); ++i) {878if (symbol.children[i].name == p_params.symbol_name) {879return &(symbol.children[i]);880}881}882}883884return nullptr;885}886887void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<LSP::DocumentLink> &r_list) {888if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) {889const List<LSP::DocumentLink> &links = parser->get_document_links();890for (const LSP::DocumentLink &E : links) {891r_list.push_back(E);892}893}894}895896Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {897Dictionary api;898if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) {899api = parser->generate_api();900}901return api;902}903904Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams &p_doc_pos, LSP::SignatureHelp &r_signature) {905if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) {906LSP::TextDocumentPositionParams text_pos;907text_pos.textDocument = p_doc_pos.textDocument;908909if (parser->get_left_function_call(p_doc_pos.position, text_pos.position, r_signature.activeParameter) == OK) {910List<const LSP::DocumentSymbol *> symbols;911912if (const LSP::DocumentSymbol *symbol = resolve_symbol(text_pos)) {913symbols.push_back(symbol);914} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {915GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols);916}917918for (const LSP::DocumentSymbol *const &symbol : symbols) {919if (symbol->kind == LSP::SymbolKind::Method || symbol->kind == LSP::SymbolKind::Function) {920LSP::SignatureInformation signature_info;921signature_info.label = symbol->detail;922signature_info.documentation = symbol->render();923924for (int i = 0; i < symbol->children.size(); i++) {925const LSP::DocumentSymbol &arg = symbol->children[i];926LSP::ParameterInformation arg_info;927arg_info.label = arg.name;928signature_info.parameters.push_back(arg_info);929}930r_signature.signatures.push_back(signature_info);931break;932}933}934935if (r_signature.signatures.size()) {936return OK;937}938}939}940return ERR_METHOD_NOT_FOUND;941}942943GDScriptWorkspace::GDScriptWorkspace() {}944945GDScriptWorkspace::~GDScriptWorkspace() {946HashSet<String> cached_parsers;947948for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) {949cached_parsers.insert(E.key);950}951952for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {953cached_parsers.insert(E.key);954}955956for (const String &E : cached_parsers) {957remove_cache_parser(E);958}959}960961962