Bladeren bron

Merge pull request #67644 from alfredbaudisch/add-selection-next-occurrence

Add Selection and Caret for Next Occurrence of Selection
Clay John 3 jaren geleden
bovenliggende
commit
0486810697

+ 6 - 1
core/input/input_map.cpp

@@ -337,6 +337,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
     { "ui_text_scroll_down.macos",                     TTRC("Scroll Down") },
     { "ui_text_select_all",                            TTRC("Select All") },
     { "ui_text_select_word_under_caret",               TTRC("Select Word Under Caret") },
+    { "ui_text_add_selection_for_next_occurrence",     TTRC("Add Selection for Next Occurrence") },
     { "ui_text_toggle_insert_mode",                    TTRC("Toggle Insert Mode") },
     { "ui_text_submit",                                TTRC("Text Submitted") },
     { "ui_graph_duplicate",                            TTRC("Duplicate Nodes") },
@@ -641,9 +642,13 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
 	default_builtin_cache.insert("ui_text_select_all", inputs);
 
 	inputs = List<Ref<InputEvent>>();
-	inputs.push_back(InputEventKey::create_reference(Key::D | KeyModifierMask::CMD_OR_CTRL));
+	inputs.push_back(InputEventKey::create_reference(Key::G | KeyModifierMask::ALT));
 	default_builtin_cache.insert("ui_text_select_word_under_caret", inputs);
 
+	inputs = List<Ref<InputEvent>>();
+	inputs.push_back(InputEventKey::create_reference(Key::D | KeyModifierMask::CMD_OR_CTRL));
+	default_builtin_cache.insert("ui_text_add_selection_for_next_occurrence", inputs);
+
 	inputs = List<Ref<InputEvent>>();
 	inputs.push_back(InputEventKey::create_reference(Key::INSERT));
 	default_builtin_cache.insert("ui_text_toggle_insert_mode", inputs);

+ 8 - 1
doc/classes/ProjectSettings.xml

@@ -830,6 +830,13 @@
 		</member>
 		<member name="input/ui_swap_input_direction" type="Dictionary" setter="" getter="">
 		</member>
+		<member name="input/ui_text_add_selection_for_next_occurrence" type="Dictionary" setter="" getter="">
+			If a selection is currently active with the last caret in text fields, searches for the next occurrence of the selection, adds a caret and selects the next occurrence.
+			If no selection is currently active with the last caret in text fields, selects the word currently under the caret.
+			The action can be performed sequentially for all occurrences of the selection of the last caret and for all existing carets.
+			The viewport is adjusted to the latest newly added caret.
+			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
+		</member>
 		<member name="input/ui_text_backspace" type="Dictionary" setter="" getter="">
 			Default [InputEventAction] to delete the character before the text cursor.
 			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
@@ -984,7 +991,7 @@
 		</member>
 		<member name="input/ui_text_select_word_under_caret" type="Dictionary" setter="" getter="">
 			If no selection is currently active, selects the word currently under the caret in text fields. If a selection is currently active, deselects the current selection.
