From 303d0994e9b25912e824cec16a858a0a51e92069 Mon Sep 17 00:00:00 2001 From: "DESKTOP-UT43QTQ\\Garrigan-Desktop" Date: Wed, 13 Sep 2023 21:30:48 -0500 Subject: [PATCH] Avoid resetting the code completion popup excessively --- scene/gui/code_edit.cpp | 45 ++++++++++++++++++++------ scene/gui/code_edit.h | 1 + tests/scene/test_code_edit.h | 61 ++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index f74d7fb906af..39ade982025d 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -3143,7 +3143,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { int line_height = get_line_height(); if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) { - code_completion_options.clear(); + Vector code_completion_options_new; code_completion_base = ""; /* Build options argument. */ @@ -3193,11 +3193,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() { if (theme_cache.font.is_valid()) { max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset); } - code_completion_options.push_back(option); + code_completion_options_new.push_back(option); } + if (_should_reset_selected_option_for_new_options(code_completion_options_new)) { + code_completion_current_selected = 0; + } + code_completion_options = code_completion_options_new; + code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size); - code_completion_current_selected = 0; code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); @@ -3272,7 +3276,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { /* For now handle only traditional quoted strings. */ bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'"; - code_completion_options.clear(); + Vector code_completion_options_new; code_completion_base = string_to_complete; /* Don't autocomplete setting numerical values. */ @@ -3304,7 +3308,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() { if (string_to_complete.length() == 0) { option.get_option_characteristics(string_to_complete); - code_completion_options.push_back(option); + code_completion_options_new.push_back(option); + if (theme_cache.font.is_valid()) { max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset); } @@ -3379,7 +3384,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { } } - code_completion_options.push_back(option); + code_completion_options_new.push_back(option); if (theme_cache.font.is_valid()) { max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset); } @@ -3387,26 +3392,46 @@ void CodeEdit::_filter_code_completion_candidates_impl() { } /* No options to complete, cancel. */ - if (code_completion_options.size() == 0) { + if (code_completion_options_new.size() == 0) { cancel_code_completion(); return; } /* A perfect match, stop completion. */ - if (code_completion_options.size() == 1 && string_to_complete == code_completion_options[0].display) { + if (code_completion_options_new.size() == 1 && string_to_complete == code_completion_options_new[0].display) { cancel_code_completion(); return; } - code_completion_options.sort_custom(); + code_completion_options_new.sort_custom(); + if (_should_reset_selected_option_for_new_options(code_completion_options_new)) { + code_completion_current_selected = 0; + } + code_completion_options = code_completion_options_new; code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size); - code_completion_current_selected = 0; code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); } +// Assumes both the new_options and the code_completion_options are sorted. +bool CodeEdit::_should_reset_selected_option_for_new_options(const Vector &p_new_options) { + if (code_completion_current_selected >= p_new_options.size()) { + return true; + } + + for (int i = 0; i < code_completion_options.size() && i < p_new_options.size(); i++) { + if (i > code_completion_current_selected) { + return false; + } + if (code_completion_options[i].display != p_new_options[i].display) { + return true; + } + } + return false; +} + void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) { _update_delimiter_cache(p_from_line, p_to_line); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 53ff65f37606..a1a3c5bbd841 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -222,6 +222,7 @@ private: void _update_scroll_selected_line(float p_mouse_y); void _filter_code_completion_candidates_impl(); + bool _should_reset_selected_option_for_new_options(const Vector &p_new_options); /* Line length guidelines */ TypedArray line_length_guideline_columns; diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index 8576b38ce268..85c628420873 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -3725,6 +3725,67 @@ TEST_CASE("[SceneTree][CodeEdit] completion") { CHECK(code_edit->get_line(0) == "sstest"); } + SUBCASE("[CodeEdit] autocomplete currently selected option") { + code_edit->set_code_completion_enabled(true); + REQUIRE(code_edit->is_code_completion_enabled()); + + // Initially select item 0. + code_edit->insert_text_at_caret("te"); + code_edit->set_caret_column(2); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); + code_edit->update_code_completion_options(); + CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Initially selected item should be 0."); + + // After adding later options shouldn't update selection. + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); // Added te4. + code_edit->update_code_completion_options(); + CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Adding later options shouldn't update selection."); + + code_edit->set_code_completion_selected_index(2); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te5", "te5"); // Added te5. + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); // Added te6. + code_edit->update_code_completion_options(); + CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Adding later options shouldn't update selection."); + + // Removing elements after selected element shouldn't update selection. + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te5", "te5"); // Removed te6. + code_edit->update_code_completion_options(); + CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Removing elements after selected element shouldn't update selection."); + + // Changing elements after selected element shouldn't update selection. + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); // Changed te5->te6. + code_edit->update_code_completion_options(); + CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Changing elements after selected element shouldn't update selection."); + + // Changing elements before selected element should reset selection. + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te2", "te2"); // Changed te1->te2. + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); + code_edit->update_code_completion_options(); + CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Changing elements before selected element should reset selection."); + + // Removing elements before selected element should reset selection. + code_edit->set_code_completion_selected_index(2); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); // Removed te2. + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); + code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); + code_edit->update_code_completion_options(); + CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Removing elements before selected element should reset selection."); + } + memdelete(code_edit); }