Browse Source

TextEdit code folding

Ian 8 years ago
parent
commit
d350f506a0

+ 6 - 0
editor/code_editor.cpp

@@ -56,6 +56,7 @@ void GotoLineDialog::ok_pressed() {
 
 
 	if (get_line() < 1 || get_line() > text_editor->get_line_count())
 	if (get_line() < 1 || get_line() > text_editor->get_line_count())
 		return;
 		return;
+	text_editor->unfold_line(get_line() - 1);
 	text_editor->cursor_set_line(get_line() - 1);
 	text_editor->cursor_set_line(get_line() - 1);
 	hide();
 	hide();
 }
 }
@@ -139,6 +140,7 @@ bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col)
 
 
 	if (found) {
 	if (found) {
 		if (!preserve_cursor) {
 		if (!preserve_cursor) {
+			text_edit->unfold_line(line);
 			text_edit->cursor_set_line(line, false);
 			text_edit->cursor_set_line(line, false);
 			text_edit->cursor_set_column(col + text.length(), false);
 			text_edit->cursor_set_column(col + text.length(), false);
 			text_edit->center_viewport_to_cursor();
 			text_edit->center_viewport_to_cursor();
@@ -167,6 +169,7 @@ void FindReplaceBar::_replace() {
 	if (result_line != -1 && result_col != -1) {
 	if (result_line != -1 && result_col != -1) {
 		text_edit->begin_complex_operation();
 		text_edit->begin_complex_operation();
 
 
+		text_edit->unfold_line(result_line);
 		text_edit->select(result_line, result_col, result_line, result_col + get_search_text().length());
 		text_edit->select(result_line, result_col, result_line, result_col + get_search_text().length());
 		text_edit->insert_text_at_cursor(get_replace_text());
 		text_edit->insert_text_at_cursor(get_replace_text());
 
 
@@ -214,6 +217,7 @@ void FindReplaceBar::_replace_all() {
 
 
 		prev_match = Point2i(result_line, result_col + replace_text.length());
 		prev_match = Point2i(result_line, result_col + replace_text.length());
 
 
+		text_edit->unfold_line(result_line);
 		text_edit->select(result_line, result_col, result_line, match_to.y);
 		text_edit->select(result_line, result_col, result_line, match_to.y);
 
 
 		if (selection_enabled && is_selection_only()) {
 		if (selection_enabled && is_selection_only()) {
@@ -751,6 +755,7 @@ bool FindReplaceDialog::_search() {
 
 
 	if (found) {
 	if (found) {
 		// print_line("found");
 		// print_line("found");
+		text_edit->unfold_line(line);
 		text_edit->cursor_set_line(line);
 		text_edit->cursor_set_line(line);
 		if (is_backwards())
 		if (is_backwards())
 			text_edit->cursor_set_column(col);
 			text_edit->cursor_set_column(col);
@@ -1093,6 +1098,7 @@ void CodeTextEditor::update_editor_settings() {
 	text_editor->cursor_set_blink_enabled(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink"));
 	text_editor->cursor_set_blink_enabled(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink"));
 	text_editor->cursor_set_blink_speed(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink_speed"));
 	text_editor->cursor_set_blink_speed(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink_speed"));
 	text_editor->set_draw_breakpoint_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/show_breakpoint_gutter"));
 	text_editor->set_draw_breakpoint_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/show_breakpoint_gutter"));
+	text_editor->set_draw_fold_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/code_folding"));
 	text_editor->cursor_set_block_mode(EditorSettings::get_singleton()->get("text_editor/cursor/block_caret"));
 	text_editor->cursor_set_block_mode(EditorSettings::get_singleton()->get("text_editor/cursor/block_caret"));
 	text_editor->set_smooth_scroll_enabled(EditorSettings::get_singleton()->get("text_editor/open_scripts/smooth_scrolling"));
 	text_editor->set_smooth_scroll_enabled(EditorSettings::get_singleton()->get("text_editor/open_scripts/smooth_scrolling"));
 	text_editor->set_v_scroll_speed(EditorSettings::get_singleton()->get("text_editor/open_scripts/v_scroll_speed"));
 	text_editor->set_v_scroll_speed(EditorSettings::get_singleton()->get("text_editor/open_scripts/v_scroll_speed"));

+ 1 - 0
editor/editor_settings.cpp

@@ -334,6 +334,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
 	_initial_set("text_editor/line_numbers/show_line_numbers", true);
 	_initial_set("text_editor/line_numbers/show_line_numbers", true);
 	_initial_set("text_editor/line_numbers/line_numbers_zero_padded", false);
 	_initial_set("text_editor/line_numbers/line_numbers_zero_padded", false);
 	_initial_set("text_editor/line_numbers/show_breakpoint_gutter", true);
 	_initial_set("text_editor/line_numbers/show_breakpoint_gutter", true);
+	_initial_set("text_editor/line_numbers/code_folding", true);
 	_initial_set("text_editor/line_numbers/show_line_length_guideline", false);
 	_initial_set("text_editor/line_numbers/show_line_length_guideline", false);
 	_initial_set("text_editor/line_numbers/line_length_guideline_column", 80);
 	_initial_set("text_editor/line_numbers/line_length_guideline_column", 80);
 	hints["text_editor/line_numbers/line_length_guideline_column"] = PropertyInfo(Variant::INT, "text_editor/line_numbers/line_length_guideline_column", PROPERTY_HINT_RANGE, "20, 160, 10");
 	hints["text_editor/line_numbers/line_length_guideline_column"] = PropertyInfo(Variant::INT, "text_editor/line_numbers/line_length_guideline_column", PROPERTY_HINT_RANGE, "20, 160, 10");

+ 48 - 11
editor/plugins/script_text_editor.cpp

@@ -712,15 +712,6 @@ void ScriptTextEditor::_breakpoint_toggled(int p_row) {
 	ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(script->get_path(), p_row + 1, code_editor->get_text_edit()->is_line_set_as_breakpoint(p_row));
 	ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(script->get_path(), p_row + 1, code_editor->get_text_edit()->is_line_set_as_breakpoint(p_row));
 }
 }
 
 
-static void swap_lines(TextEdit *tx, int line1, int line2) {
-	String tmp = tx->get_line(line1);
-	String tmp2 = tx->get_line(line2);
-	tx->set_line(line2, tmp);
-	tx->set_line(line1, tmp2);
-
-	tx->cursor_set_line(line2);
-}
-
 void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_column) {
 void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_column) {
 
 
 	Node *base = get_tree()->get_edited_scene_root();
 	Node *base = get_tree()->get_edited_scene_root();
@@ -850,6 +841,9 @@ void ScriptTextEditor::_edit_option(int p_op) {
 					if (line_id == 0 || next_id < 0)
 					if (line_id == 0 || next_id < 0)
 						return;
 						return;
 
 
+					if (tx->is_line_hidden(next_id))
+						tx->unfold_line(next_id);
+
 					tx->swap_lines(line_id, next_id);
 					tx->swap_lines(line_id, next_id);
 					tx->cursor_set_line(next_id);
 					tx->cursor_set_line(next_id);
 				}
 				}
@@ -863,6 +857,9 @@ void ScriptTextEditor::_edit_option(int p_op) {
 				if (line_id == 0 || next_id < 0)
 				if (line_id == 0 || next_id < 0)
 					return;
 					return;
 
 
+				if (tx->is_line_hidden(next_id))
+					tx->unfold_line(next_id);
+
 				tx->swap_lines(line_id, next_id);
 				tx->swap_lines(line_id, next_id);
 				tx->cursor_set_line(next_id);
 				tx->cursor_set_line(next_id);
 			}
 			}
@@ -891,6 +888,9 @@ void ScriptTextEditor::_edit_option(int p_op) {
 					if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count())
 					if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count())
 						return;
 						return;
 
 
+					if (tx->is_folded(next_id) || tx->is_line_hidden(next_id))
+						tx->unfold_line(next_id);
+
 					tx->swap_lines(line_id, next_id);
 					tx->swap_lines(line_id, next_id);
 					tx->cursor_set_line(next_id);
 					tx->cursor_set_line(next_id);
 				}
 				}
@@ -904,6 +904,9 @@ void ScriptTextEditor::_edit_option(int p_op) {
 				if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count())
 				if (line_id == tx->get_line_count() - 1 || next_id > tx->get_line_count())
 					return;
 					return;
 
 
+				if (tx->is_folded(next_id) || tx->is_line_hidden(next_id))
+					tx->unfold_line(next_id);
+
 				tx->swap_lines(line_id, next_id);
 				tx->swap_lines(line_id, next_id);
 				tx->cursor_set_line(next_id);
 				tx->cursor_set_line(next_id);
 			}
 			}
@@ -1014,6 +1017,24 @@ void ScriptTextEditor::_edit_option(int p_op) {
 			tx->update();
 			tx->update();
 
 
 		} break;
 		} break;
+		case EDIT_FOLD_LINE: {
+
+			TextEdit *tx = code_editor->get_text_edit();
+			tx->fold_line(tx->cursor_get_line());
+			tx->update();
+		} break;
+		case EDIT_UNFOLD_LINE: {
+
+			TextEdit *tx = code_editor->get_text_edit();
+			tx->unfold_line(tx->cursor_get_line());
+			tx->update();
+		} break;
+		case EDIT_UNFOLD_ALL_LINES: {
+
+			TextEdit *tx = code_editor->get_text_edit();
+			tx->unhide_all_lines();
+			tx->update();
+		} break;
 		case EDIT_TOGGLE_COMMENT: {
 		case EDIT_TOGGLE_COMMENT: {
 
 
 			TextEdit *tx = code_editor->get_text_edit();
 			TextEdit *tx = code_editor->get_text_edit();
@@ -1398,6 +1419,9 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
 			Vector2 mpos = mb->get_global_position() - tx->get_global_position();
 			Vector2 mpos = mb->get_global_position() - tx->get_global_position();
 			bool have_selection = (tx->get_selection_text().length() > 0);
 			bool have_selection = (tx->get_selection_text().length() > 0);
 			bool have_color = (tx->get_word_at_pos(mpos) == "Color");
 			bool have_color = (tx->get_word_at_pos(mpos) == "Color");
+			int fold_state = 0;
+			if (row > 0 && row < tx->get_line_count() - 1)
+				fold_state = tx->can_fold(row) ? 1 : tx->is_folded(row) ? 2 : 0;
 			if (have_color) {
 			if (have_color) {
 
 
 				String line = tx->get_line(row);
 				String line = tx->get_line(row);
@@ -1428,7 +1452,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
 					have_color = false;
 					have_color = false;
 				}
 				}
 			}
 			}
-			_make_context_menu(have_selection, have_color);
+			_make_context_menu(have_selection, have_color, fold_state);
 		}
 		}
 	}
 	}
 }
 }
@@ -1447,7 +1471,7 @@ void ScriptTextEditor::_color_changed(const Color &p_color) {
 	code_editor->get_text_edit()->set_line(color_line, new_line);
 	code_editor->get_text_edit()->set_line(color_line, new_line);
 }
 }
 
 
-void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color) {
+void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, int p_fold_state) {
 
 
 	context_menu->clear();
 	context_menu->clear();
 	if (p_selection) {
 	if (p_selection) {
@@ -1467,6 +1491,13 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color) {
 		context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT);
 		context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT);
 		context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
 		context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
 	}
 	}
