From fdd0f327749c6cb6922d4e20a12a08f7d07ce089 Mon Sep 17 00:00:00 2001 From: jinyangcruise Date: Wed, 26 Mar 2025 19:33:29 +0800 Subject: [PATCH] Support Keep Results in results of Find in Files --- editor/script/find_in_files.cpp | 227 ++++++++++++++++++++++++- editor/script/find_in_files.h | 48 ++++++ editor/script/script_editor_plugin.cpp | 17 +- editor/script/script_editor_plugin.h | 4 +- 4 files changed, 283 insertions(+), 13 deletions(-) diff --git a/editor/script/find_in_files.cpp b/editor/script/find_in_files.cpp index fd6a0dc97cd..f7adeb47f5a 100644 --- a/editor/script/find_in_files.cpp +++ b/editor/script/find_in_files.cpp @@ -39,11 +39,13 @@ #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/check_box.h" +#include "scene/gui/check_button.h" #include "scene/gui/file_dialog.h" #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/progress_bar.h" +#include "scene/gui/tab_container.h" #include "scene/gui/tree.h" const char *FindInFiles::SIGNAL_RESULT_FOUND = "result_found"; @@ -700,10 +702,11 @@ FindInFilesPanel::FindInFilesPanel() { { HBoxContainer *hbc = memnew(HBoxContainer); + hbc->set_alignment(BoxContainer::ALIGNMENT_END); - Label *find_label = memnew(Label); - find_label->set_text(TTRC("Find:")); - hbc->add_child(find_label); + _find_label = memnew(Label); + _find_label->set_text(TTRC("Find:")); + hbc->add_child(_find_label); _search_text_label = memnew(Label); _search_text_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); @@ -724,6 +727,12 @@ FindInFilesPanel::FindInFilesPanel() { _status_label->set_focus_mode(FOCUS_ACCESSIBILITY); hbc->add_child(_status_label); + _keep_results_button = memnew(CheckButton); + _keep_results_button->set_text(TTRC("Keep Results")); + _keep_results_button->set_tooltip_text(TTRC("Keep these results and show subsequent results in a new window")); + _keep_results_button->set_pressed(false); + hbc->add_child(_keep_results_button); + _refresh_button = memnew(Button); _refresh_button->set_text(TTRC("Refresh")); _refresh_button->connect(SceneStringName(pressed), callable_mp(this, &FindInFilesPanel::_on_refresh_button_clicked)); @@ -804,6 +813,16 @@ void FindInFilesPanel::set_replace_text(const String &text) { _replace_line_edit->set_text(text); } +bool FindInFilesPanel::is_keep_results() const { + return _keep_results_button->is_pressed(); +} + +void FindInFilesPanel::set_search_labels_visibility(bool p_visible) { + _find_label->set_visible(p_visible); + _search_text_label->set_visible(p_visible); + _close_button->set_visible(p_visible); +} + void FindInFilesPanel::clear() { _file_items.clear(); _file_items_results_count.clear(); @@ -1244,3 +1263,205 @@ void FindInFilesPanel::_bind_methods() { ADD_SIGNAL(MethodInfo(SIGNAL_CLOSE_BUTTON_CLICKED)); } + +//----------------------------------------------------------------------------- + +FindInFilesContainer::FindInFilesContainer() { + const Ref bottom_panel_style = EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)); + if (bottom_panel_style.is_valid()) { + add_theme_constant_override("margin_top", -bottom_panel_style->get_margin(SIDE_TOP)); + add_theme_constant_override("margin_left", -bottom_panel_style->get_margin(SIDE_LEFT)); + add_theme_constant_override("margin_right", -bottom_panel_style->get_margin(SIDE_RIGHT)); + add_theme_constant_override("margin_bottom", -bottom_panel_style->get_margin(SIDE_BOTTOM)); + } + + _tabs = memnew(TabContainer); + _tabs->set_tabs_visible(false); + add_child(_tabs); + + _tabs->set_drag_to_rearrange_enabled(true); + _tabs->get_tab_bar()->set_select_with_rmb(true); + _tabs->get_tab_bar()->set_tab_close_display_policy(TabBar::CLOSE_BUTTON_SHOW_ACTIVE_ONLY); + _tabs->get_tab_bar()->connect("tab_close_pressed", callable_mp(this, &FindInFilesContainer::_on_tab_close_pressed)); + _tabs->get_tab_bar()->connect(SceneStringName(gui_input), callable_mp(this, &FindInFilesContainer::_bar_input)); + + _tabs_context_menu = memnew(PopupMenu); + add_child(_tabs_context_menu); + _tabs_context_menu->add_item(TTRC("Close Tab"), PANEL_CLOSE); + _tabs_context_menu->add_item(TTRC("Close Other Tabs"), PANEL_CLOSE_OTHERS); + _tabs_context_menu->add_item(TTRC("Close Tabs to the Right"), PANEL_CLOSE_RIGHT); + _tabs_context_menu->add_item(TTRC("Close All Tabs"), PANEL_CLOSE_ALL); + _tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &FindInFilesContainer::_bar_menu_option)); +} + +FindInFilesPanel *FindInFilesContainer::_create_new_panel() { + int index = _tabs->get_current_tab(); + FindInFilesPanel *panel = memnew(FindInFilesPanel); + _tabs->add_child(panel); + _tabs->move_child(panel, index + 1); // New panel is added after the current activated panel. + _tabs->set_current_tab(index + 1); + _update_bar_visibility(); + + panel->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &FindInFilesContainer::_on_find_in_files_result_selected)); + panel->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &FindInFilesContainer::_on_find_in_files_modified_files)); + panel->connect(FindInFilesPanel::SIGNAL_CLOSE_BUTTON_CLICKED, callable_mp(this, &FindInFilesContainer::_on_find_in_files_close_button_clicked).bind(panel)); + return panel; +} + +FindInFilesPanel *FindInFilesContainer::_get_current_panel() { + return Object::cast_to(_tabs->get_current_tab_control()); +} + +FindInFilesPanel *FindInFilesContainer::get_panel_for_results(const String &p_label) { + FindInFilesPanel *panel = nullptr; + // Prefer the current panel. + if (_get_current_panel() && !_get_current_panel()->is_keep_results()) { + panel = _get_current_panel(); + } else { + // Find the first panel which does not keep results. + for (int i = 0; i < _tabs->get_tab_count(); i++) { + FindInFilesPanel *p = Object::cast_to(_tabs->get_tab_control(i)); + if (p && !p->is_keep_results()) { + panel = p; + _tabs->set_current_tab(i); + break; + } + } + + if (!panel) { + panel = _create_new_panel(); + } + } + _tabs->set_tab_title(_tabs->get_current_tab(), p_label); + return panel; +} + +void FindInFilesContainer::_bind_methods() { + ADD_SIGNAL(MethodInfo("result_selected", + PropertyInfo(Variant::STRING, "path"), + PropertyInfo(Variant::INT, "line_number"), + PropertyInfo(Variant::INT, "begin"), + PropertyInfo(Variant::INT, "end"))); + + ADD_SIGNAL(MethodInfo("files_modified", PropertyInfo(Variant::STRING, "paths"))); + + ADD_SIGNAL(MethodInfo("close_button_clicked")); +} + +void FindInFilesContainer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + const Ref bottom_panel_style = EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)); + if (bottom_panel_style.is_valid()) { + const int margin_top = -bottom_panel_style->get_margin(SIDE_TOP); + const int margin_left = -bottom_panel_style->get_margin(SIDE_LEFT); + const int margin_right = -bottom_panel_style->get_margin(SIDE_RIGHT); + const int margin_bottom = -bottom_panel_style->get_margin(SIDE_BOTTOM); + + if (get_theme_constant("margin_top") != margin_top) { + add_theme_constant_override("margin_top", margin_top); + } + if (get_theme_constant("margin_left") != margin_left) { + add_theme_constant_override("margin_left", margin_left); + } + if (get_theme_constant("margin_right") != margin_right) { + add_theme_constant_override("margin_right", margin_right); + } + if (get_theme_constant("margin_bottom") != margin_bottom) { + add_theme_constant_override("margin_bottom", margin_bottom); + } + } + } break; + } +} + +void FindInFilesContainer::_on_find_in_files_result_selected(const String &p_fpath, int p_line_number, int p_begin, int p_end) { + emit_signal(SNAME("result_selected"), p_fpath, p_line_number, p_begin, p_end); +} + +void FindInFilesContainer::_on_find_in_files_modified_files(const PackedStringArray &p_paths) { + emit_signal(SNAME("files_modified"), p_paths); +} + +void FindInFilesContainer::_on_find_in_files_close_button_clicked(FindInFilesPanel *p_panel) { + ERR_FAIL_COND_MSG(p_panel->get_parent() != _tabs, "This panel is not a child!"); + _tabs->remove_child(p_panel); + p_panel->queue_free(); + _update_bar_visibility(); + if (_tabs->get_tab_count() == 0) { + emit_signal(SNAME("close_button_clicked")); + } +} + +void FindInFilesContainer::_on_tab_close_pressed(int p_tab) { + FindInFilesPanel *panel = Object::cast_to(_tabs->get_tab_control(p_tab)); + if (panel) { + _on_find_in_files_close_button_clicked(panel); + } +} + +void FindInFilesContainer::_update_bar_visibility() { + if (!_update_bar) { + return; + } + + // If tab count <= 1, behaves like this is not a TabContainer and the bar is hidden. + bool bar_visible = _tabs->get_tab_count() > 1; + _tabs->set_tabs_visible(bar_visible); + + // Hide or show the search labels based on the visibility of the bar, as the search terms are displayed in the title of each tab. + for (int i = 0; i < _tabs->get_tab_count(); i++) { + FindInFilesPanel *panel = Object::cast_to(_tabs->get_tab_control(i)); + if (panel) { + panel->set_search_labels_visibility(!bar_visible); + } + } +} + +void FindInFilesContainer::_bar_menu_option(int p_option) { + int tab_index = _tabs->get_current_tab(); + switch (p_option) { + case PANEL_CLOSE: { + _on_tab_close_pressed(tab_index); + } break; + case PANEL_CLOSE_OTHERS: { + _update_bar = false; + FindInFilesPanel *panel = Object::cast_to(_tabs->get_tab_control(tab_index)); + for (int i = _tabs->get_tab_count() - 1; i >= 0; i--) { + FindInFilesPanel *p = Object::cast_to(_tabs->get_tab_control(i)); + if (p != panel) { + _on_find_in_files_close_button_clicked(p); + } + } + _update_bar = true; + _update_bar_visibility(); + } break; + case PANEL_CLOSE_RIGHT: { + _update_bar = false; + for (int i = _tabs->get_tab_count() - 1; i > tab_index; i--) { + _on_tab_close_pressed(i); + } + _update_bar = true; + _update_bar_visibility(); + } break; + case PANEL_CLOSE_ALL: { + _update_bar = false; + for (int i = _tabs->get_tab_count() - 1; i >= 0; i--) { + _on_tab_close_pressed(i); + } + _update_bar = true; + } break; + } +} + +void FindInFilesContainer::_bar_input(const Ref &p_input) { + int tab_id = _tabs->get_tab_bar()->get_hovered_tab(); + Ref mb = p_input; + + if (tab_id >= 0 && mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { + _tabs_context_menu->set_item_disabled(_tabs_context_menu->get_item_index(PANEL_CLOSE_RIGHT), tab_id == _tabs->get_tab_count() - 1); + _tabs_context_menu->set_position(_tabs->get_tab_bar()->get_screen_position() + mb->get_position()); + _tabs_context_menu->reset_size(); + _tabs_context_menu->popup(); + } +} diff --git a/editor/script/find_in_files.h b/editor/script/find_in_files.h index 74a4d56a342..95facd016f7 100644 --- a/editor/script/find_in_files.h +++ b/editor/script/find_in_files.h @@ -161,6 +161,7 @@ private: }; class Button; +class CheckButton; class Tree; class TreeItem; class ProgressBar; @@ -180,6 +181,8 @@ public: void set_with_replace(bool with_replace); void set_replace_text(const String &text); + bool is_keep_results() const; + void set_search_labels_visibility(bool p_visible); void start_search(); void stop_search(); @@ -223,9 +226,11 @@ private: void clear(); FindInFiles *_finder = nullptr; + Label *_find_label = nullptr; Label *_search_text_label = nullptr; Tree *_results_display = nullptr; Label *_status_label = nullptr; + CheckButton *_keep_results_button = nullptr; Button *_refresh_button = nullptr; Button *_cancel_button = nullptr; Button *_close_button = nullptr; @@ -239,3 +244,46 @@ private: LineEdit *_replace_line_edit = nullptr; Button *_replace_all_button = nullptr; }; + +class PopupMenu; +class TabContainer; + +// Contains several FindInFilesPanels. A FindInFilesPanel contains the results of a +// `Find in Files` search or a `Replace in Files` search, while a +// FindInFilesContainer can contain several FindInFilesPanels so that multiple search +// results can remain at the same time. +class FindInFilesContainer : public MarginContainer { + GDCLASS(FindInFilesContainer, MarginContainer); + + enum { + PANEL_CLOSE, + PANEL_CLOSE_OTHERS, + PANEL_CLOSE_RIGHT, + PANEL_CLOSE_ALL, + }; + + void _on_tab_close_pressed(int p_tab); + void _update_bar_visibility(); + void _bar_menu_option(int p_option); + void _bar_input(const Ref &p_input); + + TabContainer *_tabs = nullptr; + bool _update_bar = true; + PopupMenu *_tabs_context_menu = nullptr; + + FindInFilesPanel *_create_new_panel(); + FindInFilesPanel *_get_current_panel(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + + void _on_find_in_files_result_selected(const String &p_fpath, int p_line_number, int p_begin, int p_end); + void _on_find_in_files_modified_files(const PackedStringArray &p_paths); + void _on_find_in_files_close_button_clicked(FindInFilesPanel *p_panel); + +public: + FindInFilesContainer(); + + FindInFilesPanel *get_panel_for_results(const String &p_label); +}; diff --git a/editor/script/script_editor_plugin.cpp b/editor/script/script_editor_plugin.cpp index a76e475e3d5..5bfce094a9c 100644 --- a/editor/script/script_editor_plugin.cpp +++ b/editor/script/script_editor_plugin.cpp @@ -4079,7 +4079,8 @@ void ScriptEditor::_on_find_in_files_result_selected(const String &fpath, int li } void ScriptEditor::_start_find_in_files(bool with_replace) { - FindInFiles *f = find_in_files->get_finder(); + FindInFilesPanel *panel = find_in_files->get_panel_for_results(with_replace ? TTR("Replace:") + " " + find_in_files_dialog->get_search_text() : TTR("Find:") + " " + find_in_files_dialog->get_search_text()); + FindInFiles *f = panel->get_finder(); f->set_search_text(find_in_files_dialog->get_search_text()); f->set_match_case(find_in_files_dialog->is_match_case()); @@ -4089,9 +4090,9 @@ void ScriptEditor::_start_find_in_files(bool with_replace) { f->set_includes(find_in_files_dialog->get_includes()); f->set_excludes(find_in_files_dialog->get_excludes()); - find_in_files->set_with_replace(with_replace); - find_in_files->set_replace_text(find_in_files_dialog->get_replace_text()); - find_in_files->start_search(); + panel->set_with_replace(with_replace); + panel->set_replace_text(find_in_files_dialog->get_replace_text()); + panel->start_search(); EditorNode::get_bottom_panel()->move_item_to_end(find_in_files); find_in_files_button->show(); @@ -4490,12 +4491,12 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(false)); find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(true)); add_child(find_in_files_dialog); - find_in_files = memnew(FindInFilesPanel); + find_in_files = memnew(FindInFilesContainer); find_in_files_button = EditorNode::get_bottom_panel()->add_item(TTRC("Search Results"), find_in_files, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_search_results_bottom_panel", TTRC("Toggle Search Results Bottom Panel"))); find_in_files->set_custom_minimum_size(Size2(0, 200) * EDSCALE); - find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected)); - find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files)); - find_in_files->connect(FindInFilesPanel::SIGNAL_CLOSE_BUTTON_CLICKED, callable_mp(this, &ScriptEditor::_on_find_in_files_close_button_clicked)); + find_in_files->connect("result_selected", callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected)); + find_in_files->connect("files_modified", callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files)); + find_in_files->connect("close_button_clicked", callable_mp(this, &ScriptEditor::_on_find_in_files_close_button_clicked)); find_in_files->hide(); find_in_files_button->hide(); diff --git a/editor/script/script_editor_plugin.h b/editor/script/script_editor_plugin.h index 4c3aa9331ee..9980457e0c9 100644 --- a/editor/script/script_editor_plugin.h +++ b/editor/script/script_editor_plugin.h @@ -249,8 +249,8 @@ public: typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref &p_resource); class EditorScriptCodeCompletionCache; +class FindInFilesContainer; class FindInFilesDialog; -class FindInFilesPanel; class ScriptEditor : public PanelContainer { GDCLASS(ScriptEditor, PanelContainer); @@ -367,7 +367,7 @@ class ScriptEditor : public PanelContainer { Button *script_forward = nullptr; FindInFilesDialog *find_in_files_dialog = nullptr; - FindInFilesPanel *find_in_files = nullptr; + FindInFilesContainer *find_in_files = nullptr; Button *find_in_files_button = nullptr; WindowWrapper *window_wrapper = nullptr;