summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPucklaMotzer09 <jonaas.pucher000000@gmail.com>2022-09-28 17:09:45 +0200
committerPucklaJ <jonaas.pucher000000@gmail.com>2023-09-25 23:41:31 +0200
commitd2e651f403b7f583a66e37ef0331362ad70fd1c3 (patch)
tree9713b2ebebd061732618e1e3a817388976ca7d47
parent43b9e89a07bb0926fb66bddbf98981d25a5cccee (diff)
downloadredot-engine-d2e651f403b7f583a66e37ef0331362ad70fd1c3.tar.gz
Add Duplicate Lines shortcut to CodeTextEditor
This keyboard shortcut has been made with inspiration from the VS Code keyboard shortcut editor.action.copyLinesDownAction. It duplicates all selected lines and inserts them below no matter where the caret is within the line.
-rw-r--r--doc/classes/CodeEdit.xml6
-rw-r--r--editor/code_editor.cpp5
-rw-r--r--editor/plugins/script_text_editor.cpp6
-rw-r--r--editor/plugins/script_text_editor.h1
-rw-r--r--editor/plugins/text_editor.cpp4
-rw-r--r--editor/plugins/text_editor.h1
-rw-r--r--editor/plugins/text_shader_editor.cpp4
-rw-r--r--editor/plugins/text_shader_editor.h1
-rw-r--r--scene/gui/code_edit.cpp65
-rw-r--r--scene/gui/code_edit.h3
-rw-r--r--tests/scene/test_code_edit.h78
11 files changed, 174 insertions, 0 deletions
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml
index d9146cf604..9b4b3aa36e 100644
--- a/doc/classes/CodeEdit.xml
+++ b/doc/classes/CodeEdit.xml
@@ -152,6 +152,12 @@
Perform an indent as if the user activated the "ui_text_indent" action.
</description>
</method>
+ <method name="duplicate_lines">
+ <return type="void" />
+ <description>
+ Duplicates all lines currently selected with any caret. Duplicates the entire line beneath the current one no matter where the caret is within the line.
+ </description>
+ </method>
<method name="fold_all_lines">
<return type="void" />
<description>
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 876fef078b..9d2b54ef1a 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -805,6 +805,11 @@ void CodeTextEditor::input(const Ref<InputEvent> &event) {
accept_event();
return;
}
+ if (ED_IS_SHORTCUT("script_text_editor/duplicate_lines", key_event)) {
+ text_editor->duplicate_lines();
+ accept_event();
+ return;
+ }
}
void CodeTextEditor::_text_editor_gui_input(const Ref<InputEvent> &p_event) {
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index d5ad21e346..dc3f88705b 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1306,6 +1306,9 @@ void ScriptTextEditor::_edit_option(int p_op) {
case EDIT_DUPLICATE_SELECTION: {
code_editor->duplicate_selection();
} break;
+ case EDIT_DUPLICATE_LINES: {
+ code_editor->get_text_editor()->duplicate_lines();
+ } break;
case EDIT_TOGGLE_FOLD_LINE: {
int previous_line = -1;
for (int caret_idx : tx->get_caret_index_edit_order()) {
@@ -2173,6 +2176,7 @@ void ScriptTextEditor::_enable_code_editor() {
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
edit_menu->get_popup()->add_separator();
@@ -2395,6 +2399,8 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), Key::NONE);
ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::SHIFT | KeyModifierMask::CTRL | Key::D);
ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_selection", "macos", KeyModifierMask::SHIFT | KeyModifierMask::META | Key::C);
+ ED_SHORTCUT("script_text_editor/duplicate_lines", TTR("Duplicate Lines"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::DOWN);
+ ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_lines", "macos", KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::DOWN);
ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::E);
ED_SHORTCUT("script_text_editor/toggle_word_wrap", TTR("Toggle Word Wrap"), KeyModifierMask::ALT | Key::Z);
ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::T);
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 0efe7d54e3..8c0ba6d7e6 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -126,6 +126,7 @@ class ScriptTextEditor : public ScriptEditorBase {
EDIT_UNINDENT,
EDIT_DELETE_LINE,
EDIT_DUPLICATE_SELECTION,
+ EDIT_DUPLICATE_LINES,
EDIT_PICK_COLOR,
EDIT_TO_UPPERCASE,
EDIT_TO_LOWERCASE,
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index 92ee468bc2..b0a69cfb5c 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -392,6 +392,9 @@ void TextEditor::_edit_option(int p_op) {
case EDIT_DUPLICATE_SELECTION: {
code_editor->duplicate_selection();
} break;
+ case EDIT_DUPLICATE_LINES: {
+ code_editor->get_text_editor()->duplicate_lines();
+ } break;
case EDIT_TOGGLE_FOLD_LINE: {
int previous_line = -1;
for (int caret_idx : tx->get_caret_index_edit_order()) {
@@ -651,6 +654,7 @@ TextEditor::TextEditor() {
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_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES);
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index d1aa2a9047..a4ec2d882b 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -71,6 +71,7 @@ private:
EDIT_UNINDENT,
EDIT_DELETE_LINE,
EDIT_DUPLICATE_SELECTION,
+ EDIT_DUPLICATE_LINES,
EDIT_TO_UPPERCASE,
EDIT_TO_LOWERCASE,
EDIT_CAPITALIZE,
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index b68b283a61..27f42608c6 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -671,6 +671,9 @@ void TextShaderEditor::_menu_option(int p_option) {
case EDIT_DUPLICATE_SELECTION: {
shader_editor->duplicate_selection();
} break;
+ case EDIT_DUPLICATE_LINES: {
+ shader_editor->get_text_editor()->duplicate_lines();
+ } break;
case EDIT_TOGGLE_WORD_WRAP: {
TextEdit::LineWrappingMode wrap = shader_editor->get_text_editor()->get_line_wrapping_mode();
shader_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY);
@@ -1122,6 +1125,7 @@ TextShaderEditor::TextShaderEditor() {
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/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
diff --git a/editor/plugins/text_shader_editor.h b/editor/plugins/text_shader_editor.h
index 03827f1b83..eba2ec0bb9 100644
--- a/editor/plugins/text_shader_editor.h
+++ b/editor/plugins/text_shader_editor.h
@@ -120,6 +120,7 @@ class TextShaderEditor : public MarginContainer {
EDIT_UNINDENT,
EDIT_DELETE_LINE,
EDIT_DUPLICATE_SELECTION,
+ EDIT_DUPLICATE_LINES,
EDIT_TOGGLE_WORD_WRAP,
EDIT_TOGGLE_COMMENT,
EDIT_COMPLETE,
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index d35d35d36d..e7a2a26a29 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -2399,6 +2399,68 @@ void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) {
}
}
+/* Text manipulation */
+void CodeEdit::duplicate_lines() {
+ begin_complex_operation();
+
+ Vector<int> caret_edit_order = get_caret_index_edit_order();
+ for (const int &caret_index : caret_edit_order) {
+ // The text that will be inserted. All lines in one string.
+ String insert_text;
+
+ // The new line position of the caret after the operation.
+ int new_caret_line = get_caret_line(caret_index);
+ // The new column position of the caret after the operation.
+ int new_caret_column = get_caret_column(caret_index);
+ // The caret positions of the selection. Stays -1 if there is no selection.
+ int select_from_line = -1;
+ int select_to_line = -1;
+ int select_from_column = -1;
+ int select_to_column = -1;
+ // Number of lines of the selection.
+ int select_num_lines = -1;
+
+ if (has_selection(caret_index)) {
+ select_from_line = get_selection_from_line(caret_index);
+ select_to_line = get_selection_to_line(caret_index);
+ select_from_column = get_selection_from_column(caret_index);
+ select_to_column = get_selection_to_column(caret_index);
+ select_num_lines = select_to_line - select_from_line + 1;
+
+ for (int i = select_from_line; i <= select_to_line; i++) {
+ insert_text += "\n" + get_line(i);
+ unfold_line(i);
+ }
+ new_caret_line = select_to_line + select_num_lines;
+ } else {
+ insert_text = "\n" + get_line(new_caret_line);
+ new_caret_line++;
+
+ unfold_line(get_caret_line(caret_index));
+ }
+
+ // The text will be inserted at the end of the current line.
+ set_caret_column(get_line(get_caret_line(caret_index)).length(), false, caret_index);
+
+ deselect(caret_index);
+
+ insert_text_at_caret(insert_text, caret_index);
+ set_caret_line(new_caret_line, false, true, 0, caret_index);
+ set_caret_column(new_caret_column, true, caret_index);
+
+ if (select_from_line != -1) {
+ // Advance the selection by the number of duplicated lines.
+ select_from_line += select_num_lines;
+ select_to_line += select_num_lines;
+
+ select(select_from_line, select_from_column, select_to_line, select_to_column, caret_index);
+ }
+ }
+
+ end_complex_operation();
+ queue_redraw();
+}
+
/* Visual */
Color CodeEdit::_get_brace_mismatch_color() const {
return theme_cache.brace_mismatch_color;
@@ -2598,6 +2660,9 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid);
+ /* Text manipulation */
+ ClassDB::bind_method(D_METHOD("duplicate_lines"), &CodeEdit::duplicate_lines);
+
/* Inspector */
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symbol_lookup_on_click"), "set_symbol_lookup_on_click_enabled", "is_symbol_lookup_on_click_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled");
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index e688af2bda..97c435b52d 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -486,6 +486,9 @@ public:
void set_symbol_lookup_word_as_valid(bool p_valid);
+ /* Text manipulation */
+ void duplicate_lines();
+
CodeEdit();
~CodeEdit();
};
diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h
index 8576b38ce2..b44a47bf8a 100644
--- a/tests/scene/test_code_edit.h
+++ b/tests/scene/test_code_edit.h
@@ -3886,6 +3886,84 @@ TEST_CASE("[SceneTree][CodeEdit] New Line") {
memdelete(code_edit);
}
+TEST_CASE("[SceneTree][CodeEdit] Duplicate Lines") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+ code_edit->grab_focus();
+
+ code_edit->set_text(R"(extends Node
+
+func _ready():
+ var a := len(OS.get_cmdline_args())
+ var b := get_child_count()
+ var c := a + b
+ for i in range(c):
+ print("This is the solution: ", sin(i))
+ var pos = get_index() - 1
+ print("Make sure this exits: %b" % pos)
+)");
+
+ /* Duplicate a single line without selection. */
+ code_edit->set_caret_line(0);
+ code_edit->duplicate_lines();
+ CHECK(code_edit->get_line(0) == "extends Node");
+ CHECK(code_edit->get_line(1) == "extends Node");
+ CHECK(code_edit->get_line(2) == "");
+
+ /* Duplicate multiple lines with selection. */
+ code_edit->set_caret_line(6);
+ code_edit->set_caret_column(15);
+ code_edit->select(4, 8, 6, 15);
+ code_edit->duplicate_lines();
+ CHECK(code_edit->get_line(6) == "\tvar c := a + b");
+ CHECK(code_edit->get_line(7) == "\tvar a := len(OS.get_cmdline_args())");
+ CHECK(code_edit->get_line(8) == "\tvar b := get_child_count()");
+ CHECK(code_edit->get_line(9) == "\tvar c := a + b");
+ CHECK(code_edit->get_line(10) == "\tfor i in range(c):");
+
+ /* Duplicate single lines with multiple carets. */
+ code_edit->deselect();
+ code_edit->set_caret_line(10);
+ code_edit->set_caret_column(1);
+ code_edit->add_caret(11, 2);
+ code_edit->add_caret(12, 1);
+ code_edit->duplicate_lines();
+ CHECK(code_edit->get_line(9) == "\tvar c := a + b");
+ CHECK(code_edit->get_line(10) == "\tfor i in range(c):");
+ CHECK(code_edit->get_line(11) == "\tfor i in range(c):");
+ CHECK(code_edit->get_line(12) == "\t\tprint(\"This is the solution: \", sin(i))");
+ CHECK(code_edit->get_line(13) == "\t\tprint(\"This is the solution: \", sin(i))");
+ CHECK(code_edit->get_line(14) == "\tvar pos = get_index() - 1");
+ CHECK(code_edit->get_line(15) == "\tvar pos = get_index() - 1");
+ CHECK(code_edit->get_line(16) == "\tprint(\"Make sure this exits: %b\" % pos)");
+
+ /* Duplicate multiple lines with multiple carets. */
+ code_edit->select(0, 0, 1, 2, 0);
+ code_edit->select(3, 0, 4, 2, 1);
+ code_edit->select(16, 0, 17, 0, 2);
+ code_edit->set_caret_line(1, false, true, 0, 0);
+ code_edit->set_caret_column(2, false, 0);
+ code_edit->set_caret_line(4, false, true, 0, 1);
+ code_edit->set_caret_column(2, false, 1);
+ code_edit->set_caret_line(17, false, true, 0, 2);
+ code_edit->set_caret_column(0, false, 2);
+ code_edit->duplicate_lines();
+ CHECK(code_edit->get_line(1) == "extends Node");
+ CHECK(code_edit->get_line(2) == "extends Node");
+ CHECK(code_edit->get_line(3) == "extends Node");
+ CHECK(code_edit->get_line(4) == "");
+ CHECK(code_edit->get_line(6) == "\tvar a := len(OS.get_cmdline_args())");
+ CHECK(code_edit->get_line(7) == "func _ready():");
+ CHECK(code_edit->get_line(8) == "\tvar a := len(OS.get_cmdline_args())");
+ CHECK(code_edit->get_line(9) == "\tvar b := get_child_count()");
+ CHECK(code_edit->get_line(20) == "\tprint(\"Make sure this exits: %b\" % pos)");
+ CHECK(code_edit->get_line(21) == "");
+ CHECK(code_edit->get_line(22) == "\tprint(\"Make sure this exits: %b\" % pos)");
+ CHECK(code_edit->get_line(23) == "");
+
+ memdelete(code_edit);
+}
+
} // namespace TestCodeEdit
#endif // TEST_CODE_EDIT_H