From a537214cc7136b63291b4f2c35f0bcd8cd6b5204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 17 Jul 2025 16:43:42 -0300 Subject: [PATCH] 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. --- include/eepp/core/string.hpp | 2 +- src/eepp/core/string.cpp | 3 +- src/tools/ecode/applayout.xml.hpp | 6 +- src/tools/ecode/globalsearchcontroller.cpp | 137 ++++++++++++--------- src/tools/ecode/globalsearchcontroller.hpp | 1 + src/tools/ecode/ignorematcher.cpp | 2 +- src/tools/ecode/ignorematcher.hpp | 4 +- src/tools/ecode/notificationcenter.cpp | 12 +- src/tools/ecode/projectdirectorytree.cpp | 28 +++-- src/tools/ecode/projectdirectorytree.hpp | 2 + src/tools/ecode/projectsearch.cpp | 49 +++++--- src/tools/ecode/projectsearch.hpp | 11 +- 12 files changed, 155 insertions(+), 102 deletions(-) diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index 1595781b8..9ab249c4c 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -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 diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index eb93d2d04..794114636 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -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(); diff --git a/src/tools/ecode/applayout.xml.hpp b/src/tools/ecode/applayout.xml.hpp index 26ac77ac9..3a2a5f14f 100644 --- a/src/tools/ecode/applayout.xml.hpp +++ b/src/tools/ecode/applayout.xml.hpp @@ -551,9 +551,6 @@ R"html( - - @@ -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.")' /> + + )html" diff --git a/src/tools/ecode/globalsearchcontroller.cpp b/src/tools/ecode/globalsearchcontroller.cpp index fc23745a4..1fd2e0027 100644 --- a/src/tools/ecode/globalsearchcontroller.cpp +++ b/src/tools/ecode/globalsearchcontroller.cpp @@ -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( "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> 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& 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( "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> 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& 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 ) { diff --git a/src/tools/ecode/globalsearchcontroller.hpp b/src/tools/ecode/globalsearchcontroller.hpp index 6dd6e0023..25314521f 100644 --- a/src/tools/ecode/globalsearchcontroller.hpp +++ b/src/tools/ecode/globalsearchcontroller.hpp @@ -87,6 +87,7 @@ class GlobalSearchController { std::deque mGlobalSearchHistory; bool mValueChanging{ false }; ProjectSearch::SearchConfig mLastSearchConfig; + ProjectSearch::FindData* mCurSearch{ nullptr }; void onLoadDone( const Variant& lineNum, const Variant& colNum ); diff --git a/src/tools/ecode/ignorematcher.cpp b/src/tools/ecode/ignorematcher.cpp index c08e6144f..27e9a44d1 100644 --- a/src/tools/ecode/ignorematcher.cpp +++ b/src/tools/ecode/ignorematcher.cpp @@ -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; diff --git a/src/tools/ecode/ignorematcher.hpp b/src/tools/ecode/ignorematcher.hpp index 1c3d9ef30..a150bf650 100644 --- a/src/tools/ecode/ignorematcher.hpp +++ b/src/tools/ecode/ignorematcher.hpp @@ -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; diff --git a/src/tools/ecode/notificationcenter.cpp b/src/tools/ecode/notificationcenter.cpp index 6c6eb1b24..6ef6669ae 100644 --- a/src/tools/ecode/notificationcenter.cpp +++ b/src/tools/ecode/notificationcenter.cpp @@ -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(); 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 ); }; diff --git a/src/tools/ecode/projectdirectorytree.cpp b/src/tools/ecode/projectdirectorytree.cpp index 17d04363c..04c01e00f 100644 --- a/src/tools/ecode/projectdirectorytree.cpp +++ b/src/tools/ecode/projectdirectorytree.cpp @@ -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; } diff --git a/src/tools/ecode/projectdirectorytree.hpp b/src/tools/ecode/projectdirectorytree.hpp index f6ab7c9d3..56a8acf9c 100644 --- a/src/tools/ecode/projectdirectorytree.hpp +++ b/src/tools/ecode/projectdirectorytree.hpp @@ -167,6 +167,8 @@ class ProjectDirectoryTree { bool isRunning() const { return mRunning; } + bool isReady() const { return mIsReady; } + protected: std::string mPath; std::shared_ptr mPool; diff --git a/src/tools/ecode/projectsearch.cpp b/src/tools/ecode/projectsearch.cpp index b0bacb339..a390b8668 100644 --- a/src/tools/ecode/projectsearch.cpp +++ b/src/tools/ecode/projectsearch.cpp @@ -159,13 +159,6 @@ static std::vector searchInFileRegEx( const s return searchInFilePatternMatch( file, pattern, caseSensitive, wholeWord ); } -struct FindData { - Mutex resMutex; - Mutex countMutex; - int resCount{ 0 }; - ProjectSearch::Result res; -}; - std::vector 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 files, std::string string, - std::shared_ptr pool, ResultCb result, bool caseSensitive, - bool wholeWord, const TextDocument::FindReplaceType& type, - const std::vector& pathFilters, std::string basePath, - std::vector> openDocs ) { - if ( files.empty() ) +ProjectSearch::FindData* +ProjectSearch::find( const std::vector files, std::string string, + std::shared_ptr pool, ResultCb result, bool caseSensitive, + bool wholeWord, const TextDocument::FindReplaceType& type, + const std::vector& pathFilters, std::string basePath, + std::vector> openDocs ) { + static const std::string_view PROJECT_SEARCH_TASK_TAG = "ProjectSearchFindTag"; + static const Uint64 PROJECT_SEARCH_TASK_TAG_HASH = + std::hash()( 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 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 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 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 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() { diff --git a/src/tools/ecode/projectsearch.hpp b/src/tools/ecode/projectsearch.hpp index 04b116b3e..cce6a14ee 100644 --- a/src/tools/ecode/projectsearch.hpp +++ b/src/tools/ecode/projectsearch.hpp @@ -243,7 +243,16 @@ class ProjectSearch { fileResFromDoc( const std::string& string, bool caseSensitive, bool wholeWord, TextDocument::FindReplaceType type, std::shared_ptr 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 files, std::string string, std::shared_ptr pool, ResultCb result, bool caseSensitive, bool wholeWord = false,