Selaa lähdekoodia

Reintroduce code completion

George Marques 5 vuotta sitten
vanhempi
commit
aa09b4f85d

+ 1 - 0
editor/code_editor.cpp

@@ -1778,6 +1778,7 @@ CodeTextEditor::CodeTextEditor() {
 	cs.push_back("(");
 	cs.push_back("=");
 	cs.push_back("$");
+	cs.push_back("@");
 	text_editor->set_completion(true, cs);
 	idle->connect("timeout", callable_mp(this, &CodeTextEditor::_text_changed_idle_timeout));
 

+ 1 - 1
modules/gdscript/gdscript.cpp

@@ -471,7 +471,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
 
 						members_cache.push_back(member.variable->export_info);
 						Variant default_value;
-						if (member.variable->initializer->is_constant) {
+						if (member.variable->initializer && member.variable->initializer->is_constant) {
 							default_value = member.variable->initializer->reduced_value;
 						}
 						member_default_values_cache[member.variable->identifier->name] = default_value;

+ 14 - 0
modules/gdscript/gdscript_analyzer.cpp

@@ -1134,6 +1134,10 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
 void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression) {
 	// This one makes some magic happen.
 
+	if (p_expression == nullptr) {
+		return;
+	}
+
 	if (p_expression->reduced) {
 		// Don't do this more than once.
 		return;
@@ -1248,6 +1252,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
 	reduce_expression(p_assignment->assignee);
 	reduce_expression(p_assignment->assigned_value);
 
+	if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) {
+		return;
+	}
+
 	if (p_assignment->assignee->get_datatype().is_constant) {
 		push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
 	}
@@ -2038,6 +2046,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 
 	// Reduce index first. If it's a constant StringName, use attribute instead.
 	if (!p_subscript->is_attribute) {
+		if (p_subscript->index == nullptr) {
+			return;
+		}
 		reduce_expression(p_subscript->index);
 
 		if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) {
@@ -2053,6 +2064,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 	}
 
 	if (p_subscript->is_attribute) {
+		if (p_subscript->attribute == nullptr) {
+			return;
+		}
 		if (p_subscript->base->is_constant) {
 			// Just try to get it.
 			bool valid = false;

+ 5 - 4
modules/gdscript/gdscript_compiler.cpp

@@ -165,10 +165,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
 			}
 
 		} break;
-		default: {
+		case GDScriptParser::DataType::UNRESOLVED: {
 			ERR_PRINT("Parser bug: converting unresolved type.");
 			return GDScriptDataType();
 		}
+		default:
+			break; // FIXME
 	}
 
 	return result;
@@ -1325,10 +1327,9 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 					return -1;
 				}
 
-				// FIXME: Actually check type.
-				GDScriptDataType assign_type; // = _gdtype_from_datatype(on->arguments[0]->get_datatype());
+				GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype());
 
