From 9813c834fab309594e1856553f1aeffebe07a151 Mon Sep 17 00:00:00 2001 From: kit Date: Thu, 8 Jan 2026 18:44:53 -0500 Subject: [PATCH] Fix TextEdit Shift+Click selection start position --- scene/gui/code_edit.cpp | 2 +- scene/gui/text_edit.cpp | 7 ++-- tests/scene/test_text_edit.h | 65 ++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index f9eeb2a841e..ab59cd61432 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -459,7 +459,7 @@ void CodeEdit::gui_input(const Ref &p_gui_input) { if (line != -1) { emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col); } - return; + // Don't return here to pass event to TextEdit so it can clean up the mouse pressed state. } } } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 0cf6a6c5302..f00e67fa24e 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -6191,6 +6191,11 @@ void TextEdit::set_selection_origin_column(int p_column, int p_caret) { deselect(p_caret); } + if (get_selection_mode() == SELECTION_MODE_NONE || get_selection_mode() == SELECTION_MODE_SHIFT) { + carets.write[p_caret].selection.word_begin_column = p_column; + carets.write[p_caret].selection.word_end_column = p_column; + } + if (selection_moved && has_selection(p_caret)) { _selection_changed(p_caret); } @@ -8521,8 +8526,6 @@ void TextEdit::_pre_shift_selection(int p_caret) { set_selection_origin_line(get_caret_line(p_caret), true, -1, p_caret); set_selection_origin_column(get_caret_column(p_caret), p_caret); carets.write[p_caret].selection.active = true; - carets.write[p_caret].selection.word_begin_column = get_caret_column(p_caret); - carets.write[p_caret].selection.word_end_column = get_caret_column(p_caret); } bool TextEdit::_selection_contains(int p_caret, int p_line, int p_column, bool p_include_edges, bool p_only_selections) const { diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 2ad3bcd98bb..85ecceeaeba 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -2134,6 +2134,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { // Shift click to make a selection from the previous caret position. SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 1).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 1).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "or s"); @@ -2143,6 +2144,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); CHECK(text_edit->is_caret_after_selection_origin()); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); // Shift click above to switch selection direction. Uses original selection position. SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); @@ -2154,9 +2156,11 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 6); CHECK_FALSE(text_edit->is_caret_after_selection_origin()); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); // Clicking clears selection. SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 7); @@ -2164,13 +2168,74 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { // Cannot select when disabled, but caret still moves. text_edit->set_selecting_enabled(false); SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); text_edit->set_selecting_enabled(true); + + // Shift click to make a selection from caret. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + text_edit->set_caret_column(5, false); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 13).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "is some "); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 5); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 13); + CHECK(text_edit->is_caret_after_selection_origin()); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 13).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + text_edit->deselect(); + + // Shift click with an existing selection. + text_edit->select(1, 2, 1, 5); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 8).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "r sele"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 2); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->is_caret_after_selection_origin()); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 8).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + text_edit->deselect(); + + // Shift selecting after word selection mode keeps the selection on the word. + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "is"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 7); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 5); + + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 10).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "is so"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 10); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 5); + + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 3).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "is is"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 2); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 7); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 3).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); } SUBCASE("[TextEdit] select and deselect") {