소스 검색

Do ctrl-click on any code identifier to go to definiton or help page.

Juan Linietsky 9 년 전
부모
커밋
37f1e86108

+ 20 - 0
core/script_language.h

@@ -122,6 +122,7 @@ public:
 	virtual void get_script_method_list(List<MethodInfo> *p_list) const=0;
 	virtual void get_script_property_list(List<PropertyInfo> *p_list) const=0;
 
+	virtual int get_member_line(const StringName& p_member) const { return 0; }
 
 	Script() {}
 };
@@ -207,7 +208,26 @@ public:
 	virtual bool has_named_classes() const=0;
 	virtual int find_function(const String& p_function,const String& p_code) const=0;
 	virtual String make_function(const String& p_class,const String& p_name,const StringArray& p_args) const=0;
+
 	virtual Error complete_code(const String& p_code, const String& p_base_path, Object*p_owner,List<String>* r_options,String& r_call_hint) { return ERR_UNAVAILABLE; }
+
+	struct LookupResult {
+		enum Type {
+			RESULT_SCRIPT_LOCATION,
+			RESULT_CLASS,
+			RESULT_CLASS_CONSTANT,
+			RESULT_CLASS_PROPERTY,
+			RESULT_CLASS_METHOD
+		};
+		Type type;
+		Ref<Script> script;
+		String class_name;
+		String class_member;
+		int location;
+	};
+
+	virtual Error lookup_code(const String& p_code, const String& p_symbol,const String& p_base_path, Object*p_owner,LookupResult& r_result) { return ERR_UNAVAILABLE; }
+
 	virtual void auto_indent_code(String& p_code,int p_from_line,int p_to_line) const=0;
 	virtual void add_global_constant(const StringName& p_variable,const Variant& p_value)=0;
 

+ 19 - 0
modules/gdscript/gd_compiler.cpp

@@ -1443,6 +1443,10 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode *
 #endif
 	if (p_func) {
 		gdfunc->_initial_line=p_func->line;
+#ifdef TOOLS_ENABLED
+
+		p_script->member_lines[func_name]=p_func->line;
+#endif
 	} else {
 		gdfunc->_initial_line=0;
 	}
@@ -1672,6 +1676,12 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa
 		p_script->member_indices[name]=minfo;
 		p_script->members.insert(name);
 
+#ifdef TOOLS_ENABLED
+
+		p_script->member_lines[name]=p_class->variables[i].line;
+#endif
+
+
 	}
 
 	for(int i=0;i<p_class->constant_expressions.size();i++) {
@@ -1683,6 +1693,11 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa
 
 		p_script->constants.insert(name,constant->value);
 		//p_script->constants[constant->value].make_const();
+#ifdef TOOLS_ENABLED
+
+		p_script->member_lines[name]=p_class->constant_expressions[i].expression->line;
+#endif
+
 	}
 
 	for(int i=0;i<p_class->_signals.size();i++) {
@@ -1731,6 +1746,10 @@ Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDPa
 		if (err)
 			return err;
 
+#ifdef TOOLS_ENABLED
+
+		p_script->member_lines[name]=p_class->subclasses[i]->line;
+#endif
 
 		p_script->constants.insert(name,subclass); //once parsed, goes to the list of constants
 		p_script->subclasses.insert(name,subclass);

+ 437 - 0
modules/gdscript/gd_editor.cpp

@@ -351,6 +351,7 @@ struct GDCompletionIdentifier {
 	Ref<GDScript> script;
 	Variant::Type type;
 	Variant value; //im case there is a value, also return it
+
 };
 
 
@@ -935,6 +936,7 @@ static bool _guess_expression_type(GDCompletionContext& context,const GDParser::
 	return false;
 }
 
+
 static bool _guess_identifier_type_in_block(GDCompletionContext& context,int p_line,const StringName& p_identifier,GDCompletionIdentifier &r_type) {
 
 
@@ -2522,3 +2524,438 @@ void GDScriptLanguage::auto_indent_code(String& p_code,int p_from_line,int p_to_
 	}
 
 }
+
+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 (ObjectTypeDB::type_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<GDFunctions::FUNC_MAX;i++) {
+		if (GDFunctions::get_func_name(GDFunctions::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;
+		}
+	}
+
+	GDParser p;
+	p.parse(p_code,p_base_path,false,"",true);
+
+	if (p.get_completion_type()==GDParser::COMPLETION_NONE)
+		return ERR_CANT_RESOLVE;
+
+	GDCompletionContext 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 GDParser::COMPLETION_NONE: {
+		} break;
+		case GDParser::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 GDParser::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();
+			}
+
+			GDCompletionIdentifier identifier = _get_native_class(context);
+			print_line("identifier: "+String(identifier.obj_type));
+
+			if (ObjectTypeDB::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 GDParser::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();
+				}
+
+				GDCompletionIdentifier identifier = _get_native_class(context);
+
+
+				if (ObjectTypeDB::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 {
+
+
+				const GDParser::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==GDParser::BlockNode::TYPE_LOCAL_VAR) {
+
+							const GDParser::LocalVarNode *lv=static_cast<const GDParser::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;
+				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;
+					String name = s.get_slice("/",1);
+					if (name==String(p_symbol)) {
+
+						String path = Globals::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.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
+				for(Map<StringName,int>::Element *E=GDScriptLanguage::get_singleton()->get_global_map().front();E;E=E->next()) {
+					if (E->key()==p_symbol) {
+
+						Variant value = GDScriptLanguage::get_singleton()->get_global_array()[E->get()];
+						if (value.get_type()==Variant::OBJECT) {
+							Object *obj = value;
+							if (obj) {
+
+								if (obj->cast_to<GDNativeClass>()) {
+									r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS;
+									r_result.class_name=obj->cast_to<GDNativeClass>()->get_name();
+
+								} else {
+									r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS;
+									r_result.class_name=obj->get_type();
+								}
+								return OK;
+							}
+						} else {
+
+							r_result.type=ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT;
+							r_result.class_name="@Global Scope";
+							r_result.class_member=p_symbol;
+							return OK;
+						}
+					}
+
+				}
+#if 0
+				GDCompletionIdentifier identifier;
+				if (_guess_identifier_type(context,p.get_completion_line(),p_symbol,identifier)) {
+
+					print_line("var type: "+Variant::get_type_name(identifier.type));
+					if (identifier.script.is_valid()) {
+						print_line("var script: "+identifier.script->get_path());
+					}
+					print_line("obj type: "+String(identifier.obj_type));
+					print_line("value: "+String(identifier.value));
+				}
+#endif
+			}
+
+		} break;
+		case GDParser::COMPLETION_PARENT_FUNCTION: {
+
+		} break;
+		case GDParser::COMPLETION_METHOD:
+			isfunction=true;
+		case GDParser::COMPLETION_INDEX: {
+
+			const GDParser::Node *node = p.get_completion_node();
+			if (node->type!=GDParser::Node::TYPE_OPERATOR)
+				break;
+
+
+
+
+			GDCompletionIdentifier t;
+			if (_guess_expression_type(context,static_cast<const GDParser::OperatorNode *>(node)->arguments[0],p.get_completion_line(),t)) {
+
+				if (t.type==Variant::OBJECT && t.obj_type=="GDNativeClass") {
+					//native enum
+					Ref<GDNativeClass> 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 (ObjectTypeDB::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;
+					ObjectTypeDB::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;
+					}
+
+					ObjectTypeDB::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 GDParser::COMPLETION_CALL_ARGUMENTS: {
+
+			return ERR_CANT_RESOLVE;
+		} break;
+		case GDParser::COMPLETION_VIRTUAL_FUNC: {
+
+			GDCompletionIdentifier cid = _get_native_class(context);
+
+			if (cid.obj_type!=StringName()) {
+				List<MethodInfo> vm;
+				ObjectTypeDB::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 GDParser::COMPLETION_YIELD: {
+
+			return ERR_CANT_RESOLVE;
+
+		} break;
+
+	}
+
+
+	return ERR_CANT_RESOLVE;
+}

+ 9 - 0
modules/gdscript/gd_parser.cpp

@@ -203,6 +203,7 @@ bool GDParser::_get_completable_identifier(CompletionType p_type,StringName& ide
 		completion_line=tokenizer->get_token_line();
 		completion_block=current_block;
 		completion_found=true;
+		completion_ident_is_call=false;
 		tokenizer->advance();
 
 		if (tokenizer->get_token()==GDTokenizer::TK_IDENTIFIER) {
@@ -210,6 +211,9 @@ bool GDParser::_get_completable_identifier(CompletionType p_type,StringName& ide
 			tokenizer->advance();
 		}
 
+		if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_OPEN) {
+			completion_ident_is_call=true;
+		}
 		return true;
 	}
 
@@ -3552,6 +3556,11 @@ int GDParser::get_completion_argument_index() {
 	return completion_argument;
 }
 
+int GDParser::get_completion_identifier_is_function() {
+
+	return completion_ident_is_call;
+}
+
 GDParser::GDParser() {
 
 	head=NULL;

+ 2 - 1
modules/gdscript/gd_parser.h

@@ -432,6 +432,7 @@ private:
 	int completion_line;
 	int completion_argument;
 	bool completion_found;
+	bool completion_ident_is_call;
 
 	PropertyInfo current_export;
 
@@ -478,7 +479,7 @@ public:
 	BlockNode *get_completion_block();
 	FunctionNode *get_completion_function();
 	int get_completion_argument_index();
-
+	int get_completion_identifier_is_function();
 
 	void clear();
 	GDParser();

+ 15 - 0
modules/gdscript/gd_script.h

@@ -65,6 +65,7 @@ class GDScript : public Script {
 		StringName setter;
 		StringName getter;
 		ScriptInstance::RPCMode rpc_mode;
+
 	};
 
 friend class GDInstance;
@@ -86,8 +87,11 @@ friend class GDScriptLanguage;
 	Map<StringName,Ref<GDScript> > subclasses;
 	Map<StringName,Vector<StringName> > _signals;
 
+
 #ifdef TOOLS_ENABLED
 
+	Map<StringName,int> member_lines;
+
 	Map<StringName,Variant> member_default_values;
 
 	List<PropertyInfo> members_cache;
@@ -193,6 +197,16 @@ public:
 
 	virtual ScriptLanguage *get_language() const;
 
+	virtual int get_member_line(const StringName& p_member) const {
+#ifdef TOOLS_ENABLED
+		if (member_lines.has(p_member))
+			return member_lines[p_member];
+		else
+#endif
+			return -1;
+
+	}
+
 	GDScript();
 	~GDScript();
 };
@@ -394,6 +408,7 @@ public:
 	virtual int find_function(const String& p_function,const String& p_code) const;
 	virtual String make_function(const String& p_class,const String& p_name,const StringArray& p_args) const;
 	virtual Error complete_code(const String& p_code, const String& p_base_path, Object*p_owner,List<String>* r_options,String& r_call_hint);
+	virtual Error lookup_code(const String& p_code, const String& p_symbol, const String& p_base_path, Object*p_owner, LookupResult& r_result);
 	virtual void auto_indent_code(String& p_code,int p_from_line,int p_to_line) const;
 	virtual void add_global_constant(const StringName& p_variable,const Variant& p_value);
 

+ 143 - 4
scene/gui/text_edit.cpp

@@ -700,6 +700,7 @@ void TextEdit::_notification(int p_what) {
 				bool prev_is_char=false;
 				bool prev_is_number = false;
 				bool in_keyword=false;
+				bool underlined=false;
 				bool in_word = false;
 				bool in_function_name = false;
 				bool in_member_variable = false;
@@ -825,8 +826,10 @@ void TextEdit::_notification(int p_what) {
 							}
 						}
 
-						if (!is_char)
+						if (!is_char) {
 							in_keyword=false;
+							underlined=false;
+						}
 
 						if (in_region==-1 && !in_keyword && is_char && !prev_is_char) {
 
@@ -844,6 +847,12 @@ void TextEdit::_notification(int p_what) {
 								in_keyword=true;
 								keyword_color=*col;
 							}
+
+							if (select_identifiers_enabled && hilighted_word!=String()) {
+								if (hilighted_word==range) {
+									underlined=true;
+								}
+							}
 						}
 
 						if (!in_function_name && in_word && !in_keyword) {
@@ -1024,8 +1033,12 @@ void TextEdit::_notification(int p_what) {
 						color = cache.caret_background_color;
 					}
 
-					if (str[j]>=32)
-						cache.font->draw_char(ci,Point2i( char_ofs+char_margin, ofs_y+ascent),str[j],str[j+1],in_selection?cache.font_selected_color:color);
+					if (str[j]>=32) {
+						int w = cache.font->draw_char(ci,Point2i( char_ofs+char_margin, ofs_y+ascent),str[j],str[j+1],in_selection?cache.font_selected_color:color);
+						if (underlined) {
+							draw_rect(Rect2( char_ofs+char_margin, ofs_y+ascent+2,w,1),in_selection?cache.font_selected_color:color);
+						}
+					}
 
 					else if (draw_tabs && str[j]=='\t') {
 						int yofs= (get_row_height() - cache.tab_icon->get_height())/2;
@@ -1501,11 +1514,19 @@ void TextEdit::_input_event(const InputEvent& p_input_event) {
 				}
 				if (mb.button_index==BUTTON_LEFT) {
 
+
 					_reset_caret_blink_timer();
 
 					int row,col;
 					_get_mouse_pos(Point2i(mb.x,mb.y), row,col);
 
+					if (mb.mod.command && hilighted_word!=String()) {
+
+						emit_signal("symbol_lookup",hilighted_word,row,col);
+						return;
+					}
+
+
 					// toggle breakpoint on gutter click
 					if (draw_breakpoint_gutter) {
 						int gutter=cache.style_normal->get_margin(MARGIN_LEFT);
@@ -1652,6 +1673,22 @@ void TextEdit::_input_event(const InputEvent& p_input_event) {
 
 			const InputEventMouseMotion &mm=p_input_event.mouse_motion;
 
+			if (select_identifiers_enabled) {
+				if (mm.mod.command && mm.button_mask==0) {
+
+					String new_word = get_word_at_pos(Vector2(mm.x,mm.y));
+					if (new_word!=hilighted_word) {
+						hilighted_word=new_word;
+						update();
+					}
+				} else {
+					if (hilighted_word!=String()) {
+						hilighted_word=String();
+						update();
+					}
+				}
+			}
+
 			if (mm.button_mask&BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data()==Variant()) { //ignore if dragging
 
 				if (selection.selecting_mode!=Selection::MODE_NONE) {
@@ -1679,6 +1716,27 @@ void TextEdit::_input_event(const InputEvent& p_input_event) {
 
 			InputEventKey k=p_input_event.key;
 
+
+#ifdef OSX_ENABLED
+			if (k.scancode==KEY_META) {
+#else
+			if (k.scancode==KEY_CONTROL) {
+
+#endif
+				if (select_identifiers_enabled) {
+
+					if (k.pressed) {
+
+						hilighted_word = get_word_at_pos(get_local_mouse_pos());
+						update();
+
+					} else {
+						hilighted_word=String();
+						update();
+					}
+				}
+			}
+
 			if (!k.pressed)
 				return;
 
@@ -1846,6 +1904,8 @@ void TextEdit::_input_event(const InputEvent& p_input_event) {
 			if (!k.mod.command) {
 				_reset_caret_blink_timer();
 			}
+
+
 			// save here for insert mode, just in case it is cleared in the following section
 			bool had_selection = selection.active;
 
@@ -2559,7 +2619,7 @@ void TextEdit::_input_event(const InputEvent& p_input_event) {
 						}
 						update();
 					}
-					break;}
+				} break;
 
 				default: {
 
@@ -3223,6 +3283,9 @@ void TextEdit::insert_text_at_cursor(const String& p_text) {
 }
 
 Control::CursorShape TextEdit::get_cursor_shape(const Point2& p_pos) const {
+	if (hilighted_word!=String())
+		return CURSOR_POINTING_HAND;
+
 	int gutter=cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w+cache.breakpoint_gutter_width;
 	if((completion_active && completion_rect.has_point(p_pos)) || p_pos.x < gutter) {
 		return CURSOR_ARROW;
@@ -3265,6 +3328,36 @@ String TextEdit::get_text() {
 
 };
 
+
+String TextEdit::get_text_for_lookup_completion() {
+
+
+	int row,col;
+	_get_mouse_pos(get_local_mouse_pos(), row,col);
+
+
+	String longthing;
+	int len = text.size();
+	for (int i=0;i<len;i++) {
+
+		if (i==row) {
+			longthing+=text[i].substr(0,col);
+			longthing+=String::chr(0xFFFF); //not unicode, represents the cursor
+			longthing+=text[i].substr(col,text[i].size());
+		} else {
+
+			longthing+=text[i];
+		}
+
+
+		if (i!=len-1)
+			longthing+="\n";
+	}
+
+	return longthing;
+
+}
+
 String TextEdit::get_text_for_completion() {
 
 	String longthing;
@@ -4302,6 +4395,38 @@ void TextEdit::code_complete(const Vector<String> &p_strings) {
 }
 
 
+String TextEdit::get_word_at_pos(const Vector2& p_pos) const {
+
+	int row,col;
+	_get_mouse_pos(p_pos, row, col);
+
+	String s = text[row];
+	if (s.length()==0)
+		return "";
+	int beg=CLAMP(col,0,s.length());
+	int end=beg;
+
+
+	if (s[beg]>32 || beg==s.length()) {
+
+		bool symbol = beg < s.length() &&  _is_symbol(s[beg]); //not sure if right but most editors behave like this
+
+		while(beg>0 && s[beg-1]>32 && (symbol==_is_symbol(s[beg-1]))) {
+			beg--;
+		}
+		while(end<s.length() && s[end+1]>32 && (symbol==_is_symbol(s[end+1]))) {
+			end++;
+		}
+
+		if (end<s.length())
+			end+=1;
+
+		return s.substr(beg,end-beg);
+	}
+
+	return String();
+}
+
 String TextEdit::get_tooltip(const Point2& p_pos) const {
 
 	if (!tooltip_obj)
@@ -4425,6 +4550,18 @@ void TextEdit::menu_option(int p_option) {
 	};
 }
 
+
+void TextEdit::set_select_identifiers_on_hover(bool p_enable) {
+
+	select_identifiers_enabled=p_enable;
+}
+
+bool TextEdit::is_selecting_identifiers_on_hover_enabled() const {
+
+	return select_identifiers_enabled;
+}
+
+
 PopupMenu *TextEdit::get_menu() const {
 	return menu;
 }
@@ -4521,6 +4658,7 @@ void TextEdit::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("text_changed"));
 	ADD_SIGNAL(MethodInfo("request_completion"));
 	ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo( Variant::INT, "row")));
+	ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING,"symbol"),PropertyInfo( Variant::INT, "row"),PropertyInfo( Variant::INT, "column")));
 
 	BIND_CONSTANT( MENU_CUT );
 	BIND_CONSTANT( MENU_COPY );
@@ -4641,6 +4779,7 @@ TextEdit::TextEdit()  {
 	auto_indent=false;
 	insert_mode = false;
 	window_has_focus=true;
+	select_identifiers_enabled=false;
 
 	menu = memnew( PopupMenu );
 	add_child(menu);

+ 8 - 0
scene/gui/text_edit.h

@@ -242,6 +242,9 @@ class TextEdit : public Control  {
 	bool auto_indent;
 	bool cut_copy_line;
 	bool insert_mode;
+	bool select_identifiers_enabled;
+
+	String hilighted_word;
 
 	uint64_t last_dblclk;
 
@@ -444,6 +447,7 @@ public:
 	String get_selection_text() const;
 
 	String get_word_under_cursor() const;
+	String get_word_at_pos(const Vector2& p_pos) const;
 
 	bool search(const String &p_key,uint32_t p_search_flags, int p_from_line, int p_from_column,int &r_line,int &r_column) const;
 
@@ -492,9 +496,13 @@ public:
 	void set_code_hint(const String& p_hint);
 	void query_code_comple();
 
+	void set_select_identifiers_on_hover(bool p_enable);
+	bool is_selecting_identifiers_on_hover_enabled() const;
+
 	PopupMenu *get_menu() const;
 
 	String get_text_for_completion();
+	String get_text_for_lookup_completion();
 
 	virtual bool is_text_field() const;
 	TextEdit();

+ 46 - 1
tools/editor/plugins/script_editor_plugin.cpp

@@ -43,6 +43,17 @@
 /*** SCRIPT EDITOR ****/
 
 
+
+void ScriptEditorBase::_bind_methods() {
+
+	ADD_SIGNAL(MethodInfo("name_changed"));
+	ADD_SIGNAL(MethodInfo("request_help_search",PropertyInfo(Variant::STRING,"topic")));
+	ADD_SIGNAL(MethodInfo("request_open_script_at_line",PropertyInfo(Variant::OBJECT,"script"),PropertyInfo(Variant::INT,"line")));
+	ADD_SIGNAL(MethodInfo("request_save_history"));
+	ADD_SIGNAL(MethodInfo("go_to_help",PropertyInfo(Variant::STRING,"what")));
+
+}
+
 static bool _can_open_in_editor(Script* p_script) {
 
 	String path = p_script->get_path();
@@ -346,6 +357,34 @@ void ScriptEditor::_update_history_arrows() {
 	script_forward->set_disabled( history_pos>=history.size()-1 );
 }
 
+void ScriptEditor::_save_history() {
+
+
+	if (history_pos>=0 && history_pos<history.size() && history[history_pos].control==tab_container->get_current_tab_control()) {
+
+		Node *n = tab_container->get_current_tab_control();
+
+		if (n->cast_to<ScriptEditorBase>()) {
+
+			history[history_pos].state=n->cast_to<ScriptEditorBase>()->get_edit_state();
+		}
+		if (n->cast_to<EditorHelp>()) {
+
+			history[history_pos].state=n->cast_to<EditorHelp>()->get_scroll();
+		}
+	}
+
+	history.resize(history_pos+1);
+	ScriptHistory sh;
+	sh.control=tab_container->get_current_tab_control();
+	sh.state=Variant();
+
+	history.push_back(sh);
+	history_pos++;
+
+	_update_history_arrows();
+}
+
 
 void ScriptEditor::_go_to_tab(int p_idx) {
 
@@ -1535,6 +1574,11 @@ void ScriptEditor::edit(const Ref<Script>& p_script, bool p_grab_focus) {
 	_save_layout();
 	se->connect("name_changed",this,"_update_script_names");
 	se->connect("request_help_search",this,"_help_search");
+	se->connect("request_open_script_at_line",this,"_goto_script_line");
+	se->connect("go_to_help",this,"_help_class_goto");
+	se->connect("request_save_history",this,"_save_history");
+
+
 
 
 	//test for modification, maybe the script was not edited but was loaded
@@ -1840,7 +1884,6 @@ void ScriptEditor::_help_class_open(const String& p_class) {
 
 void ScriptEditor::_help_class_goto(const String& p_desc) {
 
-
 	String cname=p_desc.get_slice(":",1);
 
 	for(int i=0;i<tab_container->get_child_count();i++) {
@@ -2021,6 +2064,8 @@ void ScriptEditor::_bind_methods() {
 	ObjectTypeDB::bind_method("_goto_script_line",&ScriptEditor::_goto_script_line);
 	ObjectTypeDB::bind_method("_goto_script_line2",&ScriptEditor::_goto_script_line2);
 	ObjectTypeDB::bind_method("_help_search",&ScriptEditor::_help_search);
+	ObjectTypeDB::bind_method("_save_history",&ScriptEditor::_save_history);
+
 
 
 	ObjectTypeDB::bind_method("_breaked",&ScriptEditor::_breaked);

+ 3 - 1
tools/editor/plugins/script_editor_plugin.h

@@ -78,7 +78,8 @@ class ScriptEditorDebugger;
 class ScriptEditorBase : public Control {
 
 	OBJ_TYPE( ScriptEditorBase, Control );
-
+protected:
+	static void _bind_methods();
 public:
 
 	virtual void apply_code()=0;
@@ -282,6 +283,7 @@ class ScriptEditor : public VBoxContainer {
 	void _help_class_open(const String& p_class);
 	void _help_class_goto(const String& p_desc);
 	void _update_history_arrows();
+	void _save_history();
 	void _go_to_tab(int p_idx);
 	void _update_history_pos(int p_new_pos);
 	void _update_script_colors();

+ 75 - 2
tools/editor/plugins/script_text_editor.cpp

@@ -526,6 +526,74 @@ static void swap_lines(TextEdit *tx, int line1, int line2)
     tx->cursor_set_line(line2);
 }
 
+void ScriptTextEditor::_lookup_symbol(const String& p_symbol,int p_row, int p_column) {
+
+	Node *base = get_tree()->get_edited_scene_root();
+	if (base) {
+		base = _find_node_for_script(base,base,script);
+	}
+
+
+	ScriptLanguage::LookupResult result;
+	if (script->get_language()->lookup_code(code_editor->get_text_edit()->get_text_for_lookup_completion(),p_symbol,script->get_path().get_base_dir(),base,result)==OK) {
+
+		_goto_line(p_row);
+
+		switch(result.type) {
+			case ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION: {
+
+				if (result.script.is_valid()) {
+					emit_signal("request_open_script_at_line",result.script,result.location-1);
+				} else {
+					emit_signal("request_save_history");
+					_goto_line(result.location-1);
+				}
+			} break;
+			case ScriptLanguage::LookupResult::RESULT_CLASS: {
+				emit_signal("go_to_help","class_name:"+result.class_name);
+			} break;
+			case ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT: {
+
+				StringName cname = result.class_name;
+				bool success;
+				while(true) {
+					ObjectTypeDB::get_integer_constant(cname,result.class_member,&success);
+					if (success) {
+						result.class_name=cname;
+						cname=ObjectTypeDB::type_inherits_from(cname);
+					} else {
+						break;
+					}
+				}
+
+
+				emit_signal("go_to_help","class_constant:"+result.class_name+":"+result.class_member);
+
+			} break;
+			case ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY: {
+				emit_signal("go_to_help","class_property:"+result.class_name+":"+result.class_member);
+
+			} break;
+			case ScriptLanguage::LookupResult::RESULT_CLASS_METHOD: {
+
+				StringName cname = result.class_name;
+
+				while(true) {
+					if (ObjectTypeDB::has_method(cname,result.class_member)) {
+						result.class_name=cname;
+						cname=ObjectTypeDB::type_inherits_from(cname);
+					} else {
+						break;
+					}
+				}
+
+				emit_signal("go_to_help","class_method:"+result.class_name+":"+result.class_member);
+
+			} break;
+		}
+
+	}
+}
 
 void ScriptTextEditor::_edit_option(int p_op) {
 
@@ -920,13 +988,14 @@ void ScriptTextEditor::_bind_methods() {
 	ObjectTypeDB::bind_method("_breakpoint_toggled",&ScriptTextEditor::_breakpoint_toggled);
 	ObjectTypeDB::bind_method("_edit_option",&ScriptTextEditor::_edit_option);
 	ObjectTypeDB::bind_method("_goto_line",&ScriptTextEditor::_goto_line);
+	ObjectTypeDB::bind_method("_lookup_symbol",&ScriptTextEditor::_lookup_symbol);
+
+
 
 	ObjectTypeDB::bind_method("get_drag_data_fw",&ScriptTextEditor::get_drag_data_fw);
 	ObjectTypeDB::bind_method("can_drop_data_fw",&ScriptTextEditor::can_drop_data_fw);
 	ObjectTypeDB::bind_method("drop_data_fw",&ScriptTextEditor::drop_data_fw);
 
-	ADD_SIGNAL(MethodInfo("name_changed"));
-	ADD_SIGNAL(MethodInfo("request_help_search",PropertyInfo(Variant::STRING,"topic")));
 }
 
 Control *ScriptTextEditor::get_edit_menu() {
@@ -1109,6 +1178,8 @@ ScriptTextEditor::ScriptTextEditor() {
 	code_editor->connect("load_theme_settings",this,"_load_theme_settings");
 	code_editor->set_code_complete_func(_code_complete_scripts,this);
 	code_editor->get_text_edit()->connect("breakpoint_toggled", this, "_breakpoint_toggled");
+	code_editor->get_text_edit()->connect("symbol_lookup", this, "_lookup_symbol");
+
 
 	code_editor->get_text_edit()->set_scroll_pass_end_of_file(EditorSettings::get_singleton()->get("text_editor/scroll_past_end_of_file"));
 	code_editor->get_text_edit()->set_auto_brace_completion(EditorSettings::get_singleton()->get("text_editor/auto_brace_complete"));
@@ -1125,6 +1196,8 @@ ScriptTextEditor::ScriptTextEditor() {
 		EditorSettings::get_singleton()->get("text_editor/put_callhint_tooltip_below_current_line"),
 		EditorSettings::get_singleton()->get("text_editor/callhint_tooltip_offset"));
 
+	code_editor->get_text_edit()->set_select_identifiers_on_hover(true);
+
 	edit_hb = memnew (HBoxContainer);
 
 	edit_menu = memnew( MenuButton );

+ 1 - 0
tools/editor/plugins/script_text_editor.h

@@ -98,6 +98,7 @@ protected:
 	void _edit_option(int p_op);
 
 	void _goto_line(int p_line) { goto_line(p_line); }
+	void _lookup_symbol(const String& p_symbol,int p_row, int p_column);
 
 	Variant get_drag_data_fw(const Point2& p_point,Control* p_from);
 	bool can_drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from) const;