-				if (assign_type.has_type && !assignment->get_datatype().is_set()) {
+				if (assign_type.has_type && !assignment->assigned_value->get_datatype().is_variant()) {
 					// Typed assignment
 					switch (assign_type.kind) {
 						case GDScriptDataType::BUILTIN: {

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2192 - 1
modules/gdscript/gdscript_editor.cpp


+ 197 - 11
modules/gdscript/gdscript_parser.cpp

@@ -41,6 +41,10 @@
 #include "core/string_builder.h"
 #endif // DEBUG_ENABLED
 
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif // TOOLS_ENABLED
+
 static HashMap<StringName, Variant::Type> builtin_types;
 Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
 	if (builtin_types.empty()) {
@@ -99,6 +103,14 @@ GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringNam
 	return GDScriptFunctions::FUNC_MAX;
 }
 
+void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
+	List<StringName> keys;
+	valid_annotations.get_key_list(&keys);
+	for (const List<StringName>::Element *E = keys.front(); E != nullptr; E = E->next()) {
+		r_annotations->push_back(valid_annotations[E->get()].info);
+	}
+}
+
 GDScriptParser::GDScriptParser() {
 	// Register valid annotations.
 	// TODO: Should this be static?
@@ -239,9 +251,114 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
 	}
 }
 
+void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
+	if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+		return;
+	}
+	if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+		return;
+	}
+	CompletionContext context;
+	context.type = p_type;
+	context.current_class = current_class;
+	context.current_function = current_function;
+	context.current_suite = current_suite;
+	context.current_line = tokenizer.get_cursor_line();
+	context.current_argument = p_argument;
+	context.node = p_node;
+	completion_context = context;
+}
+
+void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {
+	if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+		return;
+	}
+	if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+		return;
+	}
+	CompletionContext context;
+	context.type = p_type;
+	context.current_class = current_class;
+	context.current_function = current_function;
+	context.current_suite = current_suite;
+	context.current_line = tokenizer.get_cursor_line();
+	context.builtin_type = p_builtin_type;
+	completion_context = context;
+}
+
+void GDScriptParser::push_completion_call(Node *p_call) {
+	if (!for_completion) {
+		return;
+	}
+	CompletionCall call;
+	call.call = p_call;
+	call.argument = 0;
+	completion_call_stack.push_back(call);
+	if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) {
+		completion_call = call;
+	}
+}
+
+void GDScriptParser::pop_completion_call() {
+	if (!for_completion) {
+		return;
+	}
+	ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to pop empty completion call stack");
+	completion_call_stack.pop_back();
+}
+
+void GDScriptParser::set_last_completion_call_arg(int p_argument) {
+	if (!for_completion || passed_cursor) {
+		return;
+	}
+	ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to set argument on empty completion call stack");
+	completion_call_stack.back()->get().argument = p_argument;
+}
+
 Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) {
 	clear();
-	tokenizer.set_source_code(p_source_code);
+
+	String source = p_source_code;
+	int cursor_line = -1;
+	int cursor_column = -1;
+	for_completion = p_for_completion;
+
+	int tab_size = 4;
+#ifdef TOOLS_ENABLED
+	if (EditorSettings::get_singleton()) {
+		tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size");
+	}
+#endif // TOOLS_ENABLED
+
+	if (p_for_completion) {
+		// Remove cursor sentinel char.
+		const Vector<String> lines = p_source_code.split("\n");
+		cursor_line = 1;
+		cursor_column = 1;
+		for (int i = 0; i < lines.size(); i++) {
+			bool found = false;
+			const String &line = lines[i];
+			for (int j = 0; j < line.size(); j++) {
+				if (line[j] == CharType(0xFFFF)) {
+					found = true;
+					break;
+				} else if (line[j] == '\t') {
+					cursor_column += tab_size - 1;
+				}
+				cursor_column++;
+			}
+			if (found) {
+				break;
+			}
+			cursor_line++;
+			cursor_column = 1;
+		}
+
+		source = source.replace_first(String::chr(0xFFFF), String());
+	}
+
+	tokenizer.set_source_code(source);
+	tokenizer.set_cursor_position(cursor_line, cursor_column);
 	script_path = p_script_path;
 	current = tokenizer.scan();
 	// Avoid error as the first token.
@@ -271,6 +388,12 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
 	if (current.type == GDScriptTokenizer::Token::TK_EOF) {
 		ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
 	}
+	if (for_completion && !completion_call_stack.empty()) {
+		if (completion_call.call == nullptr && tokenizer.is_past_cursor()) {
+			completion_call = completion_call_stack.back()->get();
+			passed_cursor = true;
+		}
+	}
 	previous = current;
 	current = tokenizer.scan();
 	while (current.type == GDScriptTokenizer::Token::ERROR) {
@@ -513,6 +636,8 @@ void GDScriptParser::parse_class_name() {
 void GDScriptParser::parse_extends() {
 	current_class->extends_used = true;
 
+	int chain_index = 0;
+
 	if (match(GDScriptTokenizer::Token::LITERAL)) {
 		if (previous.literal.get_type() != Variant::STRING) {
 			push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)", Variant::get_type_name(previous.literal.get_type())));
@@ -525,12 +650,15 @@ void GDScriptParser::parse_extends() {
 		}
 	}
 
+	make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
+
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
 		return;
 	}
 	current_class->extends.push_back(previous.literal);
 
 	while (match(GDScriptTokenizer::Token::PERIOD)) {
+		make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
 		if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
 			return;
 		}
@@ -645,10 +773,13 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
 			// Infer type.
 			variable->infer_datatype = true;
 		} else {
-			if (p_allow_property && check(GDScriptTokenizer::Token::IDENTIFIER)) {
-				// Check if get or set.
-				if (current.get_identifier() == "get" || current.get_identifier() == "set") {
-					return parse_property(variable, false);
+			if (p_allow_property) {
+				make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable);
+				if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
+					// Check if get or set.
+					if (current.get_identifier() == "get" || current.get_identifier() == "set") {
+						return parse_property(variable, false);
+					}
 				}
 			}
 
@@ -685,12 +816,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
 		}
 	}
 
+	VariableNode *property = p_variable;
+
+	make_completion_context(COMPLETION_PROPERTY_DECLARATION, property);
+
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) {
 		return nullptr;
 	}
 
