godot/modules/gdscript/gdscript_editor.cpp
anakimluke bd3b958a25 Removed whitespaces around arguments of functions.
Functions automatically generated by conneting
signals via GUI put whitespaces around the
arguments of the generated function. This is
inconsistent with the style guide.

This commit fixes that.

(cherry picked from commit 370f84f41c)
2018-02-24 01:34:26 +01:00

3004 lines
89 KiB
C++

/*************************************************************************/
/* gdscript_editor.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "gdscript.h"
#include "core/engine.h"
#include "editor/editor_settings.h"
#include "gdscript_compiler.h"
#include "global_constants.h"
#include "os/file_access.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_file_system.h"
#include "editor/editor_settings.h"
#include "engine.h"
#endif
void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("#");
p_delimiters->push_back("\"\"\" \"\"\"");
}
void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("\" \"");
p_delimiters->push_back("' '");
}
Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
String _template = String() +
"extends %BASE%\n\n" +
"# class member variables go here, for example:\n" +
"# var a = 2\n" +
"# var b = \"textvar\"\n\n" +
"func _ready():\n" +
"%TS%# Called every time the node is added to the scene.\n" +
"%TS%# Initialization here\n" +
"%TS%pass\n\n" +
"#func _process(delta):\n" +
"#%TS%# Called every frame. Delta is time since last frame.\n" +
"#%TS%# Update game logic here.\n" +
"#%TS%pass\n";
_template = _template.replace("%BASE%", p_base_class_name);
_template = _template.replace("%TS%", _get_indentation());
Ref<GDScript> script;
script.instance();
script->set_source_code(_template);
return script;
}
bool GDScriptLanguage::is_using_templates() {
return true;
}
void GDScriptLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {
String src = p_script->get_source_code();
src = src.replace("%BASE%", p_base_class_name);
src = src.replace("%TS%", _get_indentation());
p_script->set_source_code(src);
}
bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions) const {
GDScriptParser parser;
Error err = parser.parse(p_script, p_path.get_base_dir(), true, p_path);
if (err) {
r_line_error = parser.get_error_line();
r_col_error = parser.get_error_column();
r_test_error = parser.get_error();
return false;
} else {
const GDScriptParser::Node *root = parser.get_parse_tree();
ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, false);
const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root);
Map<int, String> funcs;
for (int i = 0; i < cl->functions.size(); i++) {
funcs[cl->functions[i]->line] = cl->functions[i]->name;
}
for (int i = 0; i < cl->static_functions.size(); i++) {
funcs[cl->static_functions[i]->line] = cl->static_functions[i]->name;
}
for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) {
r_functions->push_back(E->get() + ":" + itos(E->key()));
}
}
return true;
}
bool GDScriptLanguage::has_named_classes() const {
return false;
}
bool GDScriptLanguage::supports_builtin_mode() const {
return true;
}
int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const {
GDScriptTokenizerText tokenizer;
tokenizer.set_code(p_code);
int indent = 0;
while (tokenizer.get_token() != GDScriptTokenizer::TK_EOF && tokenizer.get_token() != GDScriptTokenizer::TK_ERROR) {
if (tokenizer.get_token() == GDScriptTokenizer::TK_NEWLINE) {
indent = tokenizer.get_token_line_indent();
}
//print_line("TOKEN: "+String(GDScriptTokenizer::get_token_name(tokenizer.get_token())));
if (indent == 0 && tokenizer.get_token() == GDScriptTokenizer::TK_PR_FUNCTION && tokenizer.get_token(1) == GDScriptTokenizer::TK_IDENTIFIER) {
String identifier = tokenizer.get_token_identifier(1);
if (identifier == p_function) {
return tokenizer.get_token_line();
}
}
tokenizer.advance();
//print_line("NEXT: "+String(GDScriptTokenizer::get_token_name(tokenizer.get_token())));
}
return -1;
}
Script *GDScriptLanguage::create_script() const {
return memnew(GDScript);
}
/* DEBUGGER FUNCTIONS */
bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
//break because of parse error
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = p_line;
_debug_parse_err_file = p_file;
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, false);
return true;
} else {
return false;
}
}
bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) {
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, p_allow_continue);
return true;
} else {
return false;
}
}
String GDScriptLanguage::debug_get_error() const {
return _debug_error;
}
int GDScriptLanguage::debug_get_stack_level_count() const {
if (_debug_parse_err_line >= 0)
return 1;
return _debug_call_stack_pos;
}
int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
if (_debug_parse_err_line >= 0)
return _debug_parse_err_line;
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, -1);
int l = _debug_call_stack_pos - p_level - 1;
return *(_call_stack[l].line);
}
String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
if (_debug_parse_err_line >= 0)
return "";
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
int l = _debug_call_stack_pos - p_level - 1;
return _call_stack[l].function->get_name();
}
String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
if (_debug_parse_err_line >= 0)
return _debug_parse_err_file;
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
int l = _debug_call_stack_pos - p_level - 1;
return _call_stack[l].function->get_source();
}
void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
if (_debug_parse_err_line >= 0)
return;
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
int l = _debug_call_stack_pos - p_level - 1;
GDScriptFunction *f = _call_stack[l].function;
List<Pair<StringName, int> > locals;
f->debug_get_stack_member_state(*_call_stack[l].line, &locals);
for (List<Pair<StringName, int> >::Element *E = locals.front(); E; E = E->next()) {
p_locals->push_back(E->get().first);
p_values->push_back(_call_stack[l].stack[E->get().second]);
}
}
void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
if (_debug_parse_err_line >= 0)
return;
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
int l = _debug_call_stack_pos - p_level - 1;
GDScriptInstance *instance = _call_stack[l].instance;
if (!instance)
return;
Ref<GDScript> script = instance->get_script();
ERR_FAIL_COND(script.is_null());
const Map<StringName, GDScript::MemberInfo> &mi = script->debug_get_member_indices();
for (const Map<StringName, GDScript::MemberInfo>::Element *E = mi.front(); E; E = E->next()) {
p_members->push_back(E->key());
p_values->push_back(instance->debug_get_member_by_index(E->get().index));
}
}
ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) {
ERR_FAIL_COND_V(_debug_parse_err_line >= 0, NULL);
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, NULL);
int l = _debug_call_stack_pos - p_level - 1;
ScriptInstance *instance = _call_stack[l].instance;
return instance;
}
void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
const Map<StringName, int> &name_idx = GDScriptLanguage::get_singleton()->get_global_map();
const Variant *globals = GDScriptLanguage::get_singleton()->get_global_array();
List<Pair<String, Variant> > cinfo;
get_public_constants(&cinfo);
for (const Map<StringName, int>::Element *E = name_idx.front(); E; E = E->next()) {
if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key()))
continue;
bool is_script_constant = false;
for (List<Pair<String, Variant> >::Element *CE = cinfo.front(); CE; CE = CE->next()) {
if (CE->get().first == E->key()) {
is_script_constant = true;
break;
}
}
if (is_script_constant)
continue;
const Variant &var = globals[E->value()];
if (Object *obj = var) {
if (Object::cast_to<GDScriptNativeClass>(obj))
continue;
}
bool skip = false;
for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
if (E->key() == GlobalConstants::get_global_constant_name(i)) {
skip = true;
break;
}
}
if (skip)
continue;
p_globals->push_back(E->key());
p_values->push_back(var);
}
}
String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) {
if (_debug_parse_err_line >= 0)
return "";
return "";
}
void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd");
}
void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const {
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
p_functions->push_back(GDScriptFunctions::get_info(GDScriptFunctions::Function(i)));
}
//not really "functions", but..
{
MethodInfo mi;
mi.name = "preload";
mi.arguments.push_back(PropertyInfo(Variant::STRING, "path"));
mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "Resource");
p_functions->push_back(mi);
}
{
MethodInfo mi;
mi.name = "yield";
mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object"));
mi.arguments.push_back(PropertyInfo(Variant::STRING, "signal"));
mi.default_arguments.push_back(Variant());
mi.default_arguments.push_back(String());
mi.return_val = PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, "GDScriptFunctionState");
p_functions->push_back(mi);
}
{
MethodInfo mi;
mi.name = "assert";
mi.return_val.type = Variant::NIL;
mi.arguments.push_back(PropertyInfo(Variant::BOOL, "condition"));
p_functions->push_back(mi);
}
}
void GDScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_constants) const {
Pair<String, Variant> pi;
pi.first = "PI";
pi.second = Math_PI;
p_constants->push_back(pi);
Pair<String, Variant> tau;
tau.first = "TAU";
tau.second = Math_TAU;
p_constants->push_back(tau);
Pair<String, Variant> infinity;
infinity.first = "INF";
infinity.second = Math_INF;
p_constants->push_back(infinity);
Pair<String, Variant> nan;
nan.first = "NAN";
nan.second = Math_NAN;
p_constants->push_back(nan);
}
String GDScriptLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const {
String s = "func " + p_name + "(";
if (p_args.size()) {
for (int i = 0; i < p_args.size(); i++) {
if (i > 0)
s += ", ";
s += p_args[i].get_slice(":", 0);
}
}
s += "):\n" + _get_indentation() + "pass # replace with function body\n";
return s;
}
#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
struct GDScriptCompletionIdentifier {
String enumeration;
StringName obj_type;
Ref<GDScript> script;
Variant::Type type;
Variant value; //im case there is a value, also return it
};
static GDScriptCompletionIdentifier _get_type_from_variant(const Variant &p_variant, bool p_allow_gdnative_class = false) {
GDScriptCompletionIdentifier t;
t.type = p_variant.get_type();
t.value = p_variant;
if (p_variant.get_type() == Variant::OBJECT) {
Object *obj = p_variant;
if (obj) {
if (p_allow_gdnative_class && Object::cast_to<GDScriptNativeClass>(obj)) {
t.obj_type = Object::cast_to<GDScriptNativeClass>(obj)->get_name();
t.value = Variant();
} else {
t.obj_type = obj->get_class();
}
}
}
return t;
}
static GDScriptCompletionIdentifier _get_type_from_pinfo(const PropertyInfo &p_info) {
GDScriptCompletionIdentifier t;
t.type = p_info.type;
if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
t.obj_type = p_info.hint_string;
}
return t;
}
struct GDScriptCompletionContext {
const GDScriptParser::ClassNode *_class;
const GDScriptParser::FunctionNode *function;
const GDScriptParser::BlockNode *block;
Object *base;
String base_path;
};
static Ref<Reference> _get_parent_class(GDScriptCompletionContext &context) {
if (context._class->extends_used) {
//do inheritance
String path = context._class->extends_file;
Ref<GDScript> script;
Ref<GDScriptNativeClass> native;
if (path != "") {
//path (and optionally subclasses)
if (path.is_rel_path()) {
path = context.base_path.plus_file(path);
}
if (ScriptCodeCompletionCache::get_singleton())
script = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(path);
else
script = ResourceLoader::load(path);
if (script.is_null()) {
return REF();
}
if (!script->is_valid()) {
return REF();
}
//print_line("EXTENDS PATH: "+path+" script is "+itos(script.is_valid())+" indices is "+itos(script->member_indices.size())+" valid? "+itos(script->valid));
if (context._class->extends_class.size()) {
for (int i = 0; i < context._class->extends_class.size(); i++) {
String sub = context._class->extends_class[i];
if (script->get_subclasses().has(sub)) {
script = script->get_subclasses()[sub];
} else {
return REF();
}
}
}
if (script.is_valid())
return script;
} else {
if (context._class->extends_class.size() == 0) {
ERR_PRINT("BUG");
return REF();
}
String base = context._class->extends_class[0];
if (context._class->extends_class.size() > 1) {
return REF();
}
//if not found, try engine classes
if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
return REF();
}
int base_idx = GDScriptLanguage::get_singleton()->get_global_map()[base];
native = GDScriptLanguage::get_singleton()->get_global_array()[base_idx];
return native;
}
}
return Ref<Reference>();
}
static GDScriptCompletionIdentifier _get_native_class(GDScriptCompletionContext &context) {
//eeh...
GDScriptCompletionIdentifier id;
id.type = Variant::NIL;
REF pc = _get_parent_class(context);
if (!pc.is_valid()) {
return id;
}
Ref<GDScriptNativeClass> nc = pc;
Ref<GDScript> s = pc;
if (s.is_null() && nc.is_null()) {
return id;
}
while (!s.is_null()) {
nc = s->get_native();
s = s->get_base();
}
if (nc.is_null()) {
return id;
}
id.type = Variant::OBJECT;
if (context.base)
id.value = context.base;
id.obj_type = nc->get_name();
return id;
}
static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing);
static bool _guess_expression_type(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, GDScriptCompletionIdentifier &r_type, bool p_for_indexing = false) {
if (p_node->type == GDScriptParser::Node::TYPE_CONSTANT) {
const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_node);
r_type = _get_type_from_variant(cn->value);
return true;
} else if (p_node->type == GDScriptParser::Node::TYPE_DICTIONARY) {
r_type.type = Variant::DICTIONARY;
//what the heck, fill it anyway
const GDScriptParser::DictionaryNode *an = static_cast<const GDScriptParser::DictionaryNode *>(p_node);
Dictionary d;
for (int i = 0; i < an->elements.size(); i++) {
GDScriptCompletionIdentifier k;
if (_guess_expression_type(context, an->elements[i].key, p_line, k) && k.value.get_type() != Variant::NIL) {
GDScriptCompletionIdentifier v;
if (_guess_expression_type(context, an->elements[i].value, p_line, v)) {
d[k.value] = v.value;
}
}
}
r_type.value = d;
return true;
} else if (p_node->type == GDScriptParser::Node::TYPE_ARRAY) {
r_type.type = Variant::ARRAY;
//what the heck, fill it anyway
const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_node);
Array arr;
arr.resize(an->elements.size());
for (int i = 0; i < an->elements.size(); i++) {
GDScriptCompletionIdentifier ci;
if (_guess_expression_type(context, an->elements[i], p_line, ci)) {
arr[i] = ci.value;
}
}
r_type.value = arr;
return true;
} else if (p_node->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
MethodInfo mi = GDScriptFunctions::get_info(static_cast<const GDScriptParser::BuiltInFunctionNode *>(p_node)->function);
r_type = _get_type_from_pinfo(mi.return_val);
return true;
} else if (p_node->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
return _guess_identifier_type(context, p_line - 1, static_cast<const GDScriptParser::IdentifierNode *>(p_node)->name, r_type, p_for_indexing);
} else if (p_node->type == GDScriptParser::Node::TYPE_SELF) {
//eeh...
r_type = _get_native_class(context);
return r_type.type != Variant::NIL;
} else if (p_node->type == GDScriptParser::Node::TYPE_OPERATOR) {
const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node);
if (op->op == GDScriptParser::OperatorNode::OP_CALL) {
if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
r_type.type = tn->vtype;
return true;
} else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
return _guess_expression_type(context, bin, p_line, r_type);
} else if (op->arguments.size() > 1 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name;
if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && String(id) == "new") {
//shortcut
StringName identifier = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name;
if (ClassDB::class_exists(identifier)) {
r_type.type = Variant::OBJECT;
r_type.value = Variant();
r_type.obj_type = identifier;
return true;
}
}
GDScriptCompletionIdentifier base;
if (!_guess_expression_type(context, op->arguments[0], p_line, base))
return false;
if (base.type == Variant::OBJECT) {
if (id.operator String() == "new" && base.value.get_type() == Variant::OBJECT) {
Object *obj = base.value;
if (obj && Object::cast_to<GDScriptNativeClass>(obj)) {
GDScriptNativeClass *gdnc = Object::cast_to<GDScriptNativeClass>(obj);
r_type.type = Variant::OBJECT;
r_type.value = Variant();
r_type.obj_type = gdnc->get_name();
return true;
} else {
if (base.obj_type != StringName()) {
r_type.type = Variant::OBJECT;
r_type.value = Variant();
r_type.obj_type = base.obj_type;
return true;
}
}
}
if (ClassDB::has_method(base.obj_type, id)) {
#ifdef TOOLS_ENABLED
MethodBind *mb = ClassDB::get_method(base.obj_type, id);
PropertyInfo pi = mb->get_return_info();
//try calling the function if constant and all args are constant, should not crash..
Object *baseptr = base.value;
if (mb->is_const() && pi.type == Variant::OBJECT) {
bool all_valid = true;
Vector<Variant> args;
for (int i = 2; i < op->arguments.size(); i++) {
GDScriptCompletionIdentifier arg;
if (_guess_expression_type(context, op->arguments[i], p_line, arg)) {
if (arg.value.get_type() != Variant::NIL && arg.value.get_type() != Variant::OBJECT) { // calling with object seems dangerous, i don' t know
args.push_back(arg.value);
} else {
all_valid = false;
break;
}
} else {
all_valid = false;
}
}
if (all_valid && String(id) == "get_node" && ClassDB::is_parent_class(base.obj_type, "Node") && args.size()) {
String arg1 = args[0];
if (arg1.begins_with("/root/")) {
String which = arg1.get_slice("/", 2);
if (which != "") {
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
//print_line("find singleton");
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
String s = E->get().name;
if (!s.begins_with("autoload/"))
continue;
//print_line("found "+s);
String name = s.get_slice("/", 1);
//print_line("name: "+name+", which: "+which);
if (name == which) {
String script = ProjectSettings::get_singleton()->get(s);
if (!script.begins_with("res://")) {
script = "res://" + script;
}
if (!script.ends_with(".gd")) {
//not a script, try find the script anyway,
//may have some success
script = script.get_basename() + ".gd";
}
if (FileAccess::exists(script)) {
//print_line("is a script");
Ref<Script> scr;
if (ScriptCodeCompletionCache::get_singleton())
scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script);
else
scr = ResourceLoader::load(script);
r_type.obj_type = "Node";
r_type.type = Variant::OBJECT;
r_type.script = scr;
r_type.value = Variant();
return true;
}
}
}
}
}
}
if (baseptr) {
if (all_valid) {
Vector<const Variant *> argptr;
for (int i = 0; i < args.size(); i++) {
argptr.push_back(&args[i]);
}
Variant::CallError ce;
Variant ret = mb->call(baseptr, (const Variant **)argptr.ptr(), argptr.size(), ce);
if (ce.error == Variant::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != NULL) {
r_type = _get_type_from_variant(ret);
return true;
}
}
}
}
}
r_type.type = pi.type;
if (pi.hint == PROPERTY_HINT_RESOURCE_TYPE) {
r_type.obj_type = pi.hint_string;
}
return true;
#else
return false;
#endif
} else {
return false;
}
} else {
//method for some variant..
Variant::CallError ce;
Variant v = Variant::construct(base.type, NULL, 0, ce);
List<MethodInfo> mi;
v.get_method_list(&mi);
for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) {
if (!E->get().name.begins_with("_") && E->get().name == id.operator String()) {
MethodInfo mi = E->get();
r_type.type = mi.return_val.type;
if (mi.return_val.hint == PROPERTY_HINT_RESOURCE_TYPE) {
r_type.obj_type = mi.return_val.hint_string;
}
return true;
}
}
}
}
} else if (op->op == GDScriptParser::OperatorNode::OP_INDEX || op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
GDScriptCompletionIdentifier p1;
GDScriptCompletionIdentifier p2;
if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
if (op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
String id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name;
p2.type = Variant::STRING;
p2.value = id;
}
} else {
if (op->arguments[1]) {
if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) {
return false;
}
}
}
if (op->arguments[0]->type == GDScriptParser::Node::TYPE_ARRAY) {
const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(op->arguments[0]);
if (p2.value.is_num()) {
int index = p2.value;
if (index < 0 || index >= an->elements.size())
return false;
return _guess_expression_type(context, an->elements[index], p_line, r_type);
}
} else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_DICTIONARY) {
const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(op->arguments[0]);
if (p2.value.get_type() == Variant::NIL)
return false;
for (int i = 0; i < dn->elements.size(); i++) {
GDScriptCompletionIdentifier k;
if (!_guess_expression_type(context, dn->elements[i].key, p_line, k)) {
return false;
}
if (k.value.get_type() == Variant::NIL)
return false;
if (k.value == p2.value) {
return _guess_expression_type(context, dn->elements[i].value, p_line, r_type);
}
}
} else {
if (op->arguments[0]) {
if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) {
return false;
}
}
if (p1.value.get_type() == Variant::OBJECT) {
//??
if (p1.obj_type != StringName() && p2.type == Variant::STRING) {
StringName base_type = p1.obj_type;
if (p1.obj_type == "GDScriptNativeClass") {
//native enum
Ref<GDScriptNativeClass> gdn = p1.value;
if (gdn.is_valid()) {
base_type = gdn->get_name();
}
}
StringName index = p2.value;
bool valid;
Variant::Type t = ClassDB::get_property_type(base_type, index, &valid);
if (t != Variant::NIL && valid) {
r_type.type = t;
if (t == Variant::INT || t == Variant::OBJECT) {
//check for enum!
#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
StringName getter = ClassDB::get_property_getter(base_type, index);
if (getter != StringName()) {
MethodBind *mb = ClassDB::get_method(base_type, getter);
if (mb) {
PropertyInfo rt = mb->get_return_info();
if ((rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && t == Variant::INT) {
r_type.enumeration = rt.class_name;
} else if (t == Variant::OBJECT) {
r_type.obj_type = rt.class_name;
}
}
}
#endif
}
return true;
}
}
} else if (p1.value.get_type() != Variant::NIL) {
bool valid;
Variant ret = p1.value.get(p2.value, &valid);
if (valid) {
r_type = _get_type_from_variant(ret);
return true;
}
} else {
if (p1.type != Variant::NIL) {
Variant::CallError ce;
Variant base = Variant::construct(p1.type, NULL, 0, ce);
bool valid;
Variant ret = base.get(p2.value, &valid);
if (valid) {
r_type = _get_type_from_variant(ret);
return true;
}
}
}
}
} else {
Variant::Operator vop = Variant::OP_MAX;
switch (op->op) {
case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break;
case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break;
case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break;
case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break;
case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break;
case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break;
case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break;
case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break;
case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break;
case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break;
default: {}
}
if (vop == Variant::OP_MAX)
return false;
GDScriptCompletionIdentifier p1;
GDScriptCompletionIdentifier p2;
if (op->arguments[0]) {
if (!_guess_expression_type(context, op->arguments[0], p_line, p1)) {
return false;
}
}
if (op->arguments.size() > 1) {
if (!_guess_expression_type(context, op->arguments[1], p_line, p2)) {
return false;
}
}
Variant::CallError ce;
bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT;
Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type, NULL, 0, ce);
bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT;
Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type, NULL, 0, ce);
// avoid potential invalid ops
if ((vop == Variant::OP_DIVIDE || vop == Variant::OP_MODULE) && v2.get_type() == Variant::INT) {
v2 = 1;
v2_use_value = false;
}
if (vop == Variant::OP_DIVIDE && v2.get_type() == Variant::REAL) {
v2 = 1.0;
v2_use_value = false;
}
Variant r;
bool valid;
Variant::evaluate(vop, v1, v2, r, valid);
if (!valid)
return false;
r_type.type = r.get_type();
if (v1_use_value && v2_use_value)
r_type.value = r;
return true;
}
}
return false;
}
static bool _guess_identifier_type_in_block(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
if (context.block->if_condition && context.block->if_condition->type == GDScriptParser::Node::TYPE_OPERATOR && static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition)->op == GDScriptParser::OperatorNode::OP_IS) {
//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::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->if_condition);
if (op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0])->name == p_identifier) {
//bingo
if (_guess_expression_type(context, op->arguments[1], op->line, r_type)) {
return true;
}
}
}
GDScriptCompletionIdentifier gdi = _get_native_class(context);
if (gdi.obj_type != StringName()) {
bool valid;
Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_identifier, &valid);
if (t != Variant::NIL && valid) {
r_type.type = t;
if (t == Variant::INT) {
//check for enum!
#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
StringName getter = ClassDB::get_property_getter(gdi.obj_type, p_identifier);
if (getter != StringName()) {
MethodBind *mb = ClassDB::get_method(gdi.obj_type, getter);
if (mb) {
PropertyInfo rt = mb->get_return_info();
if (rt.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
r_type.enumeration = rt.class_name;
}
}
}
#endif
}
return true;
}
}
const GDScriptParser::Node *last_assign = NULL;
int last_assign_line = -1;
for (int i = 0; i < context.block->statements.size(); i++) {
if (context.block->statements[i]->line > p_line)
continue;
if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) {
const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(context.block->statements[i]);
if (lv->assign && lv->name == p_identifier) {
last_assign = lv->assign;
last_assign_line = context.block->statements[i]->line;
}
}
if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) {
const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(context.block->statements[i]);
if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]);
if (id->name == p_identifier) {
last_assign = op->arguments[1];
last_assign_line = context.block->statements[i]->line;
}
}
}
}
}
//use the last assignment, (then backwards?)
if (last_assign && last_assign_line != p_line) {
return _guess_expression_type(context, last_assign, last_assign_line, r_type);
}
return false;
}
static bool _guess_identifier_from_assignment_in_function(GDScriptCompletionContext &context, int p_src_line, const StringName &p_identifier, const StringName &p_function, GDScriptCompletionIdentifier &r_type) {
const GDScriptParser::FunctionNode *func = NULL;
for (int i = 0; i < context._class->functions.size(); i++) {
if (context._class->functions[i]->name == p_function) {
func = context._class->functions[i];
break;
}
}
if (!func)
return false;
for (int i = 0; i < func->body->statements.size(); i++) {
if (func->body->statements[i]->line == p_src_line) {
break;
}
if (func->body->statements[i]->type == GDScriptParser::BlockNode::TYPE_OPERATOR) {
const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->body->statements[i]);
if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
if (op->arguments.size() && op->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[0]);
if (id->name == p_identifier) {
return _guess_expression_type(context, op->arguments[1], func->body->statements[i]->line, r_type);
}
}
}
}
}
return false;
}
static bool _guess_identifier_type(GDScriptCompletionContext &context, int p_line, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type, bool p_for_indexing) {
//go to block first
const GDScriptParser::BlockNode *block = context.block;
while (block) {
GDScriptCompletionContext c = context;
c.block = block;
if (_guess_identifier_type_in_block(c, p_line, p_identifier, r_type)) {
return true;
}
block = block->parent_block;
}
//guess from argument if virtual
if (context.function && context.function->name != StringName()) {
int argindex = -1;
for (int i = 0; i < context.function->arguments.size(); i++) {
if (context.function->arguments[i] == p_identifier) {
argindex = i;
break;
}
}
if (argindex != -1) {
GDScriptCompletionIdentifier id = _get_native_class(context);
if (id.type == Variant::OBJECT && id.obj_type != StringName()) {
//this kinda sucks but meh
List<MethodInfo> vmethods;
ClassDB::get_virtual_methods(id.obj_type, &vmethods);
for (List<MethodInfo>::Element *E = vmethods.front(); E; E = E->next()) {
if (E->get().name == context.function->name && argindex < E->get().arguments.size()) {
PropertyInfo arg = E->get().arguments[argindex];
int scp = String(arg.name).find(":");
if (scp != -1) {
r_type.type = Variant::OBJECT;
r_type.obj_type = String(arg.name).substr(scp + 1, String(arg.name).length());
return true;
} else {
r_type.type = arg.type;
if (arg.hint == PROPERTY_HINT_RESOURCE_TYPE)
r_type.obj_type = arg.hint_string;
return true;
}
}
}
}
}
}
//guess type in constant
for (int i = 0; i < context._class->constant_expressions.size(); i++) {
if (context._class->constant_expressions[i].identifier == p_identifier) {
ERR_FAIL_COND_V(context._class->constant_expressions[i].expression->type != GDScriptParser::Node::TYPE_CONSTANT, false);
r_type = _get_type_from_variant(static_cast<const GDScriptParser::ConstantNode *>(context._class->constant_expressions[i].expression)->value);
return true;
}
}
if (!(context.function && context.function->_static)) {
for (int i = 0; i < context._class->variables.size(); i++) {
if (context._class->variables[i].identifier == p_identifier) {
if (context._class->variables[i]._export.type != Variant::NIL) {
r_type = _get_type_from_pinfo(context._class->variables[i]._export);
return true;
} else if (context._class->variables[i].expression) {
if (p_line <= context._class->variables[i].line)
return false;
bool rtype = _guess_expression_type(context, context._class->variables[i].expression, context._class->variables[i].line, r_type);
if (rtype && r_type.type != Variant::NIL)
return true;
//return _guess_expression_type(context,context._class->variables[i].expression,context._class->variables[i].line,r_type);
}
//try to guess from assignment in constructor or _ready
if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_ready", r_type))
return true;
if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_enter_tree", r_type))
return true;
if (_guess_identifier_from_assignment_in_function(context, p_line + 1, p_identifier, "_init", r_type))
return true;
return false;
}
}
}
//autoloads as singletons
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
String s = E->get().name;
if (!s.begins_with("autoload/"))
continue;
String name = s.get_slice("/", 1);
if (name == String(p_identifier)) {
String path = ProjectSettings::get_singleton()->get(s);
if (path.begins_with("*")) {
String script = path.substr(1, path.length());
if (!script.ends_with(".gd")) {
//not a script, try find the script anyway,
//may have some success
script = script.get_basename() + ".gd";
}
if (FileAccess::exists(script)) {
//print_line("is a script");
Ref<Script> scr;
if (ScriptCodeCompletionCache::get_singleton())
scr = ScriptCodeCompletionCache::get_singleton()->get_cached_resource(script);
else
scr = ResourceLoader::load(script);
r_type.obj_type = "Node";
r_type.type = Variant::OBJECT;
r_type.script = scr;
r_type.value = Variant();
return true;
}
}
}
}
//global
for (Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
if (E->key() == p_identifier) {
r_type = _get_type_from_variant(GDScriptLanguage::get_singleton()->get_global_array()[E->get()], !p_for_indexing);
return true;
}
}
return false;
}
static void _find_identifiers_in_block(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) {
if (p_only_functions)
return;
for (int i = 0; i < context.block->statements.size(); i++) {
if (context.block->statements[i]->line > p_line)
continue;
if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) {
const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(context.block->statements[i]);
result.insert(lv->name.operator String());
}
}
}
static void _find_identifiers_in_class(GDScriptCompletionContext &context, bool p_static, bool p_only_functions, Set<String> &result) {
if (!p_static && !p_only_functions) {
for (int i = 0; i < context._class->variables.size(); i++) {
result.insert(context._class->variables[i].identifier);
}
}
if (!p_only_functions) {
for (int i = 0; i < context._class->constant_expressions.size(); i++) {
result.insert(context._class->constant_expressions[i].identifier);
}
for (int i = 0; i < context._class->subclasses.size(); i++) {
result.insert(context._class->subclasses[i]->name);
}
}
for (int i = 0; i < context._class->static_functions.size(); i++) {
if (context._class->static_functions[i]->arguments.size())
result.insert(context._class->static_functions[i]->name.operator String() + "(");
else
result.insert(context._class->static_functions[i]->name.operator String() + "()");
}
if (!p_static) {
for (int i = 0; i < context._class->functions.size(); i++) {
if (context._class->functions[i]->arguments.size())
result.insert(context._class->functions[i]->name.operator String() + "(");
else
result.insert(context._class->functions[i]->name.operator String() + "()");
}
}
//globals
Ref<Reference> base = _get_parent_class(context);
while (true) {
Ref<GDScript> script = base;
Ref<GDScriptNativeClass> nc = base;
if (script.is_valid()) {
if (!p_static && !p_only_functions) {
for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
result.insert(E->get().operator String());
}
}
if (!p_only_functions) {
for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) {
result.insert(E->key().operator String());
}
}
for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
if (!p_static || E->get()->is_static()) {
if (E->get()->get_argument_count())
result.insert(E->key().operator String() + "(");
else
result.insert(E->key().operator String() + "()");
}
}
if (!p_only_functions) {
for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) {
result.insert(E->key().operator String());
}
}
base = script->get_base();
if (base.is_null())
base = script->get_native();
} else if (nc.is_valid()) {
StringName type = nc->get_name();
if (!p_only_functions) {
List<String> constants;
ClassDB::get_integer_constant_list(type, &constants);
for (List<String>::Element *E = constants.front(); E; E = E->next()) {
result.insert(E->get());
}
List<PropertyInfo> pinfo;
ClassDB::get_property_list(type, &pinfo);
for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY))
continue;
if (String(E->get().name).find("/") != -1)
continue;
result.insert(E->get().name);
}
}
List<MethodInfo> methods;
ClassDB::get_method_list(type, &methods, false, true);
for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
if (E->get().name.begins_with("_"))
continue;
if (E->get().arguments.size())
result.insert(E->get().name + "(");
else
result.insert(E->get().name + "()");
}
break;
} else
break;
}
}
static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bool p_only_functions, Set<String> &result) {
const GDScriptParser::BlockNode *block = context.block;
if (context.function) {
const GDScriptParser::FunctionNode *f = context.function;
for (int i = 0; i < f->arguments.size(); i++) {
result.insert(f->arguments[i].operator String());
}
}
while (block) {
GDScriptCompletionContext c = context;
c.block = block;
_find_identifiers_in_block(c, p_line, p_only_functions, result);
block = block->parent_block;
}
const GDScriptParser::ClassNode *clss = context._class;
bool _static = context.function && context.function->_static;
while (clss) {
GDScriptCompletionContext c = context;
c._class = clss;
c.block = NULL;
c.function = NULL;
_find_identifiers_in_class(c, _static, p_only_functions, result);
clss = clss->owner;
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
result.insert(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)));
}
static const char *_type_names[Variant::VARIANT_MAX] = {
"null", "bool", "int", "float", "String", "Vector2", "Rect2", "Vector3", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform",
"Color", "NodePath", "RID", "Object", "Dictionary", "Array", "PoolByteArray", "PoolIntArray", "PoolRealArray", "PoolStringArray",
"PoolVector2Array", "PoolVector3Array", "PoolColorArray"
};
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
result.insert(_type_names[i]);
}
//autoload singletons
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
String s = E->get().name;
if (!s.begins_with("autoload/"))
continue;
String name = s.get_slice("/", 1);
String path = ProjectSettings::get_singleton()->get(s);
if (path.begins_with("*")) {
result.insert(name);
}
}
for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
result.insert(E->key().operator String());
}
}
static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = true) {
String n = p_info.name;
int idx = n.find(":");
if (idx != -1) {
return n.substr(idx + 1, n.length());
}
if (p_info.type == Variant::OBJECT && p_info.hint == PROPERTY_HINT_RESOURCE_TYPE)
return p_info.hint_string;
if (p_info.type == Variant::NIL) {
if (p_isarg)
return "var";
else
return "void";
}
return Variant::get_type_name(p_info.type);
}
static void _make_function_hint(const GDScriptParser::FunctionNode *p_func, int p_argidx, String &arghint) {
arghint = "func " + p_func->name + "(";
for (int i = 0; i < p_func->arguments.size(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
arghint += p_func->arguments[i].operator String();
int deffrom = p_func->arguments.size() - p_func->default_values.size();
if (i >= deffrom) {
int defidx = deffrom - i;
if (defidx >= 0 && defidx < p_func->default_values.size()) {
if (p_func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) {
const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[defidx]);
if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]);
arghint += "=" + cn->value.get_construct_string();
}
} else {
}
}
}
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
}
if (p_func->arguments.size() > 0)
arghint += " ";
arghint += ")";
}
void get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) {
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
get_directory_contents(p_dir->get_subdir(i), r_list);
}
for (int i = 0; i < p_dir->get_file_count(); i++) {
r_list.insert("\"" + p_dir->get_file_path(i) + "\"");
}
}
static void _find_type_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, const StringName &p_method, const GDScriptCompletionIdentifier &id, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) {
//print_line("find type arguments?");
if (id.type == Variant::OBJECT && id.obj_type != StringName()) {
MethodBind *m = ClassDB::get_method(id.obj_type, p_method);
if (!m) {
//not in static method, see script
//print_line("not in static: "+String(p_method));
Ref<GDScript> on_script;
if (id.value.get_type()) {
Object *obj = id.value;
GDScript *scr = Object::cast_to<GDScript>(obj);
if (scr) {
while (scr) {
for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
if (E->get()->is_static() && p_method == E->get()->get_name()) {
arghint = "static func " + String(p_method) + "(";
for (int i = 0; i < E->get()->get_argument_count(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
arghint += "var " + E->get()->get_argument_name(i);
int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count();
if (i >= deffrom) {
int defidx = deffrom - i;
if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) {
arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string();
}
}
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
}
arghint += ")";
return; //found
}
}
if (scr->get_base().is_valid())
scr = scr->get_base().ptr();
else
scr = NULL;
}
} else {
if (obj) {
on_script = obj->get_script();
}
}
}
//print_line("but it has a script?");
if (!on_script.is_valid() && id.script.is_valid()) {
//print_line("yes");
on_script = id.script;
}
if (on_script.is_valid()) {
GDScript *scr = on_script.ptr();
if (scr) {
while (scr) {
String code = scr->get_source_code();
//print_line("has source code!");
if (code != "") {
//if there is code, parse it. This way is slower but updates in real-time
GDScriptParser p;
//Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="",bool p_for_completion=false);
Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false);
if (err == OK) {
//print_line("checking the functions...");
//only if ok, otherwise use what is cached on the script
//GDScriptParser::ClassNode *base = p.
const GDScriptParser::Node *root = p.get_parse_tree();
ERR_FAIL_COND(root->type != GDScriptParser::Node::TYPE_CLASS);
const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root);
const GDScriptParser::FunctionNode *func = NULL;
bool st = false;
for (int i = 0; i < cl->functions.size(); i++) {
//print_line(String(cl->functions[i]->name)+" vs "+String(p_method));
if (cl->functions[i]->name == p_method) {
func = cl->functions[i];
}
}
for (int i = 0; i < cl->static_functions.size(); i++) {
//print_line(String(cl->static_functions[i]->name)+" vs "+String(p_method));
if (cl->static_functions[i]->name == p_method) {
func = cl->static_functions[i];
st = true;
}
}
if (func) {
arghint = "func " + String(p_method) + "(";
if (st)
arghint = "static " + arghint;
for (int i = 0; i < func->arguments.size(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
arghint += "var " + String(func->arguments[i]);
int deffrom = func->arguments.size() - func->default_values.size();
if (i >= deffrom) {
int defidx = deffrom - i;
if (defidx >= 0 && defidx < func->default_values.size() && func->default_values[defidx]->type == GDScriptParser::Node::TYPE_OPERATOR) {
const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(func->default_values[defidx]);
if (op->op == GDScriptParser::OperatorNode::OP_ASSIGN) {
const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(op->arguments[1]);
arghint += "=" + cn->value.get_construct_string();
}
}
}
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
}
arghint += " )";
return;
}
} else {
//print_line("failed parsing?");
code = "";
}
}
if (code == "") {
for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
if (p_method == E->get()->get_name()) {
arghint = "func " + String(p_method) + "(";
for (int i = 0; i < E->get()->get_argument_count(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
arghint += "var " + E->get()->get_argument_name(i);
int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count();
if (i >= deffrom) {
int defidx = deffrom - i;
if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) {
arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string();
}
}
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
}
arghint += ")";
return; //found
}
}
}
if (scr->get_base().is_valid())
scr = scr->get_base().ptr();
else
scr = NULL;
}
}
}
} else {
//regular method
#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
if (p_argidx < m->get_argument_count()) {
PropertyInfo pi = m->get_argument_info(p_argidx);
if (pi.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
String enumeration = pi.class_name;
if (enumeration.find(".") != -1) {
//class constant
List<StringName> constants;
String cls = enumeration.get_slice(".", 0);
String enm = enumeration.get_slice(".", 1);
ClassDB::get_enum_constants(cls, enm, &constants);
//constants.sort_custom<StringName::AlphCompare>();
for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
String add = cls + "." + E->get();
result.insert(add);
r_forced = true;
}
} else {
//global constant
StringName current_enum = enumeration;
for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
result.insert(GlobalConstants::get_global_constant_name(i));
r_forced = true;
}
}
//global
}
}
}
#endif
if (p_method.operator String() == "connect" || (p_method.operator String() == "emit_signal" && p_argidx == 0)) {
if (p_argidx == 0) {
List<MethodInfo> sigs;
ClassDB::get_signal_list(id.obj_type, &sigs);
if (id.script.is_valid()) {
id.script->get_script_signal_list(&sigs);
} else if (id.value.get_type() == Variant::OBJECT) {
Object *obj = id.value;
if (obj && !obj->get_script().is_null()) {
Ref<Script> scr = obj->get_script();
if (scr.is_valid()) {
scr->get_script_signal_list(&sigs);
}
}
}
for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) {
result.insert("\"" + E->get().name + "\"");
r_forced = true;
}
} else if (p_argidx == 2) {
if (context._class) {
for (int i = 0; i < context._class->functions.size(); i++) {
result.insert("\"" + context._class->functions[i]->name + "\"");
r_forced = true;
}
}
}
/*if (p_argidx==2) {
ERR_FAIL_COND(p_node->type!=GDScriptParser::Node::TYPE_OPERATOR);
const GDScriptParser::OperatorNode *op=static_cast<const GDScriptParser::OperatorNode *>(p_node);
if (op->arguments.size()>)
}*/
} else {
if (p_argidx == 0 && (String(p_method) == "get_node" || String(p_method) == "has_node") && ClassDB::is_parent_class(id.obj_type, "Node")) {
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
String s = E->get().name;
if (!s.begins_with("autoload/"))
continue;
//print_line("found "+s);
String name = s.get_slice("/", 1);
result.insert("\"/root/" + name + "\"");
r_forced = true;
}
}
Object *obj = id.value;
if (obj) {
List<String> options;
obj->get_argument_options(p_method, p_argidx, &options);
for (List<String>::Element *E = options.front(); E; E = E->next()) {
result.insert(E->get());
r_forced = true;
}
}
}
arghint = _get_visual_datatype(m->get_return_info(), false) + " " + p_method.operator String() + String("(");
for (int i = 0; i < m->get_argument_count(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
String n = m->get_argument_info(i).name;
int dp = n.find(":");
if (dp != -1)
n = n.substr(0, dp);
arghint += _get_visual_datatype(m->get_argument_info(i)) + " " + n;
int deffrom = m->get_argument_count() - m->get_default_argument_count();
if (i >= deffrom) {
int defidx = i - deffrom;
if (defidx >= 0 && defidx < m->get_default_argument_count()) {
Variant v = m->get_default_argument(i);
arghint += "=" + v.get_construct_string();
}
}
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
}
if (m->get_argument_count() > 0)
arghint += " ";
arghint += ")";
}
}
}
static void _find_call_arguments(GDScriptCompletionContext &context, const GDScriptParser::Node *p_node, int p_line, int p_argidx, Set<String> &result, bool &r_forced, String &arghint) {
if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) {
return;
}
const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_node);
if (op->op != GDScriptParser::OperatorNode::OP_CALL) {
return;
}
if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) {
//complete built-in function
const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]);
MethodInfo mi = GDScriptFunctions::get_info(fn->function);
if (mi.name == "load" && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), result);
}
arghint = _get_visual_datatype(mi.return_val, false) + " " + GDScriptFunctions::get_func_name(fn->function) + String("(");
for (int i = 0; i < mi.arguments.size(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) {
arghint += String::chr(0xFFFF);
}
arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name;
if (i == p_argidx || ((mi.flags & METHOD_FLAG_VARARG) && i > p_argidx)) {
arghint += String::chr(0xFFFF);
}
}
if (mi.arguments.size() > 0)
arghint += " ";
arghint += ")";
} else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) {
//complete constructor
const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]);
List<MethodInfo> mil;
Variant::get_constructor_list(tn->vtype, &mil);
for (List<MethodInfo>::Element *E = mil.front(); E; E = E->next()) {
MethodInfo mi = E->get();
if (mi.arguments.size() == 0)
continue;
if (E->prev())
arghint += "\n";
arghint += Variant::get_type_name(tn->vtype) + " " + Variant::get_type_name(tn->vtype) + String("(");
for (int i = 0; i < mi.arguments.size(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
arghint += _get_visual_datatype(mi.arguments[i]) + " " + mi.arguments[i].name;
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
}
if (mi.arguments.size() > 0)
arghint += " ";
arghint += ")";
}
} else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) {
//make sure identifier exists...
const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1]);
if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) {
//self, look up
for (int i = 0; i < context._class->static_functions.size(); i++) {
if (context._class->static_functions[i]->name == id->name) {
_make_function_hint(context._class->static_functions[i], p_argidx, arghint);
return;
}
}
if (context.function && !context.function->_static) {
for (int i = 0; i < context._class->functions.size(); i++) {
if (context._class->functions[i]->name == id->name) {
_make_function_hint(context._class->functions[i], p_argidx, arghint);
return;
}
}
}
Ref<Reference> base = _get_parent_class(context);
while (true) {
Ref<GDScript> script = base;
Ref<GDScriptNativeClass> nc = base;
if (script.is_valid()) {
for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
if (E->key() == id->name) {
if (context.function && context.function->_static && !E->get()->is_static())
continue;
arghint = "func " + id->name.operator String() + String("(");
for (int i = 0; i < E->get()->get_argument_count(); i++) {
if (i > 0)
arghint += ", ";
else
arghint += " ";
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
arghint += E->get()->get_argument_name(i);
int deffrom = E->get()->get_argument_count() - E->get()->get_default_argument_count();
if (i >= deffrom) {
int defidx = deffrom - i;
if (defidx >= 0 && defidx < E->get()->get_default_argument_count()) {
arghint += "=" + E->get()->get_default_argument(defidx).get_construct_string();
}
}
if (i == p_argidx) {
arghint += String::chr(0xFFFF);
}
}
if (E->get()->get_argument_count() > 0)
arghint += " ";
arghint += ")";
return;
}
}
base = script->get_base();
if (base.is_null())
base = script->get_native();
} else if (nc.is_valid()) {
if (!(context.function && context.function->_static)) {
GDScriptCompletionIdentifier ci;
ci.type = Variant::OBJECT;
ci.obj_type = nc->get_name();
if (!context._class->owner)
ci.value = context.base;
_find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint);
//guess type..
/*
List<MethodInfo> methods;
ClassDB::get_method_list(type,&methods);
for(List<MethodInfo>::Element *E=methods.front();E;E=E->next()) {
if (E->get().arguments.size())
result.insert(E->get().name+"(");
else
result.insert(E->get().name+"()");
}*/
}
break;
} else
break;
}
} else {
//indexed lookup
GDScriptCompletionIdentifier ci;
if (_guess_expression_type(context, op->arguments[0], p_line, ci)) {
_find_type_arguments(context, p_node, p_line, id->name, ci, p_argidx, result, r_forced, arghint);
return;
}
}
}
}
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) {
GDScriptParser p;
p.parse(p_code, p_base_path, false, "", true);
bool isfunction = false;
Set<String> options;
r_forced = false;
GDScriptCompletionContext context;
context._class = p.get_completion_class();
context.block = p.get_completion_block();
context.function = p.get_completion_function();
context.base = p_owner;
context.base_path = p_base_path;
switch (p.get_completion_type()) {
case GDScriptParser::COMPLETION_NONE: {
} break;
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
List<StringName> constants;
Variant::get_numeric_constants_for_type(p.get_completion_built_in_constant(), &constants);
for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
options.insert(E->get().operator String());
}
} break;
case GDScriptParser::COMPLETION_FUNCTION:
isfunction = true;
case GDScriptParser::COMPLETION_IDENTIFIER: {
_find_identifiers(context, p.get_completion_line(), isfunction, options);
} break;
case GDScriptParser::COMPLETION_PARENT_FUNCTION: {
} break;
case GDScriptParser::COMPLETION_GET_NODE: {
if (p_owner) {
List<String> opts;
p_owner->get_argument_options("get_node", 0, &opts);
for (List<String>::Element *E = opts.front(); E; E = E->next()) {
String opt = E->get().strip_edges();
if (opt.is_quoted()) {
r_forced = true;
String idopt = opt.unquote();
if (idopt.replace("/", "_").is_valid_identifier()) {
options.insert(idopt);
} else {
options.insert(opt);
}
}
}
}
} break;
case GDScriptParser::COMPLETION_METHOD:
isfunction = true;
case GDScriptParser::COMPLETION_INDEX: {
const GDScriptParser::Node *node = p.get_completion_node();
if (node->type != GDScriptParser::Node::TYPE_OPERATOR)
break;
GDScriptCompletionIdentifier t;
if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t, true)) {
if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") {
//native enum
Ref<GDScriptNativeClass> gdn = t.value;
if (gdn.is_valid()) {
StringName cn = gdn->get_name();
List<String> cnames;
ClassDB::get_integer_constant_list(cn, &cnames);
for (List<String>::Element *E = cnames.front(); E; E = E->next()) {
options.insert(E->get());
}
List<PropertyInfo> pinfo;
ClassDB::get_property_list(cn, &pinfo);
for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY))
continue;
if (String(E->get().name).find("/") != -1)
continue;
options.insert(E->get().name);
}
}
} else if (t.type == Variant::OBJECT && t.obj_type != StringName()) {
Ref<GDScript> on_script;
if (t.value.get_type()) {
Object *obj = t.value;
GDScript *scr = Object::cast_to<GDScript>(obj);
if (scr) {
while (scr) {
if (!isfunction) {
for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) {
options.insert(E->key());
}
}
for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
if (E->get()->is_static())
options.insert(E->key());
}
if (scr->get_base().is_valid())
scr = scr->get_base().ptr();
else
scr = NULL;
}
} else {
if (obj) {
on_script = obj->get_script();
}
}
}
if (!on_script.is_valid() && t.script.is_valid()) {
on_script = t.script;
}
if (on_script.is_valid()) {
GDScript *scr = on_script.ptr();
if (scr) {
while (scr) {
String code = scr->get_source_code();
if (code != "") {
//if there is code, parse it. This way is slower but updates in real-time
GDScriptParser p;
Error err = p.parse(scr->get_source_code(), scr->get_path().get_base_dir(), true, "", false);
if (err == OK) {
//only if ok, otherwise use what is cached on the script
//GDScriptParser::ClassNode *base = p.
const GDScriptParser::Node *root = p.get_parse_tree();
ERR_FAIL_COND_V(root->type != GDScriptParser::Node::TYPE_CLASS, ERR_PARSE_ERROR);
const GDScriptParser::ClassNode *cl = static_cast<const GDScriptParser::ClassNode *>(root);
for (int i = 0; i < cl->functions.size(); i++) {
if (cl->functions[i]->arguments.size())
options.insert(String(cl->functions[i]->name) + "(");
else
options.insert(String(cl->functions[i]->name) + "()");
}
for (int i = 0; i < cl->static_functions.size(); i++) {
if (cl->static_functions[i]->arguments.size())
options.insert(String(cl->static_functions[i]->name) + "(");
else
options.insert(String(cl->static_functions[i]->name) + "()");
}
if (!isfunction) {
for (int i = 0; i < cl->variables.size(); i++) {
options.insert(String(cl->variables[i].identifier));
}
for (int i = 0; i < cl->constant_expressions.size(); i++) {
options.insert(String(cl->constant_expressions[i].identifier));
}
}
} else {
code = ""; //well, then no code
}
}
if (code == "") {
//use class directly, no code was found
if (!isfunction) {
for (const Map<StringName, Variant>::Element *E = scr->get_constants().front(); E; E = E->next()) {
options.insert(E->key());
}
}
for (const Map<StringName, GDScriptFunction *>::Element *E = scr->get_member_functions().front(); E; E = E->next()) {
if (E->get()->get_argument_count())
options.insert(String(E->key()) + "()");
else
options.insert(String(E->key()) + "(");
}
for (const Set<StringName>::Element *E = scr->get_members().front(); E; E = E->next()) {
options.insert(E->get());
}
}
if (scr->get_base().is_valid())
scr = scr->get_base().ptr();
else
scr = NULL;
}
}
}
if (!isfunction) {
ClassDB::get_integer_constant_list(t.obj_type, r_options);
List<PropertyInfo> pinfo;
ClassDB::get_property_list(t.obj_type, &pinfo);
for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY))
continue;
if (String(E->get().name).find("/") != -1)
continue;
r_options->push_back(E->get().name);
}
}
List<MethodInfo> mi;
ClassDB::get_method_list(t.obj_type, &mi, false, true);
for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) {
if (E->get().name.begins_with("_"))
continue;
if (E->get().arguments.size())
options.insert(E->get().name + "(");
else
options.insert(E->get().name + "()");
}
} else {
//check InputEvent hint
{
if (t.value.get_type() == Variant::NIL) {
Variant::CallError ce;
t.value = Variant::construct(t.type, NULL, 0, ce);
}
if (!isfunction) {
List<PropertyInfo> pl;
t.value.get_property_list(&pl);
for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) {
if (String(E->get().name).find("/") == -1)
options.insert(E->get().name);
}
}
List<MethodInfo> mi;
t.value.get_method_list(&mi);
for (List<MethodInfo>::Element *E = mi.front(); E; E = E->next()) {
if (E->get().arguments.size())
options.insert(E->get().name + "(");
else
options.insert(E->get().name + "()");
}
}
}
}
} break;
case GDScriptParser::COMPLETION_CALL_ARGUMENTS: {
_find_call_arguments(context, p.get_completion_node(), p.get_completion_line(), p.get_completion_argument_index(), options, r_forced, r_call_hint);
} break;
case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
GDScriptCompletionIdentifier cid = _get_native_class(context);
if (cid.obj_type != StringName()) {
List<MethodInfo> vm;
ClassDB::get_virtual_methods(cid.obj_type, &vm);
for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) {
MethodInfo &mi = E->get();
String m = mi.name;
if (m.find(":") != -1)
m = m.substr(0, m.find(":"));
m += "(";
if (mi.arguments.size()) {
for (int i = 0; i < mi.arguments.size(); i++) {
if (i > 0)
m += ", ";
String n = mi.arguments[i].name;
if (n.find(":") != -1)
n = n.substr(0, n.find(":"));
m += n;
}
}
m += "):";
options.insert(m);
}
}
} break;
case GDScriptParser::COMPLETION_YIELD: {
const GDScriptParser::Node *node = p.get_completion_node();
GDScriptCompletionIdentifier t;
if (!_guess_expression_type(context, node, p.get_completion_line(), t))
break;
if (t.type == Variant::OBJECT && t.obj_type != StringName()) {
List<MethodInfo> sigs;
ClassDB::get_signal_list(t.obj_type, &sigs);
for (List<MethodInfo>::Element *E = sigs.front(); E; E = E->next()) {
options.insert("\"" + E->get().name + "\"");
r_forced = true;
}
}
} break;
case GDScriptParser::COMPLETION_RESOURCE_PATH: {
if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) {
get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options);
r_forced = true;
}
} break;
case GDScriptParser::COMPLETION_ASSIGN: {
#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
GDScriptCompletionIdentifier ci;
if (_guess_expression_type(context, p.get_completion_node(), p.get_completion_line(), ci)) {
String enumeration = ci.enumeration;
if (enumeration.find(".") != -1) {
//class constant
List<StringName> constants;
String cls = enumeration.get_slice(".", 0);
String enm = enumeration.get_slice(".", 1);
ClassDB::get_enum_constants(cls, enm, &constants);
//constants.sort_custom<StringName::AlphCompare>();
for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
String add = cls + "." + E->get();
r_options->push_back(add);
r_forced = true;
}
} else {
//global constant
StringName current_enum = enumeration;
for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
r_options->push_back(GlobalConstants::get_global_constant_name(i));
r_forced = true;
}
}
//global
}
}
#endif
} break;
}
for (Set<String>::Element *E = options.front(); E; E = E->next()) {
r_options->push_back(E->get());
}
return OK;
}
#else
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) {
return OK;
}
#endif
String GDScriptLanguage::_get_indentation() const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
bool use_space_indentation = EDITOR_DEF("text_editor/indent/type", 0);
if (use_space_indentation) {
int indent_size = EDITOR_DEF("text_editor/indent/size", 4);
String space_indent = "";
for (int i = 0; i < indent_size; i++) {
space_indent += " ";
}
return space_indent;
}
}
#endif
return "\t";
}
void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {
String indent = _get_indentation();
Vector<String> lines = p_code.split("\n");
List<int> indent_stack;
for (int i = 0; i < lines.size(); i++) {
String l = lines[i];
int tc = 0;
for (int j = 0; j < l.length(); j++) {
if (l[j] == ' ' || l[j] == '\t') {
tc++;
} else {
break;
}
}
String st = l.substr(tc, l.length()).strip_edges();
if (st == "" || st.begins_with("#"))
continue; //ignore!
int ilevel = 0;
if (indent_stack.size()) {
ilevel = indent_stack.back()->get();
}
if (tc > ilevel) {
indent_stack.push_back(tc);
} else if (tc < ilevel) {
while (indent_stack.size() && indent_stack.back()->get() > tc) {
indent_stack.pop_back();
}
if (indent_stack.size() && indent_stack.back()->get() != tc)
indent_stack.push_back(tc); //this is not right but gets the job done
}
if (i >= p_from_line) {
l = "";
for (int j = 0; j < indent_stack.size(); j++) {
l += indent;
}
l += st;
} else if (i > p_to_line) {
break;
}
//print_line(itos(indent_stack.size())+","+itos(tc)+": "+l);
lines[i] = l;
}
p_code = "";
for (int i = 0; i < lines.size(); i++) {
if (i > 0)
p_code += "\n";
p_code += lines[i];
}
}
#ifdef TOOLS_ENABLED
Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_base_path, Object *p_owner, LookupResult &r_result) {
//before parsing, try the usual stuff
if (ClassDB::class_exists(p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
r_result.class_name = p_symbol;
return OK;
}
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
Variant::Type t = Variant::Type(i);
if (Variant::get_type_name(t) == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
r_result.class_name = Variant::get_type_name(t);
return OK;
}
}
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
if (GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)) == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = "@GDScript";
r_result.class_member = p_symbol;
return OK;
}
}
GDScriptParser p;
p.parse(p_code, p_base_path, false, "", true);
if (p.get_completion_type() == GDScriptParser::COMPLETION_NONE)
return ERR_CANT_RESOLVE;
GDScriptCompletionContext context;
context._class = p.get_completion_class();
context.block = p.get_completion_block();
context.function = p.get_completion_function();
context.base = p_owner;
context.base_path = p_base_path;
bool isfunction = false;
switch (p.get_completion_type()) {
case GDScriptParser::COMPLETION_GET_NODE:
case GDScriptParser::COMPLETION_NONE: {
} break;
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
r_result.class_name = Variant::get_type_name(p.get_completion_built_in_constant());
r_result.class_member = p_symbol;
return OK;
} break;
case GDScriptParser::COMPLETION_FUNCTION: {
if (context._class && context._class->functions.size()) {
for (int i = 0; i < context._class->functions.size(); i++) {
if (context._class->functions[i]->name == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = context._class->functions[i]->line;
return OK;
}
}
}
Ref<GDScript> parent = _get_parent_class(context);
while (parent.is_valid()) {
int line = parent->get_member_line(p_symbol);
if (line >= 0) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = line;
r_result.script = parent;
return OK;
}
parent = parent->get_base();
}
GDScriptCompletionIdentifier identifier = _get_native_class(context);
print_line("identifier: " + String(identifier.obj_type));
if (ClassDB::has_method(identifier.obj_type, p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = identifier.obj_type;
r_result.class_member = p_symbol;
return OK;
}
} break;
case GDScriptParser::COMPLETION_IDENTIFIER: {
//check if a function
if (p.get_completion_identifier_is_function()) {
if (context._class && context._class->functions.size()) {
for (int i = 0; i < context._class->functions.size(); i++) {
if (context._class->functions[i]->name == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = context._class->functions[i]->line;
return OK;
}
}
}
Ref<GDScript> parent = _get_parent_class(context);
while (parent.is_valid()) {
int line = parent->get_member_line(p_symbol);
if (line >= 0) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = line;
r_result.script = parent;
return OK;
}
parent = parent->get_base();
}
GDScriptCompletionIdentifier identifier = _get_native_class(context);
if (ClassDB::has_method(identifier.obj_type, p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = identifier.obj_type;
r_result.class_member = p_symbol;
return OK;
}
} else {
GDScriptCompletionIdentifier gdi = _get_native_class(context);
if (gdi.obj_type != StringName()) {
bool valid;
Variant::Type t = ClassDB::get_property_type(gdi.obj_type, p_symbol, &valid);
if (t != Variant::NIL && valid) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
r_result.class_name = gdi.obj_type;
r_result.class_member = p_symbol;
return OK;
}
}
const GDScriptParser::BlockNode *block = context.block;
//search in blocks going up (local var?)
while (block) {
for (int i = 0; i < block->statements.size(); i++) {
if (block->statements[i]->line > p.get_completion_line())
continue;
if (block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) {
const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(block->statements[i]);
if (lv->assign && lv->name == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = block->statements[i]->line;
return OK;
}
}
}
block = block->parent_block;
}
//guess from function arguments
if (context.function && context.function->name != StringName()) {
for (int i = 0; i < context.function->arguments.size(); i++) {
if (context.function->arguments[i] == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = context.function->line;
return OK;
}
}
}
//guess in class constants
for (int i = 0; i < context._class->constant_expressions.size(); i++) {
if (context._class->constant_expressions[i].identifier == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = context._class->constant_expressions[i].expression->line;
return OK;
}
}
//guess in class variables
if (!(context.function && context.function->_static)) {
for (int i = 0; i < context._class->variables.size(); i++) {
if (context._class->variables[i].identifier == p_symbol) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = context._class->variables[i].line;
return OK;
}
}
}
//guess in autoloads as singletons
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
String s = E->get().name;
if (!s.begins_with("autoload/"))
continue;
String name = s.get_slice("/", 1);
if (name == String(p_symbol)) {
String path = ProjectSettings::get_singleton()->get(s);
if (path.begins_with("*")) {
String script = path.substr(1, path.length());
if (!script.ends_with(".gd")) {
//not a script, try find the script anyway,
//may have some success
script = script.get_basename() + ".gd";
}
if (FileAccess::exists(script)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = 0;
r_result.script = ResourceLoader::load(script);
return OK;
}
}
}
}
//global
Map<StringName, int> classes = GDScriptLanguage::get_singleton()->get_global_map();
if (classes.has(p_symbol)) {
Variant value = GDScriptLanguage::get_singleton()->get_global_array()[classes[p_symbol]];
if (value.get_type() == Variant::OBJECT) {
Object *obj = value;
if (obj) {
if (Object::cast_to<GDScriptNativeClass>(obj)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
r_result.class_name = Object::cast_to<GDScriptNativeClass>(obj)->get_name();
} else {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS;
r_result.class_name = obj->get_class();
}
// proxy class remove the underscore.
if (r_result.class_name.begins_with("_")) {
r_result.class_name = r_result.class_name.right(1);
}
return OK;
}
} else {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
r_result.class_name = "@GlobalScope";
r_result.class_member = p_symbol;
return OK;
}
}
}
} break;
case GDScriptParser::COMPLETION_PARENT_FUNCTION: {
} break;
case GDScriptParser::COMPLETION_METHOD:
isfunction = true;
case GDScriptParser::COMPLETION_INDEX: {
const GDScriptParser::Node *node = p.get_completion_node();
if (node->type != GDScriptParser::Node::TYPE_OPERATOR)
break;
GDScriptCompletionIdentifier t;
if (_guess_expression_type(context, static_cast<const GDScriptParser::OperatorNode *>(node)->arguments[0], p.get_completion_line(), t)) {
if (t.type == Variant::OBJECT && t.obj_type == "GDScriptNativeClass") {
//native enum
Ref<GDScriptNativeClass> gdn = t.value;
if (gdn.is_valid()) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
r_result.class_name = gdn->get_name();
r_result.class_member = p_symbol;
return OK;
}
} else if (t.type == Variant::OBJECT && t.obj_type != StringName()) {
Ref<GDScript> on_script;
if (t.value.get_type()) {
Object *obj = t.value;
if (obj) {
on_script = obj->get_script();
if (on_script.is_valid()) {
int loc = on_script->get_member_line(p_symbol);
if (loc >= 0) {
r_result.script = on_script;
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = loc;
return OK;
}
}
}
}
if (ClassDB::has_method(t.obj_type, p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = t.obj_type;
r_result.class_member = p_symbol;
return OK;
}
bool success;
ClassDB::get_integer_constant(t.obj_type, p_symbol, &success);
if (success) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
r_result.class_name = t.obj_type;
r_result.class_member = p_symbol;
return OK;
}
ClassDB::get_property_type(t.obj_type, p_symbol, &success);
if (success) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
r_result.class_name = t.obj_type;
r_result.class_member = p_symbol;
return OK;
}
} else {
Variant::CallError ce;
Variant v = Variant::construct(t.type, NULL, 0, ce);
bool valid;
v.get_numeric_constant_value(t.type, p_symbol, &valid);
if (valid) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
r_result.class_name = Variant::get_type_name(t.type);
r_result.class_member = p_symbol;
return OK;
}
//todo check all inputevent types for property
v.get(p_symbol, &valid);
if (valid) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY;
r_result.class_name = Variant::get_type_name(t.type);
r_result.class_member = p_symbol;
return OK;
}
if (v.has_method(p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = Variant::get_type_name(t.type);
r_result.class_member = p_symbol;
return OK;
}
}
}
} break;
case GDScriptParser::COMPLETION_CALL_ARGUMENTS: {
return ERR_CANT_RESOLVE;
} break;
case GDScriptParser::COMPLETION_VIRTUAL_FUNC: {
GDScriptCompletionIdentifier cid = _get_native_class(context);
if (cid.obj_type != StringName()) {
List<MethodInfo> vm;
ClassDB::get_virtual_methods(cid.obj_type, &vm);
for (List<MethodInfo>::Element *E = vm.front(); E; E = E->next()) {
if (p_symbol == E->get().name) {
r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD;
r_result.class_name = cid.obj_type;
r_result.class_member = p_symbol;
return OK;
}
}
}
} break;
case GDScriptParser::COMPLETION_YIELD: {
return ERR_CANT_RESOLVE;
} break;
}
return ERR_CANT_RESOLVE;
}
#endif