improved get_node(), connect(), etc code completion.
-properly completes text arguments -includes the "/root" autoloads
This commit is contained in:
parent
71cc2561c6
commit
a67486a39e
|
@ -136,6 +136,14 @@ ScriptInstance::~ScriptInstance() {
|
|||
|
||||
}
|
||||
|
||||
|
||||
ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton=NULL;
|
||||
ScriptCodeCompletionCache::ScriptCodeCompletionCache() {
|
||||
singleton=this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ScriptLanguage::frame() {
|
||||
|
||||
|
||||
|
|
|
@ -126,6 +126,19 @@ public:
|
|||
virtual ~ScriptInstance();
|
||||
};
|
||||
|
||||
class ScriptCodeCompletionCache {
|
||||
|
||||
static ScriptCodeCompletionCache *singleton;
|
||||
public:
|
||||
|
||||
virtual RES get_cached_resource(const String& p_path)=0;
|
||||
|
||||
static ScriptCodeCompletionCache* get_sigleton() { return singleton; }
|
||||
|
||||
ScriptCodeCompletionCache();
|
||||
|
||||
};
|
||||
|
||||
class ScriptLanguage {
|
||||
public:
|
||||
|
||||
|
|
|
@ -381,7 +381,12 @@ static Ref<Reference> _get_parent_class(GDCompletionContext& context) {
|
|||
|
||||
path=context.base_path.plus_file(path);
|
||||
}
|
||||
script = ResourceLoader::load(path);
|
||||
|
||||
if (ScriptCodeCompletionCache::get_sigleton())
|
||||
script = ScriptCodeCompletionCache::get_sigleton()->get_cached_resource(path);
|
||||
else
|
||||
script = ResourceLoader::load(path);
|
||||
|
||||
if (script.is_null()) {
|
||||
return REF();
|
||||
}
|
||||
|
@ -1322,6 +1327,21 @@ static void _find_type_arguments(const GDParser::Node*p_node,int p_line,const St
|
|||
if (obj) {
|
||||
List<String> options;
|
||||
obj->get_argument_options(p_method,p_argidx,&options);
|
||||
if (obj->is_type("Node") && p_argidx==0 && (String(p_method)=="get_node" || String(p_method)=="has_node")) {
|
||||
|
||||
List<PropertyInfo> props;
|
||||
Globals::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);
|
||||
options.push_back("\"/root/"+name+"\"");
|
||||
}
|
||||
}
|
||||
for(List<String>::Element *E=options.front();E;E=E->next()) {
|
||||
|
||||
result.insert(E->get());
|
||||
|
@ -1661,7 +1681,9 @@ Error GDScriptLanguage::complete_code(const String& p_code, const String& p_base
|
|||
//print_line( p_code.replace(String::chr(0xFFFF),"<cursor>"));
|
||||
|
||||
GDParser p;
|
||||
Error err = p.parse(p_code,p_base_path,true);
|
||||
//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(p_code,p_base_path,false,"",true);
|
||||
bool isfunction=false;
|
||||
Set<String> options;
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "print_string.h"
|
||||
#include "io/resource_loader.h"
|
||||
#include "os/file_access.h"
|
||||
#include "script_language.h"
|
||||
|
||||
template<class T>
|
||||
T* GDParser::alloc_node() {
|
||||
|
@ -116,6 +117,14 @@ bool GDParser::_parse_arguments(Node* p_parent,Vector<Node*>& p_args,bool p_stat
|
|||
if (tokenizer->get_token()==GDTokenizer::TK_CURSOR) {
|
||||
_make_completable_call(argidx);
|
||||
completion_node=p_parent;
|
||||
} else if (tokenizer->get_token()==GDTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type()==Variant::STRING && tokenizer->get_token(1)==GDTokenizer::TK_CURSOR) {
|
||||
//completing a string argument..
|
||||
completion_cursor=tokenizer->get_token_constant();
|
||||
|
||||
_make_completable_call(argidx);
|
||||
completion_node=p_parent;
|
||||
tokenizer->advance(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
Node*arg = _parse_expression(p_parent,p_static);
|
||||
|
@ -277,7 +286,11 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
|
|||
if (!validating) {
|
||||
|
||||
//this can be too slow for just validating code
|
||||
res = ResourceLoader::load(path);
|
||||
if (for_completion && ScriptCodeCompletionCache::get_sigleton()) {
|
||||
res = ScriptCodeCompletionCache::get_sigleton()->get_cached_resource(path);
|
||||
} else {
|
||||
res = ResourceLoader::load(path);
|
||||
}
|
||||
if (!res.is_valid()) {
|
||||
_set_error("Can't preload resource at path: "+path);
|
||||
return NULL;
|
||||
|
@ -2814,6 +2827,8 @@ Error GDParser::_parse(const String& p_base_path) {
|
|||
|
||||
Error GDParser::parse_bytecode(const Vector<uint8_t> &p_bytecode,const String& p_base_path, const String &p_self_path) {
|
||||
|
||||
for_completion=false;
|
||||
validating=false;
|
||||
completion_type=COMPLETION_NONE;
|
||||
completion_node=NULL;
|
||||
completion_class=NULL;
|
||||
|
@ -2834,7 +2849,7 @@ Error GDParser::parse_bytecode(const Vector<uint8_t> &p_bytecode,const String& p
|
|||
}
|
||||
|
||||
|
||||
Error GDParser::parse(const String& p_code, const String& p_base_path, bool p_just_validate, const String &p_self_path) {
|
||||
Error GDParser::parse(const String& p_code, const String& p_base_path, bool p_just_validate, const String &p_self_path,bool p_for_completion) {
|
||||
|
||||
completion_type=COMPLETION_NONE;
|
||||
completion_node=NULL;
|
||||
|
@ -2851,6 +2866,7 @@ Error GDParser::parse(const String& p_code, const String& p_base_path, bool p_ju
|
|||
tt->set_code(p_code);
|
||||
|
||||
validating=p_just_validate;
|
||||
for_completion=p_for_completion;
|
||||
tokenizer=tt;
|
||||
Error ret = _parse(p_base_path);
|
||||
memdelete(tt);
|
||||
|
@ -2886,6 +2902,7 @@ void GDParser::clear() {
|
|||
current_function=NULL;
|
||||
|
||||
validating=false;
|
||||
for_completion=false;
|
||||
error_set=false;
|
||||
tab_level.clear();
|
||||
tab_level.push_back(0);
|
||||
|
|
|
@ -387,6 +387,7 @@ private:
|
|||
T* alloc_node();
|
||||
|
||||
bool validating;
|
||||
bool for_completion;
|
||||
int parenthesis;
|
||||
bool error_set;
|
||||
String error;
|
||||
|
@ -443,7 +444,7 @@ public:
|
|||
String get_error() const;
|
||||
int get_error_line() const;
|
||||
int get_error_column() const;
|
||||
Error parse(const String& p_code, const String& p_base_path="", bool p_just_validate=false,const String& p_self_path="");
|
||||
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 parse_bytecode(const Vector<uint8_t> &p_bytecode,const String& p_base_path="",const String& p_self_path="");
|
||||
|
||||
const Node *get_parse_tree() const;
|
||||
|
|
|
@ -568,7 +568,10 @@ void GDTokenizerText::_advance() {
|
|||
} else if( string_mode!=STRING_MULTILINE && CharType(GETCHAR(i))=='\n') {
|
||||
_make_error("Unexpected EOL at String.");
|
||||
return;
|
||||
|
||||
} else if( CharType(GETCHAR(i))==0xFFFF) {
|
||||
//string ends here, next will be TK
|
||||
i--;
|
||||
break;
|
||||
} else if (CharType(GETCHAR(i))=='\\') {
|
||||
//escaped characters...
|
||||
i++;
|
||||
|
@ -670,19 +673,19 @@ void GDTokenizerText::_advance() {
|
|||
while(true) {
|
||||
if (GETCHAR(i)=='.') {
|
||||
if (period_found || exponent_found) {
|
||||
_make_error("Invalid numeric constant at '.'");
|
||||
_make_error("Invalid numeric constant at '.'");
|
||||
return;
|
||||
}
|
||||
period_found=true;
|
||||
} else if (GETCHAR(i)=='x') {
|
||||
if (hexa_found || str.length()!=1 || !( (i==1 && str[0]=='0') || (i==2 && str[1]=='0' && str[0]=='-') ) ) {
|
||||
_make_error("Invalid numeric constant at 'x'");
|
||||
if (hexa_found || str.length()!=1 || !( (i==1 && str[0]=='0') || (i==2 && str[1]=='0' && str[0]=='-') ) ) {
|
||||
_make_error("Invalid numeric constant at 'x'");
|
||||
return;
|
||||
}
|
||||
hexa_found=true;
|
||||
} else if (!hexa_found && GETCHAR(i)=='e') {
|
||||
} else if (!hexa_found && GETCHAR(i)=='e') {
|
||||
if (hexa_found || exponent_found) {
|
||||
_make_error("Invalid numeric constant at 'e'");
|
||||
_make_error("Invalid numeric constant at 'e'");
|
||||
return;
|
||||
}
|
||||
exponent_found=true;
|
||||
|
@ -692,7 +695,7 @@ void GDTokenizerText::_advance() {
|
|||
|
||||
} else if ((GETCHAR(i)=='-' || GETCHAR(i)=='+') && exponent_found) {
|
||||
if (sign_found) {
|
||||
_make_error("Invalid numeric constant at '-'");
|
||||
_make_error("Invalid numeric constant at '-'");
|
||||
return;
|
||||
}
|
||||
sign_found=true;
|
||||
|
@ -703,20 +706,20 @@ void GDTokenizerText::_advance() {
|
|||
i++;
|
||||
}
|
||||
|
||||
if (!( _is_number(str[str.length()-1]) || (hexa_found && _is_hex(str[str.length()-1])))) {
|
||||
_make_error("Invalid numeric constant: "+str);
|
||||
if (!( _is_number(str[str.length()-1]) || (hexa_found && _is_hex(str[str.length()-1])))) {
|
||||
_make_error("Invalid numeric constant: "+str);
|
||||
return;
|
||||
}
|
||||
|
||||
INCPOS(str.length());
|
||||
if (hexa_found) {
|
||||
int val = str.hex_to_int();
|
||||
_make_constant(val);
|
||||
} else if (period_found) {
|
||||
if (hexa_found) {
|
||||
int val = str.hex_to_int();
|
||||
_make_constant(val);
|
||||
} else if (period_found) {
|
||||
real_t val = str.to_double();
|
||||
//print_line("*%*%*%*% to convert: "+str+" result: "+rtos(val));
|
||||
_make_constant(val);
|
||||
} else {
|
||||
} else {
|
||||
int val = str.to_int();
|
||||
_make_constant(val);
|
||||
|
||||
|
@ -825,7 +828,7 @@ void GDTokenizerText::_advance() {
|
|||
|
||||
_make_built_in_func(GDFunctions::Function(i));
|
||||
found=true;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3323,9 +3323,32 @@ void TextEdit::_update_completion_candidates() {
|
|||
|
||||
//look for keywords first
|
||||
|
||||
bool pre_keyword=false;
|
||||
bool inquote=false;
|
||||
int first_quote=-1;
|
||||
|
||||
if (cofs>0 && l[cofs-1]==' ') {
|
||||
int c=cofs-1;
|
||||
while(c>=0) {
|
||||
if (l[c]=='"' || l[c]=='\'') {
|
||||
inquote=!inquote;
|
||||
if (first_quote==-1)
|
||||
first_quote=c;
|
||||
}
|
||||
c--;
|
||||
}
|
||||
|
||||
bool pre_keyword=false;
|
||||
bool cancel=false;
|
||||
|
||||
//print_line("inquote: "+itos(inquote)+"first quote "+itos(first_quote)+" cofs-1 "+itos(cofs-1));
|
||||
if (!inquote && first_quote==cofs-1) {
|
||||
//no completion here
|
||||
//print_line("cancel!");
|
||||
cancel=true;
|
||||
} if (inquote && first_quote!=-1) {
|
||||
|
||||
s=l.substr(first_quote,cofs-first_quote);
|
||||
//print_line("s: 1"+s);
|
||||
} else if (cofs>0 && l[cofs-1]==' ') {
|
||||
int kofs=cofs-1;
|
||||
String kw;
|
||||
while (kofs>=0 && l[kofs]==' ')
|
||||
|
@ -3337,7 +3360,7 @@ void TextEdit::_update_completion_candidates() {
|
|||
}
|
||||
|
||||
pre_keyword=keywords.has(kw);
|
||||
print_line("KW "+kw+"? "+itos(pre_keyword));
|
||||
//print_line("KW "+kw+"? "+itos(pre_keyword));
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -3354,7 +3377,7 @@ void TextEdit::_update_completion_candidates() {
|
|||
|
||||
update();
|
||||
|
||||
if (!pre_keyword && s=="" && (cofs==0 || !completion_prefixes.has(String::chr(l[cofs-1])))) {
|
||||
if (cancel || (!pre_keyword && s=="" && (cofs==0 || !completion_prefixes.has(String::chr(l[cofs-1]))))) {
|
||||
//none to complete, cancel
|
||||
_cancel_completion();
|
||||
return;
|
||||
|
@ -3421,7 +3444,16 @@ void TextEdit::query_code_comple() {
|
|||
String l = text[cursor.line];
|
||||
int ofs = CLAMP(cursor.column,0,l.length());
|
||||
|
||||
if (ofs>0 && (_is_completable(l[ofs-1]) || completion_prefixes.has(String::chr(l[ofs-1]))))
|
||||
bool inquote=false;
|
||||
|
||||
int c=ofs-1;
|
||||
while(c>=0) {
|
||||
if (l[c]=='"' || l[c]=='\'')
|
||||
inquote=!inquote;
|
||||
c--;
|
||||
}
|
||||
|
||||
if (ofs>0 && (inquote || _is_completable(l[ofs-1]) || completion_prefixes.has(String::chr(l[ofs-1]))))
|
||||
emit_signal("request_completion");
|
||||
|
||||
}
|
||||
|
|
|
@ -3047,7 +3047,7 @@ Error EditorNode::load_scene(const String& p_scene) {
|
|||
|
||||
//_cleanup_scene(); // i'm sorry but this MUST happen to avoid modified resources to not be reloaded.
|
||||
|
||||
Ref<PackedScene> sdata = ResourceLoader::load(lpath);
|
||||
Ref<PackedScene> sdata = ResourceLoader::load(lpath,"",true);
|
||||
if (!sdata.is_valid()) {
|
||||
|
||||
current_option=-1;
|
||||
|
@ -3064,6 +3064,8 @@ Error EditorNode::load_scene(const String& p_scene) {
|
|||
return ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
sdata->set_path(lpath,true); //take over path
|
||||
|
||||
Node*new_scene=sdata->instance(true);
|
||||
|
||||
if (!new_scene) {
|
||||
|
|
|
@ -43,6 +43,83 @@
|
|||
/*** SCRIPT EDITOR ****/
|
||||
|
||||
|
||||
class EditorScriptCodeCompletionCache : public ScriptCodeCompletionCache {
|
||||
|
||||
|
||||
struct Cache {
|
||||
uint64_t time_loaded;
|
||||
RES cache;
|
||||
};
|
||||
|
||||
Map<String,Cache> cached;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
uint64_t max_time_cache;
|
||||
int max_cache_size;
|
||||
|
||||
void cleanup() {
|
||||
|
||||
List< Map<String,Cache>::Element * > to_clean;
|
||||
|
||||
|
||||
Map<String,Cache>::Element *I=cached.front();
|
||||
while(I) {
|
||||
if ((OS::get_singleton()->get_ticks_msec()-I->get().time_loaded)>max_time_cache) {
|
||||
to_clean.push_back(I);
|
||||
}
|
||||
I=I->next();
|
||||
}
|
||||
|
||||
while(to_clean.front()) {
|
||||
cached.erase(to_clean.front()->get());
|
||||
to_clean.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
RES get_cached_resource(const String& p_path) {
|
||||
|
||||
Map<String,Cache>::Element *E=cached.find(p_path);
|
||||
if (!E) {
|
||||
|
||||
Cache c;
|
||||
c.cache=ResourceLoader::load(p_path);
|
||||
E=cached.insert(p_path,c);
|
||||
}
|
||||
|
||||
E->get().time_loaded=OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
if (cached.size()>max_cache_size) {
|
||||
uint64_t older;
|
||||
Map<String,Cache>::Element *O=cached.front();
|
||||
older=O->get().time_loaded;
|
||||
Map<String,Cache>::Element *I=O;
|
||||
while(I) {
|
||||
if (I->get().time_loaded<older) {
|
||||
older = I->get().time_loaded;
|
||||
O=I;
|
||||
}
|
||||
I=I->next();
|
||||
}
|
||||
|
||||
if (O!=E) {//should never heppane..
|
||||
cached.erase(O);
|
||||
}
|
||||
}
|
||||
|
||||
return E->get().cache;
|
||||
}
|
||||
|
||||
|
||||
EditorScriptCodeCompletionCache() {
|
||||
|
||||
max_cache_size=128;
|
||||
max_time_cache=5*60*1000; //minutes, five
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#define SORT_SCRIPT_LIST
|
||||
|
||||
void ScriptEditorQuickOpen::popup(const Vector<String>& p_functions, bool p_dontclear) {
|
||||
|
@ -1694,6 +1771,7 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) {
|
|||
|
||||
ScriptEditor::ScriptEditor(EditorNode *p_editor) {
|
||||
|
||||
completion_cache = memnew( EditorScriptCodeCompletionCache );
|
||||
restoring_layout=false;
|
||||
waiting_update_names=false;
|
||||
editor=p_editor;
|
||||
|
@ -1874,6 +1952,11 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
|
|||
}
|
||||
|
||||
|
||||
ScriptEditor::~ScriptEditor() {
|
||||
|
||||
memdelete(completion_cache);
|
||||
}
|
||||
|
||||
void ScriptEditorPlugin::edit(Object *p_object) {
|
||||
|
||||
if (!p_object->cast_to<Script>())
|
||||
|
|
|
@ -106,6 +106,8 @@ public:
|
|||
|
||||
};
|
||||
|
||||
class EditorScriptCodeCompletionCache;
|
||||
|
||||
class ScriptEditor : public VBoxContainer {
|
||||
|
||||
OBJ_TYPE(ScriptEditor, VBoxContainer );
|
||||
|
@ -191,6 +193,7 @@ class ScriptEditor : public VBoxContainer {
|
|||
|
||||
ScriptEditorQuickOpen *quick_open;
|
||||
|
||||
EditorScriptCodeCompletionCache *completion_cache;
|
||||
|
||||
void _editor_play();
|
||||
void _editor_pause();
|
||||
|
@ -248,6 +251,7 @@ public:
|
|||
void get_window_layout(Ref<ConfigFile> p_layout);
|
||||
|
||||
ScriptEditor(EditorNode *p_editor);
|
||||
~ScriptEditor();
|
||||
};
|
||||
|
||||
class ScriptEditorPlugin : public EditorPlugin {
|
||||
|
|
Loading…
Reference in New Issue