-			[b]Note:[/b] Currently, this is only implemented in [TextEdit], not [LineEdit].
+			[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
 		</member>
 		<member name="input/ui_text_submit" type="Dictionary" setter="" getter="">
 			Default [InputEventAction] to submit a text field.

+ 6 - 0
doc/classes/TextEdit.xml

@@ -70,6 +70,12 @@
 				Register a new gutter to this [TextEdit]. Use [param at] to have a specific gutter order. A value of [code]-1[/code] appends the gutter to the right.
 			</description>
 		</method>
+		<method name="add_selection_for_next_occurrence">
+			<return type="void" />
+			<description>
+				Adds a selection and a caret for the next occurrence of the current selection. If there is no active selection, selects word under caret.
+			</description>
+		</method>
 		<method name="adjust_carets_after_edit">
 			<return type="void" />
 			<param index="0" name="caret" type="int" />

+ 46 - 1
scene/gui/text_edit.cpp

@@ -2051,7 +2051,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 		}
 
 		if (is_shortcut_keys_enabled()) {
-			// SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE.
+			// SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, CUT, COPY, PASTE.
 			if (k->is_action("ui_text_select_all", true)) {
 				select_all();
 				accept_event();
@@ -2062,6 +2062,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
 				accept_event();
 				return;
 			}
+			if (k->is_action("ui_text_add_selection_for_next_occurrence", true)) {
+				add_selection_for_next_occurrence();
+				accept_event();
+				return;
+			}
 			if (k->is_action("ui_cut", true)) {
 				cut();
 				accept_event();
@@ -4832,6 +4837,45 @@ void TextEdit::select_word_under_caret(int p_caret) {
 	merge_overlapping_carets();
 }
 
+void TextEdit::add_selection_for_next_occurrence() {
+	if (!selecting_enabled || !is_multiple_carets_enabled()) {
+		return;
+	}
+
+	if (text.size() == 1 && text[0].length() == 0) {
+		return;
+	}
+
+	// Always use the last caret, to correctly search for
+	// the next occurrence that comes after this caret.
+	int caret = get_caret_count() - 1;
+
+	if (!has_selection(caret)) {
+		select_word_under_caret(caret);
+		return;
+	}
+
+	const String &highlighted_text = get_selected_text(caret);
+	int column = get_selection_from_column(caret) + 1;
+	int line = get_caret_line(caret);
+
+	const Point2i next_occurrence = search(highlighted_text, SEARCH_MATCH_CASE, line, column);
+
+	if (next_occurrence.x == -1 || next_occurrence.y == -1) {
+		return;
+	}
+
+	int to_column = get_selection_to_column(caret) + 1;
+	int end = next_occurrence.x + (to_column - column);
+	int new_caret = add_caret(next_occurrence.y, end);
+
+	if (new_caret != -1) {
+		select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret);
+		adjust_viewport_to_caret(new_caret);
+		merge_overlapping_carets();
+	}
+}
+
 void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) {
 	ERR_FAIL_INDEX(p_caret, carets.size());
 	if (!selecting_enabled) {
@@ -6000,6 +6044,7 @@ void TextEdit::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all);
 	ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence);
 	ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0));
 
 	ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1));

+ 1 - 0
scene/gui/text_edit.h

@@ -851,6 +851,7 @@ public:
 
 	void select_all();
 	void select_word_under_caret(int p_caret = -1);
+	void add_selection_for_next_occurrence();
 	void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0);
 
 	bool has_selection(int p_caret = -1) const;

+ 40 - 0
tests/scene/test_text_edit.h

@@ -733,6 +733,46 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
 			SIGNAL_CHECK_FALSE("caret_changed");
 		}
 
+		SUBCASE("[TextEdit] add selection for next occurrence") {
+			text_edit->set_text("\ntest   other_test\nrandom   test\nword test word");
+			text_edit->set_caret_column(0);
+			text_edit->set_caret_line(1);
+
+			text_edit->select_word_under_caret();
+			CHECK(text_edit->has_selection(0));
+			CHECK(text_edit->get_selected_text(0) == "test");
+
+			text_edit->add_selection_for_next_occurrence();
+			CHECK(text_edit->get_caret_count() == 2);
+			CHECK(text_edit->get_selected_text(1) == "test");
+			CHECK(text_edit->get_selection_from_line(1) == 1);
+			CHECK(text_edit->get_selection_from_column(1) == 13);
+			CHECK(text_edit->get_selection_to_line(1) == 1);
+			CHECK(text_edit->get_selection_to_column(1) == 17);
+			CHECK(text_edit->get_caret_line(1) == 1);
+			CHECK(text_edit->get_caret_column(1) == 17);
+
+			text_edit->add_selection_for_next_occurrence();
+			CHECK(text_edit->get_caret_count() == 3);
+			CHECK(text_edit->get_selected_text(2) == "test");
+			CHECK(text_edit->get_selection_from_line(2) == 2);
+			CHECK(text_edit->get_selection_from_column(2) == 9);
+			CHECK(text_edit->get_selection_to_line(2) == 2);
+			CHECK(text_edit->get_selection_to_column(2) == 13);
+			CHECK(text_edit->get_caret_line(2) == 2);
+			CHECK(text_edit->get_caret_column(2) == 13);
+
+			text_edit->add_selection_for_next_occurrence();
+			CHECK(text_edit->get_caret_count() == 4);
+			CHECK(text_edit->get_selected_text(3) == "test");
+			CHECK(text_edit->get_selection_from_line(3) == 3);
+			CHECK(text_edit->get_selection_from_column(3) == 5);
+			CHECK(text_edit->get_selection_to_line(3) == 3);
+			CHECK(text_edit->get_selection_to_column(3) == 9);
+			CHECK(text_edit->get_caret_line(3) == 3);
+			CHECK(text_edit->get_caret_column(3) == 9);
+		}
+
 		SUBCASE("[TextEdit] deselect on focus loss") {
 			text_edit->set_text("test");