+	if (p_fold_state == 1) {
+		// can fold
+		context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_line"), EDIT_FOLD_LINE);
+	} else if (p_fold_state == 2) {
+		// can unfold
+		context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_line"), EDIT_UNFOLD_LINE);
+	}
 	if (p_color) {
 	if (p_color) {
 		context_menu->add_separator();
 		context_menu->add_separator();
 		context_menu->add_item(TTR("Pick Color"), EDIT_PICK_COLOR);
 		context_menu->add_item(TTR("Pick Color"), EDIT_PICK_COLOR);
@@ -1530,6 +1561,9 @@ ScriptTextEditor::ScriptTextEditor() {
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN);
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN);
+	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_line"), EDIT_FOLD_LINE);
+	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_line"), EDIT_UNFOLD_LINE);
+	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES);
 	edit_menu->get_popup()->add_separator();
 	edit_menu->get_popup()->add_separator();
 #ifdef OSX_ENABLED
 #ifdef OSX_ENABLED
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/complete_symbol"), EDIT_COMPLETE);
 	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/complete_symbol"), EDIT_COMPLETE);
@@ -1607,6 +1641,9 @@ void ScriptTextEditor::register_editor() {
 	ED_SHORTCUT("script_text_editor/indent_right", TTR("Indent Right"), 0);
 	ED_SHORTCUT("script_text_editor/indent_right", TTR("Indent Right"), 0);
 	ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KEY_MASK_CMD | KEY_K);
 	ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KEY_MASK_CMD | KEY_K);
 	ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD | KEY_B);
 	ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD | KEY_B);
+	ED_SHORTCUT("script_text_editor/fold_line", TTR("Fold Line"), KEY_MASK_ALT | KEY_LEFT);
+	ED_SHORTCUT("script_text_editor/unfold_line", TTR("Unfold Line"), KEY_MASK_ALT | KEY_RIGHT);
+	ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), 0);
 #ifdef OSX_ENABLED
 #ifdef OSX_ENABLED
 	ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CTRL | KEY_SPACE);
 	ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CTRL | KEY_SPACE);
 #else
 #else

+ 4 - 1
editor/plugins/script_text_editor.h

@@ -91,6 +91,9 @@ class ScriptTextEditor : public ScriptEditorBase {
 		EDIT_TO_UPPERCASE,
 		EDIT_TO_UPPERCASE,
 		EDIT_TO_LOWERCASE,
 		EDIT_TO_LOWERCASE,
 		EDIT_CAPITALIZE,
 		EDIT_CAPITALIZE,
+		EDIT_FOLD_LINE,
+		EDIT_UNFOLD_LINE,
+		EDIT_UNFOLD_ALL_LINES,
 		SEARCH_FIND,
 		SEARCH_FIND,
 		SEARCH_FIND_NEXT,
 		SEARCH_FIND_NEXT,
 		SEARCH_FIND_PREV,
 		SEARCH_FIND_PREV,
@@ -118,7 +121,7 @@ protected:
 	static void _bind_methods();
 	static void _bind_methods();
 
 
 	void _edit_option(int p_op);
 	void _edit_option(int p_op);
-	void _make_context_menu(bool p_selection, bool p_color);
+	void _make_context_menu(bool p_selection, bool p_color, int p_fold_state);
 	void _text_edit_gui_input(const Ref<InputEvent> &ev);
 	void _text_edit_gui_input(const Ref<InputEvent> &ev);
 	void _color_changed(const Color &p_color);
 	void _color_changed(const Color &p_color);
 
 

+ 399 - 50
scene/gui/text_edit.cpp

@@ -274,6 +274,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) {
 	Line line;
 	Line line;
 	line.marked = false;
 	line.marked = false;
 	line.breakpoint = false;
 	line.breakpoint = false;
+	line.hidden = false;
 	line.width_cache = -1;
 	line.width_cache = -1;
 	line.data = p_text;
 	line.data = p_text;
 	text.insert(p_at, line);
 	text.insert(p_at, line);
@@ -297,6 +298,8 @@ void TextEdit::_update_scrollbars() {
 
 
 	int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1;
 	int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1;
 	int visible_rows = get_visible_rows();
 	int visible_rows = get_visible_rows();
+	int num_rows = MAX(visible_rows, num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs)));
+
 	int total_rows = text.size();
 	int total_rows = text.size();
 	if (scroll_past_end_of_file_enabled) {
 	if (scroll_past_end_of_file_enabled) {
 		total_rows += get_visible_rows() - 1;
 		total_rows += get_visible_rows() - 1;
@@ -313,6 +316,10 @@ void TextEdit::_update_scrollbars() {
 		total_width += cache.breakpoint_gutter_width;
 		total_width += cache.breakpoint_gutter_width;
 	}
 	}
 
 
+	if (draw_fold_gutter) {
+		total_width += cache.fold_gutter_width;
+	}
+
 	bool use_hscroll = true;
 	bool use_hscroll = true;
 	bool use_vscroll = true;
 	bool use_vscroll = true;
 
 
@@ -340,7 +347,7 @@ void TextEdit::_update_scrollbars() {
 
 
 		v_scroll->show();
 		v_scroll->show();
 		v_scroll->set_max(total_rows);
 		v_scroll->set_max(total_rows);
-		v_scroll->set_page(visible_rows);
+		v_scroll->set_page(num_rows);
 		if (smooth_scroll_enabled) {
 		if (smooth_scroll_enabled) {
 			v_scroll->set_step(0.25);
 			v_scroll->set_step(0.25);
 		} else {
 		} else {
@@ -551,6 +558,13 @@ void TextEdit::_notification(int p_what) {
 				cache.breakpoint_gutter_width = 0;
 				cache.breakpoint_gutter_width = 0;
 			}
 			}
 
 
+			if (draw_fold_gutter) {
+				fold_gutter_width = (get_row_height() * 55) / 100;
+				cache.fold_gutter_width = fold_gutter_width;
+			} else {
+				cache.fold_gutter_width = 0;
+			}
+
 			int line_number_char_count = 0;
 			int line_number_char_count = 0;
 
 
 			{
 			{
@@ -573,7 +587,7 @@ void TextEdit::_notification(int p_what) {
 
 
 			RID ci = get_canvas_item();
 			RID ci = get_canvas_item();
 			VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true);
 			VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true);
-			int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width;
+			int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width;
 			int xmargin_end = cache.size.width - cache.style_normal->get_margin(MARGIN_RIGHT);
 			int xmargin_end = cache.size.width - cache.style_normal->get_margin(MARGIN_RIGHT);
 			//let's do it easy for now:
 			//let's do it easy for now:
 			cache.style_normal->draw(ci, Rect2(Point2(), cache.size));
 			cache.style_normal->draw(ci, Rect2(Point2(), cache.size));
@@ -781,9 +795,20 @@ void TextEdit::_notification(int p_what) {
 
 
 			String line_num_padding = line_numbers_zero_padded ? "0" : " ";
 			String line_num_padding = line_numbers_zero_padded ? "0" : " ";
 
 
+			int line = cursor.line_ofs - 1;
 			for (int i = 0; i < visible_rows; i++) {
 			for (int i = 0; i < visible_rows; i++) {
 
 
-				int line = i + cursor.line_ofs;
+				line++;
+
+				if (line < 0 || line >= (int)text.size())
+					continue;
+
+				while (is_line_hidden(line)) {
+					line++;
+					if (line < 0 || line >= (int)text.size()) {
+						break;
+					}
+				}
 
 
 				if (line < 0 || line >= (int)text.size())
 				if (line < 0 || line >= (int)text.size())
 					continue;
 					continue;
@@ -857,6 +882,21 @@ void TextEdit::_notification(int p_what) {
 					}
 					}
 				}
 				}
 
 
+				// draw fold marker
+				if (draw_fold_gutter) {
+					int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100;
+					int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w;
+					if (is_folded(line)) {
+						int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap;
+						int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2;
+						cache.folded_icon->draw(ci, Point2(gutter_left + xofs, ofs_y + yofs));
+					} else if (can_fold(line)) {
+						int xofs = (cache.fold_gutter_width - cache.can_fold_icon->get_width()) / 2;
+						int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2;
+						cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs, ofs_y + yofs));
+					}
+				}
+
 				if (cache.line_number_w) {
 				if (cache.line_number_w) {
 					String fc = String::num(line + 1);
 					String fc = String::num(line + 1);
 					while (fc.length() < line_number_char_count) {
 					while (fc.length() < line_number_char_count) {
@@ -1538,6 +1578,12 @@ void TextEdit::backspace_at_cursor() {
 
 
 	int prev_line = cursor.column ? cursor.line : cursor.line - 1;
 	int prev_line = cursor.column ? cursor.line : cursor.line - 1;
 	int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length());
 	int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length());
+
+	if (is_line_hidden(cursor.line))
+		set_line_as_hidden(prev_line, true);
+	if (is_line_set_as_breakpoint(cursor.line))
+		set_line_as_breakpoint(prev_line, true);
+
 	if (auto_brace_completion_enabled &&
 	if (auto_brace_completion_enabled &&
 			cursor.column > 0 &&
 			cursor.column > 0 &&
 			_is_pair_left_symbol(text[cursor.line][cursor.column - 1])) {
 			_is_pair_left_symbol(text[cursor.line][cursor.column - 1])) {
@@ -1577,7 +1623,7 @@ void TextEdit::backspace_at_cursor() {
 		}
 		}
 	}
 	}
 
 
-	cursor_set_line(prev_line);
+	cursor_set_line(prev_line, true, true);
 	cursor_set_column(prev_column);
 	cursor_set_column(prev_column);
 }
 }
 
 
@@ -1653,8 +1699,22 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co
 	rows /= get_row_height();
 	rows /= get_row_height();
 	int row = cursor.line_ofs + (rows + (v_scroll->get_value() - cursor.line_ofs));
 	int row = cursor.line_ofs + (rows + (v_scroll->get_value() - cursor.line_ofs));
 
 
+	if (is_hiding_enabled()) {
+		// row will be offset by the hidden rows
+		int f_ofs = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(rows, text.size() - 1 - cursor.line_ofs));
+		row = cursor.line_ofs + (f_ofs + (v_scroll->get_value() - cursor.line_ofs));
+		// set row to spot below folded area
+		while (is_line_hidden(row)) {
+			row++;
+			if (row >= text.size() - 1)
+				break;
+		}
+	}
+	if (rows <= 1)
+		row = CLAMP(cursor.line_ofs, 0, text.size() - 1);
+
 	if (row < 0)
 	if (row < 0)
-		row = 0;
+		row = 0; //todo
 
 
 	int col = 0;
 	int col = 0;
 
 
@@ -1664,7 +1724,7 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co
 		col = text[row].size();
 		col = text[row].size();
 	} else {
 	} else {
 
 
-		col = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width);
+		col = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width);
 		col += cursor.x_ofs;
 		col += cursor.x_ofs;
 		col = get_char_pos_for(col, get_line(row));
 		col = get_char_pos_for(col, get_line(row));
 	}
 	}
