Sfoglia il codice sorgente

improved get_node(), connect(), etc code completion.

-properly completes text arguments
-includes the "/root" autoloads
Juan Linietsky 10 anni fa
parent
commit
a67486a39e

+ 8 - 0
core/script_language.cpp

@@ -136,6 +136,14 @@ ScriptInstance::~ScriptInstance() {
 
 }
 
+
+ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton=NULL;
+ScriptCodeCompletionCache::ScriptCodeCompletionCache() {
+	singleton=this;
+}
+
+
+
 void ScriptLanguage::frame() {
 
 

+ 13 - 0
core/script_language.h

@@ -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:
 

+ 24 - 2
modules/gdscript/gd_editor.cpp

@@ -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;
 

+ 19 - 2
modules/gdscript/gd_parser.cpp

@@ -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);

+ 2 - 1
modules/gdscript/gd_parser.h

@@ -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;

+ 18 - 15
modules/gdscript/gd_tokenizer.cpp

@@ -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;
 								}
 							}
 

+ 37 - 5
scene/gui/text_edit.cpp

@@ -3323,9 +3323,32 @@ void TextEdit::_update_completion_candidates() {
 
 	//look for keywords first
 
-	bool pre_keyword=false;
+	bool inquote=false;
+	int first_quote=-1;
+
+	int c=cofs-1;
+	while(c>=0) {
+		if (l[c]=='"' || l[c]=='\'') {
+			inquote=!inquote;
+			if (first_quote==-1)
+				first_quote=c;
+		}
+		c--;
+	}
 
-	if (cofs>0 && l[cofs-1]==' ') {
+	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");
 	
 }

+ 3 - 1
tools/editor/editor_node.cpp

@@ -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) {

+ 83 - 0
tools/editor/plugins/script_editor_plugin.cpp

@@ -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>())

+ 4 - 0
tools/editor/plugins/script_editor_plugin.h

@@ -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 {