Browse Source

Merge pull request #76658 from Paulb23/convert-indent-code-edit

Move convert_indent into CodeEdit
Rémi Verschelde 2 years ago
parent
commit
eb6d6ab29f

+ 9 - 0
doc/classes/CodeEdit.xml

@@ -126,6 +126,15 @@
 				Inserts the selected entry into the text. If [param replace] is true, any existing text is replaced rather then merged.
 			</description>
 		</method>
+		<method name="convert_indent">
+			<return type="void" />
+			<param index="0" name="from_line" type="int" default="-1" />
+			<param index="1" name="to_line" type="int" default="-1" />
+			<description>
+				Converts the indents of lines between [param from_line] and [param to_line] to tabs or spaces as set by [member indent_use_spaces].
+				Values of [code]-1[/code] convert the entire text.
+			</description>
+		</method>
 		<method name="do_indent">
 			<return type="void" />
 			<description>

+ 3 - 105
editor/code_editor.cpp

@@ -902,6 +902,9 @@ void CodeTextEditor::_line_col_changed() {
 	sb.append(" : ");
 	sb.append(itos(positional_column + 1).lpad(3));
 
+	sb.append(" | ");
+	sb.append(text_editor->is_indent_using_spaces() ? "Spaces" : "Tabs");
+
 	line_and_col_txt->set_text(sb.as_string());
 
 	if (find_replace_bar) {
@@ -1142,111 +1145,6 @@ void CodeTextEditor::insert_final_newline() {
 	}
 }
 
-void CodeTextEditor::convert_indent_to_spaces() {
-	int indent_size = EDITOR_GET("text_editor/behavior/indent/size");
-	String indent = String(" ").repeat(indent_size);
-
-	Vector<int> cursor_columns;
-	cursor_columns.resize(text_editor->get_caret_count());
-	for (int c = 0; c < text_editor->get_caret_count(); c++) {
-		cursor_columns.write[c] = text_editor->get_caret_column(c);
-	}
-
-	bool changed_indentation = false;
-	for (int i = 0; i < text_editor->get_line_count(); i++) {
-		String line = text_editor->get_line(i);
-
-		if (line.length() <= 0) {
-			continue;
-		}
-
-		int j = 0;
-		while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) {
-			if (line[j] == '\t') {
-				if (!changed_indentation) {
-					text_editor->begin_complex_operation();
-					changed_indentation = true;
-				}
-				for (int c = 0; c < text_editor->get_caret_count(); c++) {
-					if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) {
-						cursor_columns.write[c] += indent_size - 1;
-					}
-				}
-				line = line.left(j) + indent + line.substr(j + 1);
-			}
-			j++;
-		}
-		if (changed_indentation) {
-			text_editor->set_line(i, line);
-		}
-	}
-	if (changed_indentation) {
-		for (int c = 0; c < text_editor->get_caret_count(); c++) {
-			text_editor->set_caret_column(cursor_columns[c], c == 0, c);
-		}
-		text_editor->merge_overlapping_carets();
-		text_editor->end_complex_operation();
-		text_editor->queue_redraw();
-	}
-}
-
-void CodeTextEditor::convert_indent_to_tabs() {
-	int indent_size = EDITOR_GET("text_editor/behavior/indent/size");
-	indent_size -= 1;
-
-	Vector<int> cursor_columns;
-	cursor_columns.resize(text_editor->get_caret_count());
-	for (int c = 0; c < text_editor->get_caret_count(); c++) {
-		cursor_columns.write[c] = text_editor->get_caret_column(c);
-	}
-
-	bool changed_indentation = false;
-	for (int i = 0; i < text_editor->get_line_count(); i++) {
-		String line = text_editor->get_line(i);
-
-		if (line.length() <= 0) {
-			continue;
-		}
-
-		int j = 0;
-		int space_count = -1;
-		while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) {
-			if (line[j] != '\t') {
-				space_count++;
-
-				if (space_count == indent_size) {
-					if (!changed_indentation) {
-						text_editor->begin_complex_operation();
-						changed_indentation = true;
-					}
-					for (int c = 0; c < text_editor->get_caret_count(); c++) {
-						if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) {
-							cursor_columns.write[c] -= indent_size;
-						}
-					}
-					line = line.left(j - indent_size) + "\t" + line.substr(j + 1);
-					j = 0;
-					space_count = -1;
-				}
-			} else {
-				space_count = -1;
-			}
-			j++;
-		}
-		if (changed_indentation) {
-			text_editor->set_line(i, line);
-		}
-	}
-	if (changed_indentation) {
-		for (int c = 0; c < text_editor->get_caret_count(); c++) {
-			text_editor->set_caret_column(cursor_columns[c], c == 0, c);
-		}
-		text_editor->merge_overlapping_carets();
-		text_editor->end_complex_operation();
-		text_editor->queue_redraw();
-	}
-}
-
 void CodeTextEditor::convert_case(CaseStyle p_case) {
 	if (!text_editor->has_selection()) {
 		return;

+ 0 - 3
editor/code_editor.h

@@ -222,9 +222,6 @@ public:
 	void trim_trailing_whitespace();
 	void insert_final_newline();
 
-	void convert_indent_to_spaces();
-	void convert_indent_to_tabs();
-
 	enum CaseStyle {
 		UPPER,
 		LOWER,

+ 4 - 22
editor/plugins/script_editor_plugin.cpp

@@ -944,11 +944,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) {
 		se->insert_final_newline();
 
 		if (convert_indent_on_save) {
-			if (use_space_indentation) {
-				se->convert_indent_to_spaces();
-			} else {
-				se->convert_indent_to_tabs();
-			}
+			se->convert_indent();
 		}
 
 		Ref<TextFile> text_file = scr;
@@ -1299,11 +1295,7 @@ void ScriptEditor::_menu_option(int p_option) {
 				current->insert_final_newline();
 
 				if (convert_indent_on_save) {
-					if (use_space_indentation) {
-						current->convert_indent_to_spaces();
-					} else {
-						current->convert_indent_to_tabs();
-					}
+					current->convert_indent();
 				}
 
 				Ref<Resource> resource = current->get_edited_resource();
@@ -2451,11 +2443,7 @@ void ScriptEditor::save_current_script() {
 	current->insert_final_newline();
 
 	if (convert_indent_on_save) {
-		if (use_space_indentation) {
-			current->convert_indent_to_spaces();
-		} else {
-			current->convert_indent_to_tabs();
-		}
+		current->convert_indent();
 	}
 
 	Ref<Resource> resource = current->get_edited_resource();
@@ -2499,11 +2487,7 @@ void ScriptEditor::save_all_scripts() {
 		}
 
 		if (convert_indent_on_save) {
-			if (use_space_indentation) {
-				se->convert_indent_to_spaces();
-			} else {
-				se->convert_indent_to_tabs();
-			}
+			se->convert_indent();
 		}
 
 		if (trim_trailing_whitespace_on_save) {
@@ -2730,7 +2714,6 @@ void ScriptEditor::_editor_settings_changed() {
 
 	trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");
 	convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save");
-	use_space_indentation = EDITOR_GET("text_editor/behavior/indent/type");
 
 	members_overview_enabled = EDITOR_GET("text_editor/script_list/show_members_overview");
 	help_overview_enabled = EDITOR_GET("text_editor/help/show_help_index");
@@ -4081,7 +4064,6 @@ ScriptEditor::ScriptEditor() {
 	edit_pass = 0;
 	trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save");
 	convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save");
-	use_space_indentation = EDITOR_GET("text_editor/behavior/indent/type");
 
 	ScriptServer::edit_request_func = _open_script_request;
 

+ 1 - 3
editor/plugins/script_editor_plugin.h

@@ -169,8 +169,7 @@ public:
 	virtual void clear_executing_line() = 0;
 	virtual void trim_trailing_whitespace() = 0;
 	virtual void insert_final_newline() = 0;
-	virtual void convert_indent_to_spaces() = 0;
-	virtual void convert_indent_to_tabs() = 0;
+	virtual void convert_indent() = 0;
 	virtual void ensure_focus() = 0;
 	virtual void tag_saved_version() = 0;
 	virtual void reload(bool p_soft) {}
@@ -390,7 +389,6 @@ class ScriptEditor : public PanelContainer {
 
 	bool open_textfile_after_create = true;
 	bool trim_trailing_whitespace_on_save;
-	bool use_space_indentation;
 	bool convert_indent_on_save;
 
 	void _goto_script_line2(int p_line);

+ 6 - 8
editor/plugins/script_text_editor.cpp

@@ -378,12 +378,8 @@ void ScriptTextEditor::insert_final_newline() {
 	code_editor->insert_final_newline();
 }
 
-void ScriptTextEditor::convert_indent_to_spaces() {
-	code_editor->convert_indent_to_spaces();
-}
-
-void ScriptTextEditor::convert_indent_to_tabs() {
-	code_editor->convert_indent_to_tabs();
+void ScriptTextEditor::convert_indent() {
+	code_editor->get_text_editor()->convert_indent();
 }
 
 void ScriptTextEditor::tag_saved_version() {
@@ -1282,10 +1278,12 @@ void ScriptTextEditor::_edit_option(int p_op) {
 			trim_trailing_whitespace();
 		} break;
 		case EDIT_CONVERT_INDENT_TO_SPACES: {
-			convert_indent_to_spaces();
+			tx->set_indent_using_spaces(true);
+			convert_indent();
 		} break;
 		case EDIT_CONVERT_INDENT_TO_TABS: {
-			convert_indent_to_tabs();
+			tx->set_indent_using_spaces(false);
+			convert_indent();
 		} break;
 		case EDIT_PICK_COLOR: {
 			color_panel->popup();

+ 1 - 2
editor/plugins/script_text_editor.h

@@ -222,8 +222,7 @@ public:
 	virtual void ensure_focus() override;
 	virtual void trim_trailing_whitespace() override;
 	virtual void insert_final_newline() override;
-	virtual void convert_indent_to_spaces() override;
-	virtual void convert_indent_to_tabs() override;
+	virtual void convert_indent() override;
 	virtual void tag_saved_version() override;
 
 	virtual void goto_line(int p_line, bool p_with_error = false) override;

+ 6 - 8
editor/plugins/text_editor.cpp

@@ -288,12 +288,8 @@ void TextEditor::insert_final_newline() {
 	code_editor->insert_final_newline();
 }
 
-void TextEditor::convert_indent_to_spaces() {
-	code_editor->convert_indent_to_spaces();
-}
-
-void TextEditor::convert_indent_to_tabs() {
-	code_editor->convert_indent_to_tabs();
+void TextEditor::convert_indent() {
+	code_editor->get_text_editor()->convert_indent();
 }
 
 void TextEditor::tag_saved_version() {
@@ -419,10 +415,12 @@ void TextEditor::_edit_option(int p_op) {
 			trim_trailing_whitespace();
 		} break;
 		case EDIT_CONVERT_INDENT_TO_SPACES: {
-			convert_indent_to_spaces();
+			tx->set_indent_using_spaces(true);
+			convert_indent();
 		} break;
 		case EDIT_CONVERT_INDENT_TO_TABS: {
-			convert_indent_to_tabs();
+			tx->set_indent_using_spaces(false);
+			convert_indent();
 		} break;
 		case EDIT_TO_UPPERCASE: {
 			_convert_case(CodeTextEditor::UPPER);

+ 1 - 2
editor/plugins/text_editor.h

@@ -131,8 +131,7 @@ public:
 	virtual void clear_executing_line() override;
 	virtual void trim_trailing_whitespace() override;
 	virtual void insert_final_newline() override;
-	virtual void convert_indent_to_spaces() override;
-	virtual void convert_indent_to_tabs() override;
+	virtual void convert_indent() override;
 	virtual void ensure_focus() override;
 	virtual void tag_saved_version() override;
 	virtual void update_settings() override;

+ 112 - 0
scene/gui/code_edit.cpp

@@ -1007,6 +1007,116 @@ void CodeEdit::unindent_lines() {
 	queue_redraw();
 }
 
+void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
+	if (!is_editable()) {
+		return;
+	}
+
+	// Check line range.
+	p_from_line = (p_from_line < 0) ? 0 : p_from_line;
+	p_to_line = (p_to_line < 0) ? get_line_count() - 1 : p_to_line;
+
+	ERR_FAIL_COND(p_from_line >= get_line_count());
+	ERR_FAIL_COND(p_to_line >= get_line_count());
+	ERR_FAIL_COND(p_to_line < p_from_line);
+
+	// Store caret states.
+	Vector<int> caret_columns;
+	Vector<Pair<int, int>> from_selections;
+	Vector<Pair<int, int>> to_selections;
+	caret_columns.resize(get_caret_count());
+	from_selections.resize(get_caret_count());
+	to_selections.resize(get_caret_count());
+	for (int c = 0; c < get_caret_count(); c++) {
+		caret_columns.write[c] = get_caret_column(c);
+
+		// Set "selection_from_line" to -1 to allow checking if there was a selection later.
+		if (!has_selection(c)) {
+			from_selections.write[c].first = -1;
+			continue;
+		}
+		from_selections.write[c].first = get_selection_from_line(c);
+		from_selections.write[c].second = get_selection_from_column(c);
+		to_selections.write[c].first = get_selection_to_line(c);
+		to_selections.write[c].second = get_selection_to_column(c);
+	}
+
+	// Check lines within range.
+	const char32_t from_indent_char = indent_using_spaces ? '\t' : ' ';
+	int size_diff = indent_using_spaces ? indent_size - 1 : -(indent_size - 1);
+	bool changed_indentation = false;
+	for (int i = p_from_line; i <= p_to_line; i++) {
+		String line = get_line(i);
+
+		if (line.length() <= 0) {
+			continue;
+		}
+
+		// Check chars in the line.
+		int j = 0;
+		int space_count = 0;
+		bool line_changed = false;
+		while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) {
+			if (line[j] != from_indent_char) {
+				space_count = 0;
+				j++;
+				continue;
+			}
+			space_count++;
+
+			if (!indent_using_spaces && space_count != indent_size) {
+				j++;
+				continue;
+			}
+
+			line_changed = true;
+			if (!changed_indentation) {
+				begin_complex_operation();
+				changed_indentation = true;
+			}
+
+			// Calculate new caret state.
+			for (int c = 0; c < get_caret_count(); c++) {
+				if (get_caret_line(c) != i || caret_columns[c] <= j) {
+					continue;
+				}
+				caret_columns.write[c] += size_diff;
+
+				if (from_selections.write[c].first == -1) {
+					continue;
+				}
+				from_selections.write[c].second = from_selections[c].first == i ? from_selections[c].second + size_diff : from_selections[c].second;
+				to_selections.write[c].second = to_selections[c].first == i ? to_selections[c].second + size_diff : to_selections[c].second;
+			}
+
+			// Calculate new line.
+			line = line.left(j + ((size_diff < 0) ? size_diff : 0)) + indent_text + line.substr(j + 1);
+
+			space_count = 0;
+			j += size_diff;
+		}
+
+		if (line_changed) {
+			set_line(i, line);
+		}
+	}
+
+	if (!changed_indentation) {
+		return;
+	}
+
+	// Restore caret states.
+	for (int c = 0; c < get_caret_count(); c++) {
+		set_caret_column(caret_columns[c], c == 0, c);
+		if (from_selections.write[c].first != -1) {
+			select(from_selections.write[c].first, from_selections.write[c].second, to_selections.write[c].first, to_selections.write[c].second, c);
+		}
+	}
+	merge_overlapping_carets();
+	end_complex_operation();
+	queue_redraw();
+}
+
 int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const {
 	int spaces_till_indent = p_column % indent_size;
 	if (spaces_till_indent == 0) {
@@ -2197,6 +2307,8 @@ void CodeEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines);
 	ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines);
 
+	ClassDB::bind_method(D_METHOD("convert_indent", "from_line", "to_line"), &CodeEdit::convert_indent, DEFVAL(-1), DEFVAL(-1));
+
 	/* Auto brace completion */
 	ClassDB::bind_method(D_METHOD("set_auto_brace_completion_enabled", "enable"), &CodeEdit::set_auto_brace_completion_enabled);
 	ClassDB::bind_method(D_METHOD("is_auto_brace_completion_enabled"), &CodeEdit::is_auto_brace_completion_enabled);

+ 2 - 0
scene/gui/code_edit.h

@@ -314,6 +314,8 @@ public:
 	void indent_lines();
 	void unindent_lines();
 
+	void convert_indent(int p_from_line = -1, int p_to_line = -1);
+
 	/* Auto brace completion */
 	void set_auto_brace_completion_enabled(bool p_enabled);
 	bool is_auto_brace_completion_enabled() const;

+ 137 - 0
tests/scene/test_code_edit.h

@@ -2314,6 +2314,143 @@ TEST_CASE("[SceneTree][CodeEdit] indent") {
 		}
 	}
 
+	SUBCASE("[CodeEdit] convert indent to tabs") {
+		code_edit->set_indent_size(4);
+		code_edit->set_indent_using_spaces(false);
+
+		// Only line.
+		code_edit->insert_text_at_caret("        test");
+		code_edit->set_caret_line(0);
+		code_edit->set_caret_column(8);
+		code_edit->select(0, 8, 0, 9);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(0) == "\t\ttest");
+		CHECK(code_edit->get_caret_column() == 2);
+		CHECK(code_edit->get_selection_from_column() == 2);
+		CHECK(code_edit->get_selection_to_column() == 3);
+
+		// First line.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("        test\n");
+		code_edit->set_caret_line(0);
+		code_edit->set_caret_column(8);
+		code_edit->select(0, 8, 0, 9);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(0) == "\t\ttest");
+		CHECK(code_edit->get_caret_column() == 2);
+		CHECK(code_edit->get_selection_from_column() == 2);
+		CHECK(code_edit->get_selection_to_column() == 3);
+
+		// Middle line.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("\n        test\n");
+		code_edit->set_caret_line(1);
+		code_edit->set_caret_column(8);
+		code_edit->select(1, 8, 1, 9);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(1) == "\t\ttest");
+		CHECK(code_edit->get_caret_column() == 2);
+		CHECK(code_edit->get_selection_from_column() == 2);
+		CHECK(code_edit->get_selection_to_column() == 3);
+
+		// End line.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("\n        test");
+		code_edit->set_caret_line(1);
+		code_edit->set_caret_column(8);
+		code_edit->select(1, 8, 1, 9);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(1) == "\t\ttest");
+		CHECK(code_edit->get_caret_column() == 2);
+		CHECK(code_edit->get_selection_from_column() == 2);
+		CHECK(code_edit->get_selection_to_column() == 3);
+
+		// Within provided range.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("    test\n        test\n");
+		code_edit->set_caret_line(1);
+		code_edit->set_caret_column(8);
+		code_edit->select(1, 8, 1, 9);
+		code_edit->convert_indent(1, 1);
+		CHECK(code_edit->get_line(0) == "    test");
+		CHECK(code_edit->get_line(1) == "\t\ttest");
+		CHECK(code_edit->get_caret_column() == 2);
+		CHECK(code_edit->get_selection_from_column() == 2);
+		CHECK(code_edit->get_selection_to_column() == 3);
+	}
+
+	SUBCASE("[CodeEdit] convert indent to spaces") {
+		code_edit->set_indent_size(4);
+		code_edit->set_indent_using_spaces(true);
+
+		// Only line.
+		code_edit->insert_text_at_caret("\t\ttest");
+		code_edit->set_caret_line(0);
+		code_edit->set_caret_column(2);
+		code_edit->select(0, 2, 0, 3);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(0) == "        test");
+		CHECK(code_edit->get_caret_column() == 8);
+		CHECK(code_edit->get_selection_from_column() == 8);
+		CHECK(code_edit->get_selection_to_column() == 9);
+
+		// First line.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("\t\ttest\n");
+		code_edit->set_caret_line(0);
+		code_edit->set_caret_column(2);
+		code_edit->select(0, 2, 0, 3);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(0) == "        test");
+		CHECK(code_edit->get_caret_column() == 8);
+		CHECK(code_edit->get_selection_from_column() == 8);
+		CHECK(code_edit->get_selection_to_column() == 9);
+
+		// Middle line.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("\n\t\ttest\n");
+		code_edit->set_caret_line(1);
+		code_edit->set_caret_column(2);
+		code_edit->select(1, 2, 1, 3);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(1) == "        test");
+		CHECK(code_edit->get_caret_column() == 8);
+		CHECK(code_edit->get_selection_from_column() == 8);
+		CHECK(code_edit->get_selection_to_column() == 9);
+
+		// End line.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("\n\t\ttest");
+		code_edit->set_caret_line(1);
+		code_edit->set_caret_column(2);
+		code_edit->select(1, 2, 1, 3);
+		code_edit->convert_indent();
+		CHECK(code_edit->get_line(1) == "        test");
+		CHECK(code_edit->get_caret_column() == 8);
+		CHECK(code_edit->get_selection_from_column() == 8);
+		CHECK(code_edit->get_selection_to_column() == 9);
+
+		// Within provided range.
+		code_edit->set_text("");
+		code_edit->insert_text_at_caret("\ttest\n\t\ttest\n");
+		code_edit->set_caret_line(1);
+		code_edit->set_caret_column(2);
+		code_edit->select(1, 2, 1, 3);
+		code_edit->convert_indent(1, 1);
+		CHECK(code_edit->get_line(0) == "\ttest");
+		CHECK(code_edit->get_line(1) == "        test");
+		CHECK(code_edit->get_caret_column() == 8);
+		CHECK(code_edit->get_selection_from_column() == 8);
+		CHECK(code_edit->get_selection_to_column() == 9);
+
+		// Outside of range.
+		ERR_PRINT_OFF;
+		code_edit->convert_indent(0, 4);
+		code_edit->convert_indent(4, 5);
+		code_edit->convert_indent(4, 1);
+		ERR_PRINT_ON;
+	}
+
 	memdelete(code_edit);
 }