Path: blob/master/modules/gdscript/language_server/gdscript_extend_parser.cpp
20898 views
/**************************************************************************/1/* gdscript_extend_parser.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_extend_parser.h"3132#include "../gdscript.h"33#include "../gdscript_analyzer.h"34#include "editor/settings/editor_settings.h"35#include "gdscript_language_protocol.h"36#include "gdscript_workspace.h"3738int get_indent_size() {39if (EditorSettings::get_singleton()) {40return EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");41} else {42return 4;43}44}4546LSP::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const {47LSP::Position res;4849// Special case: `line = 0` -> root class (range covers everything).50if (line <= 0) {51return res;52}53// Special case: `line = p_lines.size() + 1` -> root class (range covers everything).54if (line >= p_lines.size() + 1) {55res.line = p_lines.size();56return res;57}58res.line = line - 1;5960// Special case: `column = 0` -> Starts at beginning of line.61if (column <= 0) {62return res;63}6465// Note: character outside of `pos_line.length()-1` is valid.66res.character = column - 1;6768String pos_line = p_lines[res.line];69if (pos_line.contains_char('\t')) {70int tab_size = get_indent_size();7172int in_col = 1;73int res_char = 0;7475while (res_char < pos_line.size() && in_col < column) {76if (pos_line[res_char] == '\t') {77in_col += tab_size;78res_char++;79} else {80in_col++;81res_char++;82}83}8485res.character = res_char;86}8788return res;89}9091GodotPosition GodotPosition::from_lsp(const LSP::Position p_pos, const Vector<String> &p_lines) {92GodotPosition res(p_pos.line + 1, p_pos.character + 1);9394// Line outside of actual text is valid (-> pos/cursor at end of text).95if (res.line > p_lines.size()) {96return res;97}9899String line = p_lines[p_pos.line];100int tabs_before_char = 0;101for (int i = 0; i < p_pos.character && i < line.length(); i++) {102if (line[i] == '\t') {103tabs_before_char++;104}105}106107if (tabs_before_char > 0) {108int tab_size = get_indent_size();109res.column += tabs_before_char * (tab_size - 1);110}111112return res;113}114115LSP::Range GodotRange::to_lsp(const Vector<String> &p_lines) const {116LSP::Range res;117res.start = start.to_lsp(p_lines);118res.end = end.to_lsp(p_lines);119return res;120}121122GodotRange GodotRange::from_lsp(const LSP::Range &p_range, const Vector<String> &p_lines) {123GodotPosition start = GodotPosition::from_lsp(p_range.start, p_lines);124GodotPosition end = GodotPosition::from_lsp(p_range.end, p_lines);125return GodotRange(start, end);126}127128void ExtendGDScriptParser::update_diagnostics() {129diagnostics.clear();130131const List<ParserError> &parser_errors = get_errors();132for (const ParserError &error : parser_errors) {133LSP::Diagnostic diagnostic;134diagnostic.severity = LSP::DiagnosticSeverity::Error;135diagnostic.message = error.message;136diagnostic.source = "gdscript";137138GodotRange godot_range(139GodotPosition(error.start_line, error.start_column),140GodotPosition(error.end_line, error.end_column));141142diagnostic.range = godot_range.to_lsp(get_lines());143diagnostics.push_back(diagnostic);144}145146const List<GDScriptWarning> &parser_warnings = get_warnings();147for (const GDScriptWarning &warning : parser_warnings) {148LSP::Diagnostic diagnostic;149diagnostic.severity = LSP::DiagnosticSeverity::Warning;150diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message();151diagnostic.source = "gdscript";152153GodotRange godot_range(154GodotPosition(warning.start_line, warning.start_column),155GodotPosition(warning.end_line, warning.end_column));156157diagnostic.range = godot_range.to_lsp(get_lines());158diagnostics.push_back(diagnostic);159}160}161162void ExtendGDScriptParser::update_symbols() {163members.clear();164165if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(get_tree())) {166parse_class_symbol(gdclass, class_symbol);167168for (int i = 0; i < class_symbol.children.size(); i++) {169const LSP::DocumentSymbol &symbol = class_symbol.children[i];170members.insert(symbol.name, &symbol);171172// Cache level one inner classes.173if (symbol.kind == LSP::SymbolKind::Class) {174ClassMembers inner_class;175for (int j = 0; j < symbol.children.size(); j++) {176const LSP::DocumentSymbol &s = symbol.children[j];177inner_class.insert(s.name, &s);178}179inner_classes.insert(symbol.name, inner_class);180}181}182}183}184185void ExtendGDScriptParser::update_document_links(const String &p_code) {186document_links.clear();187188GDScriptTokenizerText scr_tokenizer;189Ref<FileAccess> fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);190scr_tokenizer.set_source_code(p_code);191while (true) {192GDScriptTokenizer::Token token = scr_tokenizer.scan();193if (token.type == GDScriptTokenizer::Token::TK_EOF) {194break;195} else if (token.type == GDScriptTokenizer::Token::LITERAL) {196const Variant &const_val = token.literal;197if (const_val.get_type() == Variant::STRING) {198String scr_path = const_val;199if (scr_path.is_relative_path()) {200scr_path = get_path().get_base_dir().path_join(scr_path).simplify_path();201}202bool exists = fs->file_exists(scr_path);203204if (exists) {205String value = const_val;206LSP::DocumentLink link;207link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(scr_path);208link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(lines);209document_links.push_back(link);210}211}212}213}214}215216LSP::Range ExtendGDScriptParser::range_of_node(const GDScriptParser::Node *p_node) const {217GodotPosition start(p_node->start_line, p_node->start_column);218GodotPosition end(p_node->end_line, p_node->end_column);219return GodotRange(start, end).to_lsp(lines);220}221222void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, LSP::DocumentSymbol &r_symbol) {223const String uri = get_uri();224225r_symbol.uri = uri;226r_symbol.script_path = path;227r_symbol.children.clear();228r_symbol.name = p_class->identifier != nullptr ? String(p_class->identifier->name) : String();229if (r_symbol.name.is_empty()) {230r_symbol.name = path.get_file();231}232r_symbol.kind = LSP::SymbolKind::Class;233r_symbol.deprecated = false;234r_symbol.range = range_of_node(p_class);235if (p_class->identifier) {236r_symbol.selectionRange = range_of_node(p_class->identifier);237} else {238// No meaningful `selectionRange`, but we must ensure that it is inside of `range`.239r_symbol.selectionRange.start = r_symbol.range.start;240r_symbol.selectionRange.end = r_symbol.range.start;241}242r_symbol.detail = "class " + r_symbol.name;243{244String doc = p_class->doc_data.brief;245if (!p_class->doc_data.description.is_empty()) {246doc += "\n\n" + p_class->doc_data.description;247}248249if (!p_class->doc_data.tutorials.is_empty()) {250doc += "\n";251for (const Pair<String, String> &tutorial : p_class->doc_data.tutorials) {252if (tutorial.first.is_empty()) {253doc += vformat("\n@tutorial: %s", tutorial.second);254} else {255doc += vformat("\n@tutorial(%s): %s", tutorial.first, tutorial.second);256}257}258}259r_symbol.documentation = doc;260}261262for (int i = 0; i < p_class->members.size(); i++) {263const ClassNode::Member &m = p_class->members[i];264265switch (m.type) {266case ClassNode::Member::VARIABLE: {267LSP::DocumentSymbol symbol;268symbol.name = m.variable->identifier->name;269symbol.kind = m.variable->property == VariableNode::PROP_NONE ? LSP::SymbolKind::Variable : LSP::SymbolKind::Property;270symbol.deprecated = false;271symbol.range = range_of_node(m.variable);272symbol.selectionRange = range_of_node(m.variable->identifier);273if (m.variable->exported) {274symbol.detail += "@export ";275}276symbol.detail += "var " + m.variable->identifier->name;277if (m.get_datatype().is_hard_type()) {278symbol.detail += ": " + m.get_datatype().to_string();279}280if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) {281symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string();282}283284symbol.documentation = m.variable->doc_data.description;285symbol.uri = uri;286symbol.script_path = path;287288if (m.variable->initializer && m.variable->initializer->type == GDScriptParser::Node::LAMBDA) {289GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)m.variable->initializer;290LSP::DocumentSymbol lambda;291parse_function_symbol(lambda_node->function, lambda);292// Merge lambda into current variable.293symbol.children.append_array(lambda.children);294}295296if (m.variable->getter && m.variable->getter->type == GDScriptParser::Node::FUNCTION) {297LSP::DocumentSymbol get_symbol;298parse_function_symbol(m.variable->getter, get_symbol);299get_symbol.local = true;300symbol.children.push_back(get_symbol);301}302if (m.variable->setter && m.variable->setter->type == GDScriptParser::Node::FUNCTION) {303LSP::DocumentSymbol set_symbol;304parse_function_symbol(m.variable->setter, set_symbol);305set_symbol.local = true;306symbol.children.push_back(set_symbol);307}308309r_symbol.children.push_back(symbol);310} break;311case ClassNode::Member::CONSTANT: {312LSP::DocumentSymbol symbol;313314symbol.name = m.constant->identifier->name;315symbol.kind = LSP::SymbolKind::Constant;316symbol.deprecated = false;317symbol.range = range_of_node(m.constant);318symbol.selectionRange = range_of_node(m.constant->identifier);319symbol.documentation = m.constant->doc_data.description;320symbol.uri = uri;321symbol.script_path = path;322323symbol.detail = "const " + symbol.name;324if (m.constant->get_datatype().is_hard_type()) {325symbol.detail += ": " + m.constant->get_datatype().to_string();326}327328const Variant &default_value = m.constant->initializer->reduced_value;329String value_text;330if (default_value.get_type() == Variant::OBJECT) {331Ref<Resource> res = default_value;332if (res.is_valid() && !res->get_path().is_empty()) {333value_text = "preload(\"" + res->get_path() + "\")";334if (symbol.documentation.is_empty()) {335ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(res->get_path());336if (parser) {337symbol.documentation = parser->class_symbol.documentation;338}339}340} else {341value_text = default_value.to_json_string();342}343} else {344value_text = default_value.to_json_string();345}346if (!value_text.is_empty()) {347symbol.detail += " = " + value_text;348}349350r_symbol.children.push_back(symbol);351} break;352case ClassNode::Member::SIGNAL: {353LSP::DocumentSymbol symbol;354symbol.name = m.signal->identifier->name;355symbol.kind = LSP::SymbolKind::Event;356symbol.deprecated = false;357symbol.range = range_of_node(m.signal);358symbol.selectionRange = range_of_node(m.signal->identifier);359symbol.documentation = m.signal->doc_data.description;360symbol.uri = uri;361symbol.script_path = path;362symbol.detail = "signal " + String(m.signal->identifier->name) + "(";363for (int j = 0; j < m.signal->parameters.size(); j++) {364if (j > 0) {365symbol.detail += ", ";366}367symbol.detail += m.signal->parameters[j]->identifier->name;368}369symbol.detail += ")";370371for (GDScriptParser::ParameterNode *param : m.signal->parameters) {372LSP::DocumentSymbol param_symbol;373param_symbol.name = param->identifier->name;374param_symbol.kind = LSP::SymbolKind::Variable;375param_symbol.deprecated = false;376param_symbol.local = true;377param_symbol.range = range_of_node(param);378param_symbol.selectionRange = range_of_node(param->identifier);379param_symbol.uri = uri;380param_symbol.script_path = path;381param_symbol.detail = "var " + param_symbol.name;382if (param->get_datatype().is_hard_type()) {383param_symbol.detail += ": " + param->get_datatype().to_string();384}385symbol.children.push_back(param_symbol);386}387r_symbol.children.push_back(symbol);388} break;389case ClassNode::Member::ENUM_VALUE: {390LSP::DocumentSymbol symbol;391392symbol.name = m.enum_value.identifier->name;393symbol.kind = LSP::SymbolKind::EnumMember;394symbol.deprecated = false;395symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.start_column).to_lsp(lines);396symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.end_column).to_lsp(lines);397symbol.selectionRange = range_of_node(m.enum_value.identifier);398symbol.documentation = m.enum_value.doc_data.description;399symbol.uri = uri;400symbol.script_path = path;401402symbol.detail = symbol.name + " = " + itos(m.enum_value.value);403404r_symbol.children.push_back(symbol);405} break;406case ClassNode::Member::ENUM: {407LSP::DocumentSymbol symbol;408symbol.name = m.m_enum->identifier->name;409symbol.kind = LSP::SymbolKind::Enum;410symbol.range = range_of_node(m.m_enum);411symbol.selectionRange = range_of_node(m.m_enum->identifier);412symbol.documentation = m.m_enum->doc_data.description;413symbol.uri = uri;414symbol.script_path = path;415416symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{";417for (int j = 0; j < m.m_enum->values.size(); j++) {418if (j > 0) {419symbol.detail += ", ";420}421symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value);422}423symbol.detail += "}";424425for (GDScriptParser::EnumNode::Value value : m.m_enum->values) {426LSP::DocumentSymbol child;427428child.name = value.identifier->name;429child.kind = LSP::SymbolKind::EnumMember;430child.deprecated = false;431child.range.start = GodotPosition(value.line, value.start_column).to_lsp(lines);432child.range.end = GodotPosition(value.line, value.end_column).to_lsp(lines);433child.selectionRange = range_of_node(value.identifier);434child.documentation = value.doc_data.description;435child.uri = uri;436child.script_path = path;437438child.detail = child.name + " = " + itos(value.value);439440symbol.children.push_back(child);441}442443r_symbol.children.push_back(symbol);444} break;445case ClassNode::Member::FUNCTION: {446LSP::DocumentSymbol symbol;447parse_function_symbol(m.function, symbol);448r_symbol.children.push_back(symbol);449} break;450case ClassNode::Member::CLASS: {451LSP::DocumentSymbol symbol;452parse_class_symbol(m.m_class, symbol);453r_symbol.children.push_back(symbol);454} break;455case ClassNode::Member::GROUP:456break; // No-op, but silences warnings.457case ClassNode::Member::UNDEFINED:458break; // Unreachable.459}460}461}462463void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, LSP::DocumentSymbol &r_symbol) {464const String uri = get_uri();465466bool is_named = p_func->identifier != nullptr;467468r_symbol.name = is_named ? p_func->identifier->name : "";469r_symbol.kind = (p_func->is_static || p_func->source_lambda != nullptr) ? LSP::SymbolKind::Function : LSP::SymbolKind::Method;470r_symbol.detail = "func";471if (is_named) {472r_symbol.detail += " " + String(p_func->identifier->name);473}474r_symbol.detail += "(";475r_symbol.deprecated = false;476r_symbol.range = range_of_node(p_func);477if (is_named) {478r_symbol.selectionRange = range_of_node(p_func->identifier);479} else {480r_symbol.selectionRange.start = r_symbol.selectionRange.end = r_symbol.range.start;481}482r_symbol.documentation = p_func->doc_data.description;483r_symbol.uri = uri;484r_symbol.script_path = path;485486String parameters;487for (int i = 0; i < p_func->parameters.size(); i++) {488const ParameterNode *parameter = p_func->parameters[i];489if (i > 0) {490parameters += ", ";491}492parameters += String(parameter->identifier->name);493if (parameter->get_datatype().is_hard_type()) {494parameters += ": " + parameter->get_datatype().to_string();495}496if (parameter->initializer != nullptr) {497parameters += " = " + parameter->initializer->reduced_value.to_json_string();498}499}500if (p_func->is_vararg()) {501if (!p_func->parameters.is_empty()) {502parameters += ", ";503}504const ParameterNode *rest_param = p_func->rest_parameter;505parameters += "..." + rest_param->identifier->name + ": " + rest_param->get_datatype().to_string();506}507r_symbol.detail += parameters + ")";508509const DataType return_type = p_func->get_datatype();510if (return_type.is_hard_type()) {511if (return_type.kind == DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {512r_symbol.detail += " -> void";513} else {514r_symbol.detail += " -> " + return_type.to_string();515}516}517518List<GDScriptParser::SuiteNode *> function_nodes;519520List<GDScriptParser::Node *> node_stack;521node_stack.push_back(p_func->body);522523while (!node_stack.is_empty()) {524GDScriptParser::Node *node = node_stack.front()->get();525node_stack.pop_front();526527switch (node->type) {528case GDScriptParser::TypeNode::IF: {529GDScriptParser::IfNode *if_node = (GDScriptParser::IfNode *)node;530node_stack.push_back(if_node->true_block);531if (if_node->false_block) {532node_stack.push_back(if_node->false_block);533}534} break;535536case GDScriptParser::TypeNode::FOR: {537GDScriptParser::ForNode *for_node = (GDScriptParser::ForNode *)node;538node_stack.push_back(for_node->loop);539} break;540541case GDScriptParser::TypeNode::WHILE: {542GDScriptParser::WhileNode *while_node = (GDScriptParser::WhileNode *)node;543node_stack.push_back(while_node->loop);544} break;545546case GDScriptParser::TypeNode::MATCH: {547GDScriptParser::MatchNode *match_node = (GDScriptParser::MatchNode *)node;548for (GDScriptParser::MatchBranchNode *branch_node : match_node->branches) {549node_stack.push_back(branch_node);550}551} break;552553case GDScriptParser::TypeNode::MATCH_BRANCH: {554GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node;555node_stack.push_back(match_node->block);556} break;557558case GDScriptParser::TypeNode::SUITE: {559GDScriptParser::SuiteNode *suite_node = (GDScriptParser::SuiteNode *)node;560function_nodes.push_back(suite_node);561for (int i = 0; i < suite_node->statements.size(); ++i) {562node_stack.push_back(suite_node->statements[i]);563}564} break;565566default:567continue;568}569}570571for (List<GDScriptParser::SuiteNode *>::Element *N = function_nodes.front(); N; N = N->next()) {572const GDScriptParser::SuiteNode *suite_node = N->get();573for (int i = 0; i < suite_node->locals.size(); i++) {574const SuiteNode::Local &local = suite_node->locals[i];575LSP::DocumentSymbol symbol;576symbol.name = local.name;577symbol.kind = local.type == SuiteNode::Local::CONSTANT ? LSP::SymbolKind::Constant : LSP::SymbolKind::Variable;578switch (local.type) {579case SuiteNode::Local::CONSTANT:580symbol.range = range_of_node(local.constant);581symbol.selectionRange = range_of_node(local.constant->identifier);582break;583case SuiteNode::Local::VARIABLE:584symbol.range = range_of_node(local.variable);585symbol.selectionRange = range_of_node(local.variable->identifier);586if (local.variable->initializer && local.variable->initializer->type == GDScriptParser::Node::LAMBDA) {587GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)local.variable->initializer;588LSP::DocumentSymbol lambda;589parse_function_symbol(lambda_node->function, lambda);590// Merge lambda into current variable.591// -> Only interested in new variables, not lambda itself.592symbol.children.append_array(lambda.children);593}594break;595case SuiteNode::Local::PARAMETER:596symbol.range = range_of_node(local.parameter);597symbol.selectionRange = range_of_node(local.parameter->identifier);598break;599case SuiteNode::Local::FOR_VARIABLE:600case SuiteNode::Local::PATTERN_BIND:601symbol.range = range_of_node(local.bind);602symbol.selectionRange = range_of_node(local.bind);603break;604default:605// Fallback.606symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp(get_lines());607symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp(get_lines());608symbol.selectionRange = symbol.range;609break;610}611symbol.local = true;612symbol.uri = uri;613symbol.script_path = path;614symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var ";615symbol.detail += symbol.name;616if (local.get_datatype().is_hard_type()) {617symbol.detail += ": " + local.get_datatype().to_string();618}619switch (local.type) {620case SuiteNode::Local::CONSTANT:621symbol.documentation = local.constant->doc_data.description;622break;623case SuiteNode::Local::VARIABLE:624symbol.documentation = local.variable->doc_data.description;625break;626default:627break;628}629r_symbol.children.push_back(symbol);630}631}632}633634String ExtendGDScriptParser::get_text_for_completion(const LSP::Position &p_cursor) const {635String longthing;636int len = lines.size();637for (int i = 0; i < len; i++) {638if (i == p_cursor.line) {639longthing += lines[i].substr(0, p_cursor.character);640longthing += String::chr(0xFFFF); // Not unicode, represents the cursor.641longthing += lines[i].substr(p_cursor.character);642} else {643longthing += lines[i];644}645646if (i != len - 1) {647longthing += "\n";648}649}650651return longthing;652}653654String ExtendGDScriptParser::get_text_for_lookup_symbol(const LSP::Position &p_cursor, const String &p_symbol, bool p_func_required) const {655String longthing;656int len = lines.size();657for (int i = 0; i < len; i++) {658if (i == p_cursor.line) {659// This code tries to insert the symbol into the preexisting code. Due to using a simple660// algorithm, the results might not always match the option semantically (e.g. different661// identifier name). This is fine because symbol lookup will prioritize the provided662// symbol name over the actual code. Establishing a syntactic target (e.g. identifier)663// is usually sufficient.664665String line = lines[i];666String first_part = line.substr(0, p_cursor.character);667String last_part = line.substr(p_cursor.character, lines[i].length());668if (!p_symbol.is_empty()) {669String left_cursor_text;670for (int c = p_cursor.character - 1; c >= 0; c--) {671left_cursor_text = line.substr(c, p_cursor.character - c);672if (p_symbol.begins_with(left_cursor_text)) {673first_part = line.substr(0, c);674first_part += p_symbol;675break;676} else if (c == 0) {677// No preexisting code that matches the option. Insert option in place.678first_part += p_symbol;679}680}681}682683longthing += first_part;684longthing += String::chr(0xFFFF); // Not unicode, represents the cursor.685if (p_func_required) {686longthing += "("; // Tell the parser this is a function call.687}688longthing += last_part;689} else {690longthing += lines[i];691}692693if (i != len - 1) {694longthing += "\n";695}696}697698return longthing;699}700701String ExtendGDScriptParser::get_identifier_under_position(const LSP::Position &p_position, LSP::Range &r_range) const {702ERR_FAIL_INDEX_V(p_position.line, lines.size(), "");703String line = lines[p_position.line];704if (line.is_empty()) {705return "";706}707ERR_FAIL_INDEX_V(p_position.character, line.size(), "");708709// `p_position` cursor is BETWEEN chars, not ON chars.710// ->711// ```gdscript712// var member| := some_func|(some_variable|)713// ^ ^ ^714// | | | cursor on `some_variable, position on `)`715// | |716// | | cursor on `some_func`, pos on `(`717// |718// | cursor on `member`, pos on ` ` (space)719// ```720// -> Move position to previous character if:721// * Position not on valid identifier char.722// * Prev position is valid identifier char.723LSP::Position pos = p_position;724if (725pos.character >= line.length() // Cursor at end of line.726|| (!is_unicode_identifier_continue(line[pos.character]) // Not on valid identifier char.727&& (pos.character > 0 // Not line start -> there is a prev char.728&& is_unicode_identifier_continue(line[pos.character - 1]) // Prev is valid identifier char.729))) {730pos.character--;731}732733int start_pos = pos.character;734for (int c = pos.character; c >= 0; c--) {735start_pos = c;736char32_t ch = line[c];737bool valid_char = is_unicode_identifier_continue(ch);738if (!valid_char) {739break;740}741}742743int end_pos = pos.character;744for (int c = pos.character; c < line.length(); c++) {745char32_t ch = line[c];746bool valid_char = is_unicode_identifier_continue(ch);747if (!valid_char) {748break;749}750end_pos = c;751}752753if (!is_unicode_identifier_start(line[start_pos + 1])) {754return "";755}756757if (start_pos < end_pos) {758r_range.start.line = r_range.end.line = pos.line;759r_range.start.character = start_pos + 1;760r_range.end.character = end_pos + 1;761return line.substr(start_pos + 1, end_pos - start_pos);762}763764return "";765}766767String ExtendGDScriptParser::get_uri() const {768return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);769}770771const LSP::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const LSP::DocumentSymbol &p_parent, const String &p_symbol_name) const {772const LSP::DocumentSymbol *ret = nullptr;773if (p_line < p_parent.range.start.line) {774return ret;775} else if (p_parent.range.start.line == p_line && (p_symbol_name.is_empty() || p_parent.name == p_symbol_name)) {776return &p_parent;777} else {778for (int i = 0; i < p_parent.children.size(); i++) {779ret = search_symbol_defined_at_line(p_line, p_parent.children[i], p_symbol_name);780if (ret) {781break;782}783}784}785return ret;786}787788Error ExtendGDScriptParser::get_left_function_call(const LSP::Position &p_position, LSP::Position &r_func_pos, int &r_arg_index) const {789ERR_FAIL_INDEX_V(p_position.line, lines.size(), ERR_INVALID_PARAMETER);790791int bracket_stack = 0;792int index = 0;793794bool found = false;795for (int l = p_position.line; l >= 0; --l) {796String line = lines[l];797int c = line.length() - 1;798if (l == p_position.line) {799c = MIN(c, p_position.character - 1);800}801802while (c >= 0) {803const char32_t &character = line[c];804if (character == ')') {805++bracket_stack;806} else if (character == '(') {807--bracket_stack;808if (bracket_stack < 0) {809found = true;810}811}812if (bracket_stack <= 0 && character == ',') {813++index;814}815--c;816if (found) {817r_func_pos.character = c;818break;819}820}821822if (found) {823r_func_pos.line = l;824r_arg_index = index;825return OK;826}827}828829return ERR_METHOD_NOT_FOUND;830}831832const LSP::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line, const String &p_symbol_name) const {833if (p_line <= 0) {834return &class_symbol;835}836return search_symbol_defined_at_line(p_line, class_symbol, p_symbol_name);837}838839const LSP::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const {840if (p_subclass.is_empty()) {841const LSP::DocumentSymbol *const *ptr = members.getptr(p_name);842if (ptr) {843return *ptr;844}845} else {846if (const ClassMembers *_class = inner_classes.getptr(p_subclass)) {847const LSP::DocumentSymbol *const *ptr = _class->getptr(p_name);848if (ptr) {849return *ptr;850}851}852}853854return nullptr;855}856857const List<LSP::DocumentLink> &ExtendGDScriptParser::get_document_links() const {858return document_links;859}860861const Array &ExtendGDScriptParser::get_member_completions() {862if (member_completions.is_empty()) {863for (const KeyValue<String, const LSP::DocumentSymbol *> &E : members) {864const LSP::DocumentSymbol *symbol = E.value;865LSP::CompletionItem item = symbol->make_completion_item();866item.data = JOIN_SYMBOLS(path, E.key);867member_completions.push_back(item.to_json());868}869870for (const KeyValue<String, ClassMembers> &E : inner_classes) {871const ClassMembers *inner_class = &E.value;872873for (const KeyValue<String, const LSP::DocumentSymbol *> &F : *inner_class) {874const LSP::DocumentSymbol *symbol = F.value;875LSP::CompletionItem item = symbol->make_completion_item();876item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(E.key, F.key));877member_completions.push_back(item.to_json());878}879}880}881882return member_completions;883}884885Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {886ERR_FAIL_NULL_V(p_func, Dictionary());887Dictionary func;888func["name"] = p_func->identifier->name;889func["return_type"] = p_func->get_datatype().to_string();890func["rpc_config"] = p_func->rpc_config;891Array parameters;892for (int i = 0; i < p_func->parameters.size(); i++) {893Dictionary arg;894arg["name"] = p_func->parameters[i]->identifier->name;895arg["type"] = p_func->parameters[i]->get_datatype().to_string();896if (p_func->parameters[i]->initializer != nullptr) {897arg["default_value"] = p_func->parameters[i]->initializer->reduced_value;898}899parameters.push_back(arg);900}901if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->start_line))) {902func["signature"] = symbol->detail;903func["description"] = symbol->documentation;904}905func["arguments"] = parameters;906return func;907}908909Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode *p_class) const {910ERR_FAIL_NULL_V(p_class, Dictionary());911Dictionary class_api;912913class_api["name"] = p_class->identifier != nullptr ? String(p_class->identifier->name) : String();914class_api["path"] = path;915Array extends_class;916for (int i = 0; i < p_class->extends.size(); i++) {917extends_class.append(String(p_class->extends[i]->name));918}919class_api["extends_class"] = extends_class;920class_api["extends_file"] = String(p_class->extends_path);921class_api["icon"] = String(p_class->icon_path);922923if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->start_line))) {924class_api["signature"] = symbol->detail;925class_api["description"] = symbol->documentation;926}927928Array nested_classes;929Array constants;930Array class_members;931Array signals;932Array methods;933Array static_functions;934935for (int i = 0; i < p_class->members.size(); i++) {936const ClassNode::Member &m = p_class->members[i];937switch (m.type) {938case ClassNode::Member::CLASS:939nested_classes.push_back(dump_class_api(m.m_class));940break;941case ClassNode::Member::CONSTANT: {942Dictionary api;943api["name"] = m.constant->identifier->name;944api["value"] = m.constant->initializer->reduced_value;945api["data_type"] = m.constant->get_datatype().to_string();946if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.constant->start_line))) {947api["signature"] = symbol->detail;948api["description"] = symbol->documentation;949}950constants.push_back(api);951} break;952case ClassNode::Member::ENUM_VALUE: {953Dictionary api;954api["name"] = m.enum_value.identifier->name;955api["value"] = m.enum_value.value;956api["data_type"] = m.get_datatype().to_string();957if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.enum_value.line))) {958api["signature"] = symbol->detail;959api["description"] = symbol->documentation;960}961constants.push_back(api);962} break;963case ClassNode::Member::ENUM: {964Dictionary enum_dict;965for (int j = 0; j < m.m_enum->values.size(); j++) {966enum_dict[m.m_enum->values[j].identifier->name] = m.m_enum->values[j].value;967}968969Dictionary api;970api["name"] = m.m_enum->identifier->name;971api["value"] = enum_dict;972api["data_type"] = m.get_datatype().to_string();973if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.m_enum->start_line))) {974api["signature"] = symbol->detail;975api["description"] = symbol->documentation;976}977constants.push_back(api);978} break;979case ClassNode::Member::VARIABLE: {980Dictionary api;981api["name"] = m.variable->identifier->name;982api["data_type"] = m.variable->get_datatype().to_string();983api["default_value"] = m.variable->initializer != nullptr ? m.variable->initializer->reduced_value : Variant();984api["setter"] = m.variable->setter ? ("@" + String(m.variable->identifier->name) + "_setter") : (m.variable->setter_pointer != nullptr ? String(m.variable->setter_pointer->name) : String());985api["getter"] = m.variable->getter ? ("@" + String(m.variable->identifier->name) + "_getter") : (m.variable->getter_pointer != nullptr ? String(m.variable->getter_pointer->name) : String());986api["export"] = m.variable->exported;987if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.variable->start_line))) {988api["signature"] = symbol->detail;989api["description"] = symbol->documentation;990}991class_members.push_back(api);992} break;993case ClassNode::Member::SIGNAL: {994Dictionary api;995api["name"] = m.signal->identifier->name;996Array pars;997for (int j = 0; j < m.signal->parameters.size(); j++) {998pars.append(String(m.signal->parameters[j]->identifier->name));999}1000api["arguments"] = pars;1001if (const LSP::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) {1002api["signature"] = symbol->detail;1003api["description"] = symbol->documentation;1004}1005signals.push_back(api);1006} break;1007case ClassNode::Member::FUNCTION: {1008if (m.function->is_static) {1009static_functions.append(dump_function_api(m.function));1010} else {1011methods.append(dump_function_api(m.function));1012}1013} break;1014case ClassNode::Member::GROUP:1015break; // No-op, but silences warnings.1016case ClassNode::Member::UNDEFINED:1017break; // Unreachable.1018}1019}10201021class_api["sub_classes"] = nested_classes;1022class_api["constants"] = constants;1023class_api["members"] = class_members;1024class_api["signals"] = signals;1025class_api["methods"] = methods;1026class_api["static_functions"] = static_functions;10271028return class_api;1029}10301031Dictionary ExtendGDScriptParser::generate_api() const {1032Dictionary api;1033if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(get_tree())) {1034api = dump_class_api(gdclass);1035}1036return api;1037}10381039void ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {1040path = p_path;1041lines = p_code.split("\n");10421043parse_result = GDScriptParser::parse(p_code, p_path, false);1044GDScriptAnalyzer analyzer(this);10451046if (parse_result == OK) {1047parse_result = analyzer.analyze();1048}1049update_diagnostics();1050update_symbols();1051update_document_links(p_code);1052}105310541055