Browse Source

Merge pull request #4812 from neikeq/pr-search-bar

ScriptEditor/ShaderEditor: Replace find/replace dialog with a bar
Rémi Verschelde 9 years ago
parent
commit
6a7530c718

+ 76 - 18
scene/gui/text_edit.cpp

@@ -682,9 +682,13 @@ void TextEdit::_notification(int p_what) {
 
 				// check if line contains highlighted word
 				int highlighted_text_col = -1;
-				if (highlighted_text.length() != 0) {
-					highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, 0);
-				}
+				int search_text_col = -1;
+
+				if (!search_text.empty())
+					search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0);
+
+				if (highlighted_text.length() != 0 && highlighted_text != search_text)
+					highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE|SEARCH_WHOLE_WORDS, 0);
 
 				if (cache.line_number_w) {
 					String fc = String::num(line+1);
@@ -879,20 +883,45 @@ void TextEdit::_notification(int p_what) {
 							break;
 					}
 
-					bool in_selection = (selection.active && line>=selection.from_line && line<=selection.to_line && (line>selection.from_line || j>=selection.from_column) && (line<selection.to_line || j<selection.to_column));
+					bool in_search_result=false;
+
+					if (search_text_col != -1) {
+						// if we are at the end check for new search result on same line
+						if (j >= search_text_col+search_text.length())
+							search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j);
 
+						in_search_result = j >= search_text_col && j < search_text_col+search_text.length();
+
+						if (in_search_result) {
+							VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(char_w, get_row_height())),cache.search_result_color);
+						}
+					}
+
+					bool in_selection = (selection.active && line>=selection.from_line && line<=selection.to_line && (line>selection.from_line || j>=selection.from_column) && (line<selection.to_line || j<selection.to_column));
 
 					if (in_selection) {
 						//inside selection!
 						VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(char_w,get_row_height())),cache.selection_color);
 					}
 
+					if (in_search_result) {
+						Color border_color=(line==search_result_line && j>=search_result_col && j<search_result_col+search_text.length())?cache.font_color:cache.search_result_border_color;
+
+						VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(char_w,1)),border_color);
+						VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y+get_row_height()-1 ), Size2i(char_w,1)),border_color);
+
+						if (j==search_text_col)
+							VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(1,get_row_height())),border_color);
+						if (j==search_text_col+search_text.length()-1)
+							VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin+char_w-1, ofs_y ), Size2i(1,get_row_height())),border_color);
+					}
+
 					if (highlight_all_occurrences) {
 						if (highlighted_text_col != -1) {
 
 							// if we are at the end check for new word on same line
 							if (j > highlighted_text_col+highlighted_text.length()) {
-								highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, j);
+								highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE|SEARCH_WHOLE_WORDS, j);
 							}
 
 							bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col+highlighted_text.length());