@@ -1717,10 +1777,18 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 		if (mb->is_pressed()) {
 		if (mb->is_pressed()) {
 
 
 			if (mb->get_button_index() == BUTTON_WHEEL_UP && !mb->get_command()) {
 			if (mb->get_button_index() == BUTTON_WHEEL_UP && !mb->get_command()) {
+				float scroll_factor = 3 * mb->get_factor();
 				if (scrolling) {
 				if (scrolling) {
-					target_v_scroll = (target_v_scroll - (3 * mb->get_factor()));
+					target_v_scroll = (target_v_scroll - scroll_factor);
 				} else {
 				} else {
-					target_v_scroll = (v_scroll->get_value() - (3 * mb->get_factor()));
+					target_v_scroll = (v_scroll->get_value() - scroll_factor);
+				}
+
+				int hidden_lines = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), -(int)scroll_factor) - scroll_factor - 1;
+				if (hiding_enabled && hidden_lines > 0) {
+					target_v_scroll -= hidden_lines;
+					if (smooth_scroll_enabled)
+						v_scroll->set_value(v_scroll->get_value() - hidden_lines);
 				}
 				}
 
 
 				if (smooth_scroll_enabled) {
 				if (smooth_scroll_enabled) {
@@ -1734,16 +1802,26 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				}
 				}
 			}
 			}
 			if (mb->get_button_index() == BUTTON_WHEEL_DOWN && !mb->get_command()) {
 			if (mb->get_button_index() == BUTTON_WHEEL_DOWN && !mb->get_command()) {
+				float scroll_factor = 3 * mb->get_factor();
 				if (scrolling) {
 				if (scrolling) {
-					target_v_scroll = (target_v_scroll + (3 * mb->get_factor()));
+					target_v_scroll = (target_v_scroll + scroll_factor);
 				} else {
 				} else {
-					target_v_scroll = (v_scroll->get_value() + (3 * mb->get_factor()));
+					target_v_scroll = (v_scroll->get_value() + scroll_factor);
+				}
+
+				// todo fix scrolling down over large hidden sections
+				int hidden_lines = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MAX((int)scroll_factor, 1)) - scroll_factor - 1;
+				if (hiding_enabled && hidden_lines > 0) { // && !is_line_hidden(cursor.line_ofs)) {
+					target_v_scroll += hidden_lines;
+					if (smooth_scroll_enabled) {
+						v_scroll->set_value(v_scroll->get_value() + hidden_lines);
+					}
 				}
 				}
 
 
 				if (smooth_scroll_enabled) {
 				if (smooth_scroll_enabled) {
 					int max_v_scroll = get_line_count() - 1;
 					int max_v_scroll = get_line_count() - 1;
 					if (!scroll_past_end_of_file_enabled) {
 					if (!scroll_past_end_of_file_enabled) {
-						max_v_scroll -= get_visible_rows() - 1;
+						max_v_scroll -= num_lines_from(text.size() - 1, -get_visible_rows()) - 1;
 					}
 					}
 
 
 					if (target_v_scroll > max_v_scroll) {
 					if (target_v_scroll > max_v_scroll) {
@@ -1784,10 +1862,26 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 					}
 					}
 				}
 				}
 
 
+				// toggle fold on gutter click if can
+				if (draw_fold_gutter) {
+
+					int left_margin = cache.style_normal->get_margin(MARGIN_LEFT);
+					int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w;
+					if (mb->get_position().x > gutter_left - 3 && mb->get_position().x <= gutter_left + cache.fold_gutter_width + 3) {
+						if (is_folded(row)) {
+							unfold_line(row);
+						} else if (can_fold(row)) {
+							fold_line(row);
+						}
+						//emit_signal("fold_toggled", row);
+						return;
+					}
+				}
+
 				int prev_col = cursor.column;
 				int prev_col = cursor.column;
 				int prev_line = cursor.line;
 				int prev_line = cursor.line;
 
 
-				cursor_set_line(row);
+				cursor_set_line(row, true, false);
 				cursor_set_column(col);
 				cursor_set_column(col);
 
 
 				if (mb->get_shift() && (cursor.column != prev_col || cursor.line != prev_line)) {
 				if (mb->get_shift() && (cursor.column != prev_col || cursor.line != prev_line)) {
@@ -2026,17 +2120,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 						return;
 						return;
 					}
 					}
 
 
-					if (k->get_scancode() == KEY_DOWN) {
-
-						if (completion_index < completion_options.size() - 1) {
-							completion_index++;
-							completion_current = completion_options[completion_index];
-							update();
-						}
-						accept_event();
-						return;
-					}
-
 					if (k->get_scancode() == KEY_KP_ENTER || k->get_scancode() == KEY_ENTER || k->get_scancode() == KEY_TAB) {
 					if (k->get_scancode() == KEY_KP_ENTER || k->get_scancode() == KEY_ENTER || k->get_scancode() == KEY_TAB) {
 
 
 						_confirm_completion();
 						_confirm_completion();
@@ -2191,7 +2274,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				selection.active = false;
 				selection.active = false;
 				update();
 				update();
 				_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
 				_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
-				cursor_set_line(selection.from_line);
+				cursor_set_line(selection.from_line, true, false);
 				cursor_set_column(selection.from_column);
 				cursor_set_column(selection.from_column);
 				update();
 				update();
 			}
 			}
@@ -2241,6 +2324,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 					}
 					}
 				}
 				}
 
 
+				if (is_folded(cursor.line))
+					unfold_line(cursor.line);
+
 				bool brace_indent = false;
 				bool brace_indent = false;
 
 
 				// no need to indent if we are going upwards.
 				// no need to indent if we are going upwards.
@@ -2391,6 +2477,8 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 					cursor_set_column(column);
 					cursor_set_column(column);
 
 
 				} else {
 				} else {
+					if (cursor.line > 0 && is_line_hidden(cursor.line - 1))
+						unfold_line(cursor.line - 1);
 					backspace_at_cursor();
 					backspace_at_cursor();
 				}
 				}
 
 
@@ -2449,6 +2537,8 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				} else if (cursor.column == 0) {
 				} else if (cursor.column == 0) {
 
 
 					if (cursor.line > 0) {
 					if (cursor.line > 0) {
+						if (is_line_hidden(cursor.line - 1))
+							unfold_line(cursor.line - 1);
 						cursor_set_line(cursor.line - 1);
 						cursor_set_line(cursor.line - 1);
 						cursor_set_column(text[cursor.line].length());
 						cursor_set_column(text[cursor.line].length());
 					}
 					}
@@ -2512,7 +2602,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				} else if (cursor.column == text[cursor.line].length()) {
 				} else if (cursor.column == text[cursor.line].length()) {
 
 
 					if (cursor.line < text.size() - 1) {
 					if (cursor.line < text.size() - 1) {
+						if (is_folded(cursor.line + 1))
+							unfold_line(cursor.line + 1);
 						cursor_set_line(cursor.line + 1);
 						cursor_set_line(cursor.line + 1);
+
 						cursor_set_column(0);
 						cursor_set_column(0);
 					}
 					}
 				} else {
 				} else {
@@ -2553,7 +2646,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 					cursor_set_line(0);
 					cursor_set_line(0);
 				else
 				else
 #endif
 #endif
-				cursor_set_line(cursor_get_line() - 1);
+				cursor_set_line(cursor_get_line() - num_lines_from(cursor.line - 1, -1));
 
 
 				if (k->get_shift())
 				if (k->get_shift())
 					_post_shift_selection();
 					_post_shift_selection();
@@ -2587,10 +2680,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				}
 				}
 
 
 				if (k->get_command())
 				if (k->get_command())