-	VariableNode *property = p_variable;
-
 	IdentifierNode *function = parse_identifier();
 
 	if (check(GDScriptTokenizer::Token::EQUAL)) {
@@ -770,6 +903,7 @@ 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);
 			if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) {
 				p_variable->setter_pointer = parse_identifier();
 			}
@@ -788,6 +922,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
 			break;
 		case VariableNode::PROP_SETGET:
 			consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")");
+			make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
 			if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) {
 				p_variable->getter_pointer = parse_identifier();
 			}
@@ -844,6 +979,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
 			parameter->infer_datatype = true;
 		} else {
 			// Parse type.
+			make_completion_context(COMPLETION_TYPE_NAME, parameter);
 			parameter->datatype_specifier = parse_type();
 		}
 	}
@@ -964,11 +1100,13 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
 		_static = true;
 	}
 
+	FunctionNode *function = alloc_node<FunctionNode>();
+	make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
+
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
 		return nullptr;
 	}
 
-	FunctionNode *function = alloc_node<FunctionNode>();
 	FunctionNode *previous_function = current_function;
 	current_function = function;
 
@@ -1015,6 +1153,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
 	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*");
 
 	if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
+		make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
 		function->return_type = parse_type(true);
 	}
 
@@ -1033,6 +1172,8 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
 
 	annotation->name = previous.literal;
 
+	make_completion_context(COMPLETION_ANNOTATION, annotation);
+
 	bool valid = true;
 
 	if (!valid_annotations.has(annotation->name)) {
@@ -1049,8 +1190,13 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
 
 	if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
 		// Arguments.
+		push_completion_call(annotation);
+		make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
 		if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
+			int argument = 0;
 			do {
+				make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument, true);
+				set_last_completion_call_arg(argument++, true);
 				ExpressionNode *argument = parse_expression(false);
 				if (argument == nullptr) {
 					valid = false;
@@ -1061,6 +1207,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
 
 			consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
 		}
+		pop_completion_call();
 	}
 
 	match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
@@ -1075,7 +1222,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
 void GDScriptParser::clear_unused_annotations() {
 	for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) {
 		AnnotationNode *annotation = E->get();
-		push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name));
+		push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation);
 	}
 
 	annotation_stack.clear();
@@ -1342,6 +1489,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
 
 	SuiteNode *suite = alloc_node<SuiteNode>();
 	suite->add_local(SuiteNode::Local(n_for->variable));
+	suite->parent_for = n_for;
 
 	n_for->loop = parse_suite(R"("for" block)", suite);
 
@@ -1363,6 +1511,7 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
 	consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token));
 
 	n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token));
+	n_if->true_block->parent_if = n_if;
 
 	if (n_if->true_block->has_continue) {
 		current_suite->has_continue = true;
@@ -1722,6 +1871,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
 	IdentifierNode *identifier = alloc_node<IdentifierNode>();
 	identifier->name = previous.get_identifier();
 
+	make_completion_context(COMPLETION_IDENTIFIER, identifier);
+
 	if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
 		const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
 		switch (declaration.type) {
@@ -2000,6 +2151,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
 	}
 
 	AssignmentNode *assignment = alloc_node<AssignmentNode>();
+	make_completion_context(COMPLETION_ASSIGN, assignment);
 	bool has_operator = true;
 	switch (previous.type) {
 		case GDScriptTokenizer::Token::EQUAL:
@@ -2168,11 +2320,26 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p
 GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	SubscriptNode *attribute = alloc_node<SubscriptNode>();
 
+	if (for_completion) {
+		bool is_builtin = false;
+		if (p_previous_operand->type == Node::IDENTIFIER) {
+			const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);
+			Variant::Type builtin_type = get_builtin_type(id->name);
+			if (builtin_type < Variant::VARIANT_MAX) {
+				make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT, builtin_type, true);
+				is_builtin = true;
+			}
+		}
+		if (!is_builtin) {
+			make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true);
+		}
+	}
+
 	attribute->is_attribute = true;
 	attribute->base = p_previous_operand;
 
 	if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
