diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 815a7e74b2fc..fdefa1fc9236 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -340,6 +340,18 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) { completion_call_stack.back()->get().argument = p_argument; } +void GDScriptParser::make_refactor_context(GDScriptParser::RefactorSymbolType p_type, Node *p_node) { + if (!for_refactor) { + return; + } + RefactorContext context; + context.type = p_type; + context.current_line = tokenizer->get_cursor_line(); + context.node = p_node; + context.parser = this; + refactor_context = context; +} + Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, ParserContext p_context, bool p_parse_body) { clear(); @@ -486,6 +498,9 @@ GDScriptTokenizer::Token GDScriptParser::advance() { passed_cursor = true; } } + if (for_refactor && tokenizer->is_past_cursor()) { + passed_cursor = true; + } previous = current; current = tokenizer->scan(); while (current.type == GDScriptTokenizer::Token::ERROR) { @@ -926,6 +941,7 @@ void GDScriptParser::parse_extends() { } make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_INHERIT_TYPE, current_class); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) { return; @@ -934,6 +950,8 @@ void GDScriptParser::parse_extends() { while (match(GDScriptTokenizer::Token::PERIOD)) { make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_INHERIT_TYPE, current_class); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) { return; } @@ -1090,6 +1108,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { default: // Display a completion with identifiers. make_completion_context(COMPLETION_IDENTIFIER, nullptr); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_IDENTIFIER, nullptr); + advance(); push_error(vformat(R"(Unexpected %s in class body.)", previous.get_debug_name())); break; @@ -1138,6 +1158,8 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, b } else { if (p_allow_property) { make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_DECLARATION_OR_TYPE, variable); + if (check(GDScriptTokenizer::Token::IDENTIFIER)) { // Check if get or set. if (current.get_identifier() == "get" || current.get_identifier() == "set") { @@ -1185,6 +1207,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var VariableNode *property = p_variable; make_completion_context(COMPLETION_PROPERTY_DECLARATION, property); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_DECLARATION, property); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) { complete_extents(p_variable); @@ -1295,7 +1318,10 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { } case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")"); + make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_METHOD, p_variable); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) { p_variable->setter_pointer = parse_identifier(); } @@ -1336,7 +1362,10 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { } case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")"); + make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_PROPERTY_METHOD, p_variable); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) { p_variable->getter_pointer = parse_identifier(); } @@ -1401,6 +1430,8 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { } else { // Parse type. make_completion_context(COMPLETION_TYPE_NAME, parameter); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME, parameter); + parameter->datatype_specifier = parse_type(); } } @@ -1592,6 +1623,8 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, p_function); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME_OR_VOID, p_function); + p_function->return_type = parse_type(true); if (p_function->return_type == nullptr) { push_error(R"(Expected return type or "void" after "->".)"); @@ -1616,6 +1649,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) { FunctionNode *function = alloc_node(); make_completion_context(COMPLETION_OVERRIDE_METHOD, function); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_OVERRIDE_METHOD, function); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { complete_extents(function); @@ -1656,6 +1690,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali annotation->name = previous.literal; make_completion_context(COMPLETION_ANNOTATION, annotation); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ANNOTATION, annotation); bool valid = true; @@ -1693,6 +1728,8 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali int argument_index = 0; do { make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ANNOTATION_ARGUMENTS, annotation); + set_last_completion_call_arg(argument_index); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. @@ -2574,6 +2611,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr // Completion can appear whenever an expression is expected. make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_IDENTIFIER, nullptr); GDScriptTokenizer::Token token = current; GDScriptTokenizer::Token::Type token_type = token.type; @@ -2598,6 +2636,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr // HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion. if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast(&GDScriptParser::parse_identifier)) { make_completion_context(COMPLETION_IDENTIFIER, previous_operand); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_IDENTIFIER, previous_operand); } #endif @@ -2702,7 +2741,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ literal->value = previous.literal; reset_extents(literal, p_previous_operand); update_extents(literal); + make_completion_context(COMPLETION_NONE, literal, -1); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_NONE, literal); + complete_extents(literal); return literal; } @@ -2981,6 +3023,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode update_extents(assignment); make_completion_context(COMPLETION_ASSIGN, assignment); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ASSIGN, assignment); + switch (previous.type) { case GDScriptTokenizer::Token::EQUAL: assignment->operation = AssignmentNode::OP_NONE; @@ -3204,11 +3248,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * Variant::Type builtin_type = get_builtin_type(id->name); if (builtin_type < Variant::VARIANT_MAX) { make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, nullptr); + is_builtin = true; } } if (!is_builtin) { make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ATTRIBUTE, attribute); } } @@ -3235,6 +3282,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * update_extents(subscript); make_completion_context(COMPLETION_SUBSCRIPT, subscript); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_SUBSCRIPT, subscript); subscript->base = p_previous_operand; subscript->index = parse_expression(false); @@ -3296,7 +3344,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre } } else { consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); + make_completion_context(COMPLETION_SUPER_METHOD, call, true); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_SUPER_METHOD, call); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) { pop_multiline(); complete_extents(call); @@ -3314,14 +3365,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*"); } else if (call->callee->type == Node::IDENTIFIER) { call->function_name = static_cast(call->callee)->name; + make_completion_context(COMPLETION_METHOD, call->callee); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_METHOD, call->callee); } else if (call->callee->type == Node::SUBSCRIPT) { SubscriptNode *attribute = static_cast(call->callee); if (attribute->is_attribute) { if (attribute->attribute) { call->function_name = attribute->attribute->name; } + make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ATTRIBUTE_METHOD, call->callee); } else { // TODO: The analyzer can see if this is actually a Callable and give better error message. push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*"); @@ -3332,14 +3387,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre } // Arguments. - CompletionType ct = COMPLETION_CALL_ARGUMENTS; + CompletionType completion_type = COMPLETION_CALL_ARGUMENTS; + RefactorSymbolType symbol_type = REFACTOR_SYMBOL_CALL_ARGUMENTS; if (call->function_name == SNAME("load")) { - ct = COMPLETION_RESOURCE_PATH; + completion_type = COMPLETION_RESOURCE_PATH; + symbol_type = REFACTOR_SYMBOL_CALL_ARGUMENTS; } push_completion_call(call); int argument_index = 0; do { - make_completion_context(ct, call, argument_index); + make_completion_context(completion_type, call, argument_index); + make_refactor_context(symbol_type, call); + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; @@ -3351,11 +3410,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre call->arguments.push_back(argument); if (argument->type == Node::LITERAL) { - override_completion_context(argument, ct, call, argument_index); + override_completion_context(argument, completion_type, call, argument_index); } } - ct = COMPLETION_CALL_ARGUMENTS; + completion_type = COMPLETION_CALL_ARGUMENTS; argument_index++; } while (match(GDScriptTokenizer::Token::COMMA)); pop_completion_call(); @@ -3370,6 +3429,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { // We want code completion after a DOLLAR even if the current code is invalid. make_completion_context(COMPLETION_GET_NODE, nullptr, -1); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_GET_NODE, nullptr); if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name())); @@ -3427,6 +3487,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p } make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_GET_NODE, get_node); if (match(GDScriptTokenizer::Token::LITERAL)) { if (previous.literal.get_type() != Variant::STRING) { @@ -3484,6 +3545,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)"); make_completion_context(COMPLETION_RESOURCE_PATH, preload); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_RESOURCE_PATH, preload); + push_completion_call(preload); preload->path = parse_expression(false); @@ -3649,7 +3712,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNo GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { TypeNode *type = alloc_node(); + make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type); + make_refactor_context(p_allow_void ? RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME_OR_VOID : RefactorSymbolType::REFACTOR_SYMBOL_TYPE_NAME, type); + if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { if (match(GDScriptTokenizer::Token::VOID)) { if (p_allow_void) { @@ -3696,6 +3762,8 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { int chain_index = 1; while (match(GDScriptTokenizer::Token::PERIOD)) { make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++); + make_refactor_context(RefactorSymbolType::REFACTOR_SYMBOL_ATTRIBUTE, type); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) { type_element = parse_identifier(); type->type_chain.push_back(type_element); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index c195bb3a05fd..d6047fc31ad0 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1285,6 +1285,7 @@ class GDScriptParser { } }; + // Completion. enum CompletionType { COMPLETION_NONE, COMPLETION_ANNOTATION, // Annotation (following @). @@ -1329,6 +1330,46 @@ class GDScriptParser { int argument = -1; }; + // Refactor. + enum RefactorSymbolType { + REFACTOR_SYMBOL_NONE, + REFACTOR_SYMBOL_ANNOTATION, // Annotation (following @). + REFACTOR_SYMBOL_ANNOTATION_ARGUMENTS, // Annotation arguments hint. + REFACTOR_SYMBOL_ASSIGN, // Assignment based on type (e.g. enum values). + REFACTOR_SYMBOL_ATTRIBUTE, // After id.| to look for members. + REFACTOR_SYMBOL_ATTRIBUTE_METHOD, // After id.| to look for methods. + REFACTOR_SYMBOL_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, // Constants inside a built-in type (e.g. Color.BLUE) or static methods (e.g. Color.html). + REFACTOR_SYMBOL_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions). + // TODO: REFACTOR_SYMBOL_DECLARATION, // Potential declaration (var, const, func). + REFACTOR_SYMBOL_GET_NODE, // Get node with $ notation. + REFACTOR_SYMBOL_IDENTIFIER, // List available identifiers in scope. + REFACTOR_SYMBOL_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index. + REFACTOR_SYMBOL_METHOD, // List available methods in scope. + REFACTOR_SYMBOL_OVERRIDE_METHOD, // Override implementation, also for native virtuals. + REFACTOR_SYMBOL_PROPERTY_DECLARATION, // Property declaration (get, set). + REFACTOR_SYMBOL_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint. + REFACTOR_SYMBOL_PROPERTY_METHOD, // Property setter or getter (list available methods). + REFACTOR_SYMBOL_RESOURCE_PATH, // For load/preload. + REFACTOR_SYMBOL_SUBSCRIPT, // Inside id[|]. + REFACTOR_SYMBOL_SUPER_METHOD, // After super. + REFACTOR_SYMBOL_TYPE_ATTRIBUTE, // Attribute in type name (Type.|). + REFACTOR_SYMBOL_TYPE_NAME, // Name of type (after :). + REFACTOR_SYMBOL_TYPE_NAME_OR_VOID, // Same as TYPE_NAME, but allows void (in function return type). + }; + + struct RefactorContext { + RefactorSymbolType type = REFACTOR_SYMBOL_NONE; + ClassNode *current_class = nullptr; + FunctionNode *current_function = nullptr; + SuiteNode *current_suite = nullptr; + int current_line = -1; + int current_column = -1; + Node *node = nullptr; + Object *base = nullptr; + GDScriptParser *parser = nullptr; + }; + + // Parser context. enum ParserContext { PARSER_CONTEXT_STANDARD = 1 << 0, PARSER_CONTEXT_COMPLETION = 1 << 1, @@ -1386,6 +1427,8 @@ class GDScriptParser { bool in_lambda = false; bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed. + RefactorContext refactor_context; + typedef bool (GDScriptParser::*AnnotationAction)(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); struct AnnotationInfo { enum TargetKind { @@ -1482,6 +1525,8 @@ class GDScriptParser { void pop_completion_call(); void set_last_completion_call_arg(int p_argument); + void make_refactor_context(RefactorSymbolType p_type, Node *p_node); + GDScriptTokenizer::Token advance(); bool match(GDScriptTokenizer::Token::Type p_token_type); bool check(GDScriptTokenizer::Token::Type p_token_type) const;