-					cursor_set_line(text.size() - 1);
+					cursor_set_line(text.size() - 1, true, false);
 				else
 				else
 #endif
 #endif
-				cursor_set_line(cursor_get_line() + 1);
+				cursor_set_line(cursor_get_line() + num_lines_from(cursor.line + 1, 1));
 
 
 				if (k->get_shift())
 				if (k->get_shift())
 					_post_shift_selection();
 					_post_shift_selection();
@@ -2737,7 +2830,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				if (k->get_shift())
 				if (k->get_shift())
 					_pre_shift_selection();
 					_pre_shift_selection();
 
 
-				cursor_set_line(text.size() - 1);
+				cursor_set_line(text.size() - 1, true, false);
 
 
 				if (k->get_shift())
 				if (k->get_shift())
 					_post_shift_selection();
 					_post_shift_selection();
@@ -2752,7 +2845,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 					_pre_shift_selection();
 					_pre_shift_selection();
 
 
 				if (k->get_command())
 				if (k->get_command())
-					cursor_set_line(text.size() - 1);
+					cursor_set_line(text.size() - 1, true, false);
 				cursor_set_column(text[cursor.line].length());
 				cursor_set_column(text[cursor.line].length());
 
 
 				if (k->get_shift())
 				if (k->get_shift())
@@ -2777,7 +2870,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				if (k->get_shift())
 				if (k->get_shift())
 					_pre_shift_selection();
 					_pre_shift_selection();
 
 
-				cursor_set_line(cursor_get_line() - get_visible_rows());
+				cursor_set_line(cursor_get_line() - num_lines_from(cursor.line, -get_visible_rows()), true, false);
 
 
 				if (k->get_shift())
 				if (k->get_shift())
 					_post_shift_selection();
 					_post_shift_selection();
@@ -2798,7 +2891,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
 				if (k->get_shift())
 				if (k->get_shift())
 					_pre_shift_selection();
 					_pre_shift_selection();
 
 
-				cursor_set_line(cursor_get_line() + get_visible_rows());
+				cursor_set_line(cursor_get_line() + num_lines_from(cursor.line, get_visible_rows()), true, false);
 
 
 				if (k->get_shift())
 				if (k->get_shift())
 					_post_shift_selection();
 					_post_shift_selection();
@@ -3012,12 +3105,12 @@ void TextEdit::_scroll_lines_up() {
 
 
 	// adjust the vertical scroll
 	// adjust the vertical scroll
 	if (get_v_scroll() > 0) {
 	if (get_v_scroll() > 0) {
-		set_v_scroll(get_v_scroll() - 1);
+		set_v_scroll(get_v_scroll() - num_lines_from(get_v_scroll(), -1));
 	}
 	}
 
 
 	// adjust the cursor
 	// adjust the cursor
 	if (cursor_get_line() >= (get_visible_rows() + get_v_scroll()) && !selection.active) {
 	if (cursor_get_line() >= (get_visible_rows() + get_v_scroll()) && !selection.active) {
-		cursor_set_line((get_visible_rows() + get_v_scroll()) - 1, false);
+		cursor_set_line((get_visible_rows() + get_v_scroll()) - 1, false, false);
 	}
 	}
 }
 }
 
 
@@ -3032,12 +3125,12 @@ void TextEdit::_scroll_lines_down() {
 
 
 	// adjust the vertical scroll
 	// adjust the vertical scroll
 	if (get_v_scroll() < max_v_scroll) {
 	if (get_v_scroll() < max_v_scroll) {
-		set_v_scroll(get_v_scroll() + 1);
+		set_v_scroll(get_v_scroll() + num_lines_from(get_v_scroll(), 1));
 	}
 	}
 
 
 	// adjust the cursor
 	// adjust the cursor
 	if ((cursor_get_line()) <= get_v_scroll() - 1 && !selection.active) {
 	if ((cursor_get_line()) <= get_v_scroll() - 1 && !selection.active) {
-		cursor_set_line(get_v_scroll(), false);
+		cursor_set_line(get_v_scroll(), false, false);
 	}
 	}
 }
 }
 
 
@@ -3082,6 +3175,15 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
 		}
 		}
 	}
 	}
 
 
+	// if we are just making a new empty line, reset breakpoints and hidden status
+	if (p_char == 0 && p_text.replace("\r", "") == "\n") {
+
+		text.set_breakpoint(p_line + 1, text.is_breakpoint(p_line));
+		text.set_hidden(p_line + 1, text.is_hidden(p_line));
+		text.set_breakpoint(p_line, false);
+		text.set_hidden(p_line, false);
+	}
+
 	r_end_line = p_line + substrings.size() - 1;
 	r_end_line = p_line + substrings.size() - 1;
 	r_end_column = text[r_end_line].length() - postinsert_text.length();
 	r_end_column = text[r_end_line].length() - postinsert_text.length();
 
 
@@ -3280,6 +3382,7 @@ Size2 TextEdit::get_minimum_size() const {
 
 
 	return cache.style_normal->get_minimum_size();
 	return cache.style_normal->get_minimum_size();
 }
 }
+
 int TextEdit::get_visible_rows() const {
 int TextEdit::get_visible_rows() const {
 
 
 	int total = cache.size.height;
 	int total = cache.size.height;
@@ -3287,31 +3390,31 @@ int TextEdit::get_visible_rows() const {
 	total /= get_row_height();
 	total /= get_row_height();
 	return total;
 	return total;
 }
 }
+
 void TextEdit::adjust_viewport_to_cursor() {
 void TextEdit::adjust_viewport_to_cursor() {
 	scrolling = false;
 	scrolling = false;
 
 
 	if (cursor.line_ofs > cursor.line)
 	if (cursor.line_ofs > cursor.line)
 		cursor.line_ofs = cursor.line;
 		cursor.line_ofs = cursor.line;
 
 
-	int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width;
+	int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width;
 	if (v_scroll->is_visible_in_tree())
 	if (v_scroll->is_visible_in_tree())
 		visible_width -= v_scroll->get_combined_minimum_size().width;
 		visible_width -= v_scroll->get_combined_minimum_size().width;
 	visible_width -= 20; // give it a little more space
 	visible_width -= 20; // give it a little more space
 
 
-	//printf("rowofs %i, visrows %i, cursor.line %i\n",cursor.line_ofs,get_visible_rows(),cursor.line);
-
 	int visible_rows = get_visible_rows();
 	int visible_rows = get_visible_rows();
 	if (h_scroll->is_visible_in_tree())
 	if (h_scroll->is_visible_in_tree())
 		visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height());
 		visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height());
 
 
-	if (cursor.line >= (cursor.line_ofs + visible_rows))
-		cursor.line_ofs = cursor.line - visible_rows;
+	int num_rows = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs));
+	if (cursor.line >= (cursor.line_ofs + MAX(num_rows, visible_rows)))
+		cursor.line_ofs = cursor.line - MAX(num_lines_from(CLAMP(cursor.line, 0, text.size() - 1), -visible_rows), visible_rows);
 	if (cursor.line < cursor.line_ofs)
 	if (cursor.line < cursor.line_ofs)
 		cursor.line_ofs = cursor.line;
 		cursor.line_ofs = cursor.line;
 
 
 	if (cursor.line_ofs + visible_rows > text.size() && !scroll_past_end_of_file_enabled) {
 	if (cursor.line_ofs + visible_rows > text.size() && !scroll_past_end_of_file_enabled) {
-		cursor.line_ofs = text.size() - visible_rows;
-		v_scroll->set_value(text.size() - visible_rows);
+		cursor.line_ofs = text.size() - MAX(num_lines_from(text.size() - 1, -visible_rows), visible_rows);
+		v_scroll->set_value(text.size() - MAX(num_lines_from(text.size() - 1, -visible_rows), visible_rows));
 	}
 	}
 
 
 	int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]);
 	int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]);