-		return nullptr;
+		return attribute;
 	}
 	attribute->attribute = parse_identifier();
 
@@ -2182,6 +2349,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
 GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) {
 	SubscriptNode *subscript = alloc_node<SubscriptNode>();
 
+	make_completion_context(COMPLETION_SUBSCRIPT, subscript);
+
 	subscript->base = p_previous_operand;
 	subscript->index = parse_expression(false);
 
@@ -2222,6 +2391,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
 			call->function_name = current_function->identifier->name;
 		} else {
 			consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");
+			make_completion_context(COMPLETION_SUPER_METHOD, call);
 			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
 				pop_multiline();
 				return nullptr;
@@ -2251,7 +2421,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
 
 	if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
 		// Arguments.
+		push_completion_call(call);
+		make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0);
+		int argument = 0;
 		do {
+			make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument++);
 			if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
 				// Allow for trailing comma.
 				break;
@@ -2263,6 +2437,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
 				call->arguments.push_back(argument);
 			}
 		} while (match(GDScriptTokenizer::Token::COMMA));
+		pop_completion_call();
 	}
 
 	pop_multiline();
@@ -2278,11 +2453,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
 			return nullptr;
 		}
 		GetNodeNode *get_node = alloc_node<GetNodeNode>();
+		make_completion_context(COMPLETION_GET_NODE, get_node);
 		get_node->string = parse_literal();
 		return get_node;
 	} else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
 		GetNodeNode *get_node = alloc_node<GetNodeNode>();
+		int chain_position = 0;
 		do {
+			make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
 			if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
 				return nullptr;
 			}
@@ -2303,6 +2481,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
 	push_multiline(true);
 	consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)");
 
+	make_completion_context(COMPLETION_RESOURCE_PATH, preload);
+	push_completion_call(preload);
+
 	preload->path = parse_expression(false);
 
 	if (preload->path == nullptr) {
@@ -2332,6 +2513,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
 		}
 	}
 
+	pop_completion_call();
+
 	pop_multiline();
 	consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*");
 
@@ -2355,6 +2538,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNo
 }
 
 GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
+	TypeNode *type = alloc_node<TypeNode>();
+	make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type);
 	if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
 		if (match(GDScriptTokenizer::Token::VOID)) {
 			if (p_allow_void) {
@@ -2368,12 +2553,13 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
 		return nullptr;
 	}
 
-	TypeNode *type = alloc_node<TypeNode>();
 	IdentifierNode *type_element = parse_identifier();
 
 	type->type_chain.push_back(type_element);
 
+	int chain_index = 1;
 	while (match(GDScriptTokenizer::Token::PERIOD)) {
+		make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++);
 		if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) {
 			type_element = parse_identifier();
 			type->type_chain.push_back(type_element);

+ 59 - 1
modules/gdscript/gdscript_parser.h

@@ -565,6 +565,9 @@ public:
 		bool has_member(const StringName &p_name) const {
 			return members_indices.has(p_name);
 		}
+		bool has_function(const StringName &p_name) const {
+			return has_member(p_name) && members[members_indices[p_name]].type == Member::FUNCTION;
+		}
 		template <class T>
 		void add_member(T *p_member_node) {
 			members_indices[p_member_node->identifier->name] = members.size();
@@ -908,7 +911,6 @@ public:
 		FunctionNode *parent_function = nullptr;
 		ForNode *parent_for = nullptr;
 		IfNode *parent_if = nullptr;
-		PatternNode *parent_pattern = nullptr;
 
 		bool has_return = false;
 		bool has_continue = false;
@@ -1011,6 +1013,47 @@ public:
 		}
 	};
 
