diff options
-rw-r--r-- | core/script_language.h | 20 | ||||
-rw-r--r-- | modules/gdscript/gd_compiler.cpp | 19 | ||||
-rw-r--r-- | modules/gdscript/gd_editor.cpp | 437 | ||||
-rw-r--r-- | modules/gdscript/gd_parser.cpp | 9 | ||||
-rw-r--r-- | modules/gdscript/gd_parser.h | 3 | ||||
-rw-r--r-- | modules/gdscript/gd_script.h | 15 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 147 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 8 | ||||
-rw-r--r-- | tools/editor/plugins/script_editor_plugin.cpp | 47 | ||||
-rw-r--r-- | tools/editor/plugins/script_editor_plugin.h | 4 | ||||
-rw-r--r-- | tools/editor/plugins/script_text_editor.cpp | 77 | ||||
-rw-r--r-- | tools/editor/plugins/script_text_editor.h | 1 |
12 files changed, 778 insertions, 9 deletions
diff --git a/core/script_language.h b/core/script_language.h index 1b037e908c..53af4c74d1 100644 --- a/core/script_language.h +++ b/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; diff --git a/modules/gdscript/gd_compiler.cpp b/modules/gdscript/gd_compiler.cpp index 923f8090f7..2e2cbe7b29 100644 --- a/modules/gdscript/gd_compiler.cpp +++ b/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); diff --git a/modules/gdscript/gd_editor.cpp b/modules/gdscript/gd_editor.cpp index 29dbe82a47..48b58c16b8 100644 --- a/modules/gdscript/gd_editor.cpp +++ b/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; +} diff --git a/modules/gdscript/gd_parser.cpp b/modules/gdscript/gd_parser.cpp index 2bebcb5fe1..8f4f5ef4ca 100644 --- a/modules/gdscript/gd_parser.cpp +++ b/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; diff --git a/modules/gdscript/gd_parser.h b/modules/gdscript/gd_parser.h index aca77e0df0..75653e0916 100644 --- a/modules/gdscript/gd_parser.h +++ b/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(); diff --git a/modules/gdscript/gd_script.h b/modules/gdscript/gd_script.h index 0c3e1eb614..cb20361068 100644 --- a/modules/gdscript/gd_script.h +++ b/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); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 87df8c076e..9db0a66395 100644 --- a/scene/gui/text_edit.cpp +++ b/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); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index f0301fc250..cb49618f18 100644 --- a/scene/gui/text_edit.h +++ b/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(); diff --git a/tools/editor/plugins/script_editor_plugin.cpp b/tools/editor/plugins/script_editor_plugin.cpp index 77b4106473..340e5cca4e 100644 --- a/tools/editor/plugins/script_editor_plugin.cpp +++ b/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); diff --git a/tools/editor/plugins/script_editor_plugin.h b/tools/editor/plugins/script_editor_plugin.h index 907240a352..10f3bce14e 100644 --- a/tools/editor/plugins/script_editor_plugin.h +++ b/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(); diff --git a/tools/editor/plugins/script_text_editor.cpp b/tools/editor/plugins/script_text_editor.cpp index 68b1042565..ca0398f069 100644 --- a/tools/editor/plugins/script_text_editor.cpp +++ b/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 ); diff --git a/tools/editor/plugins/script_text_editor.h b/tools/editor/plugins/script_text_editor.h index c311a21131..2c7eac6095 100644 --- a/tools/editor/plugins/script_text_editor.h +++ b/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; |