@@ -3338,7 +3441,10 @@ void TextEdit::center_viewport_to_cursor() {
 	if (cursor.line_ofs > cursor.line)
 	if (cursor.line_ofs > cursor.line)
 		cursor.line_ofs = cursor.line;
 		cursor.line_ofs = cursor.line;
 
 
-	int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width;
+	if (is_line_hidden(cursor.line))
+		unfold_line(cursor.line);
+
+	int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width;
 	if (v_scroll->is_visible_in_tree())
 	if (v_scroll->is_visible_in_tree())
 		visible_width -= v_scroll->get_combined_minimum_size().width;
 		visible_width -= v_scroll->get_combined_minimum_size().width;
 	visible_width -= 20; // give it a little more space
 	visible_width -= 20; // give it a little more space
@@ -3347,8 +3453,8 @@ void TextEdit::center_viewport_to_cursor() {
 	if (h_scroll->is_visible_in_tree())
 	if (h_scroll->is_visible_in_tree())
 		visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height());
 		visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height());
 
 
-	int max_ofs = text.size() - (scroll_past_end_of_file_enabled ? 1 : visible_rows);
-	cursor.line_ofs = CLAMP(cursor.line - (visible_rows / 2), 0, max_ofs);
+	int max_ofs = text.size() - (scroll_past_end_of_file_enabled ? 1 : num_lines_from(text.size() - 1, -visible_rows));
+	cursor.line_ofs = CLAMP(cursor.line - num_lines_from(cursor.line - visible_rows / 2, -visible_rows / 2), 0, max_ofs);
 
 
 	int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]);
 	int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]);
 
 
@@ -3382,7 +3488,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) {
 	}
 	}
 }
 }
 
 
-void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport) {
+void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden) {
 
 
 	if (setting_row)
 	if (setting_row)
 		return;
 		return;
@@ -3394,6 +3500,31 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport) {
 	if (p_row >= (int)text.size())
 	if (p_row >= (int)text.size())
 		p_row = (int)text.size() - 1;
 		p_row = (int)text.size() - 1;
 
 
+	if (!p_can_be_hidden) {
+		if (is_line_hidden(p_row)) {
+			WARN_PRINTS(("Cursor set to hidden line " + itos(p_row)));
+			// search in a direction until we are not in a hidden line anymore
+			bool search_down = (p_row == 0 || cursor.line < p_row) && p_row != text.size() - 1;
+			bool orig_search_down = search_down;
+			while (is_line_hidden(p_row)) {
+				if (search_down)
+					p_row++;
+				else
+					p_row--;
+				// hit end of file, change dir
+				if (p_row >= text.size() - 1) {
+					search_down = false;
+					if (orig_search_down == search_down)
+						break;
+				}
+				if (p_row <= 0) {
+					search_down = true;
+					if (orig_search_down == search_down)
+						break;
+				}
+			}
+		}
+	}
 	cursor.line = p_row;
 	cursor.line = p_row;
 	cursor.column = get_char_pos_for(cursor.last_fit_x, get_line(cursor.line));
 	cursor.column = get_char_pos_for(cursor.last_fit_x, get_line(cursor.line));
 
 
@@ -3553,7 +3684,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
 	if (highlighted_word != String())
 	if (highlighted_word != String())
 		return CURSOR_POINTING_HAND;
 		return CURSOR_POINTING_HAND;
 
 
-	int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width;
+	int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width;
 	if ((completion_active && completion_rect.has_point(p_pos)) || p_pos.x < gutter) {
 	if ((completion_active && completion_rect.has_point(p_pos)) || p_pos.x < gutter) {
 		return CURSOR_ARROW;
 		return CURSOR_ARROW;
 	}
 	}
@@ -3733,6 +3864,8 @@ void TextEdit::_update_caches() {
 	cache.line_spacing = get_constant("line_spacing");
 	cache.line_spacing = get_constant("line_spacing");
 	cache.row_height = cache.font->get_height() + cache.line_spacing;
 	cache.row_height = cache.font->get_height() + cache.line_spacing;
 	cache.tab_icon = get_icon("tab");
 	cache.tab_icon = get_icon("tab");
+	cache.folded_icon = get_icon("Collapse", "EditorIcons");
+	cache.can_fold_icon = get_icon("Forward", "EditorIcons");
 	text.set_font(cache.font);
 	text.set_font(cache.font);
 }
 }
 
 
@@ -4179,6 +4312,173 @@ void TextEdit::get_breakpoints(List<int> *p_breakpoints) const {
 	}
 	}
 }
 }
 
 
+void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) {
+
+	ERR_FAIL_INDEX(p_line, text.size());
+	if (is_hiding_enabled() || !p_hidden)
+		text.set_hidden(p_line, p_hidden);
+	update();
+}
+
+bool TextEdit::is_line_hidden(int p_line) const {
+
+	ERR_FAIL_INDEX_V(p_line, text.size(), false);
+	return text.is_hidden(p_line);
+}
+
+void TextEdit::unhide_all_lines() {
+
+	for (int i = 0; i < text.size(); i++) {
+		text.set_hidden(i, false);
+	}
+	update();
+}
+
+int TextEdit::num_lines_from(int p_line_from, int unhidden_amount) const {
+
+	// returns the number of hidden and unhidden lines from p_line_from to p_line_from + amount of visible lines
+	ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(unhidden_amount));
+
+	if (!is_hiding_enabled())
+		return unhidden_amount;
+	int num_visible = 0;
+	int num_total = 0;
+	if (unhidden_amount >= 0) {
+		for (int i = p_line_from; i < text.size(); i++) {
+			num_total++;
+			if (!is_line_hidden(i))
+				num_visible++;
+			if (num_visible >= unhidden_amount)
+				break;
+		}
+	} else {
+		unhidden_amount = ABS(unhidden_amount);
+		for (int i = p_line_from; i >= 0; i--) {
+			num_total++;
+			if (!is_line_hidden(i))
+				num_visible++;
+			if (num_visible >= unhidden_amount)
+				break;
+		}
+	}
+	return num_total;
+}
+
+int TextEdit::get_whitespace_level(int p_line) const {
+
+	ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+
+	// counts number of tabs and spaces before line starts
+	int whitespace_count = 0;
+	int line_length = text[p_line].size();
+	for (int i = 0; i < line_length - 1; i++) {
+		if (text[p_line][i] == '\t') {
+			whitespace_count++;
+		} else if (text[p_line][i] == ' ') {
+			whitespace_count++;
+		} else if (text[p_line][i] == '#') {
+			if (whitespace_count != 0) {
+				break;
+			}
+		} else {
+			break;
+		}
+	}
+	return whitespace_count;
+}
+
+bool TextEdit::can_fold(int p_line) const {
+
+	ERR_FAIL_INDEX_V(p_line, text.size(), false);
+	if (!is_hiding_enabled())
+		return false;
+	if (!draw_fold_gutter)
+		return false;
+	if (p_line + 2 >= text.size())
+		return false;
+	if (text[p_line].size() == 0)
+		return false;
+	if (is_folded(p_line))
+		return false;
+
+	int start_indent = get_whitespace_level(p_line);
+
+	for (int i = p_line + 1; i < text.size(); i++) {
+		if (text[i].size() == 0)
+			continue;
+		int next_indent = get_whitespace_level(i);
+		if (next_indent > start_indent)
+			return true;
+		else
+			return false;
+	}
+
+	return false;
+}
+
+bool TextEdit::is_folded(int p_line) const {
+
+	ERR_FAIL_INDEX_V(p_line, text.size(), false);
+	if (p_line + 1 >= text.size() - 1)
+		return false;
+	if (!is_line_hidden(p_line) && is_line_hidden(p_line + 1))
+		return true;
+	return false;
+}
+
+void TextEdit::fold_line(int p_line) {
+
+	ERR_FAIL_INDEX(p_line, text.size());
+	if (!is_hiding_enabled())
+		return;
+	if (!can_fold(p_line))
+		return;
+
+	int start_indent = get_whitespace_level(p_line);
+	for (int i = p_line + 1; i < text.size(); i++) {
+		int cur_indent = get_whitespace_level(i);
+		if (text[i].size() == 0 || cur_indent > start_indent) {
+			set_line_as_hidden(i, true);
+		} else {
+			// exclude trailing empty lines
+			for (int trail_i = i - 1; trail_i > p_line; trail_i--) {
+				if (text[trail_i].size() == 0)
+					set_line_as_hidden(trail_i, false);
+				else
+					break;
+			}
+			if (is_line_hidden(cursor.line)) {
+				cursor_set_line(i, true, false);
+			}
+			break;
+		}
+	}
+	adjust_viewport_to_cursor();
+	update();
+}
+
+void TextEdit::unfold_line(int p_line) {
+
+	ERR_FAIL_INDEX(p_line, text.size());
+
+	int fold_start = p_line;
+	for (fold_start = p_line; fold_start > 0; fold_start--) {
+		if (is_folded(fold_start))
+			break;
+	}
+	fold_start = is_folded(fold_start) ? fold_start : p_line;
+
+	for (int i = fold_start + 1; i < text.size(); i++) {
+		if (is_line_hidden(i)) {
+			set_line_as_hidden(i, false);
+		} else {
+			break;
+		}
+	}
+	adjust_viewport_to_cursor();
+	update();
+}
+
 int TextEdit::get_line_count() const {
 int TextEdit::get_line_count() const {
 
 
 	return text.size();
 	return text.size();
@@ -4406,7 +4706,8 @@ void TextEdit::set_v_scroll(int p_scroll) {
 	}
 	}
 	if (!scroll_past_end_of_file_enabled) {
 	if (!scroll_past_end_of_file_enabled) {
 		if (p_scroll + get_visible_rows() > get_line_count()) {
 		if (p_scroll + get_visible_rows() > get_line_count()) {
-			p_scroll = get_line_count() - get_visible_rows();
+			int num_rows = num_lines_from(CLAMP(p_scroll, 0, text.size() - 1), MIN(get_visible_rows(), text.size() - 1 - p_scroll));
+			p_scroll = get_line_count() - num_rows;
 		}
 		}
 	}
 	}
 	v_scroll->set_value(p_scroll);
 	v_scroll->set_value(p_scroll);
@@ -4772,7 +5073,7 @@ void TextEdit::set_line(int line, String new_text) {
 
 
 void TextEdit::insert_at(const String &p_text, int at) {
 void TextEdit::insert_at(const String &p_text, int at) {
 	cursor_set_column(0);
 	cursor_set_column(0);
-	cursor_set_line(at);
+	cursor_set_line(at, false, true);
 	_insert_text(at, 0, p_text + "\n");
 	_insert_text(at, 0, p_text + "\n");
 }
 }
 
 
@@ -4820,6 +5121,39 @@ int TextEdit::get_breakpoint_gutter_width() const {
 	return cache.breakpoint_gutter_width;
 	return cache.breakpoint_gutter_width;
 }
 }
 
 
+void TextEdit::set_draw_fold_gutter(bool p_draw) {
+	draw_fold_gutter = p_draw;
+	if (draw_fold_gutter)
+		set_hiding_enabled(true);
+	else
+		unhide_all_lines();
+	update();
+}
+
+bool TextEdit::is_drawing_fold_gutter() const {
+	return draw_fold_gutter;
+}
+
+void TextEdit::set_fold_gutter_width(int p_gutter_width) {
+	fold_gutter_width = p_gutter_width;
+	update();
+}
+
+int TextEdit::get_fold_gutter_width() const {
+	return cache.fold_gutter_width;
+}
+
+void TextEdit::set_hiding_enabled(int p_enabled) {
+	hiding_enabled = p_enabled;
+	if (!hiding_enabled)
+		set_draw_fold_gutter(false);
+	update();
+}
+
+int TextEdit::is_hiding_enabled() const {
+	return hiding_enabled;
+}
+
 void TextEdit::set_highlight_current_line(bool p_enabled) {
 void TextEdit::set_highlight_current_line(bool p_enabled) {
 	highlight_current_line = p_enabled;
 	highlight_current_line = p_enabled;
 	update();
 	update();
@@ -4914,7 +5248,7 @@ void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line);
 	ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line);
 
 
 	ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true));
-	ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport"), &TextEdit::cursor_set_line, DEFVAL(true));
+	ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport, can_be_hidden"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true));
 
 
 	ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column);
 	ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column);
 	ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line);
 	ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line);
@@ -4955,6 +5289,16 @@ void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_show_line_numbers", "enable"), &TextEdit::set_show_line_numbers);
 	ClassDB::bind_method(D_METHOD("set_show_line_numbers", "enable"), &TextEdit::set_show_line_numbers);
 	ClassDB::bind_method(D_METHOD("is_show_line_numbers_enabled"), &TextEdit::is_show_line_numbers_enabled);
 	ClassDB::bind_method(D_METHOD("is_show_line_numbers_enabled"), &TextEdit::is_show_line_numbers_enabled);
 
 
+	ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled);
+	ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled);
+	ClassDB::bind_method(D_METHOD("set_line_as_hidden", "line", "enable"), &TextEdit::set_line_as_hidden);
+	ClassDB::bind_method(D_METHOD("is_line_hidden"), &TextEdit::is_line_hidden);
+	ClassDB::bind_method(D_METHOD("unhide_all_lines"), &TextEdit::unhide_all_lines);
+	ClassDB::bind_method(D_METHOD("fold_line", "line"), &TextEdit::fold_line);
+	ClassDB::bind_method(D_METHOD("unfold_line", "line"), &TextEdit::unfold_line);
+	ClassDB::bind_method(D_METHOD("can_fold", "line"), &TextEdit::can_fold);
+	ClassDB::bind_method(D_METHOD("is_folded", "line"), &TextEdit::is_folded);
+
 	ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enable"), &TextEdit::set_highlight_all_occurrences);
 	ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enable"), &TextEdit::set_highlight_all_occurrences);
 	ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled);
 	ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled);
 
 
