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

Add Selection and Caret for Next Occurrence of Selection
This commit is contained in:
Clay John 2022-10-27 17:21:49 -07:00 committed by GitHub
commit 0486810697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 3 deletions

View File

@ -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);

View File

@ -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.

View File

@ -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" />

View File

@ -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));

View File

@ -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;

View File

@ -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");