diff options
Diffstat (limited to 'modules/gdscript/gdscript_editor.cpp')
-rw-r--r-- | modules/gdscript/gdscript_editor.cpp | 219 |
1 files changed, 134 insertions, 85 deletions
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 12c10642ec..f3b1266bda 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -30,9 +30,6 @@ #include "gdscript.h" -#include "core/config/engine.h" -#include "core/core_constants.h" -#include "core/io/file_access.h" #include "gdscript_analyzer.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" @@ -40,10 +37,17 @@ #include "gdscript_utility_functions.h" #ifdef TOOLS_ENABLED +#include "editor/script_templates/templates.gen.h" +#endif + +#include "core/config/engine.h" +#include "core/core_constants.h" +#include "core/io/file_access.h" + +#ifdef TOOLS_ENABLED #include "core/config/project_settings.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" -#include "editor/script_templates/templates.gen.h" #endif void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { @@ -54,6 +58,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); + p_delimiters->push_back("''' '''"); } bool GDScriptLanguage::is_using_templates() { @@ -73,9 +78,11 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri .replace(": String", "") .replace(": Array[String]", "") .replace(": float", "") + .replace(": CharFXTransform", "") .replace(":=", "=") .replace(" -> String", "") .replace(" -> int", "") + .replace(" -> bool", "") .replace(" -> void", ""); } @@ -578,29 +585,34 @@ static int _get_enum_constant_location(StringName p_class, StringName p_enum_con // END LOCATION METHODS -static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { - if (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { - String enum_name = p_info.class_name; - if (!enum_name.contains(".")) { - return enum_name; +static String _trim_parent_class(const String &p_class, const String &p_base_class) { + if (p_base_class.is_empty()) { + return p_class; + } + Vector<String> names = p_class.split(".", false, 1); + if (names.size() == 2) { + String first = names[0]; + String rest = names[1]; + if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) { + return rest; } - return enum_name.get_slice(".", 1); } + return p_class; +} - String n = p_info.name; - int idx = n.find(":"); - if (idx != -1) { - return n.substr(idx + 1, n.length()); - } +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, const String &p_base_class = "") { + String class_name = p_info.class_name; + bool is_enum = p_info.type == Variant::INT && p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM; + // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. - if (p_info.type == Variant::OBJECT) { - if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - return p_info.hint_string; - } else { - return p_info.class_name.operator String(); + if ((p_info.type == Variant::OBJECT || is_enum) && !class_name.is_empty()) { + if (is_enum && CoreConstants::is_global_enum(p_info.class_name)) { + return class_name; } - } - if (p_info.type == Variant::NIL) { + return _trim_parent_class(class_name, p_base_class); + } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { + return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]"; + } else if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; } else { @@ -800,6 +812,15 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a warning.insert_text = warning.display.quote(p_quote_style); r_result.insert(warning.display, warning); } + } else if (p_annotation->name == SNAME("@rpc")) { + if (p_argument == 0 || p_argument == 1 || p_argument == 2) { + static const char *options[7] = { "call_local", "call_remote", "any_peer", "authority", "reliable", "unreliable", "unreliable_ordered" }; + for (int i = 0; i < 7; i++) { + ScriptLanguage::CodeCompletionOption option(options[i], ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + option.insert_text = option.display.quote(p_quote_style); + r_result.insert(option.display, option); + } + } } } @@ -889,19 +910,20 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } } -static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { +static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth = 0) { for (int i = 0; i < p_suite->locals.size(); i++) { ScriptLanguage::CodeCompletionOption option; + int location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { - option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_LOCAL); + option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); option.default_value = p_suite->locals[i].constant->initializer->reduced_value; } else { - option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, ScriptLanguage::LOCATION_LOCAL); + option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, location); } r_result.insert(option.display, option); } if (p_suite->parent_block) { - _find_identifiers_in_suite(p_suite->parent_block, r_result); + _find_identifiers_in_suite(p_suite->parent_block, r_result, p_recursion_depth + 1); } } @@ -916,7 +938,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, int classes_processed = 0; while (clss) { for (int i = 0; i < clss->members.size(); i++) { - const int location = (classes_processed + p_recursion_depth) | ScriptLanguage::LOCATION_PARENT_MASK; + const int location = p_recursion_depth == 0 ? classes_processed : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); const GDScriptParser::ClassNode::Member &member = clss->members[i]; ScriptLanguage::CodeCompletionOption option; switch (member.type) { @@ -968,7 +990,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, } break; case GDScriptParser::ClassNode::Member::SIGNAL: - if (p_only_functions || outer) { + if (p_only_functions || outer || p_static) { continue; } option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); @@ -1008,7 +1030,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base while (!base_type.has_no_type()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { - _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1); + _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth); // This already finds all parent identifiers, so we are done. base_type = GDScriptParser::DataType(); } break; @@ -1024,6 +1046,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } + + List<MethodInfo> signals; + scr->get_script_signal_list(&signals); + for (const MethodInfo &E : signals) { + int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); + r_result.insert(option.display, option); + } } HashMap<StringName, Variant> constants; scr->get_constants(&constants); @@ -1032,14 +1062,6 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); r_result.insert(option.display, option); } - - List<MethodInfo> signals; - scr->get_script_signal_list(&signals); - for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); - r_result.insert(option.display, option); - } } List<MethodInfo> methods; @@ -1084,14 +1106,6 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base r_result.insert(option.display, option); } - List<MethodInfo> signals; - ClassDB::get_signal_list(type, &signals); - for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(type, StringName(E.name)); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); - r_result.insert(option.display, option); - } - if (!base_type.is_meta_type || Engine::get_singleton()->has_singleton(type)) { List<PropertyInfo> pinfo; ClassDB::get_property_list(type, &pinfo); @@ -1106,6 +1120,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } + + List<MethodInfo> signals; + ClassDB::get_signal_list(type, &signals); + for (const MethodInfo &E : signals) { + int location = p_recursion_depth + _get_signal_location(type, StringName(E.name)); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); + r_result.insert(option.display, option); + } } } @@ -1188,7 +1210,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context } if (p_context.current_class) { - _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); + _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth); } List<StringName> functions; @@ -1344,6 +1366,21 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr return ci; } +#define MAX_COMPLETION_RECURSION 100 +struct RecursionCheck { + int *counter; + _FORCE_INLINE_ bool check() { + return (*counter) > MAX_COMPLETION_RECURSION; + } + RecursionCheck(int *p_counter) : + counter(p_counter) { + (*counter)++; + } + ~RecursionCheck() { + (*counter)--; + } +}; + static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); @@ -1376,6 +1413,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, return false; } + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + if (p_expression->is_constant) { // Already has a value, so just use that. r_type = _type_from_variant(p_expression->reduced_value); @@ -1846,6 +1889,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + // Look in blocks first. int last_assign_line = -1; const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; @@ -1909,21 +1958,19 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::BINARY_OPERATOR && static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition)->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { + if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::TYPE_TEST) { // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. // Super dirty hack, but very useful. // Credit: Zylann. // TODO: this could be hacked to detect ANDed conditions too... - const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition); - if (op->left_operand && op->right_operand && op->left_operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->left_operand)->name == p_identifier) { + const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition); + if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier) { // Bingo. GDScriptParser::CompletionContext c = p_context; - c.current_line = op->left_operand->start_line; + c.current_line = type_test->operand->start_line; c.current_suite = suite; - GDScriptCompletionIdentifier is_type; - if (_guess_expression_type(c, op->right_operand, is_type)) { - id_type = is_type.type; - id_type.is_meta_type = false; + if (type_test->test_datatype.is_hard_type()) { + id_type = type_test->test_datatype; if (last_assign_line < c.current_line) { // Override last assignment. last_assign_line = c.current_line; @@ -2022,6 +2069,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); + r_type.type.is_meta_type = true; r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); @@ -2064,6 +2112,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; while (base_type.is_set()) { @@ -2133,6 +2187,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.type.class_type = member.m_class; + r_type.type.is_meta_type = true; return true; case GDScriptParser::ClassNode::Member::GROUP: return false; // No-op, but silences warnings. @@ -2276,6 +2331,12 @@ static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_cont } static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; @@ -2936,6 +2997,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c List<MethodInfo> virtual_methods; ClassDB::get_virtual_methods(class_name, &virtual_methods); + + { + // Not truly a virtual method, but can also be "overridden". + MethodInfo static_init("_static_init"); + static_init.return_val.type = Variant::NIL; + static_init.flags |= METHOD_FLAG_STATIC | METHOD_FLAG_VIRTUAL; + virtual_methods.push_back(static_init); + } + for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; if (method_hint.contains(":")) { @@ -2953,26 +3023,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c arg = arg.substr(0, arg.find(":")); } method_hint += arg; - if (use_type_hint && mi.arguments[i].type != Variant::NIL) { - method_hint += ": "; - if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) { - method_hint += mi.arguments[i].class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.arguments[i].type); - } + if (use_type_hint) { + method_hint += ": " + _get_visual_datatype(mi.arguments[i], true, class_name); } } } method_hint += ")"; - if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { - method_hint += " -> "; - if (mi.return_val.type == Variant::NIL) { - method_hint += "void"; - } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) { - method_hint += mi.return_val.class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.return_val.type); - } + if (use_type_hint) { + method_hint += " -> " + _get_visual_datatype(mi.return_val, false, class_name); } method_hint += ":"; @@ -3045,12 +3103,7 @@ String GDScriptLanguage::_get_indentation() const { if (use_space_indentation) { int indent_size = EDITOR_GET("text_editor/behavior/indent/size"); - - String space_indent = ""; - for (int i = 0; i < indent_size; i++) { - space_indent += " "; - } - return space_indent; + return String(" ").repeat(indent_size); } } #endif @@ -3097,12 +3150,7 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t } if (i >= p_from_line) { - l = ""; - for (int j = 0; j < indent_stack.size(); j++) { - l += indent; - } - l += st; - + l = indent.repeat(indent_stack.size()) + st; } else if (i > p_to_line) { break; } @@ -3320,10 +3368,10 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; - ClassDB::get_integer_constant(context.current_class->extends[0], p_symbol, &success); + ClassDB::get_integer_constant(context.current_class->extends[0]->name, p_symbol, &success); if (success) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; - r_result.class_name = context.current_class->extends[0]; + r_result.class_name = context.current_class->extends[0]->name; r_result.class_member = p_symbol; return OK; } @@ -3355,7 +3403,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } case GDScriptParser::COMPLETION_ASSIGN: case GDScriptParser::COMPLETION_CALL_ARGUMENTS: - case GDScriptParser::COMPLETION_IDENTIFIER: { + case GDScriptParser::COMPLETION_IDENTIFIER: + case GDScriptParser::COMPLETION_PROPERTY_METHOD: { GDScriptParser::DataType base_type; if (context.current_class) { if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { |