Add RegEx support for global search.

Minor change in how minimap is dragged.
This commit is contained in:
Martín Lucas Golini
2024-09-22 14:03:07 -03:00
parent 2c617b1deb
commit a60cff4d93
9 changed files with 203 additions and 69 deletions

View File

@@ -188,6 +188,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
globalSearchBarConfig.caseSensitive =
ini.getValueB( "global_search_bar", "case_sensitive", false );
globalSearchBarConfig.luaPattern = ini.getValueB( "global_search_bar", "lua_pattern", false );
globalSearchBarConfig.regex = ini.getValueB( "global_search_bar", "regex", false );
globalSearchBarConfig.wholeWord = ini.getValueB( "global_search_bar", "whole_word", false );
globalSearchBarConfig.escapeSequence =
ini.getValueB( "global_search_bar", "escape_sequence", false );
@@ -327,6 +328,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
ini.setValueB( "global_search_bar", "case_sensitive", globalSearchBarConfig.caseSensitive );
ini.setValueB( "global_search_bar", "lua_pattern", globalSearchBarConfig.luaPattern );
ini.setValueB( "global_search_bar", "regex", globalSearchBarConfig.regex );
ini.setValueB( "global_search_bar", "whole_word", globalSearchBarConfig.wholeWord );
ini.setValueB( "global_search_bar", "escape_sequence", globalSearchBarConfig.escapeSequence );

View File

@@ -112,6 +112,7 @@ struct SearchBarConfig {
struct GlobalSearchBarConfig {
bool caseSensitive{ false };
bool regex{ false };
bool luaPattern{ false };
bool wholeWord{ false };
bool escapeSequence{ false };

View File

@@ -529,24 +529,24 @@ R"html(
</RelativeLayout>
</Splitter>
<searchbar id="search_bar" lw="mp" lh="wc">
<vbox lw="wc" lh="wc" margin-right="4dp">
<vbox lw="wc" lh="wc" margin-right="4dp" layout-gravity="center">
<TextView lw="wc" lh="18dp" text='@string("find_text", "Find:")' margin-bottom="2dp" />
<TextView lw="wc" lh="18dp" text='@string("replace_with_text", "Replace with:")' />
</vbox>
<vbox lw="0" lw8="1" lh="wc" margin-right="4dp">
<vbox lw="0" lw8="1" lh="wc" margin-right="4dp" layout-gravity="center">
<TextInput id="search_find" lw="mp" lh="18dp" padding="0" margin-bottom="2dp" />
<TextInput id="search_replace" lw="mp" lh="18dp" padding="0" />
</vbox>
<vbox lw="wc" lh="wc" margin-right="4dp">
<vbox lw="wc" lh="wc" margin-right="4dp" layout-gravity="center">
<CheckBox id="case_sensitive" lw="wc" lh="wc" text='@string(case_sensitive, "Case sensitive")' selected="false" />
<CheckBox id="regex" lw="wc" lh="wc" text='@string(regular_expression, "Regular Expression")' selected="false" />
<CheckBox id="lua_pattern" lw="wc" lh="wc" text='@string(lua_pattern, "Lua Pattern")' selected="false" />
</vbox>
<vbox lw="wc" lh="wc" margin-right="4dp">
<vbox lw="wc" lh="wc" margin-right="4dp" layout-gravity="center">
<CheckBox id="whole_word" lw="wc" lh="wc" text='@string(match_whole_word, "Match Whole Word")' selected="false" />
<CheckBox id="escape_sequence" lw="wc" lh="wc" text='@string(use_escape_sequences, "Use escape sequences")' selected="false" tooltip='@string(escape_sequence_tooltip, "Replace \\, \\t, \\n, \\r and \\uXXXX (Unicode characters) with the corresponding control")' />
</vbox>
<vbox lw="wc" lh="wc">
<vbox lw="wc" lh="wc" layout-gravity="center">
<hbox lw="wc" lh="wc" margin-bottom="2dp">
<PushButton id="find_prev" lw="wc" lh="18dp" text='@string(previous, "Previous")' margin-right="4dp" />
<PushButton id="find_next" lw="wc" lh="18dp" text='@string(next, "Next")' margin-right="4dp" />"
@@ -582,6 +582,7 @@ R"html(
<StackLayout lw="mp" lh="wc" margin-bottom="2dp">
<CheckBox id="case_sensitive" text='@string(case_sensitive, "Case sensitive")' selected="true" margin-right="8dp" />
<CheckBox id="whole_word" text='@string(match_whole_word, "Match Whole Word")' selected="false" margin-right="8dp" />
<CheckBox id="regex" text='@string(regular_expression, "Regular Expression")' selected="false" margin-right="8dp" />
<CheckBox id="lua_pattern" text='@string(lua_pattern, "Lua Pattern")' selected="false" margin-right="8dp" />
<CheckBox id="escape_sequence" text='@string(use_escape_sequences, "Use escape sequences")' margin-right="8dp" selected="false"
tooltip='@string(escape_sequence_tooltip, "Replace \\, \t, \n, \r and \uXXXX (Unicode characters) with the corresponding control")' />

View File

@@ -1,5 +1,5 @@
#include "ecode.hpp"
#include "globalsearchcontroller.hpp"
#include "ecode.hpp"
#include "uitreeviewglobalsearch.hpp"
namespace ecode {
@@ -62,8 +62,8 @@ size_t GlobalSearchController::replaceInFiles( const std::string& replaceText,
}
const ProjectSearch::Result& res = model->getResult();
bool hasCaptures =
model->isResultFromLuaPattern() && LuaPattern::hasMatches( replaceText, "$%d+" );
bool hasCaptures = ( model->isResultFromLuaPattern() || model->isResultFromRegEx() ) &&
LuaPattern::hasMatches( replaceText, "$%d+" );
if ( hasCaptures ) {
for ( const auto& fileResult : res ) {
@@ -166,6 +166,9 @@ void GlobalSearchController::initGlobalSearchBar(
UICheckBox* wholeWordChk = mGlobalSearchBarLayout->find<UICheckBox>( "whole_word" );
wholeWordChk->setTooltipText( kbind.getCommandKeybindString( "change-whole-word" ) );
UICheckBox* regexChk = mGlobalSearchBarLayout->find<UICheckBox>( "regex" );
regexChk->setTooltipText( kbind.getCommandKeybindString( "toggle-regex" ) );
UICheckBox* luaPatternChk = mGlobalSearchBarLayout->find<UICheckBox>( "lua_pattern" );
luaPatternChk->setTooltipText( kbind.getCommandKeybindString( "toggle-lua-pattern" ) );
@@ -178,6 +181,7 @@ void GlobalSearchController::initGlobalSearchBar(
UIWidget* searchBarClose = mGlobalSearchBarLayout->find<UIWidget>( "global_searchbar_close" );
caseSensitiveChk->setChecked( globalSearchBarConfig.caseSensitive );
regexChk->setChecked( globalSearchBarConfig.regex );
luaPatternChk->setChecked( globalSearchBarConfig.luaPattern );
wholeWordChk->setChecked( globalSearchBarConfig.wholeWord );
escapeSequenceChk->setChecked( globalSearchBarConfig.escapeSequence );
@@ -188,24 +192,34 @@ void GlobalSearchController::initGlobalSearchBar(
mGlobalSearchHistoryList =
mGlobalSearchBarLayout->find<UIDropDownList>( "global_search_history" );
mGlobalSearchBarLayout->setCommand( "global-search-clear-history", [this] { clearHistory(); } );
mGlobalSearchBarLayout->setCommand( "search-in-files", [this, caseSensitiveChk, wholeWordChk,
luaPatternChk, escapeSequenceChk] {
doGlobalSearch( mGlobalSearchInput->getText(), mGlobalSearchWhereInput->getText(),
caseSensitiveChk->isChecked(), wholeWordChk->isChecked(),
luaPatternChk->isChecked(), escapeSequenceChk->isChecked(), false );
} );
mGlobalSearchBarLayout->setCommand(
"search-again", [this, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk] {
auto listBox = mGlobalSearchHistoryList->getListBox();
if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) {
const auto& item = mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 -
listBox->getItemSelectedIndex()];
doGlobalSearch( item.search, item.filter, caseSensitiveChk->isChecked(),
wholeWordChk->isChecked(), luaPatternChk->isChecked(),
escapeSequenceChk->isChecked(),
mGlobalSearchTreeReplace == mGlobalSearchTree, true );
}
"search-in-files",
[this, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk, regexChk] {
doGlobalSearch( mGlobalSearchInput->getText(), mGlobalSearchWhereInput->getText(),
caseSensitiveChk->isChecked(), wholeWordChk->isChecked(),
luaPatternChk->isChecked()
? TextDocument::FindReplaceType::LuaPattern
: ( regexChk->isChecked() ? TextDocument::FindReplaceType::RegEx
: TextDocument::FindReplaceType::Normal ),
escapeSequenceChk->isChecked(), false );
} );
mGlobalSearchBarLayout->setCommand( "search-again", [this, caseSensitiveChk, wholeWordChk,
luaPatternChk, escapeSequenceChk,
regexChk] {
auto listBox = mGlobalSearchHistoryList->getListBox();
if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) {
const auto& item = mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 -
listBox->getItemSelectedIndex()];
doGlobalSearch( item.search, item.filter, caseSensitiveChk->isChecked(),
wholeWordChk->isChecked(),
luaPatternChk->isChecked()
? TextDocument::FindReplaceType::LuaPattern
: ( regexChk->isChecked() ? TextDocument::FindReplaceType::RegEx
: TextDocument::FindReplaceType::Normal ),
escapeSequenceChk->isChecked(),
mGlobalSearchTreeReplace == mGlobalSearchTree, true );
}
} );
mGlobalSearchBarLayout->setCommand( "search-set-string", [this] {
auto listBox = mGlobalSearchHistoryList->getListBox();
const auto& item =
@@ -232,6 +246,8 @@ void GlobalSearchController::initGlobalSearchBar(
mGlobalSearchBarLayout->setCommand( "change-whole-word", [wholeWordChk] {
wholeWordChk->setChecked( !wholeWordChk->isChecked() );
} );
mGlobalSearchBarLayout->setCommand(
"toggle-regex", [regexChk] { regexChk->setChecked( !regexChk->isChecked() ); } );
mGlobalSearchBarLayout->setCommand( "toggle-lua-pattern", [luaPatternChk] {
luaPatternChk->setChecked( !luaPatternChk->isChecked() );
} );
@@ -248,6 +264,22 @@ void GlobalSearchController::initGlobalSearchBar(
mGlobalSearchTree->forceKeyDown( keyEvent );
}
};
luaPatternChk->on( Event::OnValueChange, [this, luaPatternChk, regexChk]( const Event* ) {
if ( mValueChanging )
return;
BoolScopedOp op( mValueChanging, true );
if ( luaPatternChk->isChecked() && regexChk->isChecked() )
regexChk->setChecked( false );
} );
regexChk->on( Event::OnValueChange, [this, luaPatternChk, regexChk]( const Event* ) {
if ( mValueChanging )
return;
BoolScopedOp op( mValueChanging, true );
if ( regexChk->isChecked() && luaPatternChk->isChecked() )
luaPatternChk->setChecked( false );
} );
mGlobalSearchInput->setSelectAllDocOnTabNavigate( false );
mGlobalSearchWhereInput->setSelectAllDocOnTabNavigate( false );
mGlobalSearchInput->on( Event::OnPressEnter, pressEnterCb );
@@ -330,28 +362,33 @@ void GlobalSearchController::initGlobalSearchBar(
}
mGlobalSearchBarLayout->forceKeyDown( *keyEvent );
} );
mGlobalSearchBarLayout->setCommand(
"search-replace-in-files",
[this, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk, replaceInput] {
if ( mGlobalSearchTreeReplace == mGlobalSearchTree ) {
replaceInput->setFocus();
replaceInput->getDocument().selectAll();
return;
}
mGlobalSearchBarLayout->setCommand( "search-replace-in-files", [this, caseSensitiveChk,
wholeWordChk, luaPatternChk,
escapeSequenceChk, replaceInput,
regexChk] {
if ( mGlobalSearchTreeReplace == mGlobalSearchTree ) {
replaceInput->setFocus();
replaceInput->getDocument().selectAll();
return;
}
// TODO Implement replacement from result from symbol reference
/*if ( mGlobalSearchHistory.back().second->isResultFromSymbolReference() ) {
mGlobalSearchTreeReplace->setModel( mGlobalSearchHistory.back().second );
showGlobalSearch( true );
updateGlobalSearchBarResults( mGlobalSearchHistory.back().first,
mGlobalSearchHistory.back().second, true, false );
} else*/
{
doGlobalSearch( mGlobalSearchInput->getText(), mGlobalSearchWhereInput->getText(),
caseSensitiveChk->isChecked(), wholeWordChk->isChecked(),
luaPatternChk->isChecked(), escapeSequenceChk->isChecked(), true );
}
} );
// TODO Implement replacement from result from symbol reference
/*if ( mGlobalSearchHistory.back().second->isResultFromSymbolReference() ) {
mGlobalSearchTreeReplace->setModel( mGlobalSearchHistory.back().second );
showGlobalSearch( true );
updateGlobalSearchBarResults( mGlobalSearchHistory.back().first,
mGlobalSearchHistory.back().second, true, false );
} else*/
{
doGlobalSearch( mGlobalSearchInput->getText(), mGlobalSearchWhereInput->getText(),
caseSensitiveChk->isChecked(), wholeWordChk->isChecked(),
luaPatternChk->isChecked()
? TextDocument::FindReplaceType::LuaPattern
: ( regexChk->isChecked() ? TextDocument::FindReplaceType::RegEx
: TextDocument::FindReplaceType::Normal ),
escapeSequenceChk->isChecked(), true );
}
} );
mGlobalSearchBarLayout->setCommand(
"replace-in-files", [this, replaceInput, escapeSequenceChk] {
auto listBox = mGlobalSearchHistoryList->getListBox();
@@ -482,10 +519,12 @@ void GlobalSearchController::clearHistory() {
GlobalSearchBarConfig GlobalSearchController::getGlobalSearchBarConfig() const {
UICheckBox* caseSensitiveChk = mGlobalSearchBarLayout->find<UICheckBox>( "case_sensitive" );
UICheckBox* wholeWordChk = mGlobalSearchBarLayout->find<UICheckBox>( "whole_word" );
UICheckBox* regexChk = mGlobalSearchBarLayout->find<UICheckBox>( "regex" );
UICheckBox* luaPatternChk = mGlobalSearchBarLayout->find<UICheckBox>( "lua_pattern" );
UICheckBox* escapeSequenceChk = mGlobalSearchBarLayout->find<UICheckBox>( "escape_sequence" );
GlobalSearchBarConfig globalSeachBarConfig;
globalSeachBarConfig.caseSensitive = caseSensitiveChk->isChecked();
globalSeachBarConfig.regex = regexChk->isChecked();
globalSeachBarConfig.luaPattern = luaPatternChk->isChecked();
globalSeachBarConfig.wholeWord = wholeWordChk->isChecked();
globalSeachBarConfig.escapeSequence = escapeSequenceChk->isChecked();
@@ -599,8 +638,10 @@ std::vector<GlobMatch> GlobalSearchController::parseGlobMatches( const String& s
}
void GlobalSearchController::doGlobalSearch( String text, String filter, bool caseSensitive,
bool wholeWord, bool luaPattern, bool escapeSequence,
bool searchReplace, bool searchAgain ) {
bool wholeWord,
TextDocument::FindReplaceType searchType,
bool escapeSequence, bool searchReplace,
bool searchAgain ) {
if ( mApp->getDirTree() && mApp->getDirTree()->getFilesCount() > 0 && !text.empty() ) {
mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch;
mGlobalSearchTreeSearch->setVisible( !searchReplace );
@@ -629,15 +670,17 @@ void GlobalSearchController::doGlobalSearch( String text, String filter, bool ca
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
mApp->getThreadPool(),
#endif
[this, clock, search, loader, searchReplace, searchAgain, escapeSequence, luaPattern,
[this, clock, search, loader, searchReplace, searchAgain, escapeSequence, searchType,
filter]( const ProjectSearch::Result& res ) {
Log::info( "Global search for \"%s\" took %.2fms", search.c_str(),
clock->getElapsedTime().asMilliseconds() );
eeDelete( clock );
mUISceneNode->runOnMainThread( [this, loader, res, search, searchReplace,
searchAgain, escapeSequence, luaPattern, filter] {
searchAgain, escapeSequence, searchType, filter] {
auto model = ProjectSearch::asModel( res );
model->setResultFromLuaPattern( luaPattern );
model->setResultFromLuaPattern( searchType ==
TextDocument::FindReplaceType::LuaPattern );
model->setResultFromRegEx( searchType == TextDocument::FindReplaceType::RegEx );
updateGlobalSearchHistory( model, search, filter, searchReplace, searchAgain,
escapeSequence );
updateGlobalSearchBarResults( search, model, searchReplace, escapeSequence );
@@ -645,10 +688,8 @@ void GlobalSearchController::doGlobalSearch( String text, String filter, bool ca
loader->close();
} );
},
caseSensitive, wholeWord,
luaPattern ? TextDocument::FindReplaceType::LuaPattern
: TextDocument::FindReplaceType::Normal,
parseGlobMatches( filter ), mApp->getCurrentProject(), openDocs );
caseSensitive, wholeWord, searchType, parseGlobMatches( filter ),
mApp->getCurrentProject(), openDocs );
}
}

View File

@@ -19,6 +19,7 @@ class GlobalSearchController {
{ "escape", "close-global-searchbar" },
{ "mod+s", "change-case" },
{ "mod+w", "change-whole-word" },
{ "mod+p", "toggle-regex" },
{ "mod+l", "toggle-lua-pattern" },
{ "mod+r", "search-replace-in-files" },
{ "mod+g", "search-again" },
@@ -48,8 +49,8 @@ class GlobalSearchController {
void initGlobalSearchTree( UITreeViewGlobalSearch* searchTree );
void doGlobalSearch( String text, String filter, bool caseSensitive, bool wholeWord,
bool luaPattern, bool escapeSequence, bool searchReplace,
bool searchAgain = false );
TextDocument::FindReplaceType searchType, bool escapeSequence,
bool searchReplace, bool searchAgain = false );
size_t replaceInFiles( const std::string& replaceText,
std::shared_ptr<ProjectSearch::ResultModel> model );
@@ -86,6 +87,7 @@ class GlobalSearchController {
std::shared_ptr<ProjectSearch::ResultModel> result;
};
std::deque<SearchHistoryItem> mGlobalSearchHistory;
bool mValueChanging{ false };
void onLoadDone( const Variant& lineNum, const Variant& colNum );

View File

@@ -1,6 +1,7 @@
#include "projectsearch.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/system/regex.hpp>
#if EE_PLATFORM == EE_PLATFORM_LINUX
// For malloc_trim, which is a GNU extension
@@ -142,6 +143,63 @@ searchInFileLuaPattern( const std::string& file, const std::string& text, const
return results;
}
static std::vector<ProjectSearch::ResultData::Result> searchInFileRegEx( const std::string& file,
const std::string& text,
const bool& caseSensitive,
const bool& wholeWord ) {
std::string fileText;
FileSystem::fileGet( file, fileText );
RegEx pattern( text );
std::vector<ProjectSearch::ResultData::Result> results;
Int64 totNl = 0;
bool matched = false;
Int64 searchRes = 0;
std::string fileTextOriginal;
if ( !caseSensitive ) {
fileTextOriginal = fileText;
String::toLowerInPlace( fileText );
}
PatternMatcher::Range matches[12];
do {
int start, end = 0;
if ( ( matched = pattern.matches( fileText, matches, searchRes ) ) ) {
start = matches[0].start;
end = matches[0].end;
if ( wholeWord &&
!String::isWholeWord( fileText, fileText.substr( start, end - start ), start ) ) {
searchRes = end;
continue;
}
Int64 relCol;
totNl += countNewLines( fileText, searchRes, start );
String str( textLine( caseSensitive ? fileText : fileTextOriginal, start, relCol ) );
int len = end - start;
ProjectSearch::ResultData::Result res;
res.line = std::move( str );
res.position = { { totNl, (Int64)relCol }, { totNl, (Int64)( relCol + len ) } };
res.start = start;
res.end = end;
for ( size_t c = 1; c < 12; c++ ) {
if ( matches[c].isValid() ) {
res.captures.push_back(
fileText.substr( matches[c].start, matches[c].end - matches[c].start ) );
} else {
break;
}
}
results.emplace_back( std::move( res ) );
searchRes = end;
}
} while ( matched );
return results;
}
void ProjectSearch::find( const std::vector<std::string> files, const std::string& string,
ResultCb result, bool caseSensitive, bool wholeWord,
const TextDocument::FindReplaceType& type,
@@ -168,9 +226,12 @@ void ProjectSearch::find( const std::vector<std::string> files, const std::strin
if ( skip )
continue;
auto fileRes = type == TextDocument::FindReplaceType::Normal
? searchInFileHorspool( file, string, caseSensitive, wholeWord, occ )
: searchInFileLuaPattern( file, string, caseSensitive, wholeWord );
auto fileRes =
type == TextDocument::FindReplaceType::Normal
? searchInFileHorspool( file, string, caseSensitive, wholeWord, occ )
: ( type == TextDocument::FindReplaceType::LuaPattern
? searchInFileLuaPattern( file, string, caseSensitive, wholeWord )
: searchInFileRegEx( file, string, caseSensitive, wholeWord ) );
if ( !fileRes.empty() )
res.push_back( { file, fileRes } );
}
@@ -306,11 +367,14 @@ void ProjectSearch::find( const std::vector<std::string> files, std::string stri
} else {
pool->run(
[findData, file, string, caseSensitive, wholeWord, occ, type] {
auto fileRes =
type == TextDocument::FindReplaceType::Normal
? searchInFileHorspool( file, string, caseSensitive, wholeWord,
occ )
: searchInFileLuaPattern( file, string, caseSensitive, wholeWord );
auto fileRes = type == TextDocument::FindReplaceType::Normal
? searchInFileHorspool( file, string, caseSensitive,
wholeWord, occ )
: ( type == TextDocument::FindReplaceType::LuaPattern
? searchInFileLuaPattern(
file, string, caseSensitive, wholeWord )
: searchInFileRegEx( file, string, caseSensitive,
wholeWord ) );
if ( !fileRes.empty() ) {
Lock l( findData->resMutex );
findData->res.push_back( { std::move( file ), std::move( fileRes ) } );

View File

@@ -199,10 +199,15 @@ class ProjectSearch {
bool isResultFromLuaPattern() const { return mResultFromLuaPattern; }
void setResultFromRegEx( bool ref ) { mResultFromRegEx = ref; }
bool isResultFromRegEx() const { return mResultFromRegEx; }
protected:
Result mResult;
bool mResultFromSymbolReference{ false };
bool mResultFromLuaPattern{ false };
bool mResultFromRegEx{ false };
};
static std::shared_ptr<ResultModel> asModel( const Result& result ) {