+	enum CompletionType {
+		COMPLETION_NONE,
+		COMPLETION_ANNOTATION, // Annotation (following @).
+		COMPLETION_ANNOTATION_ARGUMENTS, // Annotation arguments hint.
+		COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values).
+		COMPLETION_ATTRIBUTE, // After id.| to look for members.
+		COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue).
+		COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions).
+		// TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func).
+		COMPLETION_GET_NODE, // Get node with $ notation.
+		COMPLETION_IDENTIFIER, // List available identifiers in scope.
+		COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index.
+		COMPLETION_OVERRIDE_METHOD, // Override implementation, also for native virtuals.
+		COMPLETION_PROPERTY_DECLARATION, // Property declaration (get, set).
+		COMPLETION_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint.
+		COMPLETION_PROPERTY_METHOD, // Property setter or getter (list available methods).
+		COMPLETION_RESOURCE_PATH, // For load/preload.
+		COMPLETION_SUBSCRIPT, // Inside id[|].
+		COMPLETION_SUPER_METHOD, // After super.
+		COMPLETION_TYPE_ATTRIBUTE, // Attribute in type name (Type.|).
+		COMPLETION_TYPE_NAME, // Name of type (after :).
+		COMPLETION_TYPE_NAME_OR_VOID, // Same as TYPE_NAME, but allows void (in function return type).
+	};
+
+	struct CompletionContext {
+		CompletionType type = COMPLETION_NONE;
+		ClassNode *current_class = nullptr;
+		FunctionNode *current_function = nullptr;
+		SuiteNode *current_suite = nullptr;
+		int current_line = -1;
+		int current_argument = -1;
+		Variant::Type builtin_type = Variant::VARIANT_MAX;
+		Node *node = nullptr;
+		Object *base = nullptr;
+	};
+
+	struct CompletionCall {
+		Node *call = nullptr;
+		int argument = -1;
+	};
+
 private:
 	friend class GDScriptAnalyzer;
 
@@ -1038,6 +1081,11 @@ private:
 	FunctionNode *current_function = nullptr;
 	SuiteNode *current_suite = nullptr;
 
+	CompletionContext completion_context;
+	CompletionCall completion_call;
+	List<CompletionCall> completion_call_stack;
+	bool passed_cursor = false;
+
 	typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target);
 	struct AnnotationInfo {
 		enum TargetKind {
@@ -1100,6 +1148,12 @@ private:
 	void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
 	void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
 
+	void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
+	void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false);
+	void push_completion_call(Node *p_call);
+	void pop_completion_call();
+	void set_last_completion_call_arg(int p_argument);
+
 	GDScriptTokenizer::Token advance();
 	bool match(GDScriptTokenizer::Token::Type p_token_type);
 	bool check(GDScriptTokenizer::Token::Type p_token_type);
@@ -1187,6 +1241,10 @@ public:
 	static Variant::Type get_builtin_type(const StringName &p_type);
 	static GDScriptFunctions::Function get_builtin_function(const StringName &p_name);
 
+	CompletionContext get_completion_context() const { return completion_context; }
+	CompletionCall get_completion_call() const { return completion_call; }
+	void get_annotation_list(List<MethodInfo> *r_annotations) const;
+
 	const List<ParserError> &get_errors() const { return errors; }
 	const List<GDScriptWarning> &get_warnings() const { return warnings; }
 	const List<String> get_dependencies() const {

+ 65 - 6
modules/gdscript/gdscript_tokenizer.cpp

@@ -191,13 +191,26 @@ int GDScriptTokenizer::get_cursor_column() const {
 	return cursor_column;
 }
 
+bool GDScriptTokenizer::is_past_cursor() const {
+	if (line < cursor_line) {
+		return false;
+	}
+	if (line > cursor_line) {
+		return true;
+	}
+	if (column < cursor_column) {
+		return false;
+	}
+	return true;
+}
+
 CharType GDScriptTokenizer::_advance() {
 	if (unlikely(_is_at_end())) {
 		return '\0';
 	}
 	_current++;
-	position++;
 	column++;
+	position++;
 	if (column > rightmost_column) {
 		rightmost_column = column;
 	}
@@ -246,7 +259,7 @@ static bool _is_binary_digit(CharType c) {
 	return (c == '0' || c == '1');
 }
 
-GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) const {
+GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
 	Token token(p_type);
 	token.start_line = start_line;
 	token.end_line = line;
@@ -254,17 +267,63 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) const
 	token.end_column = column;
 	token.leftmost_column = leftmost_column;
 	token.rightmost_column = rightmost_column;
+	token.source = String(_start, _current - _start);
+
+	if (p_type != Token::ERROR && cursor_line > -1) {
+		// Also count whitespace after token.
+		int offset = 0;
+		while (_peek(offset) == ' ' || _peek(offset) == '\t') {
+			offset++;
+		}
+		int last_column = column + offset;
+		// Check cursor position in token.
+		if (start_line == line) {
+			// Single line token.
+			if (cursor_line == start_line && cursor_column >= start_column && cursor_column <= last_column) {
+				token.cursor_position = cursor_column - start_column;
+				if (cursor_column == start_column) {
+					token.cursor_place = CURSOR_BEGINNING;
+				} else if (cursor_column < column) {
+					token.cursor_place = CURSOR_MIDDLE;
+				} else {
+					token.cursor_place = CURSOR_END;
+				}
+			}
+		} else {
+			// Multi line token.
+			if (cursor_line == start_line && cursor_column >= start_column) {
+				// Is in first line.
+				token.cursor_position = cursor_column - start_column;
+				if (cursor_column == start_column) {
+					token.cursor_place = CURSOR_BEGINNING;
+				} else {
+					token.cursor_place = CURSOR_MIDDLE;
+				}
+			} else if (cursor_line == line && cursor_column <= last_column) {
+				// Is in last line.
+				token.cursor_position = cursor_column - start_column;
+				if (cursor_column < column) {
+					token.cursor_place = CURSOR_MIDDLE;
+				} else {
+					token.cursor_place = CURSOR_END;
+				}
+			} else if (cursor_line > start_line && cursor_line < line) {
+				// Is in middle line.
+				token.cursor_position = CURSOR_MIDDLE;
+			}
+		}
+	}
 
 	return token;
 }
 
-GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) const {
+GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) {
 	Token token = make_token(Token::LITERAL);
 	token.literal = p_literal;
 	return token;
 }
 
-GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) const {
+GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) {
 	Token identifier = make_token(Token::IDENTIFIER);
 	identifier.literal = p_identifier;
 	return identifier;
@@ -321,14 +380,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, To
 
 GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
 	if (!_is_alphanumeric(_peek())) {
-		return make_error("Expected annotation identifier after \"@\".");
+		push_error("Expected annotation identifier after \"@\".");
 	}
 	while (_is_alphanumeric(_peek())) {
 		// Consume all identifier characters.
 		_advance();
 	}
 	Token annotation = make_token(Token::ANNOTATION);
-	annotation.literal = StringName(String(_start, _current - _start));
+	annotation.literal = StringName(annotation.source);
 	return annotation;
 }
 

+ 16 - 5
modules/gdscript/gdscript_tokenizer.h

@@ -38,6 +38,13 @@
 
 class GDScriptTokenizer {
 public:
+	enum CursorPlace {
+		CURSOR_NONE,
+		CURSOR_BEGINNING,
+		CURSOR_MIDDLE,
+		CURSOR_END,
+	};
+
 	struct Token {
 		enum Type {
 			EMPTY,
@@ -155,6 +162,9 @@ public:
 		Variant literal;
 		int start_line = 0, end_line = 0, start_column = 0, end_column = 0;
 		int leftmost_column = 0, rightmost_column = 0; // Column span for multiline tokens.
+		int cursor_position = -1;
+		CursorPlace cursor_place = CURSOR_NONE;
+		String source;
 
 		const char *get_name() const;
 		// TODO: Allow some keywords as identifiers?
@@ -174,8 +184,8 @@ private:
 	String source;
 	const CharType *_source = nullptr;
 	const CharType *_current = nullptr;
-	int line = 0, column = 0;
-	int cursor_line = 0, cursor_column = 0;
+	int line = -1, column = -1;
+	int cursor_line = -1, cursor_column = -1;
 	int tab_size = 4;
 
 	// Keep track of multichar tokens.
@@ -209,9 +219,9 @@ private:
 	void push_error(const String &p_message);
 	void push_error(const Token &p_error);
 	Token make_paren_error(CharType p_paren);
-	Token make_token(Token::Type p_type) const;
-	Token make_literal(const Variant &p_literal) const;
-	Token make_identifier(const StringName &p_identifier) const;
+	Token make_token(Token::Type p_type);
+	Token make_literal(const Variant &p_literal);
+	Token make_identifier(const StringName &p_identifier);
 	Token check_vcs_marker(CharType p_test, Token::Type p_double_type);
 	void push_paren(CharType p_char);
 	bool pop_paren(CharType p_expected);
@@ -231,6 +241,7 @@ public:
 	int get_cursor_column() const;
 	void set_cursor_position(int p_line, int p_column);
 	void set_multiline_mode(bool p_state);
+	bool is_past_cursor() const;
 	static String get_token_name(Token::Type p_token_type);
 
 	GDScriptTokenizer();

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä