Allow to add a filtered file to the project file tree by adding it in .ecode/.prjallowed (SpartanJ/ecode#592).

Allow canceling the global search with escape.
This commit is contained in:
Martín Lucas Golini
2025-07-17 16:43:42 -03:00
parent e5fd8c1647
commit a537214cc7
12 changed files with 155 additions and 102 deletions

View File

@@ -541,7 +541,7 @@ class EE_API String {
/** glob matches a string against a glob
** @return True if matches
*/
static bool globMatch( const std::string_view& text, const std::string_view& glob,
static bool globMatch( std::string_view text, std::string_view glob,
bool caseInsensitive = false );
/** glob matches a string against a set of globs

View File

@@ -227,8 +227,7 @@ const std::size_t String::InvalidPos = StringType::npos;
#define PATHSEP '/'
#define CASE( c, caseInsensitive ) ( caseInsensitive ? std::tolower( c ) : ( c ) )
bool String::globMatch( const std::string_view& text, const std::string_view& glob,
bool caseInsensitive ) {
bool String::globMatch( std::string_view text, std::string_view glob, bool caseInsensitive ) {
size_t i = 0;
size_t j = 0;
size_t n = text.size();

View File

@@ -551,9 +551,6 @@ R"html(
<TextView id="image_close" lw="wc" lh="wc" text="&#xeb99;" lg="top|right" enabled="false" />
<Loader id="image_loader" lw="64dp" lh="64dp" outline-thickness="6dp" lg="center" visible="false" />
</RelativeLayout>
<vbox id="notification_center" lw="256dp" lh="wc"
lg="right|bottom" margin-right="22dp" margin-bottom="56dp">
</vbox>
</RelativeLayout>
</Splitter>
<searchbar id="search_bar" lw="mp" lh="wc">
@@ -643,4 +640,7 @@ R"html(
tooltip='@string(menu_hold_shift_hint_desc, "Keeping \"Shift\" clicked while changing any options it will keep the menu open.")' />
</MainLayout>
</vbox>
<vbox id="notification_center" lw="256dp" lh="wc"
lg="right|bottom" margin-right="22dp" margin-bottom="56dp">
</vbox>
)html"

View File

@@ -593,7 +593,7 @@ void GlobalSearchController::showGlobalSearch( bool searchReplace ) {
mGlobalSearchInput->setText( text );
}
mGlobalSearchInput->getDocument().selectAll();
auto* loader = mGlobalSearchTree->getParent()->find( "loader" );
auto* loader = mGlobalSearchLayout->getParent()->find( "loader" );
if ( loader )
loader->setVisible( true );
if ( !searchReplace ) {
@@ -671,10 +671,16 @@ void GlobalSearchController::updateGlobalSearchBar() {
void GlobalSearchController::hideGlobalSearchBar() {
mGlobalSearchBarLayout->setEnabled( false )->setVisible( false );
mGlobalSearchLayout->setVisible( false );
auto* loader = mGlobalSearchTree->getParent()->find( "loader" );
if ( loader )
auto* loader = mGlobalSearchLayout->getParent()->find( "loader" );
if ( loader ) {
loader->setVisible( false );
loader->close();
}
mApp->getStatusBar()->updateState();
if ( mCurSearch ) {
mCurSearch->active = false;
mApp->getThreadPool()->removeWithTag( mCurSearch->taskTag );
}
}
void GlobalSearchController::toggleGlobalSearchBar() {
@@ -762,67 +768,78 @@ void GlobalSearchController::doGlobalSearch( String text, String filter, bool ca
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 );
mGlobalSearchTreeReplace->setVisible( searchReplace );
mGlobalSearchLayout->findByClass( "status_box" )->setVisible( true );
mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( false );
mGlobalSearchLayout->findByClass<UITextView>( "search_str" )->setText( text );
UILoader* loader = UILoader::New();
loader->setId( "loader" );
loader->setRadius( 48 );
loader->setOutlineThickness( 6 );
loader->setParent( mGlobalSearchLayout->getParent() );
loader->setPosition( mGlobalSearchLayout->getPosition() +
mGlobalSearchLayout->getSize() * 0.5f - loader->getSize() * 0.5f );
Clock clock;
if ( escapeSequence )
text.unescape();
std::string search( text.toUtf8() );
if ( nullptr == mApp->getDirTree() || !mApp->getDirTree()->isReady() ) {
mApp->getNotificationCenter()->addNotification( mApp->i18n(
"project_still_indexing", "Project is still being indexed, please wait a moment." ) );
return;
}
std::vector<std::shared_ptr<TextDocument>> openDocs;
mSplitter->forEachDocSharedPtr(
[&openDocs]( auto doc ) { openDocs.emplace_back( std::move( doc ) ); } );
auto filters = parseGlobMatches( filter );
auto imageExts = Image::getImageExtensionsSupported();
imageExts.erase( std::remove_if( imageExts.begin(), imageExts.end(),
[]( const std::string& ext ) { return ext == "svg"; } ),
imageExts.end() );
filters.reserve( filters.size() + imageExts.size() );
for ( const auto& ext : imageExts ) {
// If user explicitly requested to filter some extension, respect it and do not add the
// ignore for it.
std::string extGlob( "*." + ext );
if ( std::find_if( filters.begin(), filters.end(),
[&extGlob]( const std::pair<std::string, bool>& filter ) {
return filter.first == extGlob;
} ) != filters.end() )
continue;
filters.emplace_back( extGlob, true );
}
if ( mApp->getDirTree()->getFilesCount() == 0 || text.empty() ) {
return;
}
ProjectSearch::find(
mApp->getDirTree()->getFiles(), search, mApp->getThreadPool(),
[this, clock, search, loader, searchReplace, searchAgain, escapeSequence, searchType,
filter]( const ProjectSearch::ConsolidatedResult& res ) {
Log::info( "Global search for \"%s\" took %s", search.c_str(),
clock.getElapsedTime().toString() );
mUISceneNode->runOnMainThread( [this, loader, res = std::move( res ), search,
searchReplace, searchAgain, escapeSequence,
searchType, filter] {
mLastSearchConfig = std::move( res.first );
auto model = ProjectSearch::asModel( res.second );
model->setOpType( searchType );
updateGlobalSearchHistory( model, search, filter, searchReplace, searchAgain,
escapeSequence );
updateGlobalSearchBarResults( search, model, searchReplace, escapeSequence );
mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch;
mGlobalSearchTreeSearch->setVisible( !searchReplace );
mGlobalSearchTreeReplace->setVisible( searchReplace );
mGlobalSearchLayout->findByClass( "status_box" )->setVisible( true );
mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( false );
mGlobalSearchLayout->findByClass<UITextView>( "search_str" )->setText( text );
UILoader* loader = UILoader::New();
loader->setId( "loader" );
loader->setRadius( 48 );
loader->setOutlineThickness( 6 );
loader->setParent( mGlobalSearchLayout->getParent() );
loader->setPosition( mGlobalSearchLayout->getPosition() +
mGlobalSearchLayout->getSize() * 0.5f - loader->getSize() * 0.5f );
Clock clock;
if ( escapeSequence )
text.unescape();
std::string search( text.toUtf8() );
std::vector<std::shared_ptr<TextDocument>> openDocs;
mSplitter->forEachDocSharedPtr(
[&openDocs]( auto doc ) { openDocs.emplace_back( std::move( doc ) ); } );
auto filters = parseGlobMatches( filter );
auto imageExts = Image::getImageExtensionsSupported();
imageExts.erase( std::remove_if( imageExts.begin(), imageExts.end(),
[]( const std::string& ext ) { return ext == "svg"; } ),
imageExts.end() );
filters.reserve( filters.size() + imageExts.size() );
for ( const auto& ext : imageExts ) {
// If user explicitly requested to filter some extension, respect it and do not add the
// ignore for it.
std::string extGlob( "*." + ext );
if ( std::find_if( filters.begin(), filters.end(),
[&extGlob]( const std::pair<std::string, bool>& filter ) {
return filter.first == extGlob;
} ) != filters.end() )
continue;
filters.emplace_back( extGlob, true );
}
mCurSearch = ProjectSearch::find(
mApp->getDirTree()->getFiles(), search, mApp->getThreadPool(),
[this, clock, search, searchReplace, searchAgain, escapeSequence, searchType,
filter]( const ProjectSearch::ConsolidatedResult& res ) {
Log::info( "Global search for \"%s\" took %s", search.c_str(),
clock.getElapsedTime().toString() );
mUISceneNode->runOnMainThread( [this, res = std::move( res ), search, searchReplace,
searchAgain, escapeSequence, searchType, filter] {
mLastSearchConfig = std::move( res.first );
auto model = ProjectSearch::asModel( res.second );
model->setOpType( searchType );
updateGlobalSearchHistory( model, search, filter, searchReplace, searchAgain,
escapeSequence );
updateGlobalSearchBarResults( search, model, searchReplace, escapeSequence );
auto* loader = mGlobalSearchLayout->getParent()->find( "loader" );
if ( loader ) {
loader->setVisible( false );
loader->close();
} );
},
caseSensitive, wholeWord, searchType, filters, mApp->getCurrentProject(), openDocs );
}
}
mCurSearch = nullptr;
} );
},
caseSensitive, wholeWord, searchType, filters, mApp->getCurrentProject(), openDocs );
}
void GlobalSearchController::onLoadDone( const Variant& lineNum, const Variant& colNum ) {

View File

@@ -87,6 +87,7 @@ class GlobalSearchController {
std::deque<SearchHistoryItem> mGlobalSearchHistory;
bool mValueChanging{ false };
ProjectSearch::SearchConfig mLastSearchConfig;
ProjectSearch::FindData* mCurSearch{ nullptr };
void onLoadDone( const Variant& lineNum, const Variant& colNum );

View File

@@ -57,7 +57,7 @@ bool GitIgnoreMatcher::parse() {
return !mPatterns.empty();
}
bool GitIgnoreMatcher::match( const std::string& value ) const {
bool GitIgnoreMatcher::match( std::string_view value ) const {
if ( mPatterns.empty() )
return false;
bool match = false;

View File

@@ -18,7 +18,7 @@ class IgnoreMatcher {
virtual bool canMatch() = 0;
virtual bool match( const std::string& value ) const = 0;
virtual bool match( std::string_view value ) const = 0;
virtual std::string findRepositoryRootPath() const = 0;
@@ -47,7 +47,7 @@ class GitIgnoreMatcher : public IgnoreMatcher {
const std::string& getIgnoreFilePath() const override;
bool match( const std::string& value ) const override;
bool match( std::string_view value ) const override;
std::string findRepositoryRootPath() const override;

View File

@@ -45,6 +45,7 @@ void NotificationCenter::addNotification( const String& text, const Time& delay,
tv->setText( text );
tv->addClass( "notification" );
tv->setTextSelection( allowCopy );
mLayout->toFront();
Action* sequence = Actions::Sequence::New(
{ Actions::FadeIn::New( Seconds( 0.125 ) ), Actions::Delay::New( delay ),
Actions::FadeOut::New( Seconds( 0.125 ) ), Actions::Close::New() } );
@@ -81,6 +82,7 @@ void NotificationCenter::addShowRequest( const String& uri, const String& action
Action* sequence = Actions::Sequence::New(
{ Actions::FadeIn::New( Seconds( 0.125 ) ), Actions::Delay::New( delay ),
Actions::FadeOut::New( Seconds( 0.125 ) ), Actions::Close::New() } );
mLayout->toFront();
lay->runAction( sequence );
};
@@ -114,14 +116,14 @@ void NotificationCenter::addInteractiveNotification( String text, String actionT
} );
UIPushButton* pb = lay->findByType( UI_TYPE_PUSHBUTTON )->asType<UIPushButton>();
pb->setText( actionText );
pb->onClick(
[actionText, onInteraction = std::move( onInteraction )]( const MouseEvent* event ) {
if ( onInteraction )
onInteraction();
} );
pb->onClick( [actionText, onInteraction = std::move( onInteraction )]( const MouseEvent* ) {
if ( onInteraction )
onInteraction();
} );
Action* sequence = Actions::Sequence::New(
{ Actions::FadeIn::New( Seconds( 0.125 ) ), Actions::Delay::New( delay ),
Actions::FadeOut::New( Seconds( 0.125 ) ), Actions::Close::New() } );
mLayout->toFront();
lay->runAction( sequence );
};

View File

@@ -80,6 +80,18 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent&
break;
}
}
if ( !found && mAllowedMatcher ) {
std::string_view file{ files[i] };
if ( String::startsWith( file, mAllowedMatcher->getPath() ) ) {
std::string_view localPath( std::string_view{ file }.substr(
mAllowedMatcher->getPath().size() ) );
if ( mAllowedMatcher->match( localPath ) ) {
found = true;
}
} else if ( mAllowedMatcher->match( file ) ) {
found = true;
}
}
if ( found ) {
mFiles.emplace_back( std::move( files[i] ) );
mNames.emplace_back( std::move( names[i] ) );
@@ -339,16 +351,18 @@ void ProjectDirectoryTree::getDirectoryFiles(
if ( ignoreMatcher.foundMatch() && ignoreMatcher.match( directory, file ) ) {
if ( !allowedMatcher || !allowedMatcher->hasPatterns() )
continue;
std::string localPath;
std::string_view localPath( fullpath );
if ( String::startsWith( directory, allowedMatcher->getPath() ) )
localPath = directory.substr( allowedMatcher->getPath().size() );
if ( !allowedMatcher->match( localPath + file ) )
localPath = std::string_view{ fullpath }.substr( allowedMatcher->getPath().size() );
if ( !allowedMatcher->match( localPath ) )
continue;
} else if ( disallowedMatcher && disallowedMatcher->hasPatterns() ) {
std::string localPath;
if ( String::startsWith( directory, disallowedMatcher->getPath() ) )
localPath = directory.substr( disallowedMatcher->getPath().size() );
if ( disallowedMatcher->match( localPath + file ) )
std::string_view localPath( fullpath );
if ( String::startsWith( directory, disallowedMatcher->getPath() ) ) {
localPath =
std::string_view{ fullpath }.substr( disallowedMatcher->getPath().size() );
}
if ( disallowedMatcher->match( localPath ) )
continue;
}

View File

@@ -167,6 +167,8 @@ class ProjectDirectoryTree {
bool isRunning() const { return mRunning; }
bool isReady() const { return mIsReady; }
protected:
std::string mPath;
std::shared_ptr<ThreadPool> mPool;

View File

@@ -159,13 +159,6 @@ static std::vector<ProjectSearch::ResultData::Result> searchInFileRegEx( const s
return searchInFilePatternMatch( file, pattern, caseSensitive, wholeWord );
}
struct FindData {
Mutex resMutex;
Mutex countMutex;
int resCount{ 0 };
ProjectSearch::Result res;
};
std::vector<ProjectSearch::ResultData::Result>
ProjectSearch::fileResFromDoc( const std::string& string, bool caseSensitive, bool wholeWord,
TextDocument::FindReplaceType type,
@@ -192,20 +185,29 @@ ProjectSearch::fileResFromDoc( const std::string& string, bool caseSensitive, bo
return fileRes;
}
void ProjectSearch::find( const std::vector<std::string> files, std::string string,
std::shared_ptr<ThreadPool> pool, ResultCb result, bool caseSensitive,
bool wholeWord, const TextDocument::FindReplaceType& type,
const std::vector<GlobMatch>& pathFilters, std::string basePath,
std::vector<std::shared_ptr<TextDocument>> openDocs ) {
if ( files.empty() )
ProjectSearch::FindData*
ProjectSearch::find( const std::vector<std::string> files, std::string string,
std::shared_ptr<ThreadPool> pool, ResultCb result, bool caseSensitive,
bool wholeWord, const TextDocument::FindReplaceType& type,
const std::vector<GlobMatch>& pathFilters, std::string basePath,
std::vector<std::shared_ptr<TextDocument>> openDocs ) {
static const std::string_view PROJECT_SEARCH_TASK_TAG = "ProjectSearchFindTag";
static const Uint64 PROJECT_SEARCH_TASK_TAG_HASH =
std::hash<std::string_view>()( PROJECT_SEARCH_TASK_TAG );
if ( files.empty() ) {
result( {} );
return nullptr;
}
FindData* findData = eeNew( FindData, () );
findData->taskTag = PROJECT_SEARCH_TASK_TAG_HASH;
FileSystem::dirAddSlashAtEnd( basePath );
pool->run( [files = std::move( files ), string = std::move( string ), pool = std::move( pool ),
result = std::move( result ), caseSensitive, wholeWord, type,
pathFilters = std::move( pathFilters ), basePath = std::move( basePath ),
pool->run( [findData, files = std::move( files ), string = std::move( string ),
pool = std::move( pool ), result = std::move( result ), caseSensitive, wholeWord,
type, pathFilters = std::move( pathFilters ), basePath = std::move( basePath ),
openDocs = std::move( openDocs )]() mutable {
SearchConfig searchConfig( string, caseSensitive, wholeWord, type );
FindData* findData = eeNew( FindData, () );
findData->resCount = files.size();
if ( !caseSensitive )
String::toLowerInPlace( string );
@@ -244,6 +246,12 @@ void ProjectSearch::find( const std::vector<std::string> files, std::string stri
}
}
if ( !findData->active ) {
result( {} );
eeDelete( findData );
return;
}
if ( skip ) {
search[pos++] = false;
continue;
@@ -255,7 +263,7 @@ void ProjectSearch::find( const std::vector<std::string> files, std::string stri
findData->resCount = count;
if ( count == 0 ) {
if ( count == 0 || !findData->active ) {
result( { searchConfig, findData->res } );
eeDelete( findData );
return;
@@ -310,7 +318,7 @@ void ProjectSearch::find( const std::vector<std::string> files, std::string stri
doc );
}
},
onSearchEnd );
onSearchEnd, PROJECT_SEARCH_TASK_TAG_HASH );
} else {
pool->run(
[findData, file, string, caseSensitive, wholeWord, occ, type]() mutable {
@@ -327,10 +335,11 @@ void ProjectSearch::find( const std::vector<std::string> files, std::string stri
findData->res.emplace_back( std::string( file ), std::move( fileRes ) );
}
},
onSearchEnd );
onSearchEnd, PROJECT_SEARCH_TASK_TAG_HASH );
}
}
} );
return findData;
}
void ProjectSearch::ResultModel::removeLastNewLineCharacter() {

View File

@@ -243,7 +243,16 @@ class ProjectSearch {
fileResFromDoc( const std::string& string, bool caseSensitive, bool wholeWord,
TextDocument::FindReplaceType type, std::shared_ptr<TextDocument> doc );
static void
struct FindData {
Mutex resMutex;
Mutex countMutex;
int resCount{ 0 };
ProjectSearch::Result res;
bool active{ true };
Uint64 taskTag{ 0 };
};
static FindData*
find( const std::vector<std::string> files, std::string string,
std::shared_ptr<ThreadPool> pool, ResultCb result, bool caseSensitive,
bool wholeWord = false,