Project search filters WIP.

This commit is contained in:
Martín Lucas Golini
2024-03-14 00:07:14 -03:00
parent 6db1e5ec43
commit 5975174e48
9 changed files with 309 additions and 212 deletions
+12
View File
@@ -398,6 +398,18 @@ class EE_API String {
/** @return The next character in a utf8 null terminated string */ /** @return The next character in a utf8 null terminated string */
static Uint32 utf8Next( char*& utf8String ); static Uint32 utf8Next( char*& utf8String );
/** glob matches a string against a glob
** @return True if matches
*/
static bool globMatch( const std::string& text, const std::string_view& glob,
bool caseInsensitive = false );
/** glob matches a string against a set of globs
** @return True if matches
*/
static bool globMatch( const std::string& text, const std::vector<std::string>& globs,
bool caseInsensitive = false );
/** @brief Default constructor /** @brief Default constructor
** This constructor creates an empty string. ** This constructor creates an empty string.
**/ **/
+148 -2
View File
@@ -14,6 +14,152 @@ namespace EE {
const std::size_t String::InvalidPos = StringType::npos; const std::size_t String::InvalidPos = StringType::npos;
// Author: Robert A. van Engelen, engelen@genivia.com
// Date: August 5, 2019
// License: The Code Project Open License (CPOL)
// https://www.codeproject.com/info/cpol10.aspx
#define DOTGLOB 1
#define PATHSEP '/'
#define CASE( c, caseInsensitive ) ( caseInsensitive ? std::tolower( c ) : ( c ) )
bool String::globMatch( const std::string& text, const std::string_view& glob,
bool caseInsensitive ) {
size_t i = 0;
size_t j = 0;
size_t n = text.size();
size_t m = glob.size();
size_t text1_backup = std::string::npos;
size_t glob1_backup = std::string::npos;
size_t text2_backup = std::string::npos;
size_t glob2_backup = std::string::npos;
bool nodot = !DOTGLOB;
// match pathname if glob contains a / otherwise match the basename
if ( j + 1 < m && glob[j] == '/' ) {
// if pathname starts with ./ then ignore these pairs
while ( i + 1 < n && text[i] == '.' && text[i + 1] == PATHSEP )
i += 2;
// if pathname starts with a / then ignore it
if ( i < n && text[i] == PATHSEP )
i++;
j++;
} else if ( glob.find( '/' ) == std::string::npos ) {
size_t sep = text.rfind( PATHSEP );
if ( sep != std::string::npos )
i = sep + 1;
}
while ( i < n ) {
if ( j < m ) {
switch ( glob[j] ) {
case '*':
// match anything except . after /
if ( nodot && text[i] == '.' )
break;
if ( ++j < m && glob[j] == '*' ) {
// trailing ** match everything after /
if ( ++j >= m )
return true;
// ** followed by a / match zero or more directories
if ( glob[j] != '/' )
return false;
// new **-loop, discard *-loop
text1_backup = std::string::npos;
glob1_backup = std::string::npos;
text2_backup = i;
glob2_backup = ++j;
continue;
}
// trailing * matches everything except /
text1_backup = i;
glob1_backup = j;
continue;
case '?':
// match anything except . after /
if ( nodot && text[i] == '.' )
break;
// match any character except /
if ( text[i] == PATHSEP )
break;
i++;
j++;
continue;
case '[': {
// match anything except . after /
if ( nodot && text[i] == '.' )
break;
// match any character in [...] except /
if ( text[i] == PATHSEP )
break;
int lastchr;
bool matched = false;
bool reverse = j + 1 < m && ( glob[j + 1] == '^' || glob[j + 1] == '!' );
// inverted character class
if ( reverse )
j++;
// match character class
for ( lastchr = 256; ++j < m && glob[j] != ']';
lastchr = CASE( glob[j], caseInsensitive ) )
if ( lastchr < 256 && glob[j] == '-' && j + 1 < m && glob[j + 1] != ']'
? CASE( text[i], caseInsensitive ) <=
CASE( glob[++j], caseInsensitive ) &&
CASE( text[i], caseInsensitive ) >= lastchr
: CASE( text[i], caseInsensitive ) ==
CASE( glob[j], caseInsensitive ) )
matched = true;
if ( matched == reverse )
break;
i++;
if ( j < m )
j++;
continue;
}
case '\\':
// literal match \-escaped character
if ( j + 1 < m )
j++;
// FALLTHROUGH
default:
// match the current non-NUL character
if ( CASE( glob[j], caseInsensitive ) != CASE( text[i], caseInsensitive ) &&
!( glob[j] == '/' && text[i] == PATHSEP ) )
break;
// do not match a . with *, ? [] after /
nodot = !DOTGLOB && glob[j] == '/';
i++;
j++;
continue;
}
}
if ( glob1_backup != std::string::npos && text[text1_backup] != PATHSEP ) {
// *-loop: backtrack to the last * but do not jump over /
i = ++text1_backup;
j = glob1_backup;
continue;
}
if ( glob2_backup != std::string::npos ) {
// **-loop: backtrack to the last **
i = ++text2_backup;
j = glob2_backup;
continue;
}
return false;
}
// ignore trailing stars
while ( j < m && glob[j] == '*' )
j++;
// at end of text means success if nothing else is left to match
return j >= m;
}
bool String::globMatch( const std::string& text, const std::vector<std::string>& globs,
bool caseInsensitive ) {
for ( const auto& glob : globs ) {
if ( globMatch( text, glob, caseInsensitive ) )
return true;
}
return false;
}
/* /*
* Originally written by Joel Yliluoma <joel.yliluoma@iki.fi> * Originally written by Joel Yliluoma <joel.yliluoma@iki.fi>
* http://en.wikipedia.org/wiki/Talk:Boyer%E2%80%93Moore_string_search_algorithm#Common_functions * http://en.wikipedia.org/wiki/Talk:Boyer%E2%80%93Moore_string_search_algorithm#Common_functions
@@ -919,7 +1065,7 @@ void String::numberCleanInPlace( std::string& strNumber ) {
std::string String::fromFloat( const Float& value, const std::string& append, std::string String::fromFloat( const Float& value, const std::string& append,
const std::string& prepend, size_t digitsAfterComma ) { const std::string& prepend, size_t digitsAfterComma ) {
std::ostringstream ss; std::ostringstream ss;
ss.precision(digitsAfterComma); ss.precision( digitsAfterComma );
ss << std::fixed << value; ss << std::fixed << value;
std::string val( ss.str() ); std::string val( ss.str() );
numberCleanInPlace( val ); numberCleanInPlace( val );
@@ -929,7 +1075,7 @@ std::string String::fromFloat( const Float& value, const std::string& append,
std::string String::fromDouble( const double& value, const std::string& append, std::string String::fromDouble( const double& value, const std::string& append,
const std::string& prepend, size_t digitsAfterComma ) { const std::string& prepend, size_t digitsAfterComma ) {
std::ostringstream ss; std::ostringstream ss;
ss.precision(digitsAfterComma); ss.precision( digitsAfterComma );
ss << std::fixed << value; ss << std::fixed << value;
std::string val( ss.str() ); std::string val( ss.str() );
numberCleanInPlace( val ); numberCleanInPlace( val );
+14 -5
View File
@@ -498,15 +498,24 @@ R"html(
</locatebar> </locatebar>
<globalsearchbar id="global_search_bar" lw="mp" lh="wc"> <globalsearchbar id="global_search_bar" lw="mp" lh="wc">
<hbox lw="mp" lh="wc"> <hbox lw="mp" lh="wc">
<TextView text='@string(search_for, "Search for:")' margin-right="4dp" /> <vbox lw="wc" lh="wc">
<TextView lh="18dp" text='@string(search_for, "Search for:")' margin-right="4dp" margin-bottom="2dp" />
<TextView lh="18dp" text='@string(path_filters, "Path Filters:")' margin-right="4dp" />
</vbox>
<vbox lw="0" lw8="1" lh="wc"> <vbox lw="0" lw8="1" lh="wc">
<TextInput id="global_search_find" lw="mp" lh="wc" lh="18dp" padding="0" margin-bottom="2dp" /> <TextInput id="global_search_find" lw="mp" lh="wc" lh="18dp" padding="0" margin-bottom="2dp" />
<hbox lw="mp" lh="wc" margin-bottom="4dp"> <hbox lw="mp" lh="wc" margin-bottom="4dp">
<CheckBox id="case_sensitive" text='@string(case_sensitive, "Case sensitive")' selected="true" /> <TextInput id="global_search_where" lw="0" lw8="1" lh="wc" lh="18dp" padding="0" margin-right="4dp"
<CheckBox id="whole_word" text='@string(match_whole_word, "Match Whole Word")' selected="false" margin-left="8dp" /> hint='@string(search_where_example, "e.g. *.ts, src/**/include, -src/**/exclude")' hint-display="focus" />
<CheckBox id="lua_pattern" text='@string(lua_pattern, "Lua Pattern")' selected="false" margin-left="8dp" /> <PushButton lw="wc" lh="mp" text="..." />
<CheckBox id="escape_sequence" text='@string(use_escape_sequences, "Use escape sequences")' margin-left="8dp" selected="false" tooltip='@string(escape_sequence_tooltip, "Replace \\, \t, \n, \r and \uXXXX (Unicode characters) with the corresponding control")' />
</hbox> </hbox>
<StackLayout lw="mp" lh="wc" margin-bottom="4dp">
<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="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")' />
</StackLayout>
<hbox lw="mp" lh="wc"> <hbox lw="mp" lh="wc">
<TextView text='@string(history, "History:")' margin-right="4dp" lh="18dp" /> <TextView text='@string(history, "History:")' margin-right="4dp" lh="18dp" />
<DropDownList id="global_search_history" lw="0" lh="18dp" lw8="1" margin-right="4dp" /> <DropDownList id="global_search_history" lw="0" lh="18dp" lw8="1" margin-right="4dp" />
+61 -41
View File
@@ -1,5 +1,5 @@
#include "ecode.hpp"
#include "globalsearchcontroller.hpp" #include "globalsearchcontroller.hpp"
#include "ecode.hpp"
#include "uitreeviewglobalsearch.hpp" #include "uitreeviewglobalsearch.hpp"
namespace ecode { namespace ecode {
@@ -120,7 +120,7 @@ void GlobalSearchController::initGlobalSearchBar(
mGlobalSearchBarLayout = globalSearchBar; mGlobalSearchBarLayout = globalSearchBar;
mGlobalSearchBarLayout->setVisible( false )->setEnabled( false ); mGlobalSearchBarLayout->setVisible( false )->setEnabled( false );
auto addClickListener = [this]( UIWidget* widget, std::string cmd ) { auto addClickListener = [this]( UIWidget* widget, std::string cmd ) {
widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { widget->on( Event::MouseClick, [this, cmd]( const Event* event ) {
const MouseEvent* mouseEvent = static_cast<const MouseEvent*>( event ); const MouseEvent* mouseEvent = static_cast<const MouseEvent*>( event );
if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) if ( mouseEvent->getFlags() & EE_BUTTON_LMASK )
mGlobalSearchBarLayout->execute( cmd ); mGlobalSearchBarLayout->execute( cmd );
@@ -165,33 +165,34 @@ void GlobalSearchController::initGlobalSearchBar(
escapeSequenceChk->setChecked( globalSearchBarConfig.escapeSequence ); escapeSequenceChk->setChecked( globalSearchBarConfig.escapeSequence );
mGlobalSearchInput = mGlobalSearchBarLayout->find<UITextInput>( "global_search_find" ); mGlobalSearchInput = mGlobalSearchBarLayout->find<UITextInput>( "global_search_find" );
mGlobalSearchWhereInput = mGlobalSearchBarLayout->find<UITextInput>( "global_search_where" );
mGlobalSearchHistoryList = mGlobalSearchHistoryList =
mGlobalSearchBarLayout->find<UIDropDownList>( "global_search_history" ); mGlobalSearchBarLayout->find<UIDropDownList>( "global_search_history" );
mGlobalSearchBarLayout->setCommand( "global-search-clear-history", [this] { clearHistory(); } ); mGlobalSearchBarLayout->setCommand( "global-search-clear-history", [this] { clearHistory(); } );
mGlobalSearchBarLayout->setCommand( "search-in-files", [this, caseSensitiveChk, wholeWordChk, mGlobalSearchBarLayout->setCommand( "search-in-files", [this, caseSensitiveChk, wholeWordChk,
luaPatternChk, escapeSequenceChk] { luaPatternChk, escapeSequenceChk] {
doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), doGlobalSearch( mGlobalSearchInput->getText(), mGlobalSearchWhereInput->getText(),
wholeWordChk->isChecked(), luaPatternChk->isChecked(), caseSensitiveChk->isChecked(), wholeWordChk->isChecked(),
escapeSequenceChk->isChecked(), false ); luaPatternChk->isChecked(), escapeSequenceChk->isChecked(), false );
} ); } );
mGlobalSearchBarLayout->setCommand( mGlobalSearchBarLayout->setCommand(
"search-again", [this, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk] { "search-again", [this, caseSensitiveChk, wholeWordChk, luaPatternChk, escapeSequenceChk] {
auto listBox = mGlobalSearchHistoryList->getListBox(); auto listBox = mGlobalSearchHistoryList->getListBox();
if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) {
doGlobalSearch( mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - const auto& item = mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 -
listBox->getItemSelectedIndex()] listBox->getItemSelectedIndex()];
.first, doGlobalSearch( item.search, item.filter, caseSensitiveChk->isChecked(),
caseSensitiveChk->isChecked(), wholeWordChk->isChecked(), wholeWordChk->isChecked(), luaPatternChk->isChecked(),
luaPatternChk->isChecked(), escapeSequenceChk->isChecked(), escapeSequenceChk->isChecked(),
mGlobalSearchTreeReplace == mGlobalSearchTree, true ); mGlobalSearchTreeReplace == mGlobalSearchTree, true );
} }
} ); } );
mGlobalSearchBarLayout->setCommand( "search-set-string", [this] { mGlobalSearchBarLayout->setCommand( "search-set-string", [this] {
auto listBox = mGlobalSearchHistoryList->getListBox(); auto listBox = mGlobalSearchHistoryList->getListBox();
mGlobalSearchInput->setText( const auto& item =
mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - listBox->getItemSelectedIndex()] mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - listBox->getItemSelectedIndex()];
.first ); mGlobalSearchInput->setText( item.search );
; ;
} ); } );
mGlobalSearchBarLayout->setCommand( "close-global-searchbar", [this] { mGlobalSearchBarLayout->setCommand( "close-global-searchbar", [this] {
@@ -220,15 +221,17 @@ void GlobalSearchController::initGlobalSearchBar(
escapeSequenceChk->setChecked( !escapeSequenceChk->isChecked() ); escapeSequenceChk->setChecked( !escapeSequenceChk->isChecked() );
} ); } );
mGlobalSearchBarLayout->setCommand( "find-replace", [this] { mApp->showFindView(); } ); mGlobalSearchBarLayout->setCommand( "find-replace", [this] { mApp->showFindView(); } );
mGlobalSearchInput->addEventListener( Event::OnPressEnter, [this]( const Event* ) { const auto pressEnterCb = [this]( const Event* event ) {
if ( mGlobalSearchInput->hasFocus() ) { if ( event->getNode()->hasFocus() ) {
mGlobalSearchBarLayout->execute( "search-in-files" ); mGlobalSearchBarLayout->execute( "search-in-files" );
} else { } else {
KeyEvent keyEvent( mGlobalSearchTree, Event::KeyDown, KEY_RETURN, SCANCODE_UNKNOWN, 0, KeyEvent keyEvent( mGlobalSearchTree, Event::KeyDown, KEY_RETURN, SCANCODE_UNKNOWN, 0,
0 ); 0 );
mGlobalSearchTree->forceKeyDown( keyEvent ); mGlobalSearchTree->forceKeyDown( keyEvent );
} }
} ); };
mGlobalSearchInput->on( Event::OnPressEnter, pressEnterCb );
mGlobalSearchWhereInput->on( Event::OnPressEnter, pressEnterCb );
auto switchInputToTree = [this]( const Event* event ) { auto switchInputToTree = [this]( const Event* event ) {
const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event ); const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event );
Uint32 keyCode = keyEvent->getKeyCode(); Uint32 keyCode = keyEvent->getKeyCode();
@@ -238,8 +241,8 @@ void GlobalSearchController::initGlobalSearchBar(
mGlobalSearchTree->setFocus(); mGlobalSearchTree->setFocus();
} }
}; };
mGlobalSearchInput->addEventListener( Event::KeyDown, switchInputToTree ); mGlobalSearchInput->on( Event::KeyDown, switchInputToTree );
mGlobalSearchInput->addEventListener( Event::OnSizeChange, [this]( const Event* ) { mGlobalSearchInput->on( Event::OnSizeChange, [this]( const Event* ) {
if ( mGlobalSearchBarLayout->isVisible() ) if ( mGlobalSearchBarLayout->isVisible() )
updateGlobalSearchBar(); updateGlobalSearchBar();
} ); } );
@@ -292,12 +295,12 @@ void GlobalSearchController::initGlobalSearchBar(
addClickListener( replaceButton, "replace-in-files" ); addClickListener( replaceButton, "replace-in-files" );
addClickListener( searchExpandButton, "expand-all" ); addClickListener( searchExpandButton, "expand-all" );
addClickListener( searchCollapseButton, "collapse-all" ); addClickListener( searchCollapseButton, "collapse-all" );
replaceInput->addEventListener( Event::OnPressEnter, [this, replaceInput]( const Event* ) { replaceInput->on( Event::OnPressEnter, [this, replaceInput]( const Event* ) {
if ( replaceInput->hasFocus() ) if ( replaceInput->hasFocus() )
mGlobalSearchBarLayout->execute( "replace-in-files" ); mGlobalSearchBarLayout->execute( "replace-in-files" );
} ); } );
replaceInput->addEventListener( Event::KeyDown, switchInputToTree ); replaceInput->on( Event::KeyDown, switchInputToTree );
mGlobalSearchLayout->addEventListener( Event::KeyDown, [this]( const Event* event ) { mGlobalSearchLayout->on( Event::KeyDown, [this]( const Event* event ) {
const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event ); const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event );
if ( keyEvent->getKeyCode() == KEY_ESCAPE ) { if ( keyEvent->getKeyCode() == KEY_ESCAPE ) {
mGlobalSearchBarLayout->execute( "close-global-searchbar" ); mGlobalSearchBarLayout->execute( "close-global-searchbar" );
@@ -322,9 +325,9 @@ void GlobalSearchController::initGlobalSearchBar(
mGlobalSearchHistory.back().second, true, false ); mGlobalSearchHistory.back().second, true, false );
} else*/ } else*/
{ {
doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), doGlobalSearch( mGlobalSearchInput->getText(), mGlobalSearchWhereInput->getText(),
wholeWordChk->isChecked(), luaPatternChk->isChecked(), caseSensitiveChk->isChecked(), wholeWordChk->isChecked(),
escapeSequenceChk->isChecked(), true ); luaPatternChk->isChecked(), escapeSequenceChk->isChecked(), true );
} }
} ); } );
mGlobalSearchBarLayout->setCommand( mGlobalSearchBarLayout->setCommand(
@@ -336,7 +339,7 @@ void GlobalSearchController::initGlobalSearchBar(
String text( replaceInput->getText() ); String text( replaceInput->getText() );
if ( escapeSequenceChk->isChecked() ) if ( escapeSequenceChk->isChecked() )
text.unescape(); text.unescape();
size_t count = replaceInFiles( text.toUtf8(), replaceData.second ); size_t count = replaceInFiles( text.toUtf8(), replaceData.result );
mGlobalSearchBarLayout->execute( "search-again" ); mGlobalSearchBarLayout->execute( "search-again" );
mGlobalSearchBarLayout->execute( "close-global-searchbar" ); mGlobalSearchBarLayout->execute( "close-global-searchbar" );
mApp->getNotificationCenter()->addNotification( mApp->getNotificationCenter()->addNotification(
@@ -349,7 +352,7 @@ void GlobalSearchController::initGlobalSearchBar(
UITreeViewGlobalSearch::New( mSplitter->getCurrentColorScheme(), true ); UITreeViewGlobalSearch::New( mSplitter->getCurrentColorScheme(), true );
initGlobalSearchTree( mGlobalSearchTreeSearch ); initGlobalSearchTree( mGlobalSearchTreeSearch );
initGlobalSearchTree( mGlobalSearchTreeReplace ); initGlobalSearchTree( mGlobalSearchTreeReplace );
mGlobalSearchTreeReplace->addEventListener( Event::KeyDown, [this]( const Event* event ) { mGlobalSearchTreeReplace->on( Event::KeyDown, [this]( const Event* event ) {
const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event ); const KeyEvent* keyEvent = static_cast<const KeyEvent*>( event );
if ( keyEvent->getSanitizedMod() == KeyMod::getDefaultModifier() && if ( keyEvent->getSanitizedMod() == KeyMod::getDefaultModifier() &&
keyEvent->getKeyCode() == KEY_RETURN ) keyEvent->getKeyCode() == KEY_RETURN )
@@ -416,6 +419,7 @@ void GlobalSearchController::clearHistory() {
mGlobalSearchTree->setSearchStr( "" ); mGlobalSearchTree->setSearchStr( "" );
mGlobalSearchTree->setModel( nullptr ); mGlobalSearchTree->setModel( nullptr );
mGlobalSearchInput->setText( "" ); mGlobalSearchInput->setText( "" );
mGlobalSearchWhereInput->setText( "" );
mGlobalSearchLayout->findByClass( "status_box" )->setVisible( false ); mGlobalSearchLayout->findByClass( "status_box" )->setVisible( false );
mGlobalSearchLayout->findByClass<UITextView>( "search_str" )->setText( "" ); mGlobalSearchLayout->findByClass<UITextView>( "search_str" )->setText( "" );
updateGlobalSearchBar(); updateGlobalSearchBar();
@@ -494,18 +498,18 @@ void GlobalSearchController::updateGlobalSearchBarResults(
void GlobalSearchController::updateGlobalSearchHistory( void GlobalSearchController::updateGlobalSearchHistory(
std::shared_ptr<ProjectSearch::ResultModel> model, const std::string& search, std::shared_ptr<ProjectSearch::ResultModel> model, const std::string& search,
bool searchReplace, bool searchAgain, bool escapeSequence ) { const std::string& filter, bool searchReplace, bool searchAgain, bool escapeSequence ) {
auto listBox = mGlobalSearchHistoryList->getListBox(); auto listBox = mGlobalSearchHistoryList->getListBox();
if ( !searchAgain ) { if ( !searchAgain ) {
mGlobalSearchHistory.push_back( std::make_pair( search, model ) ); mGlobalSearchHistory.push_back( { search, filter, model } );
if ( mGlobalSearchHistory.size() > 10 ) if ( mGlobalSearchHistory.size() > 10 )
mGlobalSearchHistory.pop_front(); mGlobalSearchHistory.pop_front();
std::vector<String> items; std::vector<String> items;
for ( auto item = mGlobalSearchHistory.rbegin(); item != mGlobalSearchHistory.rend(); for ( auto item = mGlobalSearchHistory.rbegin(); item != mGlobalSearchHistory.rend();
item++ ) { item++ ) {
items.push_back( item->first ); items.push_back( item->search );
} }
listBox->clear(); listBox->clear();
@@ -513,21 +517,35 @@ void GlobalSearchController::updateGlobalSearchHistory(
if ( mGlobalSearchHistoryOnItemSelectedCb ) if ( mGlobalSearchHistoryOnItemSelectedCb )
mGlobalSearchHistoryList->removeEventListener( mGlobalSearchHistoryOnItemSelectedCb ); mGlobalSearchHistoryList->removeEventListener( mGlobalSearchHistoryOnItemSelectedCb );
listBox->setSelected( 0 ); listBox->setSelected( 0 );
mGlobalSearchHistoryOnItemSelectedCb = mGlobalSearchHistoryList->addEventListener( mGlobalSearchHistoryOnItemSelectedCb = mGlobalSearchHistoryList->on(
Event::OnItemSelected, [this, escapeSequence, searchReplace]( const Event* ) { Event::OnItemSelected, [this, escapeSequence, searchReplace]( const Event* ) {
auto idx = mGlobalSearchHistoryList->getListBox()->getItemSelectedIndex(); auto idx = mGlobalSearchHistoryList->getListBox()->getItemSelectedIndex();
auto idxItem = mGlobalSearchHistory.at( mGlobalSearchHistory.size() - 1 - idx ); auto idxItem = mGlobalSearchHistory.at( mGlobalSearchHistory.size() - 1 - idx );
updateGlobalSearchBarResults( idxItem.first, idxItem.second, searchReplace, updateGlobalSearchBarResults( idxItem.search, idxItem.result, searchReplace,
escapeSequence ); escapeSequence );
} ); } );
} else if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { } else if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) {
mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - listBox->getItemSelectedIndex()] mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - listBox->getItemSelectedIndex()]
.second = model; .result = model;
} }
} }
void GlobalSearchController::doGlobalSearch( String text, bool caseSensitive, bool wholeWord, std::vector<GlobMatch> GlobalSearchController::parseGlobMatches( const String& str ) {
bool luaPattern, bool escapeSequence, std::vector<GlobMatch> ret;
auto globs = str.split( ',' );
for ( auto& glob : globs ) {
String::trimInPlace( glob );
if ( !glob.empty() && glob[0] == '-' ) {
ret.push_back( { glob.substr( 1 ), true } );
} else {
ret.push_back( { glob, false } );
}
}
return ret;
}
void GlobalSearchController::doGlobalSearch( String text, String filter, bool caseSensitive,
bool wholeWord, bool luaPattern, bool escapeSequence,
bool searchReplace, bool searchAgain ) { bool searchReplace, bool searchAgain ) {
if ( mApp->getDirTree() && mApp->getDirTree()->getFilesCount() > 0 && !text.empty() ) { if ( mApp->getDirTree() && mApp->getDirTree()->getFilesCount() > 0 && !text.empty() ) {
mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch; mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch;
@@ -552,16 +570,16 @@ void GlobalSearchController::doGlobalSearch( String text, bool caseSensitive, bo
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
mApp->getThreadPool(), mApp->getThreadPool(),
#endif #endif
[this, clock, search, loader, searchReplace, searchAgain, escapeSequence, [this, clock, search, loader, searchReplace, searchAgain, escapeSequence, luaPattern,
luaPattern]( const ProjectSearch::Result& res ) { filter]( const ProjectSearch::Result& res ) {
Log::info( "Global search for \"%s\" took %.2fms", search.c_str(), Log::info( "Global search for \"%s\" took %.2fms", search.c_str(),
clock->getElapsedTime().asMilliseconds() ); clock->getElapsedTime().asMilliseconds() );
eeDelete( clock ); eeDelete( clock );
mUISceneNode->runOnMainThread( [this, loader, res, search, searchReplace, mUISceneNode->runOnMainThread( [this, loader, res, search, searchReplace,
searchAgain, escapeSequence, luaPattern] { searchAgain, escapeSequence, luaPattern, filter] {
auto model = ProjectSearch::asModel( res ); auto model = ProjectSearch::asModel( res );
model->setResultFromLuaPattern( luaPattern ); model->setResultFromLuaPattern( luaPattern );
updateGlobalSearchHistory( model, search, searchReplace, searchAgain, updateGlobalSearchHistory( model, search, filter, searchReplace, searchAgain,
escapeSequence ); escapeSequence );
updateGlobalSearchBarResults( search, model, searchReplace, escapeSequence ); updateGlobalSearchBarResults( search, model, searchReplace, escapeSequence );
loader->setVisible( false ); loader->setVisible( false );
@@ -570,7 +588,8 @@ void GlobalSearchController::doGlobalSearch( String text, bool caseSensitive, bo
}, },
caseSensitive, wholeWord, caseSensitive, wholeWord,
luaPattern ? TextDocument::FindReplaceType::LuaPattern luaPattern ? TextDocument::FindReplaceType::LuaPattern
: TextDocument::FindReplaceType::Normal ); : TextDocument::FindReplaceType::Normal,
parseGlobMatches( filter ) );
} }
} }
@@ -594,7 +613,7 @@ void GlobalSearchController::initGlobalSearchTree( UITreeViewGlobalSearch* searc
searchTree->setHeadersVisible( false ); searchTree->setHeadersVisible( false );
searchTree->setColumnsHidden( searchTree->setColumnsHidden(
{ ProjectSearch::ResultModel::Line, ProjectSearch::ResultModel::ColumnStart }, true ); { ProjectSearch::ResultModel::Line, ProjectSearch::ResultModel::ColumnStart }, true );
searchTree->addEventListener( Event::OnModelEvent, [this]( const Event* event ) { searchTree->on( Event::OnModelEvent, [this]( const Event* event ) {
const ModelEvent* modelEvent = static_cast<const ModelEvent*>( event ); const ModelEvent* modelEvent = static_cast<const ModelEvent*>( event );
if ( modelEvent->getModelEventType() == ModelEventType::Open ) { if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
const Model* model = modelEvent->getModel(); const Model* model = modelEvent->getModel();
@@ -678,7 +697,8 @@ PluginRequestHandle GlobalSearchController::processMessage( const PluginMessage&
mUISceneNode->runOnMainThread( [this, search, model] { mUISceneNode->runOnMainThread( [this, search, model] {
showGlobalSearch( false ); showGlobalSearch( false );
mGlobalSearchInput->setText( search ); mGlobalSearchInput->setText( search );
updateGlobalSearchHistory( model, search, false, false, false ); mGlobalSearchWhereInput->setText( "" );
updateGlobalSearchHistory( model, search, "", false, false, false );
updateGlobalSearchBarResults( search, model, false, false ); updateGlobalSearchBarResults( search, model, false, false );
} ); } );
+14 -6
View File
@@ -47,8 +47,9 @@ class GlobalSearchController {
void initGlobalSearchTree( UITreeViewGlobalSearch* searchTree ); void initGlobalSearchTree( UITreeViewGlobalSearch* searchTree );
void doGlobalSearch( String text, bool caseSensitive, bool wholeWord, bool luaPattern, void doGlobalSearch( String text, String filter, bool caseSensitive, bool wholeWord,
bool escapeSequence, bool searchReplace, bool searchAgain = false ); bool luaPattern, bool escapeSequence, bool searchReplace,
bool searchAgain = false );
size_t replaceInFiles( const std::string& replaceText, size_t replaceInFiles( const std::string& replaceText,
std::shared_ptr<ProjectSearch::ResultModel> model ); std::shared_ptr<ProjectSearch::ResultModel> model );
@@ -76,18 +77,25 @@ class GlobalSearchController {
UITreeViewGlobalSearch* mGlobalSearchTreeSearch{ nullptr }; UITreeViewGlobalSearch* mGlobalSearchTreeSearch{ nullptr };
UITreeViewGlobalSearch* mGlobalSearchTreeReplace{ nullptr }; UITreeViewGlobalSearch* mGlobalSearchTreeReplace{ nullptr };
UITextInput* mGlobalSearchInput{ nullptr }; UITextInput* mGlobalSearchInput{ nullptr };
UITextInput* mGlobalSearchWhereInput{ nullptr };
UIDropDownList* mGlobalSearchHistoryList{ nullptr }; UIDropDownList* mGlobalSearchHistoryList{ nullptr };
Uint32 mGlobalSearchHistoryOnItemSelectedCb{ 0 }; Uint32 mGlobalSearchHistoryOnItemSelectedCb{ 0 };
std::deque<std::pair<std::string, std::shared_ptr<ProjectSearch::ResultModel>>> struct SearchHistoryItem {
mGlobalSearchHistory; std::string search;
std::string filter;
std::shared_ptr<ProjectSearch::ResultModel> result;
};
std::deque<SearchHistoryItem> mGlobalSearchHistory;
void onLoadDone( const Variant& lineNum, const Variant& colNum ); void onLoadDone( const Variant& lineNum, const Variant& colNum );
PluginRequestHandle processMessage( const PluginMessage& msg ); PluginRequestHandle processMessage( const PluginMessage& msg );
void updateGlobalSearchHistory( std::shared_ptr<ProjectSearch::ResultModel> model, void updateGlobalSearchHistory( std::shared_ptr<ProjectSearch::ResultModel> model,
const std::string& search, bool searchReplace, bool searchAgain, const std::string& search, const std::string& filter,
bool escapeSequence ); bool searchReplace, bool searchAgain, bool escapeSequence );
std::vector<GlobMatch> parseGlobMatches( const String& str );
}; };
} // namespace ecode } // namespace ecode
+2 -148
View File
@@ -5,152 +5,6 @@
namespace ecode { namespace ecode {
// Author: Robert A. van Engelen, engelen@genivia.com
// Date: August 5, 2019
// License: The Code Project Open License (CPOL)
// https://www.codeproject.com/info/cpol10.aspx
#define DOTGLOB 1
#define PATHSEP '/'
#define CASE( c, caseInsensitive ) ( caseInsensitive ? std::tolower( c ) : ( c ) )
bool IgnoreMatcher::globMatch( const std::string& text, const std::string_view& glob,
bool caseInsensitive ) {
size_t i = 0;
size_t j = 0;
size_t n = text.size();
size_t m = glob.size();
size_t text1_backup = std::string::npos;
size_t glob1_backup = std::string::npos;
size_t text2_backup = std::string::npos;
size_t glob2_backup = std::string::npos;
bool nodot = !DOTGLOB;
// match pathname if glob contains a / otherwise match the basename
if ( j + 1 < m && glob[j] == '/' ) {
// if pathname starts with ./ then ignore these pairs
while ( i + 1 < n && text[i] == '.' && text[i + 1] == PATHSEP )
i += 2;
// if pathname starts with a / then ignore it
if ( i < n && text[i] == PATHSEP )
i++;
j++;
} else if ( glob.find( '/' ) == std::string::npos ) {
size_t sep = text.rfind( PATHSEP );
if ( sep != std::string::npos )
i = sep + 1;
}
while ( i < n ) {
if ( j < m ) {
switch ( glob[j] ) {
case '*':
// match anything except . after /
if ( nodot && text[i] == '.' )
break;
if ( ++j < m && glob[j] == '*' ) {
// trailing ** match everything after /
if ( ++j >= m )
return true;
// ** followed by a / match zero or more directories
if ( glob[j] != '/' )
return false;
// new **-loop, discard *-loop
text1_backup = std::string::npos;
glob1_backup = std::string::npos;
text2_backup = i;
glob2_backup = ++j;
continue;
}
// trailing * matches everything except /
text1_backup = i;
glob1_backup = j;
continue;
case '?':
// match anything except . after /
if ( nodot && text[i] == '.' )
break;
// match any character except /
if ( text[i] == PATHSEP )
break;
i++;
j++;
continue;
case '[': {
// match anything except . after /
if ( nodot && text[i] == '.' )
break;
// match any character in [...] except /
if ( text[i] == PATHSEP )
break;
int lastchr;
bool matched = false;
bool reverse = j + 1 < m && ( glob[j + 1] == '^' || glob[j + 1] == '!' );
// inverted character class
if ( reverse )
j++;
// match character class
for ( lastchr = 256; ++j < m && glob[j] != ']';
lastchr = CASE( glob[j], caseInsensitive ) )
if ( lastchr < 256 && glob[j] == '-' && j + 1 < m && glob[j + 1] != ']'
? CASE( text[i], caseInsensitive ) <=
CASE( glob[++j], caseInsensitive ) &&
CASE( text[i], caseInsensitive ) >= lastchr
: CASE( text[i], caseInsensitive ) ==
CASE( glob[j], caseInsensitive ) )
matched = true;
if ( matched == reverse )
break;
i++;
if ( j < m )
j++;
continue;
}
case '\\':
// literal match \-escaped character
if ( j + 1 < m )
j++;
// FALLTHROUGH
default:
// match the current non-NUL character
if ( CASE( glob[j], caseInsensitive ) != CASE( text[i], caseInsensitive ) &&
!( glob[j] == '/' && text[i] == PATHSEP ) )
break;
// do not match a . with *, ? [] after /
nodot = !DOTGLOB && glob[j] == '/';
i++;
j++;
continue;
}
}
if ( glob1_backup != std::string::npos && text[text1_backup] != PATHSEP ) {
// *-loop: backtrack to the last * but do not jump over /
i = ++text1_backup;
j = glob1_backup;
continue;
}
if ( glob2_backup != std::string::npos ) {
// **-loop: backtrack to the last **
i = ++text2_backup;
j = glob2_backup;
continue;
}
return false;
}
// ignore trailing stars
while ( j < m && glob[j] == '*' )
j++;
// at end of text means success if nothing else is left to match
return j >= m;
}
bool IgnoreMatcher::globMatch( const std::string& text, const std::vector<std::string>& globs,
bool caseInsensitive ) {
for ( const auto& glob : globs ) {
if ( globMatch( text, glob, caseInsensitive ) )
return true;
}
return false;
}
IgnoreMatcher::IgnoreMatcher( const std::string& rootPath ) : mPath( rootPath ) { IgnoreMatcher::IgnoreMatcher( const std::string& rootPath ) : mPath( rootPath ) {
FileSystem::dirAddSlashAtEnd( mPath ); FileSystem::dirAddSlashAtEnd( mPath );
} }
@@ -207,7 +61,7 @@ bool GitIgnoreMatcher::match( const std::string& value ) const {
bool match = false; bool match = false;
for ( size_t i = 0; i < mPatterns.size(); i++ ) { for ( size_t i = 0; i < mPatterns.size(); i++ ) {
auto& pattern = mPatterns[i]; auto& pattern = mPatterns[i];
match = globMatch( value, pattern.first ); match = String::globMatch( value, pattern.first );
if ( pattern.second ) if ( pattern.second )
match = !match; match = !match;
if ( match && !pattern.second ) { if ( match && !pattern.second ) {
@@ -215,7 +69,7 @@ bool GitIgnoreMatcher::match( const std::string& value ) const {
for ( size_t n = i + 1; n < mPatterns.size(); n++ ) { for ( size_t n = i + 1; n < mPatterns.size(); n++ ) {
// Check if there's a positive negate after the match // Check if there's a positive negate after the match
if ( mPatterns[n].second ) { if ( mPatterns[n].second ) {
if ( globMatch( value, mPatterns[n].first ) ) if ( String::globMatch( value, mPatterns[n].first ) )
return false; return false;
} else { } else {
break; break;
-6
View File
@@ -28,12 +28,6 @@ class IgnoreMatcher {
const bool& matcherReady() const { return mMatcherReady; } const bool& matcherReady() const { return mMatcherReady; }
static bool globMatch( const std::string& text, const std::string_view& glob,
bool caseInsensitive = false );
static bool globMatch( const std::string& text, const std::vector<std::string>& globs,
bool caseInsensitive = false );
protected: protected:
std::string mPath; std::string mPath;
bool mMatcherReady{ false }; bool mMatcherReady{ false };
+51 -2
View File
@@ -144,13 +144,25 @@ searchInFileLuaPattern( const std::string& file, const std::string& text, const
void ProjectSearch::find( const std::vector<std::string> files, const std::string& string, void ProjectSearch::find( const std::vector<std::string> files, const std::string& string,
ResultCb result, bool caseSensitive, bool wholeWord, ResultCb result, bool caseSensitive, bool wholeWord,
const TextDocument::FindReplaceType& type ) { const TextDocument::FindReplaceType& type,
const std::vector<GlobMatch>& pathFilters ) {
Result res; Result res;
const auto occ = const auto occ =
type == TextDocument::FindReplaceType::Normal type == TextDocument::FindReplaceType::Normal
? String::BMH::createOccTable( (const unsigned char*)string.c_str(), string.size() ) ? String::BMH::createOccTable( (const unsigned char*)string.c_str(), string.size() )
: std::vector<size_t>(); : std::vector<size_t>();
for ( auto& file : files ) { for ( auto& file : files ) {
bool skip = false;
for ( const auto& filter : pathFilters ) {
bool matches = String::globMatch( file, filter.first );
if ( ( matches && filter.second ) || ( !matches && !filter.second ) ) {
skip = true;
break;
}
}
if ( skip )
continue;
auto fileRes = type == TextDocument::FindReplaceType::Normal auto fileRes = type == TextDocument::FindReplaceType::Normal
? searchInFileHorspool( file, string, caseSensitive, wholeWord, occ ) ? searchInFileHorspool( file, string, caseSensitive, wholeWord, occ )
: searchInFileLuaPattern( file, string, caseSensitive, wholeWord ); : searchInFileLuaPattern( file, string, caseSensitive, wholeWord );
@@ -169,7 +181,8 @@ struct FindData {
void ProjectSearch::find( const std::vector<std::string> files, std::string string, void ProjectSearch::find( const std::vector<std::string> files, std::string string,
std::shared_ptr<ThreadPool> pool, ResultCb result, bool caseSensitive, std::shared_ptr<ThreadPool> pool, ResultCb result, bool caseSensitive,
bool wholeWord, const TextDocument::FindReplaceType& type ) { bool wholeWord, const TextDocument::FindReplaceType& type,
const std::vector<GlobMatch>& pathFilters ) {
if ( files.empty() ) if ( files.empty() )
result( {} ); result( {} );
FindData* findData = eeNew( FindData, () ); FindData* findData = eeNew( FindData, () );
@@ -180,7 +193,43 @@ void ProjectSearch::find( const std::vector<std::string> files, std::string stri
type == TextDocument::FindReplaceType::Normal type == TextDocument::FindReplaceType::Normal
? String::BMH::createOccTable( (const unsigned char*)string.c_str(), string.size() ) ? String::BMH::createOccTable( (const unsigned char*)string.c_str(), string.size() )
: std::vector<size_t>(); : std::vector<size_t>();
std::vector<bool> search;
search.resize( files.size() );
size_t pos = 0;
size_t count = 0;
for ( auto& file : files ) { for ( auto& file : files ) {
bool skip = false;
for ( const auto& filter : pathFilters ) {
bool matches = String::globMatch( file, filter.first );
if ( ( matches && filter.second ) || ( !matches && !filter.second ) ) {
skip = true;
break;
}
}
if ( skip ) {
search[pos++] = false;
continue;
}
search[pos++] = true;
count++;
}
findData->resCount = count;
if ( count == 0 ) {
result( findData->res );
eeDelete( findData );
return;
}
pos = 0;
for ( const auto& file : files ) {
if ( !search[pos] ) {
pos++;
continue;
}
pos++;
pool->run( pool->run(
[findData, file, string, caseSensitive, wholeWord, occ, type] { [findData, file, string, caseSensitive, wholeWord, occ, type] {
auto fileRes = auto fileRes =
+7 -2
View File
@@ -16,6 +16,9 @@ using namespace EE::UI::Models;
namespace ecode { namespace ecode {
using GlobMatch = std::pair<std::string, bool>; // where string is the glob and bool true
// indicates that it's inverted / negated
class ProjectSearch { class ProjectSearch {
public: public:
struct ResultData { struct ResultData {
@@ -208,13 +211,15 @@ class ProjectSearch {
static void static void
find( const std::vector<std::string> files, const std::string& string, ResultCb result, find( const std::vector<std::string> files, const std::string& string, ResultCb result,
bool caseSensitive, bool wholeWord = false, bool caseSensitive, bool wholeWord = false,
const TextDocument::FindReplaceType& type = TextDocument::FindReplaceType::Normal ); const TextDocument::FindReplaceType& type = TextDocument::FindReplaceType::Normal,
const std::vector<GlobMatch>& pathFilters = {} );
static void static void
find( const std::vector<std::string> files, std::string string, find( const std::vector<std::string> files, std::string string,
std::shared_ptr<ThreadPool> pool, ResultCb result, bool caseSensitive, std::shared_ptr<ThreadPool> pool, ResultCb result, bool caseSensitive,
bool wholeWord = false, bool wholeWord = false,
const TextDocument::FindReplaceType& type = TextDocument::FindReplaceType::Normal ); const TextDocument::FindReplaceType& type = TextDocument::FindReplaceType::Normal,
const std::vector<GlobMatch>& pathFilters = {} );
}; };
} // namespace ecode } // namespace ecode