@@ -4988,6 +5332,7 @@ void TextEdit::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled");
 
 
 	ADD_GROUP("Caret", "caret_");
 	ADD_GROUP("Caret", "caret_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode");
@@ -5029,6 +5374,8 @@ TextEdit::TextEdit() {
 	cache.line_number_w = 1;
 	cache.line_number_w = 1;
 	cache.breakpoint_gutter_width = 0;
 	cache.breakpoint_gutter_width = 0;
 	breakpoint_gutter_width = 0;
 	breakpoint_gutter_width = 0;
+	cache.fold_gutter_width = 0;
+	fold_gutter_width = 0;
 
 
 	indent_size = 4;
 	indent_size = 4;
 	text.set_indent_size(indent_size);
 	text.set_indent_size(indent_size);
@@ -5098,6 +5445,8 @@ TextEdit::TextEdit() {
 	line_length_guideline = false;
 	line_length_guideline = false;
 	line_length_guideline_col = 80;
 	line_length_guideline_col = 80;
 	draw_breakpoint_gutter = false;
 	draw_breakpoint_gutter = false;
+	draw_fold_gutter = false;
+	hiding_enabled = false;
 	next_operation_is_complex = false;
 	next_operation_is_complex = false;
 	scroll_past_end_of_file_enabled = false;
 	scroll_past_end_of_file_enabled = false;
 	auto_brace_completion_enabled = false;
 	auto_brace_completion_enabled = false;

+ 31 - 1
scene/gui/text_edit.h

@@ -73,6 +73,8 @@ class TextEdit : public Control {
 	struct Cache {
 	struct Cache {
 
 
 		Ref<Texture> tab_icon;
 		Ref<Texture> tab_icon;
+		Ref<Texture> can_fold_icon;
+		Ref<Texture> folded_icon;
 		Ref<StyleBox> style_normal;
 		Ref<StyleBox> style_normal;
 		Ref<StyleBox> style_focus;
 		Ref<StyleBox> style_focus;
 		Ref<Font> font;
 		Ref<Font> font;
@@ -105,6 +107,7 @@ class TextEdit : public Control {
 		int line_spacing;
 		int line_spacing;
 		int line_number_w;
 		int line_number_w;
 		int breakpoint_gutter_width;
 		int breakpoint_gutter_width;
+		int fold_gutter_width;
 		Size2 size;
 		Size2 size;
 	} cache;
 	} cache;
 
 
@@ -136,6 +139,7 @@ class TextEdit : public Control {
 			int width_cache : 24;
 			int width_cache : 24;
 			bool marked : 1;
 			bool marked : 1;
 			bool breakpoint : 1;
 			bool breakpoint : 1;
+			bool hidden : 1;
 			Map<int, ColorRegionInfo> region_info;
 			Map<int, ColorRegionInfo> region_info;
 			String data;
 			String data;
 		};
 		};
@@ -160,6 +164,8 @@ class TextEdit : public Control {
 		bool is_marked(int p_line) const { return text[p_line].marked; }
 		bool is_marked(int p_line) const { return text[p_line].marked; }
 		void set_breakpoint(int p_line, bool p_breakpoint) { text[p_line].breakpoint = p_breakpoint; }
 		void set_breakpoint(int p_line, bool p_breakpoint) { text[p_line].breakpoint = p_breakpoint; }
 		bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; }
 		bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; }
+		void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; }
+		bool is_hidden(int p_line) const { return text[p_line].hidden; }
 		void insert(int p_at, const String &p_text);
 		void insert(int p_at, const String &p_text);
 		void remove(int p_at);
 		void remove(int p_at);
 		int size() const { return text.size(); }
 		int size() const { return text.size(); }
@@ -251,6 +257,9 @@ class TextEdit : public Control {
 	int line_length_guideline_col;
 	int line_length_guideline_col;
 	bool draw_breakpoint_gutter;
 	bool draw_breakpoint_gutter;
 	int breakpoint_gutter_width;
 	int breakpoint_gutter_width;
+	bool draw_fold_gutter;
+	int fold_gutter_width;
+	bool hiding_enabled;
 
 
 	bool highlight_all_occurrences;
 	bool highlight_all_occurrences;
 	bool scroll_past_end_of_file_enabled;
 	bool scroll_past_end_of_file_enabled;
@@ -325,6 +334,8 @@ class TextEdit : public Control {
 
 
 	int get_row_height() const;
 	int get_row_height() const;
 
 
+	// int _get_fold_offset(int p_line_from, int p_line_to) const;
+
 	void _reset_caret_blink_timer();
 	void _reset_caret_blink_timer();
 	void _toggle_draw_caret();
 	void _toggle_draw_caret();
 
 
@@ -405,6 +416,16 @@ public:
 	void set_line_as_breakpoint(int p_line, bool p_breakpoint);
 	void set_line_as_breakpoint(int p_line, bool p_breakpoint);
 	bool is_line_set_as_breakpoint(int p_line) const;
 	bool is_line_set_as_breakpoint(int p_line) const;
 	void get_breakpoints(List<int> *p_breakpoints) const;
 	void get_breakpoints(List<int> *p_breakpoints) const;
+	void set_line_as_hidden(int p_line, bool p_hidden);
+	bool is_line_hidden(int p_line) const;
+	void unhide_all_lines();
+	int num_lines_from(int p_line_from, int unhidden_amount) const;
+	int get_whitespace_level(int p_line) const;
+	bool can_fold(int p_line) const;
+	bool is_folded(int p_line) const;
+	void fold_line(int p_line);
+	void unfold_line(int p_line);
+
 	String get_text();
 	String get_text();
 	String get_line(int line) const;
 	String get_line(int line) const;
 	void set_line(int line, String new_text);
 	void set_line(int line, String new_text);
@@ -433,7 +454,7 @@ public:
 	void center_viewport_to_cursor();
 	void center_viewport_to_cursor();
 
 
 	void cursor_set_column(int p_col, bool p_adjust_viewport = true);
 	void cursor_set_column(int p_col, bool p_adjust_viewport = true);
-	void cursor_set_line(int p_row, bool p_adjust_viewport = true);
+	void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true);
 
 
 	int cursor_get_column() const;
 	int cursor_get_column() const;
 	int cursor_get_line() const;
 	int cursor_get_line() const;
@@ -538,6 +559,15 @@ public:
 	void set_breakpoint_gutter_width(int p_gutter_width);
 	void set_breakpoint_gutter_width(int p_gutter_width);
 	int get_breakpoint_gutter_width() const;
 	int get_breakpoint_gutter_width() const;
 
 
+	void set_draw_fold_gutter(bool p_draw);
+	bool is_drawing_fold_gutter() const;
+
+	void set_fold_gutter_width(int p_gutter_width);
+	int get_fold_gutter_width() const;
+
+	void set_hiding_enabled(int p_enabled);
+	int is_hiding_enabled() const;
+
 	void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata);
 	void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata);
 
 
 	void set_completion(bool p_enabled, const Vector<String> &p_prefixes);
 	void set_completion(bool p_enabled, const Vector<String> &p_prefixes);