Merge pull request #67644 from alfredbaudisch/add-selection-next-occurrence
Add Selection and Caret for Next Occurrence of Selection
This commit is contained in:
commit
0486810697
@ -337,6 +337,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
|
|||||||
{ "ui_text_scroll_down.macos", TTRC("Scroll Down") },
|
{ "ui_text_scroll_down.macos", TTRC("Scroll Down") },
|
||||||
{ "ui_text_select_all", TTRC("Select All") },
|
{ "ui_text_select_all", TTRC("Select All") },
|
||||||
{ "ui_text_select_word_under_caret", TTRC("Select Word Under Caret") },
|
{ "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_toggle_insert_mode", TTRC("Toggle Insert Mode") },
|
||||||
{ "ui_text_submit", TTRC("Text Submitted") },
|
{ "ui_text_submit", TTRC("Text Submitted") },
|
||||||
{ "ui_graph_duplicate", TTRC("Duplicate Nodes") },
|
{ "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);
|
default_builtin_cache.insert("ui_text_select_all", inputs);
|
||||||
|
|
||||||
inputs = List<Ref<InputEvent>>();
|
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);
|
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 = List<Ref<InputEvent>>();
|
||||||
inputs.push_back(InputEventKey::create_reference(Key::INSERT));
|
inputs.push_back(InputEventKey::create_reference(Key::INSERT));
|
||||||
default_builtin_cache.insert("ui_text_toggle_insert_mode", inputs);
|
default_builtin_cache.insert("ui_text_toggle_insert_mode", inputs);
|
||||||
|
@ -830,6 +830,13 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="input/ui_swap_input_direction" type="Dictionary" setter="" getter="">
|
<member name="input/ui_swap_input_direction" type="Dictionary" setter="" getter="">
|
||||||
</member>
|
</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="">
|
<member name="input/ui_text_backspace" type="Dictionary" setter="" getter="">
|
||||||
Default [InputEventAction] to delete the character before the text cursor.
|
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.
|
[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>
|
||||||
<member name="input/ui_text_select_word_under_caret" type="Dictionary" setter="" getter="">
|
<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.
|
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>
|
||||||
<member name="input/ui_text_submit" type="Dictionary" setter="" getter="">
|
<member name="input/ui_text_submit" type="Dictionary" setter="" getter="">
|
||||||
Default [InputEventAction] to submit a text field.
|
Default [InputEventAction] to submit a text field.
|
||||||
|
@ -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.
|
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>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="adjust_carets_after_edit">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="caret" type="int" />
|
<param index="0" name="caret" type="int" />
|
||||||
|
@ -2051,7 +2051,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_shortcut_keys_enabled()) {
|
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)) {
|
if (k->is_action("ui_text_select_all", true)) {
|
||||||
select_all();
|
select_all();
|
||||||
accept_event();
|
accept_event();
|
||||||
@ -2062,6 +2062,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
|
|||||||
accept_event();
|
accept_event();
|
||||||
return;
|
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)) {
|
if (k->is_action("ui_cut", true)) {
|
||||||
cut();
|
cut();
|
||||||
accept_event();
|
accept_event();
|
||||||
@ -4832,6 +4837,45 @@ void TextEdit::select_word_under_caret(int p_caret) {
|
|||||||
merge_overlapping_carets();
|
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) {
|
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());
|
ERR_FAIL_INDEX(p_caret, carets.size());
|
||||||
if (!selecting_enabled) {
|
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_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("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("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));
|
ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1));
|
||||||
|
@ -851,6 +851,7 @@ public:
|
|||||||
|
|
||||||
void select_all();
|
void select_all();
|
||||||
void select_word_under_caret(int p_caret = -1);
|
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);
|
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;
|
bool has_selection(int p_caret = -1) const;
|
||||||
|
@ -733,6 +733,46 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
|
|||||||
SIGNAL_CHECK_FALSE("caret_changed");
|
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") {
|
SUBCASE("[TextEdit] deselect on focus loss") {
|
||||||
text_edit->set_text("test");
|
text_edit->set_text("test");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user