@@ -1892,7 +1921,8 @@ void TextEdit::_input_event(const InputEvent& p_input_event) {
 					if (completion_hint!="") {
 						completion_hint="";
 						update();
-
+					} else {
+						scancode_handled=false;
 					}
 				} break;
 				case KEY_TAB: {
@@ -3220,6 +3250,8 @@ void TextEdit::_update_caches() {
 	cache.breakpoint_color=get_color("breakpoint_color");
 	cache.brace_mismatch_color=get_color("brace_mismatch_color");
 	cache.word_highlighted_color=get_color("word_highlighted_color");
+	cache.search_result_color=get_color("search_result_color");
+	cache.search_result_border_color=get_color("search_result_border_color");
 	cache.line_spacing=get_constant("line_spacing");
 	cache.row_height = cache.font->get_height() + cache.line_spacing;
 	cache.tab_icon=get_icon("tab");
@@ -3481,12 +3513,25 @@ String TextEdit::get_word_under_cursor() const {
 	return text[cursor.line].substr(prev_cc, next_cc-prev_cc);
 }
 
+void TextEdit::set_search_text(const String &p_search_text) {
+	search_text = p_search_text;
+}
+
+void TextEdit::set_search_flags(uint32_t p_flags) {
+	search_flags = p_flags;
+}
+
+void TextEdit::set_current_search_result(int line, int col) {
+	search_result_line = line;
+	search_result_col = col;
+}
+
 void TextEdit::set_highlight_all_occurrences(const bool p_enabled) {
 	highlight_all_occurrences = p_enabled;
 	update();
 }
 
-int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_search, int p_from_column) {
+int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column) {
 	int col = -1;
 
 	if (p_key.length() > 0 && p_search.length() > 0) {
@@ -3494,12 +3539,15 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc
 			p_from_column = 0;
 		}
 
-		 while (col == -1 && p_from_column <= p_search.length()) {
-			// match case
-			col = p_search.findn(p_key, p_from_column);
+		while (col == -1 && p_from_column <= p_search.length()) {
+			if (p_search_flags&SEARCH_MATCH_CASE) {
+				col = p_search.find(p_key,p_from_column);
+			} else {
+				col = p_search.findn(p_key,p_from_column);
+			}
 
 			// whole words only
-			if (col != -1) {
+			if (col != -1 && p_search_flags&SEARCH_WHOLE_WORDS) {
 				p_from_column=col;
 
 				if (col > 0 && _is_text_char(p_search[col-1])) {
@@ -3565,10 +3613,8 @@ bool TextEdit::search(const String &p_key,uint32_t p_search_flags, int p_from_li
 				//wrapped
 
 				if (p_search_flags&SEARCH_BACKWARDS) {
-					text_line=text_line.substr(from_column,text_line.length());
 					from_column=text_line.length();
 				} else {
-					text_line=text_line.substr(0,from_column);
 					from_column=0;
 				}
 
@@ -3579,7 +3625,6 @@ bool TextEdit::search(const String &p_key,uint32_t p_search_flags, int p_from_li
 
 
 		} else {
-			//text_line=text_line.substr(0,p_from_column); //wrap around for missing begining.
 			if (p_search_flags&SEARCH_BACKWARDS)
 				from_column=text_line.length()-1;
 			else
@@ -3588,12 +3633,25 @@ bool TextEdit::search(const String &p_key,uint32_t p_search_flags, int p_from_li
 
 		pos=-1;
 
-		if (!(p_search_flags&SEARCH_BACKWARDS)) {
+		int pos_from=0;
+		int last_pos=-1;
+		while ((last_pos=(p_search_flags&SEARCH_MATCH_CASE)?text_line.find(p_key,pos_from):text_line.findn(p_key,pos_from))!=-1) {
 
-			pos = (p_search_flags&SEARCH_MATCH_CASE)?text_line.find(p_key,from_column):text_line.findn(p_key,from_column);
-		} else {
+			if (p_search_flags&SEARCH_BACKWARDS) {
+
+				if (last_pos>from_column)
+					break;
+				pos=last_pos;
+
+			} else {
+
+				if (last_pos>=from_column) {
+					pos=last_pos;
+					break;
+				}
+			}
 
-			pos = (p_search_flags&SEARCH_MATCH_CASE)?text_line.rfind(p_key,from_column):text_line.rfindn(p_key,from_column);
+			pos_from=last_pos+p_key.length();
 		}
 
 		if (pos!=-1 && (p_search_flags&SEARCH_WHOLE_WORDS)) {

+ 12 - 1
scene/gui/text_edit.h

@@ -88,6 +88,8 @@ class TextEdit : public Control  {
 		Color current_line_color;
 		Color brace_mismatch_color;
 		Color word_highlighted_color;
+		Color search_result_color;
+		Color search_result_border_color;
 
 		int row_height;
 		int line_spacing;
@@ -249,6 +251,11 @@ class TextEdit : public Control  {
 	bool callhint_below;
 	Vector2 callhint_offset;
 
+	String search_text;
+	uint32_t search_flags;
+	int search_result_line;
+	int search_result_col;
+
 	int get_visible_rows() const;
 
 	int get_char_count();
@@ -287,7 +294,7 @@ class TextEdit : public Control  {
 	String _base_get_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column) const;
 	void _base_remove_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column);
 
-	int _get_column_pos_of_word(const String &p_key, const String &p_search, int p_from_column);
+	int _get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column);
 
 	DVector<int> _search_bind(const String &p_key,uint32_t p_search_flags, int p_from_line,int p_from_column) const;
 
@@ -408,6 +415,10 @@ public:
 	void select(int p_from_line,int p_from_column,int p_to_line,int p_to_column);
 	void deselect();
 
+	void set_search_text(const String& p_search_text);
+	void set_search_flags(uint32_t p_flags);
+	void set_current_search_result(int line, int col);
+
 	void set_highlight_all_occurrences(const bool p_enabled);
 	bool is_selection_active() const;
 	int get_selection_from_line() const;

+ 457 - 14
tools/editor/code_editor.cpp

@@ -30,6 +30,7 @@
 #include "editor_settings.h"
 #include "scene/gui/margin_container.h"
 #include "scene/gui/separator.h"
+#include "os/keyboard.h"
 
 void GotoLineDialog::popup_find_line(TextEdit *p_edit) {
 
@@ -76,6 +77,436 @@ GotoLineDialog::GotoLineDialog() {
 }
 
 
+void FindReplaceBar::_notification(int p_what) {
+
+	if (p_what == NOTIFICATION_READY) {
+
+		find_prev->set_icon(get_icon("MoveUp", "EditorIcons"));
+		find_next->set_icon(get_icon("MoveDown", "EditorIcons"));
+		hide_button->set_normal_texture(get_icon("Close","EditorIcons"));
+		hide_button->set_hover_texture(get_icon("CloseHover","EditorIcons"));
+		hide_button->set_pressed_texture(get_icon("Close","EditorIcons"));
+
+	} else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+
+		set_process_unhandled_input(is_visible());
+	}
+}
+
+void FindReplaceBar::_unhandled_input(const InputEvent &p_event) {
+
+	if (p_event.type == InputEvent::KEY) {
+
+		const InputEventKey& k = p_event.key;
+
+		if (k.pressed && (text_edit->has_focus() || text_vbc->is_a_parent_of(get_focus_owner()))) {
+
+			bool accepted = true;
+
+			switch (k.scancode) {
+
+				case KEY_ESCAPE: {
+
+					_hide_bar();
+				} break;
+				default: {
+
+					accepted = false;
+				} break;
+			}
+
+			if (accepted) {
+				accept_event();
+			}
+		}
+	}
+}
+
+bool FindReplaceBar::_search(bool p_include_current, bool p_backwards) {
+
+	String text=get_search_text();
+	uint32_t flags=0;
+
+	if (is_whole_words())
+		flags|=TextEdit::SEARCH_WHOLE_WORDS;
+	if (is_case_sensitive())
+		flags|=TextEdit::SEARCH_MATCH_CASE;
+	if (p_backwards)
+		flags|=TextEdit::SEARCH_BACKWARDS;
+
+	int line=text_edit->cursor_get_line();
+	int col=text_edit->cursor_get_column();
+
+	if (text_edit->is_selection_active() && !replace_all_mode) {
+		line = text_edit->get_selection_from_line();
+		col = text_edit->get_selection_from_column();
+	}
+
+	bool cursor_at_result=false;
+
+	if (line==current_result_line && col>=current_result_col && col<=current_result_col+text.length()) {
+		col=current_result_col;
+		cursor_at_result=true;
+	}
+
+	if (!p_include_current) {
+		if (p_backwards) {
+			col-=text.length();
+			if (col<0) {
+				line-=1;
+				if (line<0)
+					line=text_edit->get_line_count()-1;
+				col=text_edit->get_line(line).length();
+			}
+		} else if (cursor_at_result) {
+			col+=text.length();
+			if (col>text_edit->get_line(line).length()) {
+				line+=1;
+				if (line>=text_edit->get_line_count())
+					line=0;
+				col=0;
+			}
+		}
+	}
+
+	bool found = text_edit->search(text,flags,line,col,line,col);
+
+	if (!found) {
+		if (p_backwards) {
+			line = text_edit->get_line_count()-1;
+			col = text_edit->get_line(line).length()-1;
+		} else {
+			line = 0;
+			col = 0;
+		}
+
+		found = text_edit->search(text,flags,line,col,line,col);
+	}
+
+	if (found) {
+		text_edit->cursor_set_line(line);
+		text_edit->cursor_set_column(p_backwards?col:col+text.length());
+		text_edit->select(line,col,line,col+text.length());
+		text_edit->set_search_text(text);
+		text_edit->set_search_flags(flags);
+		text_edit->set_current_search_result(line,col);
+
+		current_result_line = line;
+		current_result_col = col;
+
+		set_error("");
+	} else {
+		current_result_line = -1;
+		current_result_col = -1;
+		text_edit->set_search_text("");
+		set_error(text.empty()?"":TTR("No Matches"));
+	}
+
+	return found;
+}
+
+void FindReplaceBar::_replace() {
+
+	if (text_edit->get_selection_text()==get_search_text()) {
+		text_edit->insert_text_at_cursor(get_replace_text());
+	}
+
+	search_current();
+}
+
+void FindReplaceBar::_replace_all() {
+
+	// line as x so it gets priority in comparison, column as y
+	Point2i orig_cursor(text_edit->cursor_get_line(),text_edit->cursor_get_column());
+	Point2i prev_match=Point2(-1,-1);
+
+	bool selection_enabled = text_edit->is_selection_active();
+	Point2i selection_begin,selection_end;
+	if (selection_enabled) {
+		selection_begin=Point2i(text_edit->get_selection_from_line(),text_edit->get_selection_from_column());
+		selection_end=Point2i(text_edit->get_selection_to_line(),text_edit->get_selection_to_column());
+	}
+
+	int vsval = text_edit->get_v_scroll();
+
+	text_edit->cursor_set_line(0);
+	text_edit->cursor_set_column(0);
+
+	int rc=0;
+
+	replace_all_mode = true;
+
+	text_edit->begin_complex_operation();
+
+	while(_search(false)) {
+
+		if (!text_edit->is_selection_active()) {
+			// search selects
+			break;
+		}
+
+		// replace area
+		Point2i match_from(text_edit->get_selection_from_line(),text_edit->get_selection_from_column());
+		Point2i match_to(text_edit->get_selection_to_line(),text_edit->get_selection_to_column());
+
+		if (match_from < prev_match)
+			break; // done
+
+		prev_match=match_to;
+
+		if (selection_enabled && is_selection_only()) {
+
+			if (match_from<selection_begin || match_to>selection_end)
+				continue;
+
+			// replace but adjust selection bounds
+			text_edit->insert_text_at_cursor(get_replace_text());
+			if (match_to.x==selection_end.x)
+				selection_end.y+=get_replace_text().length() - get_search_text().length();
+		} else {
+			//just replace
+			text_edit->insert_text_at_cursor(get_replace_text());
+		}
+
+		rc++;
+	}
+
+	text_edit->end_complex_operation();
+
+	replace_all_mode = false;
+
+	// restore editor state (selection, cursor, scroll)
+	text_edit->cursor_set_line(orig_cursor.x);
+	text_edit->cursor_set_column(orig_cursor.y);
+
+	if (selection_enabled && is_selection_only()) {
+		// reselect
+		text_edit->select(selection_begin.x,selection_begin.y,selection_end.x,selection_end.y);
+	} else {
+		text_edit->deselect();
+	}
+
+	text_edit->set_v_scroll(vsval);
+	set_error(vformat(TTR("Replaced %d Ocurrence(s)."), rc));
+}
+
+void FindReplaceBar::search_current() {
+
+	_search(true);
+}
+
+void FindReplaceBar::search_prev() {
+
+	_search(false, true);
+}
+
+void FindReplaceBar::search_next() {
+
+	_search();
+}
+
+void FindReplaceBar::_hide_bar() {
+
+	text_edit->set_search_text("");
+	current_result_line = -1;
+	current_result_col = -1;
+	replace_hbc->hide();
+	replace_options_hbc->hide();
+	hide();
+}
+
+void FindReplaceBar::_show_search() {
+
+	show();
+	search_text->grab_focus();
+
+	if (text_edit->is_selection_active()) {
+		search_text->set_text(text_edit->get_selection_text());
+	}
+
+	if (!get_search_text().empty()) {
+		search_text->select_all();
+		search_text->set_cursor_pos(search_text->get_text().length());
+		search_current();
+	}
+}
+
+void FindReplaceBar::popup_search() {
+
+	replace_hbc->hide();
+	replace_options_hbc->hide();
+	_show_search();
+}
+
+void FindReplaceBar::popup_replace() {
+
+	if (!replace_hbc->is_visible() || !replace_options_hbc->is_visible()) {
+		replace_text->clear();
+		replace_hbc->show();
+		replace_options_hbc->show();
+	}
+
+	_show_search();
+}
+
+void FindReplaceBar::_search_options_changed(bool p_pressed) {
+
+	search_current();
+}
+
+void FindReplaceBar::_search_text_changed(const String& p_text) {
+
+	search_current();
+}
+
+void FindReplaceBar::_search_text_entered(const String& p_text) {
+
+	search_next();
+}
+
+String FindReplaceBar::get_search_text() const {
+
+	return search_text->get_text();
+}
+
+String FindReplaceBar::get_replace_text() const {
+
+	return replace_text->get_text();
+}
+
+bool FindReplaceBar::is_case_sensitive() const {
+
+	return case_sensitive->is_pressed();
+}
+
+bool FindReplaceBar::is_whole_words() const {
+
+	return whole_words->is_pressed();
+}
+
+bool FindReplaceBar::is_selection_only() const {
+
+	return selection_only->is_pressed();
+}
+
+void FindReplaceBar::set_error(const String &p_label) {
+
+	error_label->set_text(p_label);
+}
+
+void FindReplaceBar::set_text_edit(TextEdit *p_text_edit) {
+
+	text_edit = p_text_edit;
+	text_edit->connect("_text_changed",this,"_search_text_changed",varray(String()));
+}
+
+void FindReplaceBar::_bind_methods() {
+
+	ObjectTypeDB::bind_method("_unhandled_input",&FindReplaceBar::_unhandled_input);
+
+	ObjectTypeDB::bind_method("_search_text_changed",&FindReplaceBar::_search_text_changed);
+	ObjectTypeDB::bind_method("_search_text_entered",&FindReplaceBar::_search_text_entered);
+	ObjectTypeDB::bind_method("_search_current",&FindReplaceBar::search_current);
+	ObjectTypeDB::bind_method("_search_next",&FindReplaceBar::search_next);
+	ObjectTypeDB::bind_method("_search_prev",&FindReplaceBar::search_prev);
+	ObjectTypeDB::bind_method("_replace_pressed",&FindReplaceBar::_replace);
+	ObjectTypeDB::bind_method("_replace_all_pressed",&FindReplaceBar::_replace_all);
+	ObjectTypeDB::bind_method("_search_options_changed",&FindReplaceBar::_search_options_changed);
+	ObjectTypeDB::bind_method("_hide_pressed",&FindReplaceBar::_hide_bar);
+
+	ADD_SIGNAL(MethodInfo("search"));
+}
+
+FindReplaceBar::FindReplaceBar() {
+
+	text_vbc = memnew(VBoxContainer);
+	add_child(text_vbc);
+
+	HBoxContainer *search_hbc = memnew(HBoxContainer);
+	text_vbc->add_child(search_hbc);
+
+	search_text = memnew(LineEdit);
+	search_hbc->add_child(search_text);
+	search_text->set_custom_minimum_size(Size2(200, 0));
+	search_text->connect("text_changed",this,"_search_text_changed");
+	search_text->connect("text_entered",this,"_search_text_entered");
+
+	find_prev = memnew(ToolButton);
+	search_hbc->add_child(find_prev);
+	find_prev->set_focus_mode(FOCUS_NONE);
+	find_prev->connect("pressed",this,"_search_prev");
+
+	find_next = memnew(ToolButton);
+	search_hbc->add_child(find_next);
+	find_next->set_focus_mode(FOCUS_NONE);
+	find_next->connect("pressed",this,"_search_next");
+
+	replace_hbc = memnew(HBoxContainer);
+	text_vbc->add_child(replace_hbc);
+	replace_hbc->hide();
+
+	replace_text = memnew(LineEdit);
+	replace_hbc->add_child(replace_text);
+	replace_text->set_custom_minimum_size(Size2(200, 0));
+	replace_text->connect("text_entered",this,"_search_text_entered");
+
+	replace = memnew(ToolButton);
+	replace_hbc->add_child(replace);
+	replace->set_text(TTR("Replace"));
+	replace->set_focus_mode(FOCUS_NONE);
+	replace->connect("pressed",this,"_replace_pressed");
+
+	replace_all = memnew(ToolButton);
+	replace_hbc->add_child(replace_all);
+	replace_all->set_text(TTR("Replace All"));
+	replace_all->set_focus_mode(FOCUS_NONE);
+	replace_all->connect("pressed",this,"_replace_all_pressed");
+
+	Control *spacer_split = memnew( Control );
+	spacer_split->set_custom_minimum_size(Size2(0, 1));
+	text_vbc->add_child(spacer_split);
+
+	VBoxContainer *options_vbc = memnew(VBoxContainer);
+	add_child(options_vbc);
+	options_vbc->set_h_size_flags(SIZE_EXPAND_FILL);
+
+	HBoxContainer *search_options = memnew(HBoxContainer);
+	options_vbc->add_child(search_options);
+
+	case_sensitive = memnew(CheckBox);
+	search_options->add_child(case_sensitive);
+	case_sensitive->set_text(TTR("Match Case"));
+	case_sensitive->set_focus_mode(FOCUS_NONE);
+	case_sensitive->connect("toggled",this,"_search_options_changed");
+
+	whole_words = memnew(CheckBox);
+	search_options->add_child(whole_words);
+	whole_words->set_text(TTR("Whole Words"));
+	whole_words->set_focus_mode(FOCUS_NONE);
+	whole_words->connect("toggled",this,"_search_options_changed");
+
+	error_label = memnew(Label);
+	search_options->add_child(error_label);
+
+	search_options->add_spacer();
+
+	hide_button = memnew(TextureButton);
+	search_options->add_child(hide_button);
+	hide_button->set_focus_mode(FOCUS_NONE);
+	hide_button->connect("pressed",this,"_hide_pressed");
+
+	replace_options_hbc = memnew(HBoxContainer);
+	options_vbc->add_child(replace_options_hbc);
+	replace_options_hbc->hide();
+
+	selection_only = memnew(CheckBox);
+	replace_options_hbc->add_child(selection_only);
+	selection_only->set_text(TTR("Selection Only"));
+	selection_only->set_focus_mode(FOCUS_NONE);
+	selection_only->connect("toggled",this,"_search_options_changed");
+}
+
+
 void FindReplaceDialog::popup_search() {
 
 	set_title(TTR("Search"));
@@ -610,22 +1041,32 @@ void CodeTextEditor::_bind_methods() {
 
 CodeTextEditor::CodeTextEditor() {
 
+	find_replace_bar = memnew( FindReplaceBar );
+	add_child(find_replace_bar);
+	find_replace_bar->set_h_size_flags(SIZE_EXPAND_FILL);
+	find_replace_bar->hide();
+
 	text_editor = memnew( TextEdit );
 	add_child(text_editor);
-	text_editor->set_area_as_parent_rect();
-	text_editor->set_margin(MARGIN_BOTTOM,20);
+	text_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+
+	find_replace_bar->set_text_edit(text_editor);
 
 	text_editor->set_show_line_numbers(true);
 	text_editor->set_brace_matching(true);
 	text_editor->set_auto_indent(true);
 
-	line_col = memnew( Label );
-	add_child(line_col);
-	line_col->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,135);
-	line_col->set_anchor_and_margin(MARGIN_TOP,ANCHOR_END,15);
-	line_col->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,1);
-	line_col->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,5);
-	//line_col->set_align(Label::ALIGN_RIGHT);
+	MarginContainer *status_mc = memnew( MarginContainer );
+	add_child(status_mc);
+	status_mc->set("custom_constants/margin_left", 2);
+	status_mc->set("custom_constants/margin_top", 5);
+	status_mc->set("custom_constants/margin_right", 2);
+	status_mc->set("custom_constants/margin_bottom", 1);
+
+	HBoxContainer *status_bar = memnew( HBoxContainer );
+	status_mc->add_child(status_bar);
+	status_bar->set_h_size_flags(SIZE_EXPAND_FILL);
+
 	idle = memnew( Timer );
 	add_child(idle);
 	idle->set_one_shot(true);
@@ -639,14 +1080,16 @@ CodeTextEditor::CodeTextEditor() {
 	code_complete_timer->set_wait_time(EDITOR_DEF("text_editor/code_complete_delay",.3f));
 
 	error = memnew( Label );
-	add_child(error);
-	error->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,5);
-	error->set_anchor_and_margin(MARGIN_TOP,ANCHOR_END,15);
-	error->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,1);
-	error->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,130);
+	status_bar->add_child(error);
 	error->hide();
+	error->set_valign(Label::VALIGN_CENTER);
 	error->add_color_override("font_color",Color(1,0.7,0.6,0.9));
 
+	status_bar->add_spacer();
+
+	line_col = memnew( Label );
+	status_bar->add_child(line_col);
+	line_col->set_valign(Label::VALIGN_CENTER);
 
 
 	text_editor->connect("cursor_changed", this,"_line_col_changed");

+ 69 - 2
tools/editor/code_editor.h

@@ -33,7 +33,9 @@
 #include "scene/gui/text_edit.h"
 #include "scene/gui/dialogs.h"
 #include "scene/main/timer.h"
+#include "scene/gui/tool_button.h"
 #include "scene/gui/check_button.h"
+#include "scene/gui/check_box.h"
 #include "scene/gui/line_edit.h"
 
 
@@ -58,8 +60,71 @@ public:
 	GotoLineDialog();
 };
 
+class FindReplaceBar : public HBoxContainer {
 
+	OBJ_TYPE(FindReplaceBar,HBoxContainer);
 
+	LineEdit *search_text;
+	ToolButton *find_prev;
+	ToolButton *find_next;
+	CheckBox *case_sensitive;
+	CheckBox *whole_words;
+	Label *error_label;
+	TextureButton *hide_button;
+
+	LineEdit *replace_text;
+	ToolButton *replace;
+	ToolButton *replace_all;
+	CheckBox *selection_only;
+
+	VBoxContainer *text_vbc;
+	HBoxContainer *replace_hbc;
+	HBoxContainer *replace_options_hbc;
+
+	TextEdit *text_edit;
+
+	int current_result_line;
+	int current_result_col;
+
+	bool replace_all_mode;
+
+	void _show_search();
+	void _hide_bar();
+	void _search_options_changed(bool p_pressed);
+	void _search_text_changed(const String& p_text);
+	void _search_text_entered(const String& p_text);
+
+protected:
+	void _notification(int p_what);
+	void _unhandled_input(const InputEvent &p_event);
+
+	bool _search(bool p_include_current=false, bool p_backwards=false);
+
+	void _replace();
+	void _replace_all();
+
+	static void _bind_methods();
+
+public:
+	String get_search_text() const;
+	String get_replace_text() const;
+
+	bool is_case_sensitive() const;
+	bool is_whole_words() const;
+	bool is_selection_only() const;
+	void set_error(const String& p_label);
+
+	void set_text_edit(TextEdit *p_text_edit);
+
+	void popup_search();
+	void popup_replace();
+
+	void search_current();
+	void search_prev();
+	void search_next();
+
+	FindReplaceBar();
+};
 
 class FindReplaceDialog : public ConfirmationDialog {
 
@@ -119,11 +184,12 @@ public:
 };
 
 
-class CodeTextEditor : public Control {
+class CodeTextEditor : public VBoxContainer {
 
-	OBJ_TYPE(CodeTextEditor,Control);
+	OBJ_TYPE(CodeTextEditor,VBoxContainer);
 
 	TextEdit *text_editor;
+	FindReplaceBar *find_replace_bar;
 
 	Label *line_col;
 	Label *info;
@@ -158,6 +224,7 @@ protected:
 public:
 
 	TextEdit *get_text_edit() { return text_editor; }
+	FindReplaceBar *get_find_replace_bar() { return find_replace_bar; }
 	virtual void apply_code() {}
 
 	CodeTextEditor();

+ 4 - 0
tools/editor/editor_settings.cpp

@@ -620,6 +620,8 @@ void EditorSettings::_load_default_text_editor_theme() {
 	set("text_editor/mark_color", Color(1.0,0.4,0.4,0.4));
 	set("text_editor/breakpoint_color", Color(0.8,0.8,0.4,0.2));
 	set("text_editor/word_highlighted_color",Color(0.8,0.9,0.9,0.15));
+	set("text_editor/search_result_color",Color(0.05,0.25,0.05,1));
+	set("text_editor/search_result_border_color",Color(0.1,0.45,0.1,1));
 }
 
 void EditorSettings::notify_changes() {
@@ -851,6 +853,8 @@ bool EditorSettings::_save_text_editor_theme(String p_file) {
 	cf->set_value(theme_section, "mark_color", ((Color)get("text_editor/mark_color")).to_html());
 	cf->set_value(theme_section, "breakpoint_color", ((Color)get("text_editor/breakpoint_color")).to_html());
 	cf->set_value(theme_section, "word_highlighted_color", ((Color)get("text_editor/word_highlighted_color")).to_html());
+	cf->set_value(theme_section, "search_result_color", ((Color)get("text_editor/search_result_color")).to_html());
+	cf->set_value(theme_section, "search_result_border_color", ((Color)get("text_editor/search_result_border_color")).to_html());
 	Error err = cf->save(p_file);
 
 	if (err == OK) {

+ 10 - 9
tools/editor/plugins/script_editor_plugin.cpp

@@ -300,6 +300,8 @@ void ScriptTextEditor::_load_theme_settings() {
 	get_text_edit()->add_color_override("member_variable_color",EDITOR_DEF("text_editor/member_variable_color",Color(0.9,0.3,0.3)));
 	get_text_edit()->add_color_override("mark_color", EDITOR_DEF("text_editor/mark_color", Color(1.0,0.4,0.4,0.4)));
 	get_text_edit()->add_color_override("breakpoint_color", EDITOR_DEF("text_editor/breakpoint_color", Color(0.8,0.8,0.4,0.2)));
+	get_text_edit()->add_color_override("search_result_color",EDITOR_DEF("text_editor/search_result_color",Color(0.05,0.25,0.05,1)));
+	get_text_edit()->add_color_override("search_result_border_color",EDITOR_DEF("text_editor/search_result_border_color",Color(0.1,0.45,0.1,1)));
 
 	Color keyword_color= EDITOR_DEF("text_editor/keyword_color",Color(0.5,0.0,0.2));
 
@@ -1405,18 +1407,19 @@ void ScriptEditor::_menu_option(int p_option) {
 			} break;
 			case SEARCH_FIND: {
 
-				find_replace_dialog->set_text_edit(current->get_text_edit());
-				find_replace_dialog->popup_search();
+				current->get_find_replace_bar()->popup_search();
 			} break;
 			case SEARCH_FIND_NEXT: {
 
-				find_replace_dialog->set_text_edit(current->get_text_edit());
-				find_replace_dialog->search_next();
+				current->get_find_replace_bar()->search_next();
+			} break;
+			case SEARCH_FIND_PREV: {
+
+				current->get_find_replace_bar()->search_prev();
 			} break;
 			case SEARCH_REPLACE: {
 
-				find_replace_dialog->set_text_edit(current->get_text_edit());
-				find_replace_dialog->popup_replace();
+				current->get_find_replace_bar()->popup_replace();
 			} break;
 			case SEARCH_LOCATE_FUNCTION: {
 
@@ -2531,6 +2534,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
 	search_menu->set_text(TTR("Search"));
 	search_menu->get_popup()->add_item(TTR("Find.."),SEARCH_FIND,KEY_MASK_CMD|KEY_F);
 	search_menu->get_popup()->add_item(TTR("Find Next"),SEARCH_FIND_NEXT,KEY_F3);
+	search_menu->get_popup()->add_item(TTR("Find Previous"),SEARCH_FIND_PREV,KEY_MASK_SHIFT|KEY_F3);
 	search_menu->get_popup()->add_item(TTR("Replace.."),SEARCH_REPLACE,KEY_MASK_CMD|KEY_R);
 	search_menu->get_popup()->add_separator();
 	search_menu->get_popup()->add_item(TTR("Goto Function.."),SEARCH_LOCATE_FUNCTION,KEY_MASK_SHIFT|KEY_MASK_CMD|KEY_F);
@@ -2635,9 +2639,6 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
 
 	tab_container->connect("tab_changed", this,"_tab_changed");
 
-	find_replace_dialog = memnew(FindReplaceDialog);
-	add_child(find_replace_dialog);
-
 	erase_tab_confirm = memnew( ConfirmationDialog );
 	add_child(erase_tab_confirm);
 	erase_tab_confirm->connect("confirmed", this,"_close_current_tab");

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

@@ -144,6 +144,7 @@ class ScriptEditor : public VBoxContainer {
 		EDIT_CLONE_DOWN,
 		SEARCH_FIND,
 		SEARCH_FIND_NEXT,
+		SEARCH_FIND_PREV,
 		SEARCH_REPLACE,
 		SEARCH_LOCATE_FUNCTION,
 		SEARCH_GOTO_LINE,
@@ -184,7 +185,6 @@ class ScriptEditor : public VBoxContainer {
 	HSplitContainer *script_split;
 	TabContainer *tab_container;
 	EditorFileDialog *file_dialog;
-	FindReplaceDialog *find_replace_dialog;
 	GotoLineDialog *goto_line_dialog;
 	ConfirmationDialog *erase_tab_confirm;
 	ScriptCreateDialog *script_create_dialog;

+ 10 - 9
tools/editor/plugins/shader_editor_plugin.cpp

@@ -90,6 +90,8 @@ void ShaderTextEditor::_load_theme_settings() {
 	get_text_edit()->add_color_override("member_variable_color",EDITOR_DEF("text_editor/member_variable_color",Color(0.9,0.3,0.3)));
 	get_text_edit()->add_color_override("mark_color", EDITOR_DEF("text_editor/mark_color", Color(1.0,0.4,0.4,0.4)));
 	get_text_edit()->add_color_override("breakpoint_color", EDITOR_DEF("text_editor/breakpoint_color", Color(0.8,0.8,0.4,0.2)));
+	get_text_edit()->add_color_override("search_result_color",EDITOR_DEF("text_editor/search_result_color",Color(0.05,0.25,0.05,1)));
+	get_text_edit()->add_color_override("search_result_border_color",EDITOR_DEF("text_editor/search_result_border_color",Color(0.1,0.45,0.1,1)));
 
 	Color keyword_color= EDITOR_DEF("text_editor/keyword_color",Color(0.5,0.0,0.2));
 
@@ -212,18 +214,19 @@ void ShaderEditor::_menu_option(int p_option) {
 		} break;
 		case SEARCH_FIND: {
 
-			find_replace_dialog->set_text_edit(current->get_text_edit());
-			find_replace_dialog->popup_search();
+			current->get_find_replace_bar()->popup_search();
 		} break;
 		case SEARCH_FIND_NEXT: {
 
-			find_replace_dialog->set_text_edit(current->get_text_edit());
-			 find_replace_dialog->search_next();
+			current->get_find_replace_bar()->search_next();
+		} break;
+		case SEARCH_FIND_PREV: {
+
+			current->get_find_replace_bar()->search_prev();
 		} break;
 		case SEARCH_REPLACE: {
 
-			find_replace_dialog->set_text_edit(current->get_text_edit());
-			find_replace_dialog->popup_replace();
+			current->get_find_replace_bar()->popup_replace();
 		} break;
 //		case SEARCH_LOCATE_SYMBOL: {
 
@@ -507,6 +510,7 @@ ShaderEditor::ShaderEditor() {
 	search_menu->set_text(TTR("Search"));
 	search_menu->get_popup()->add_item(TTR("Find.."),SEARCH_FIND,KEY_MASK_CMD|KEY_F);
 	search_menu->get_popup()->add_item(TTR("Find Next"),SEARCH_FIND_NEXT,KEY_F3);
+	search_menu->get_popup()->add_item(TTR("Find Previous"),SEARCH_FIND_PREV,KEY_MASK_SHIFT|KEY_F3);
 	search_menu->get_popup()->add_item(TTR("Replace.."),SEARCH_REPLACE,KEY_MASK_CMD|KEY_R);
 	search_menu->get_popup()->add_separator();
 //	search_menu->get_popup()->add_item("Locate Symbol..",SEARCH_LOCATE_SYMBOL,KEY_MASK_CMD|KEY_K);
@@ -516,9 +520,6 @@ ShaderEditor::ShaderEditor() {
 
 	tab_container->connect("tab_changed", this,"_tab_changed");
 
-	find_replace_dialog = memnew(FindReplaceDialog);
-	add_child(find_replace_dialog);
-
 	erase_tab_confirm = memnew( ConfirmationDialog );
 	add_child(erase_tab_confirm);
 	erase_tab_confirm->connect("confirmed", this,"_close_current_tab");

+ 1 - 1
tools/editor/plugins/shader_editor_plugin.h

@@ -76,6 +76,7 @@ class ShaderEditor : public Control {
 		EDIT_SELECT_ALL,
 		SEARCH_FIND,
 		SEARCH_FIND_NEXT,
+		SEARCH_FIND_PREV,
 		SEARCH_REPLACE,
 		//SEARCH_LOCATE_SYMBOL,
 		SEARCH_GOTO_LINE,
@@ -88,7 +89,6 @@ class ShaderEditor : public Control {
 	uint64_t idle;
 
 	TabContainer *tab_container;
-	FindReplaceDialog *find_replace_dialog;
 	GotoLineDialog *goto_line_dialog;
 	ConfirmationDialog *erase_tab_confirm;