2018-02-12 01:36:15 +00:00
/**************************************************************************/
/* find_in_files.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
# include "find_in_files.h"
2018-11-05 06:51:34 +00:00
2022-02-12 01:46:22 +00:00
# include "core/config/project_settings.h"
2021-06-11 12:51:48 +00:00
# include "core/io/dir_access.h"
2018-02-12 01:36:15 +00:00
# include "core/os/os.h"
2022-02-12 01:46:22 +00:00
# include "editor/editor_node.h"
2023-08-13 00:33:39 +00:00
# include "editor/editor_string_names.h"
2024-01-15 12:14:55 +00:00
# include "editor/themes/editor_scale.h"
2018-02-12 01:36:15 +00:00
# include "scene/gui/box_container.h"
# include "scene/gui/button.h"
# include "scene/gui/check_box.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"
2018-08-18 23:36:44 +00:00
# include "scene/gui/tree.h"
2018-02-12 01:36:15 +00:00
const char * FindInFiles : : SIGNAL_RESULT_FOUND = " result_found " ;
2021-10-27 14:13:49 +00:00
// TODO: Would be nice in Vector and Vectors.
2018-02-12 01:36:15 +00:00
template < typename T >
inline void pop_back ( T & container ) {
container . resize ( container . size ( ) - 1 ) ;
}
2024-02-15 16:25:58 +00:00
static bool find_next ( const String & line , const String & pattern , int from , bool match_case , bool whole_words , int & out_begin , int & out_end ) {
2018-08-18 23:36:44 +00:00
int end = from ;
while ( true ) {
int begin = match_case ? line . find ( pattern , end ) : line . findn ( pattern , end ) ;
2020-05-14 14:41:43 +00:00
if ( begin = = - 1 ) {
2018-08-18 23:36:44 +00:00
return false ;
2020-05-14 14:41:43 +00:00
}
2018-08-18 23:36:44 +00:00
end = begin + pattern . length ( ) ;
out_begin = begin ;
out_end = end ;
if ( whole_words ) {
2022-02-04 08:32:20 +00:00
if ( begin > 0 & & ( is_ascii_identifier_char ( line [ begin - 1 ] ) ) ) {
2018-08-18 23:36:44 +00:00
continue ;
}
2022-02-04 08:32:20 +00:00
if ( end < line . size ( ) & & ( is_ascii_identifier_char ( line [ end ] ) ) ) {
2018-08-18 23:36:44 +00:00
continue ;
}
}
return true ;
}
}
//--------------------------------------------------------------------------------
2018-02-12 01:36:15 +00:00
2024-02-15 16:25:58 +00:00
void FindInFiles : : set_search_text ( const String & p_pattern ) {
2018-02-12 01:36:15 +00:00
_pattern = p_pattern ;
}
void FindInFiles : : set_whole_words ( bool p_whole_word ) {
_whole_words = p_whole_word ;
}
void FindInFiles : : set_match_case ( bool p_match_case ) {
_match_case = p_match_case ;
}
2024-02-15 16:25:58 +00:00
void FindInFiles : : set_folder ( const String & folder ) {
2018-02-12 01:36:15 +00:00
_root_dir = folder ;
}
2022-05-19 15:00:06 +00:00
void FindInFiles : : set_filter ( const HashSet < String > & exts ) {
2018-02-12 01:36:15 +00:00
_extension_filter = exts ;
}
2022-02-15 23:52:32 +00:00
void FindInFiles : : _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_PROCESS : {
_process ( ) ;
} break ;
2018-02-12 01:36:15 +00:00
}
}
void FindInFiles : : start ( ) {
2021-12-09 09:42:46 +00:00
if ( _pattern . is_empty ( ) ) {
2018-08-24 07:35:07 +00:00
print_verbose ( " Nothing to search, pattern is empty " ) ;
2024-05-13 14:56:03 +00:00
emit_signal ( SceneStringName ( finished ) ) ;
2018-02-12 01:36:15 +00:00
return ;
}
if ( _extension_filter . size ( ) = = 0 ) {
2018-08-24 07:35:07 +00:00
print_verbose ( " Nothing to search, filter matches no files " ) ;
2024-05-13 14:56:03 +00:00
emit_signal ( SceneStringName ( finished ) ) ;
2018-02-12 01:36:15 +00:00
return ;
}
2021-10-27 14:13:49 +00:00
// Init search.
2018-02-12 01:36:15 +00:00
_current_dir = " " ;
2020-02-17 21:06:54 +00:00
PackedStringArray init_folder ;
init_folder . push_back ( _root_dir ) ;
2018-05-23 22:59:39 +00:00
_folders_stack . clear ( ) ;
2018-02-12 01:36:15 +00:00
_folders_stack . push_back ( init_folder ) ;
_initial_files_count = 0 ;
_searching = true ;
set_process ( true ) ;
}
void FindInFiles : : stop ( ) {
_searching = false ;
_current_dir = " " ;
set_process ( false ) ;
}
void FindInFiles : : _process ( ) {
2021-10-27 14:13:49 +00:00
// This part can be moved to a thread if needed.
2018-02-12 01:36:15 +00:00
OS & os = * OS : : get_singleton ( ) ;
2021-10-26 06:44:50 +00:00
uint64_t time_before = os . get_ticks_msec ( ) ;
2018-05-23 22:59:39 +00:00
while ( is_processing ( ) ) {
2018-02-12 01:36:15 +00:00
_iterate ( ) ;
2021-10-26 06:44:50 +00:00
uint64_t elapsed = ( os . get_ticks_msec ( ) - time_before ) ;
2021-10-27 14:13:49 +00:00
if ( elapsed > 8 ) { // Process again after waiting 8 ticks.
2018-05-23 22:59:39 +00:00
break ;
2020-05-14 14:41:43 +00:00
}
2018-02-12 01:36:15 +00:00
}
}
void FindInFiles : : _iterate ( ) {
if ( _folders_stack . size ( ) ! = 0 ) {
2021-10-27 14:13:49 +00:00
// Scan folders first so we can build a list of files and have progress info later.
2018-02-12 01:36:15 +00:00
2020-02-17 21:06:54 +00:00
PackedStringArray & folders_to_scan = _folders_stack . write [ _folders_stack . size ( ) - 1 ] ;
2018-02-12 01:36:15 +00:00
if ( folders_to_scan . size ( ) ! = 0 ) {
2021-10-27 14:13:49 +00:00
// Scan one folder below.
2018-02-12 01:36:15 +00:00
String folder_name = folders_to_scan [ folders_to_scan . size ( ) - 1 ] ;
pop_back ( folders_to_scan ) ;
2022-08-30 00:34:01 +00:00
_current_dir = _current_dir . path_join ( folder_name ) ;
2018-02-12 01:36:15 +00:00
2020-02-17 21:06:54 +00:00
PackedStringArray sub_dirs ;
2023-12-08 22:24:36 +00:00
PackedStringArray files_to_scan ;
_scan_dir ( " res:// " + _current_dir , sub_dirs , files_to_scan ) ;
2018-02-12 01:36:15 +00:00
2018-04-21 15:19:25 +00:00
_folders_stack . push_back ( sub_dirs ) ;
2023-12-08 22:24:36 +00:00
_files_to_scan . append_array ( files_to_scan ) ;
2018-02-12 01:36:15 +00:00
} else {
2021-10-27 14:13:49 +00:00
// Go back one level.
2018-02-12 01:36:15 +00:00
pop_back ( _folders_stack ) ;
_current_dir = _current_dir . get_base_dir ( ) ;
if ( _folders_stack . size ( ) = = 0 ) {
2021-10-27 14:13:49 +00:00
// All folders scanned.
2018-02-12 01:36:15 +00:00
_initial_files_count = _files_to_scan . size ( ) ;
}
}
} else if ( _files_to_scan . size ( ) ! = 0 ) {
2021-10-27 14:13:49 +00:00
// Then scan files.
2018-02-12 01:36:15 +00:00
String fpath = _files_to_scan [ _files_to_scan . size ( ) - 1 ] ;
pop_back ( _files_to_scan ) ;
2018-04-21 15:19:25 +00:00
_scan_file ( fpath ) ;
2018-02-12 01:36:15 +00:00
} else {
2018-08-24 07:35:07 +00:00
print_verbose ( " Search complete " ) ;
2018-02-12 01:36:15 +00:00
set_process ( false ) ;
_current_dir = " " ;
_searching = false ;
2024-05-13 14:56:03 +00:00
emit_signal ( SceneStringName ( finished ) ) ;
2018-02-12 01:36:15 +00:00
}
}
float FindInFiles : : get_progress ( ) const {
if ( _initial_files_count ! = 0 ) {
return static_cast < float > ( _initial_files_count - _files_to_scan . size ( ) ) / static_cast < float > ( _initial_files_count ) ;
}
return 0 ;
}
2024-02-15 16:25:58 +00:00
void FindInFiles : : _scan_dir ( const String & path , PackedStringArray & out_folders , PackedStringArray & out_files_to_scan ) {
2022-03-23 09:08:58 +00:00
Ref < DirAccess > dir = DirAccess : : open ( path ) ;
if ( dir . is_null ( ) ) {
2018-08-24 07:35:07 +00:00
print_verbose ( " Cannot open directory! " + path ) ;
2018-02-12 01:36:15 +00:00
return ;
}
dir - > list_dir_begin ( ) ;
2024-04-29 23:01:28 +00:00
// Limit to 100,000 iterations to avoid an infinite loop just in case
// (this technically limits results to 100,000 files per folder).
for ( int i = 0 ; i < 100'000 ; + + i ) {
2018-02-12 01:36:15 +00:00
String file = dir - > get_next ( ) ;
2021-12-09 09:42:46 +00:00
if ( file . is_empty ( ) ) {
2018-02-12 01:36:15 +00:00
break ;
2020-05-14 14:41:43 +00:00
}
2018-02-12 01:36:15 +00:00
2023-12-08 22:24:36 +00:00
// If there is a .gdignore file in the directory, clear all the files/folders
// to be searched on this path and skip searching the directory.
2021-07-30 19:10:05 +00:00
if ( file = = " .gdignore " ) {
2023-12-08 22:24:36 +00:00
out_folders . clear ( ) ;
out_files_to_scan . clear ( ) ;
2021-07-30 19:10:05 +00:00
break ;
}
2021-10-27 14:13:49 +00:00
// Ignore special directories (such as those beginning with . and the project data directory).
2021-09-10 15:32:29 +00:00
String project_data_dir_name = ProjectSettings : : get_singleton ( ) - > get_project_data_dir_name ( ) ;
2021-10-13 20:56:18 +00:00
if ( file . begins_with ( " . " ) | | file = = project_data_dir_name ) {
2018-02-12 01:36:15 +00:00
continue ;
2020-05-14 14:41:43 +00:00
}
if ( dir - > current_is_hidden ( ) ) {
2020-02-10 08:19:02 +00:00
continue ;
2020-05-14 14:41:43 +00:00
}
2018-02-12 01:36:15 +00:00
2020-05-14 14:41:43 +00:00
if ( dir - > current_is_dir ( ) ) {
2020-02-17 21:06:54 +00:00
out_folders . push_back ( file ) ;
2018-02-12 01:36:15 +00:00
2020-05-14 14:41:43 +00:00
} else {
2018-02-12 01:36:15 +00:00
String file_ext = file . get_extension ( ) ;
if ( _extension_filter . has ( file_ext ) ) {
2023-12-08 22:24:36 +00:00
out_files_to_scan . push_back ( path . path_join ( file ) ) ;
2018-02-12 01:36:15 +00:00
}
}
}
}
2024-02-15 16:25:58 +00:00
void FindInFiles : : _scan_file ( const String & fpath ) {
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( fpath , FileAccess : : READ ) ;
if ( f . is_null ( ) ) {
2018-08-24 07:35:07 +00:00
print_verbose ( String ( " Cannot open file " ) + fpath ) ;
2018-02-12 01:36:15 +00:00
return ;
}
int line_number = 0 ;
while ( ! f - > eof_reached ( ) ) {
2021-10-27 14:13:49 +00:00
// Line number starts at 1.
2018-02-12 01:36:15 +00:00
+ + line_number ;
int begin = 0 ;
int end = 0 ;
String line = f - > get_line ( ) ;
2018-08-18 23:36:44 +00:00
while ( find_next ( line , _pattern , end , _match_case , _whole_words , begin , end ) ) {
2021-07-17 21:22:52 +00:00
emit_signal ( SNAME ( SIGNAL_RESULT_FOUND ) , fpath , line_number , begin , end , line ) ;
2018-02-12 01:36:15 +00:00
}
}
}
void FindInFiles : : _bind_methods ( ) {
ADD_SIGNAL ( MethodInfo ( SIGNAL_RESULT_FOUND ,
PropertyInfo ( Variant : : STRING , " path " ) ,
PropertyInfo ( Variant : : INT , " line_number " ) ,
PropertyInfo ( Variant : : INT , " begin " ) ,
PropertyInfo ( Variant : : INT , " end " ) ,
PropertyInfo ( Variant : : STRING , " text " ) ) ) ;
2024-05-13 14:56:03 +00:00
ADD_SIGNAL ( MethodInfo ( " finished " ) ) ;
2018-02-12 01:36:15 +00:00
}
//-----------------------------------------------------------------------------
const char * FindInFilesDialog : : SIGNAL_FIND_REQUESTED = " find_requested " ;
const char * FindInFilesDialog : : SIGNAL_REPLACE_REQUESTED = " replace_requested " ;
FindInFilesDialog : : FindInFilesDialog ( ) {
2020-03-06 17:00:16 +00:00
set_min_size ( Size2 ( 500 * EDSCALE , 0 ) ) ;
2018-09-25 20:44:50 +00:00
set_title ( TTR ( " Find in Files " ) ) ;
2018-02-12 01:36:15 +00:00
VBoxContainer * vbc = memnew ( VBoxContainer ) ;
2020-12-22 16:24:29 +00:00
vbc - > set_anchor_and_offset ( SIDE_LEFT , Control : : ANCHOR_BEGIN , 8 * EDSCALE ) ;
vbc - > set_anchor_and_offset ( SIDE_TOP , Control : : ANCHOR_BEGIN , 8 * EDSCALE ) ;
vbc - > set_anchor_and_offset ( SIDE_RIGHT , Control : : ANCHOR_END , - 8 * EDSCALE ) ;
vbc - > set_anchor_and_offset ( SIDE_BOTTOM , Control : : ANCHOR_END , - 8 * EDSCALE ) ;
2018-02-12 01:36:15 +00:00
add_child ( vbc ) ;
GridContainer * gc = memnew ( GridContainer ) ;
gc - > set_columns ( 2 ) ;
vbc - > add_child ( gc ) ;
Label * find_label = memnew ( Label ) ;
2018-11-05 06:51:34 +00:00
find_label - > set_text ( TTR ( " Find: " ) ) ;
2018-02-12 01:36:15 +00:00
gc - > add_child ( find_label ) ;
_search_text_line_edit = memnew ( LineEdit ) ;
2020-03-06 17:00:16 +00:00
_search_text_line_edit - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
2020-02-21 17:28:45 +00:00
_search_text_line_edit - > connect ( " text_changed " , callable_mp ( this , & FindInFilesDialog : : _on_search_text_modified ) ) ;
2021-06-16 16:43:34 +00:00
_search_text_line_edit - > connect ( " text_submitted " , callable_mp ( this , & FindInFilesDialog : : _on_search_text_submitted ) ) ;
2018-02-12 01:36:15 +00:00
gc - > add_child ( _search_text_line_edit ) ;
2020-02-09 09:10:58 +00:00
_replace_label = memnew ( Label ) ;
_replace_label - > set_text ( TTR ( " Replace: " ) ) ;
_replace_label - > hide ( ) ;
gc - > add_child ( _replace_label ) ;
_replace_text_line_edit = memnew ( LineEdit ) ;
2020-03-06 17:00:16 +00:00
_replace_text_line_edit - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
2021-06-16 16:43:34 +00:00
_replace_text_line_edit - > connect ( " text_submitted " , callable_mp ( this , & FindInFilesDialog : : _on_replace_text_submitted ) ) ;
2020-02-09 09:10:58 +00:00
_replace_text_line_edit - > hide ( ) ;
gc - > add_child ( _replace_text_line_edit ) ;
2021-10-27 14:13:49 +00:00
gc - > add_child ( memnew ( Control ) ) ; // Space to maintain the grid alignment.
2018-02-12 01:36:15 +00:00
{
HBoxContainer * hbc = memnew ( HBoxContainer ) ;
_whole_words_checkbox = memnew ( CheckBox ) ;
2018-10-03 20:43:26 +00:00
_whole_words_checkbox - > set_text ( TTR ( " Whole Words " ) ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( _whole_words_checkbox ) ;
_match_case_checkbox = memnew ( CheckBox ) ;
2018-10-03 20:43:26 +00:00
_match_case_checkbox - > set_text ( TTR ( " Match Case " ) ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( _match_case_checkbox ) ;
gc - > add_child ( hbc ) ;
}
Label * folder_label = memnew ( Label ) ;
2018-11-05 06:51:34 +00:00
folder_label - > set_text ( TTR ( " Folder: " ) ) ;
2018-02-12 01:36:15 +00:00
gc - > add_child ( folder_label ) ;
{
HBoxContainer * hbc = memnew ( HBoxContainer ) ;
Label * prefix_label = memnew ( Label ) ;
2018-10-27 15:24:41 +00:00
prefix_label - > set_text ( " res:// " ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( prefix_label ) ;
_folder_line_edit = memnew ( LineEdit ) ;
2020-03-06 17:00:16 +00:00
_folder_line_edit - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( _folder_line_edit ) ;
Button * folder_button = memnew ( Button ) ;
folder_button - > set_text ( " ... " ) ;
2024-05-14 07:40:21 +00:00
folder_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & FindInFilesDialog : : _on_folder_button_pressed ) ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( folder_button ) ;
_folder_dialog = memnew ( FileDialog ) ;
2020-03-06 17:00:16 +00:00
_folder_dialog - > set_file_mode ( FileDialog : : FILE_MODE_OPEN_DIR ) ;
2020-02-21 17:28:45 +00:00
_folder_dialog - > connect ( " dir_selected " , callable_mp ( this , & FindInFilesDialog : : _on_folder_selected ) ) ;
2018-02-12 01:36:15 +00:00
add_child ( _folder_dialog ) ;
gc - > add_child ( hbc ) ;
}
Label * filter_label = memnew ( Label ) ;
2018-11-05 06:51:34 +00:00
filter_label - > set_text ( TTR ( " Filters: " ) ) ;
2022-08-25 10:42:17 +00:00
filter_label - > set_tooltip_text ( TTR ( " Include the files with the following extensions. Add or remove them in ProjectSettings. " ) ) ;
2018-02-12 01:36:15 +00:00
gc - > add_child ( filter_label ) ;
2019-02-04 21:59:51 +00:00
_filters_container = memnew ( HBoxContainer ) ;
gc - > add_child ( _filters_container ) ;
2018-02-12 01:36:15 +00:00
2018-11-05 06:51:34 +00:00
_find_button = add_button ( TTR ( " Find... " ) , false , " find " ) ;
_find_button - > set_disabled ( true ) ;
2018-02-12 01:36:15 +00:00
2018-11-05 06:51:34 +00:00
_replace_button = add_button ( TTR ( " Replace... " ) , false , " replace " ) ;
_replace_button - > set_disabled ( true ) ;
2018-02-12 01:36:15 +00:00
2020-12-14 18:37:30 +00:00
Button * cancel_button = get_ok_button ( ) ;
2018-11-05 06:51:34 +00:00
cancel_button - > set_text ( TTR ( " Cancel " ) ) ;
2020-02-09 09:10:58 +00:00
_mode = SEARCH_MODE ;
2018-02-12 01:36:15 +00:00
}
2024-02-15 16:25:58 +00:00
void FindInFilesDialog : : set_search_text ( const String & text ) {
2024-03-02 15:21:15 +00:00
if ( _mode = = SEARCH_MODE ) {
if ( ! text . is_empty ( ) ) {
_search_text_line_edit - > set_text ( text ) ;
_on_search_text_modified ( text ) ;
}
callable_mp ( ( Control * ) _search_text_line_edit , & Control : : grab_focus ) . call_deferred ( ) ;
_search_text_line_edit - > select_all ( ) ;
} else if ( _mode = = REPLACE_MODE ) {
if ( ! text . is_empty ( ) ) {
_search_text_line_edit - > set_text ( text ) ;
callable_mp ( ( Control * ) _replace_text_line_edit , & Control : : grab_focus ) . call_deferred ( ) ;
_replace_text_line_edit - > select_all ( ) ;
_on_search_text_modified ( text ) ;
} else {
callable_mp ( ( Control * ) _search_text_line_edit , & Control : : grab_focus ) . call_deferred ( ) ;
_search_text_line_edit - > select_all ( ) ;
}
}
2018-02-12 01:36:15 +00:00
}
2024-02-15 16:25:58 +00:00
void FindInFilesDialog : : set_replace_text ( const String & text ) {
2020-02-09 09:10:58 +00:00
_replace_text_line_edit - > set_text ( text ) ;
}
void FindInFilesDialog : : set_find_in_files_mode ( FindInFilesMode p_mode ) {
2020-05-14 14:41:43 +00:00
if ( _mode = = p_mode ) {
2020-02-09 09:10:58 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2020-02-09 09:10:58 +00:00
_mode = p_mode ;
if ( p_mode = = SEARCH_MODE ) {
set_title ( TTR ( " Find in Files " ) ) ;
_replace_label - > hide ( ) ;
_replace_text_line_edit - > hide ( ) ;
} else if ( p_mode = = REPLACE_MODE ) {
set_title ( TTR ( " Replace in Files " ) ) ;
_replace_label - > show ( ) ;
_replace_text_line_edit - > show ( ) ;
}
2021-10-27 14:13:49 +00:00
// Recalculate the dialog size after hiding child controls.
2020-02-09 09:10:58 +00:00
set_size ( Size2 ( get_size ( ) . x , 0 ) ) ;
}
2018-02-12 01:36:15 +00:00
String FindInFilesDialog : : get_search_text ( ) const {
2022-09-06 21:28:16 +00:00
return _search_text_line_edit - > get_text ( ) ;
2018-02-12 01:36:15 +00:00
}
2020-02-09 09:10:58 +00:00
String FindInFilesDialog : : get_replace_text ( ) const {
return _replace_text_line_edit - > get_text ( ) ;
}
2018-02-12 01:36:15 +00:00
bool FindInFilesDialog : : is_match_case ( ) const {
return _match_case_checkbox - > is_pressed ( ) ;
}
bool FindInFilesDialog : : is_whole_words ( ) const {
return _whole_words_checkbox - > is_pressed ( ) ;
}
String FindInFilesDialog : : get_folder ( ) const {
String text = _folder_line_edit - > get_text ( ) ;
return text . strip_edges ( ) ;
}
2022-05-19 15:00:06 +00:00
HashSet < String > FindInFilesDialog : : get_filter ( ) const {
2021-10-27 14:13:49 +00:00
// Could check the _filters_preferences but it might not have been generated yet.
2022-05-19 15:00:06 +00:00
HashSet < String > filters ;
2019-02-04 21:59:51 +00:00
for ( int i = 0 ; i < _filters_container - > get_child_count ( ) ; + + i ) {
2022-04-05 10:40:26 +00:00
CheckBox * cb = static_cast < CheckBox * > ( _filters_container - > get_child ( i ) ) ;
2018-02-12 01:36:15 +00:00
if ( cb - > is_pressed ( ) ) {
2019-02-04 21:59:51 +00:00
filters . insert ( cb - > get_text ( ) ) ;
2018-02-12 01:36:15 +00:00
}
}
return filters ;
}
void FindInFilesDialog : : _notification ( int p_what ) {
2022-02-15 23:52:32 +00:00
switch ( p_what ) {
case NOTIFICATION_VISIBILITY_CHANGED : {
if ( is_visible ( ) ) {
// Extensions might have changed in the meantime, we clean them and instance them again.
for ( int i = 0 ; i < _filters_container - > get_child_count ( ) ; i + + ) {
2022-10-24 21:07:02 +00:00
_filters_container - > get_child ( i ) - > queue_free ( ) ;
2022-02-15 23:52:32 +00:00
}
2022-10-18 14:43:37 +00:00
Array exts = GLOBAL_GET ( " editor/script/search_in_file_extensions " ) ;
2022-02-15 23:52:32 +00:00
for ( int i = 0 ; i < exts . size ( ) ; + + i ) {
CheckBox * cb = memnew ( CheckBox ) ;
cb - > set_text ( exts [ i ] ) ;
if ( ! _filters_preferences . has ( exts [ i ] ) ) {
_filters_preferences [ exts [ i ] ] = true ;
}
cb - > set_pressed ( _filters_preferences [ exts [ i ] ] ) ;
_filters_container - > add_child ( cb ) ;
2019-02-04 21:59:51 +00:00
}
}
2022-02-15 23:52:32 +00:00
} break ;
2018-02-12 01:36:15 +00:00
}
}
void FindInFilesDialog : : _on_folder_button_pressed ( ) {
2020-07-11 16:45:19 +00:00
_folder_dialog - > popup_file_dialog ( ) ;
2018-02-12 01:36:15 +00:00
}
2018-09-18 14:10:36 +00:00
void FindInFilesDialog : : custom_action ( const String & p_action ) {
2019-02-04 21:59:51 +00:00
for ( int i = 0 ; i < _filters_container - > get_child_count ( ) ; + + i ) {
2022-04-05 10:40:26 +00:00
CheckBox * cb = static_cast < CheckBox * > ( _filters_container - > get_child ( i ) ) ;
2019-02-04 21:59:51 +00:00
_filters_preferences [ cb - > get_text ( ) ] = cb - > is_pressed ( ) ;
}
2021-10-27 14:13:49 +00:00
2018-09-18 14:10:36 +00:00
if ( p_action = = " find " ) {
2021-07-17 21:22:52 +00:00
emit_signal ( SNAME ( SIGNAL_FIND_REQUESTED ) ) ;
2018-09-18 14:10:36 +00:00
hide ( ) ;
} else if ( p_action = = " replace " ) {
2021-07-17 21:22:52 +00:00
emit_signal ( SNAME ( SIGNAL_REPLACE_REQUESTED ) ) ;
2018-09-18 14:10:36 +00:00
hide ( ) ;
}
2018-02-12 01:36:15 +00:00
}
2024-02-15 16:25:58 +00:00
void FindInFilesDialog : : _on_search_text_modified ( const String & text ) {
2023-09-09 15:24:40 +00:00
ERR_FAIL_NULL ( _find_button ) ;
ERR_FAIL_NULL ( _replace_button ) ;
2018-02-12 01:36:15 +00:00
2020-12-15 12:04:21 +00:00
_find_button - > set_disabled ( get_search_text ( ) . is_empty ( ) ) ;
_replace_button - > set_disabled ( get_search_text ( ) . is_empty ( ) ) ;
2018-02-12 01:36:15 +00:00
}
2024-02-15 16:25:58 +00:00
void FindInFilesDialog : : _on_search_text_submitted ( const String & text ) {
2021-10-27 14:13:49 +00:00
// This allows to trigger a global search without leaving the keyboard.
2020-02-09 09:10:58 +00:00
if ( ! _find_button - > is_disabled ( ) ) {
if ( _mode = = SEARCH_MODE ) {
custom_action ( " find " ) ;
}
}
if ( ! _replace_button - > is_disabled ( ) ) {
if ( _mode = = REPLACE_MODE ) {
custom_action ( " replace " ) ;
}
}
}
2024-02-15 16:25:58 +00:00
void FindInFilesDialog : : _on_replace_text_submitted ( const String & text ) {
2021-10-27 14:13:49 +00:00
// This allows to trigger a global search without leaving the keyboard.
2020-02-09 09:10:58 +00:00
if ( ! _replace_button - > is_disabled ( ) ) {
if ( _mode = = REPLACE_MODE ) {
custom_action ( " replace " ) ;
}
}
2018-02-12 01:36:15 +00:00
}
void FindInFilesDialog : : _on_folder_selected ( String path ) {
int i = path . find ( " :// " ) ;
2020-05-14 14:41:43 +00:00
if ( i ! = - 1 ) {
2020-02-13 15:42:49 +00:00
path = path . substr ( i + 3 ) ;
2020-05-14 14:41:43 +00:00
}
2018-02-12 01:36:15 +00:00
_folder_line_edit - > set_text ( path ) ;
}
void FindInFilesDialog : : _bind_methods ( ) {
ADD_SIGNAL ( MethodInfo ( SIGNAL_FIND_REQUESTED ) ) ;
ADD_SIGNAL ( MethodInfo ( SIGNAL_REPLACE_REQUESTED ) ) ;
}
//-----------------------------------------------------------------------------
const char * FindInFilesPanel : : SIGNAL_RESULT_SELECTED = " result_selected " ;
const char * FindInFilesPanel : : SIGNAL_FILES_MODIFIED = " files_modified " ;
2024-02-25 14:40:39 +00:00
const char * FindInFilesPanel : : SIGNAL_CLOSE_BUTTON_CLICKED = " close_button_clicked " ;
2018-02-12 01:36:15 +00:00
FindInFilesPanel : : FindInFilesPanel ( ) {
_finder = memnew ( FindInFiles ) ;
2020-02-21 22:26:13 +00:00
_finder - > connect ( FindInFiles : : SIGNAL_RESULT_FOUND , callable_mp ( this , & FindInFilesPanel : : _on_result_found ) ) ;
2024-05-13 14:56:03 +00:00
_finder - > connect ( SceneStringName ( finished ) , callable_mp ( this , & FindInFilesPanel : : _on_finished ) ) ;
2018-02-12 01:36:15 +00:00
add_child ( _finder ) ;
VBoxContainer * vbc = memnew ( VBoxContainer ) ;
2020-12-22 16:24:29 +00:00
vbc - > set_anchor_and_offset ( SIDE_LEFT , ANCHOR_BEGIN , 0 ) ;
vbc - > set_anchor_and_offset ( SIDE_TOP , ANCHOR_BEGIN , 0 ) ;
vbc - > set_anchor_and_offset ( SIDE_RIGHT , ANCHOR_END , 0 ) ;
vbc - > set_anchor_and_offset ( SIDE_BOTTOM , ANCHOR_END , 0 ) ;
2018-02-12 01:36:15 +00:00
add_child ( vbc ) ;
{
HBoxContainer * hbc = memnew ( HBoxContainer ) ;
Label * find_label = memnew ( Label ) ;
2022-06-08 09:42:51 +00:00
find_label - > set_text ( TTR ( " Find: " ) ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( find_label ) ;
_search_text_label = memnew ( Label ) ;
hbc - > add_child ( _search_text_label ) ;
_progress_bar = memnew ( ProgressBar ) ;
_progress_bar - > set_h_size_flags ( SIZE_EXPAND_FILL ) ;
2019-08-08 02:06:33 +00:00
_progress_bar - > set_v_size_flags ( SIZE_SHRINK_CENTER ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( _progress_bar ) ;
set_progress_visible ( false ) ;
_status_label = memnew ( Label ) ;
hbc - > add_child ( _status_label ) ;
2020-02-10 13:04:15 +00:00
_refresh_button = memnew ( Button ) ;
_refresh_button - > set_text ( TTR ( " Refresh " ) ) ;
2024-05-14 07:40:21 +00:00
_refresh_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & FindInFilesPanel : : _on_refresh_button_clicked ) ) ;
2020-02-10 13:04:15 +00:00
_refresh_button - > hide ( ) ;
hbc - > add_child ( _refresh_button ) ;
2018-02-12 01:36:15 +00:00
_cancel_button = memnew ( Button ) ;
_cancel_button - > set_text ( TTR ( " Cancel " ) ) ;
2024-05-14 07:40:21 +00:00
_cancel_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & FindInFilesPanel : : _on_cancel_button_clicked ) ) ;
2018-10-08 21:57:20 +00:00
_cancel_button - > hide ( ) ;
2018-02-12 01:36:15 +00:00
hbc - > add_child ( _cancel_button ) ;
2024-02-25 14:40:39 +00:00
_close_button = memnew ( Button ) ;
_close_button - > set_text ( TTR ( " Close " ) ) ;
2024-05-14 07:40:21 +00:00
_close_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & FindInFilesPanel : : _on_close_button_clicked ) ) ;
2024-02-25 14:40:39 +00:00
hbc - > add_child ( _close_button ) ;
2018-02-12 01:36:15 +00:00
vbc - > add_child ( hbc ) ;
}
2018-08-18 23:36:44 +00:00
_results_display = memnew ( Tree ) ;
2024-03-17 08:28:18 +00:00
_results_display - > set_auto_translate_mode ( AUTO_TRANSLATE_MODE_DISABLED ) ;
2018-02-12 01:36:15 +00:00
_results_display - > set_v_size_flags ( SIZE_EXPAND_FILL ) ;
2020-02-21 17:28:45 +00:00
_results_display - > connect ( " item_selected " , callable_mp ( this , & FindInFilesPanel : : _on_result_selected ) ) ;
_results_display - > connect ( " item_edited " , callable_mp ( this , & FindInFilesPanel : : _on_item_edited ) ) ;
2018-08-18 23:36:44 +00:00
_results_display - > set_hide_root ( true ) ;
_results_display - > set_select_mode ( Tree : : SELECT_ROW ) ;
2019-08-12 08:56:19 +00:00
_results_display - > set_allow_rmb_select ( true ) ;
2022-09-29 01:21:48 +00:00
_results_display - > set_allow_reselect ( true ) ;
2023-10-10 12:04:14 +00:00
_results_display - > add_theme_constant_override ( " inner_item_margin_left " , 0 ) ;
_results_display - > add_theme_constant_override ( " inner_item_margin_right " , 0 ) ;
2018-08-18 23:36:44 +00:00
_results_display - > create_item ( ) ; // Root
2018-02-12 01:36:15 +00:00
vbc - > add_child ( _results_display ) ;
{
_replace_container = memnew ( HBoxContainer ) ;
Label * replace_label = memnew ( Label ) ;
2022-06-08 09:42:51 +00:00
replace_label - > set_text ( TTR ( " Replace: " ) ) ;
2018-02-12 01:36:15 +00:00
_replace_container - > add_child ( replace_label ) ;
_replace_line_edit = memnew ( LineEdit ) ;
_replace_line_edit - > set_h_size_flags ( SIZE_EXPAND_FILL ) ;
2020-02-21 17:28:45 +00:00
_replace_line_edit - > connect ( " text_changed " , callable_mp ( this , & FindInFilesPanel : : _on_replace_text_changed ) ) ;
2018-02-12 01:36:15 +00:00
_replace_container - > add_child ( _replace_line_edit ) ;
_replace_all_button = memnew ( Button ) ;
_replace_all_button - > set_text ( TTR ( " Replace all (no undo) " ) ) ;
2024-05-14 07:40:21 +00:00
_replace_all_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & FindInFilesPanel : : _on_replace_all_clicked ) ) ;
2018-02-12 01:36:15 +00:00
_replace_container - > add_child ( _replace_all_button ) ;
_replace_container - > hide ( ) ;
vbc - > add_child ( _replace_container ) ;
}
}
void FindInFilesPanel : : set_with_replace ( bool with_replace ) {
2018-08-18 23:36:44 +00:00
_with_replace = with_replace ;
2018-02-12 01:36:15 +00:00
_replace_container - > set_visible ( with_replace ) ;
2018-08-18 23:36:44 +00:00
if ( with_replace ) {
2021-10-27 14:13:49 +00:00
// Results show checkboxes on their left so they can be opted out.
2018-08-18 23:36:44 +00:00
_results_display - > set_columns ( 2 ) ;
_results_display - > set_column_expand ( 0 , false ) ;
2021-06-28 13:40:56 +00:00
_results_display - > set_column_custom_minimum_width ( 0 , 48 * EDSCALE ) ;
2018-08-18 23:36:44 +00:00
} else {
2021-10-27 14:13:49 +00:00
// Results are single-cell items.
2018-08-18 23:36:44 +00:00
_results_display - > set_column_expand ( 0 , true ) ;
_results_display - > set_columns ( 1 ) ;
}
}
2024-02-15 16:25:58 +00:00
void FindInFilesPanel : : set_replace_text ( const String & text ) {
2020-02-09 09:10:58 +00:00
_replace_line_edit - > set_text ( text ) ;
}
2018-08-18 23:36:44 +00:00
void FindInFilesPanel : : clear ( ) {
_file_items . clear ( ) ;
_result_items . clear ( ) ;
_results_display - > clear ( ) ;
_results_display - > create_item ( ) ; // Root
2018-02-12 01:36:15 +00:00
}
void FindInFilesPanel : : start_search ( ) {
2018-08-18 23:36:44 +00:00
clear ( ) ;
2018-02-12 01:36:15 +00:00
_status_label - > set_text ( TTR ( " Searching... " ) ) ;
_search_text_label - > set_text ( _finder - > get_search_text ( ) ) ;
set_process ( true ) ;
set_progress_visible ( true ) ;
_finder - > start ( ) ;
update_replace_buttons ( ) ;
2020-02-10 13:04:15 +00:00
_refresh_button - > hide ( ) ;
2018-10-08 21:57:20 +00:00
_cancel_button - > show ( ) ;
2018-02-12 01:36:15 +00:00
}
void FindInFilesPanel : : stop_search ( ) {
_finder - > stop ( ) ;
_status_label - > set_text ( " " ) ;
update_replace_buttons ( ) ;
set_progress_visible ( false ) ;
2020-02-10 13:04:15 +00:00
_refresh_button - > show ( ) ;
2018-10-08 21:57:20 +00:00
_cancel_button - > hide ( ) ;
2018-02-12 01:36:15 +00:00
}
void FindInFilesPanel : : _notification ( int p_what ) {
2022-02-15 23:52:32 +00:00
switch ( p_what ) {
case NOTIFICATION_THEME_CHANGED : {
2024-05-14 13:57:29 +00:00
_search_text_label - > add_theme_font_override ( SceneStringName ( font ) , get_theme_font ( SNAME ( " source " ) , EditorStringName ( EditorFonts ) ) ) ;
_search_text_label - > add_theme_font_size_override ( SceneStringName ( font_size ) , get_theme_font_size ( SNAME ( " source_size " ) , EditorStringName ( EditorFonts ) ) ) ;
_results_display - > add_theme_font_override ( SceneStringName ( font ) , get_theme_font ( SNAME ( " source " ) , EditorStringName ( EditorFonts ) ) ) ;
_results_display - > add_theme_font_size_override ( SceneStringName ( font_size ) , get_theme_font_size ( SNAME ( " source_size " ) , EditorStringName ( EditorFonts ) ) ) ;
2022-12-21 17:15:40 +00:00
// Rebuild search tree.
if ( ! _finder - > get_search_text ( ) . is_empty ( ) ) {
start_search ( ) ;
}
} break ;
case NOTIFICATION_PROCESS : {
_progress_bar - > set_as_ratio ( _finder - > get_progress ( ) ) ;
2022-02-15 23:52:32 +00:00
} break ;
2018-02-12 01:36:15 +00:00
}
}
2024-02-15 16:25:58 +00:00
void FindInFilesPanel : : _on_result_found ( const String & fpath , int line_number , int begin , int end , String text ) {
2018-08-18 23:36:44 +00:00
TreeItem * file_item ;
2022-05-13 13:04:37 +00:00
HashMap < String , TreeItem * > : : Iterator E = _file_items . find ( fpath ) ;
2018-08-18 23:36:44 +00:00
2022-05-13 13:04:37 +00:00
if ( ! E ) {
2018-08-18 23:36:44 +00:00
file_item = _results_display - > create_item ( ) ;
file_item - > set_text ( 0 , fpath ) ;
file_item - > set_metadata ( 0 , fpath ) ;
2021-10-27 14:13:49 +00:00
// The width of this column is restrained to checkboxes,
// but that doesn't make sense for the parent items,
// so we override their width so they can expand to full width.
2018-08-18 23:36:44 +00:00
file_item - > set_expand_right ( 0 , true ) ;
_file_items [ fpath ] = file_item ;
} else {
2022-05-13 13:04:37 +00:00
file_item = E - > value ;
2018-08-18 23:36:44 +00:00
}
2024-05-14 13:57:29 +00:00
Color file_item_color = _results_display - > get_theme_color ( SceneStringName ( font_color ) ) * Color ( 1 , 1 , 1 , 0.67 ) ;
2022-09-29 01:21:48 +00:00
file_item - > set_custom_color ( 0 , file_item_color ) ;
file_item - > set_selectable ( 0 , false ) ;
2018-08-18 23:36:44 +00:00
int text_index = _with_replace ? 1 : 0 ;
TreeItem * item = _results_display - > create_item ( file_item ) ;
// Do this first because it resets properties of the cell...
item - > set_cell_mode ( text_index , TreeItem : : CELL_MODE_CUSTOM ) ;
2021-10-27 14:13:49 +00:00
// Trim result item line.
2020-06-16 22:53:23 +00:00
int old_text_size = text . size ( ) ;
text = text . strip_edges ( true , false ) ;
int chars_removed = old_text_size - text . size ( ) ;
String start = vformat ( " %3s: " , line_number ) ;
2018-08-18 23:36:44 +00:00
2020-06-16 22:53:23 +00:00
item - > set_text ( text_index , start + text ) ;
2024-01-20 16:55:34 +00:00
item - > set_custom_draw_callback ( text_index , callable_mp ( this , & FindInFilesPanel : : draw_result_text ) ) ;
2018-08-18 23:36:44 +00:00
Result r ;
r . line_number = line_number ;
r . begin = begin ;
r . end = end ;
2020-06-16 22:53:23 +00:00
r . begin_trimmed = begin - chars_removed + start . size ( ) - 1 ;
2018-08-18 23:36:44 +00:00
_result_items [ item ] = r ;
if ( _with_replace ) {
item - > set_cell_mode ( 0 , TreeItem : : CELL_MODE_CHECK ) ;
item - > set_checked ( 0 , true ) ;
item - > set_editable ( 0 , true ) ;
}
}
void FindInFilesPanel : : draw_result_text ( Object * item_obj , Rect2 rect ) {
TreeItem * item = Object : : cast_to < TreeItem > ( item_obj ) ;
2020-05-14 14:41:43 +00:00
if ( ! item ) {
2018-08-18 23:36:44 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2018-08-18 23:36:44 +00:00
2022-05-13 13:04:37 +00:00
HashMap < TreeItem * , Result > : : Iterator E = _result_items . find ( item ) ;
2020-05-14 14:41:43 +00:00
if ( ! E ) {
2018-08-18 23:36:44 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2022-05-13 13:04:37 +00:00
Result r = E - > value ;
2020-06-16 22:53:23 +00:00
String item_text = item - > get_text ( _with_replace ? 1 : 0 ) ;
2024-05-14 13:57:29 +00:00
Ref < Font > font = _results_display - > get_theme_font ( SceneStringName ( font ) ) ;
int font_size = _results_display - > get_theme_font_size ( SceneStringName ( font_size ) ) ;
2018-08-18 23:36:44 +00:00
Rect2 match_rect = rect ;
2022-09-29 01:21:48 +00:00
match_rect . position . x + = font - > get_string_size ( item_text . left ( r . begin_trimmed ) , HORIZONTAL_ALIGNMENT_LEFT , - 1 , font_size ) . x - 1 ;
2022-12-05 12:53:09 +00:00
match_rect . size . x = font - > get_string_size ( _search_text_label - > get_text ( ) , HORIZONTAL_ALIGNMENT_LEFT , - 1 , font_size ) . x + 1 ;
2018-08-18 23:36:44 +00:00
match_rect . position . y + = 1 * EDSCALE ;
match_rect . size . y - = 2 * EDSCALE ;
2023-08-13 00:33:39 +00:00
_results_display - > draw_rect ( match_rect , get_theme_color ( SNAME ( " accent_color " ) , EditorStringName ( Editor ) ) * Color ( 1 , 1 , 1 , 0.33 ) , false , 2.0 ) ;
_results_display - > draw_rect ( match_rect , get_theme_color ( SNAME ( " accent_color " ) , EditorStringName ( Editor ) ) * Color ( 1 , 1 , 1 , 0.17 ) , true ) ;
2021-05-15 22:29:56 +00:00
// Text is drawn by Tree already.
2018-08-18 23:36:44 +00:00
}
void FindInFilesPanel : : _on_item_edited ( ) {
TreeItem * item = _results_display - > get_selected ( ) ;
2022-09-29 01:21:48 +00:00
// Change opacity to half if checkbox is checked, otherwise full.
2024-05-14 13:57:29 +00:00
Color use_color = _results_display - > get_theme_color ( SceneStringName ( font_color ) ) ;
2022-09-29 01:21:48 +00:00
if ( ! item - > is_checked ( 0 ) ) {
use_color . a * = 0.5 ;
2018-08-18 23:36:44 +00:00
}
2022-09-29 01:21:48 +00:00
item - > set_custom_color ( 1 , use_color ) ;
2018-02-12 01:36:15 +00:00
}
void FindInFilesPanel : : _on_finished ( ) {
2020-12-11 16:13:33 +00:00
String results_text ;
int result_count = _result_items . size ( ) ;
int file_count = _file_items . size ( ) ;
if ( result_count = = 1 & & file_count = = 1 ) {
2022-09-29 01:21:48 +00:00
results_text = vformat ( TTR ( " %d match in %d file " ) , result_count , file_count ) ;
2020-12-11 16:13:33 +00:00
} else if ( result_count ! = 1 & & file_count = = 1 ) {
2022-09-29 01:21:48 +00:00
results_text = vformat ( TTR ( " %d matches in %d file " ) , result_count , file_count ) ;
2020-12-11 16:13:33 +00:00
} else {
2022-09-29 01:21:48 +00:00
results_text = vformat ( TTR ( " %d matches in %d files " ) , result_count , file_count ) ;
2020-12-11 16:13:33 +00:00
}
_status_label - > set_text ( results_text ) ;
2018-02-12 01:36:15 +00:00
update_replace_buttons ( ) ;
set_progress_visible ( false ) ;
2020-02-10 13:04:15 +00:00
_refresh_button - > show ( ) ;
2018-10-08 21:57:20 +00:00
_cancel_button - > hide ( ) ;
2018-02-12 01:36:15 +00:00
}
2020-02-10 13:04:15 +00:00
void FindInFilesPanel : : _on_refresh_button_clicked ( ) {
start_search ( ) ;
}
2018-02-12 01:36:15 +00:00
void FindInFilesPanel : : _on_cancel_button_clicked ( ) {
stop_search ( ) ;
}
2024-02-25 14:40:39 +00:00
void FindInFilesPanel : : _on_close_button_clicked ( ) {
emit_signal ( SNAME ( SIGNAL_CLOSE_BUTTON_CLICKED ) ) ;
}
2018-08-18 23:36:44 +00:00
void FindInFilesPanel : : _on_result_selected ( ) {
TreeItem * item = _results_display - > get_selected ( ) ;
2022-05-13 13:04:37 +00:00
HashMap < TreeItem * , Result > : : Iterator E = _result_items . find ( item ) ;
2018-08-18 23:36:44 +00:00
2022-05-13 13:04:37 +00:00
if ( ! E ) {
2018-08-18 23:36:44 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2022-05-13 13:04:37 +00:00
Result r = E - > value ;
2018-08-18 23:36:44 +00:00
TreeItem * file_item = item - > get_parent ( ) ;
String fpath = file_item - > get_metadata ( 0 ) ;
2018-02-12 01:36:15 +00:00
2021-07-17 21:22:52 +00:00
emit_signal ( SNAME ( SIGNAL_RESULT_SELECTED ) , fpath , r . line_number , r . begin , r . end ) ;
2018-02-12 01:36:15 +00:00
}
2024-02-15 16:25:58 +00:00
void FindInFilesPanel : : _on_replace_text_changed ( const String & text ) {
2018-02-12 01:36:15 +00:00
update_replace_buttons ( ) ;
}
void FindInFilesPanel : : _on_replace_all_clicked ( ) {
String replace_text = get_replace_text ( ) ;
2020-02-17 21:06:54 +00:00
PackedStringArray modified_files ;
2018-02-12 01:36:15 +00:00
2021-08-09 20:13:42 +00:00
for ( KeyValue < String , TreeItem * > & E : _file_items ) {
TreeItem * file_item = E . value ;
2018-08-18 23:36:44 +00:00
String fpath = file_item - > get_metadata ( 0 ) ;
2018-02-12 01:36:15 +00:00
2018-08-18 23:36:44 +00:00
Vector < Result > locations ;
2021-03-07 20:07:30 +00:00
for ( TreeItem * item = file_item - > get_first_child ( ) ; item ; item = item - > get_next ( ) ) {
2020-05-14 14:41:43 +00:00
if ( ! item - > is_checked ( 0 ) ) {
2018-08-18 23:36:44 +00:00
continue ;
2020-05-14 14:41:43 +00:00
}
2018-02-12 01:36:15 +00:00
2022-05-13 13:04:37 +00:00
HashMap < TreeItem * , Result > : : Iterator F = _result_items . find ( item ) ;
ERR_FAIL_COND ( ! F ) ;
locations . push_back ( F - > value ) ;
2018-08-18 23:36:44 +00:00
}
2018-02-12 01:36:15 +00:00
2018-08-18 23:36:44 +00:00
if ( locations . size ( ) ! = 0 ) {
2021-10-27 14:13:49 +00:00
// Results are sorted by file, so we can batch replaces.
2018-08-18 23:36:44 +00:00
apply_replaces_in_file ( fpath , locations , replace_text ) ;
2020-02-17 21:06:54 +00:00
modified_files . push_back ( fpath ) ;
2018-08-18 23:36:44 +00:00
}
2018-02-12 01:36:15 +00:00
}
2021-10-27 14:13:49 +00:00
// Hide replace bar so we can't trigger the action twice without doing a new search.
2018-08-18 23:36:44 +00:00
_replace_container - > hide ( ) ;
2018-02-12 01:36:15 +00:00
2021-07-17 21:22:52 +00:00
emit_signal ( SNAME ( SIGNAL_FILES_MODIFIED ) , modified_files ) ;
2018-02-12 01:36:15 +00:00
}
2021-10-27 14:13:49 +00:00
// Same as get_line, but preserves line ending characters.
2018-02-12 01:36:15 +00:00
class ConservativeGetLine {
public :
2022-03-23 09:08:58 +00:00
String get_line ( Ref < FileAccess > f ) {
2018-02-12 01:36:15 +00:00
_line_buffer . clear ( ) ;
2020-07-27 10:43:20 +00:00
char32_t c = f - > get_8 ( ) ;
2018-02-12 01:36:15 +00:00
while ( ! f - > eof_reached ( ) ) {
if ( c = = ' \n ' ) {
_line_buffer . push_back ( c ) ;
_line_buffer . push_back ( 0 ) ;
return String : : utf8 ( _line_buffer . ptr ( ) ) ;
} else if ( c = = ' \0 ' ) {
_line_buffer . push_back ( c ) ;
return String : : utf8 ( _line_buffer . ptr ( ) ) ;
} else if ( c ! = ' \r ' ) {
_line_buffer . push_back ( c ) ;
}
c = f - > get_8 ( ) ;
}
_line_buffer . push_back ( 0 ) ;
return String : : utf8 ( _line_buffer . ptr ( ) ) ;
}
private :
Vector < char > _line_buffer ;
} ;
2024-02-15 16:25:58 +00:00
void FindInFilesPanel : : apply_replaces_in_file ( const String & fpath , const Vector < Result > & locations , const String & new_text ) {
2018-02-12 01:36:15 +00:00
// If the file is already open, I assume the editor will reload it.
// If there are unsaved changes, the user will be asked on focus,
2018-09-13 01:38:39 +00:00
// however that means either losing changes or losing replaces.
2018-02-12 01:36:15 +00:00
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( fpath , FileAccess : : READ ) ;
ERR_FAIL_COND_MSG ( f . is_null ( ) , " Cannot open file from path ' " + fpath + " '. " ) ;
2018-02-12 01:36:15 +00:00
String buffer ;
int current_line = 1 ;
ConservativeGetLine conservative ;
String line = conservative . get_line ( f ) ;
2018-08-18 23:36:44 +00:00
String search_text = _finder - > get_search_text ( ) ;
int offset = 0 ;
2018-02-12 01:36:15 +00:00
2018-08-18 23:36:44 +00:00
for ( int i = 0 ; i < locations . size ( ) ; + + i ) {
int repl_line_number = locations [ i ] . line_number ;
2018-02-12 01:36:15 +00:00
while ( current_line < repl_line_number ) {
buffer + = line ;
line = conservative . get_line ( f ) ;
+ + current_line ;
2018-08-18 23:36:44 +00:00
offset = 0 ;
}
int repl_begin = locations [ i ] . begin + offset ;
int repl_end = locations [ i ] . end + offset ;
int _ ;
if ( ! find_next ( line , search_text , repl_begin , _finder - > is_match_case ( ) , _finder - > is_whole_words ( ) , _ , _ ) ) {
// Make sure the replace is still valid in case the file was tampered with.
2018-08-24 07:35:07 +00:00
print_verbose ( String ( " Occurrence no longer matches, replace will be ignored in {0}: line {1}, col {2} " ) . format ( varray ( fpath , repl_line_number , repl_begin ) ) ) ;
2018-08-18 23:36:44 +00:00
continue ;
2018-02-12 01:36:15 +00:00
}
2020-02-13 15:42:49 +00:00
line = line . left ( repl_begin ) + new_text + line . substr ( repl_end ) ;
2021-10-27 14:13:49 +00:00
// Keep an offset in case there are successive replaces in the same line.
2018-08-18 23:36:44 +00:00
offset + = new_text . length ( ) - ( repl_end - repl_begin ) ;
2018-02-12 01:36:15 +00:00
}
buffer + = line ;
while ( ! f - > eof_reached ( ) ) {
buffer + = conservative . get_line ( f ) ;
}
2021-10-27 14:13:49 +00:00
// Now the modified contents are in the buffer, rewrite the file with our changes.
2018-02-12 01:36:15 +00:00
Error err = f - > reopen ( fpath , FileAccess : : WRITE ) ;
2019-09-25 08:28:50 +00:00
ERR_FAIL_COND_MSG ( err ! = OK , " Cannot create file in path ' " + fpath + " '. " ) ;
2018-02-12 01:36:15 +00:00
f - > store_string ( buffer ) ;
}
String FindInFilesPanel : : get_replace_text ( ) {
2020-02-09 09:10:58 +00:00
return _replace_line_edit - > get_text ( ) ;
2018-02-12 01:36:15 +00:00
}
void FindInFilesPanel : : update_replace_buttons ( ) {
2019-05-20 12:43:42 +00:00
bool disabled = _finder - > is_searching ( ) ;
2018-02-12 01:36:15 +00:00
_replace_all_button - > set_disabled ( disabled ) ;
}
2022-09-29 09:53:28 +00:00
void FindInFilesPanel : : set_progress_visible ( bool p_visible ) {
_progress_bar - > set_self_modulate ( Color ( 1 , 1 , 1 , p_visible ? 1 : 0 ) ) ;
2018-02-12 01:36:15 +00:00
}
void FindInFilesPanel : : _bind_methods ( ) {
ClassDB : : bind_method ( " _on_result_found " , & FindInFilesPanel : : _on_result_found ) ;
ClassDB : : bind_method ( " _on_finished " , & FindInFilesPanel : : _on_finished ) ;
ADD_SIGNAL ( MethodInfo ( SIGNAL_RESULT_SELECTED ,
PropertyInfo ( Variant : : STRING , " path " ) ,
PropertyInfo ( Variant : : INT , " line_number " ) ,
PropertyInfo ( Variant : : INT , " begin " ) ,
PropertyInfo ( Variant : : INT , " end " ) ) ) ;
ADD_SIGNAL ( MethodInfo ( SIGNAL_FILES_MODIFIED , PropertyInfo ( Variant : : STRING , " paths " ) ) ) ;
2024-02-25 14:40:39 +00:00
ADD_SIGNAL ( MethodInfo ( SIGNAL_CLOSE_BUTTON_CLICKED ) ) ;
2018-02-12 01:36:15 +00:00
}