From bef41153c14e10df52e89f78e5349cf61d304eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Tue, 24 Sep 2024 23:13:53 -0300 Subject: [PATCH] Implement glob match for path and extension search in Locate bar #336 (issue SpartanJ/ecode#336). --- include/eepp/core/string.hpp | 7 +++ src/eepp/core/string.cpp | 5 ++ src/tools/ecode/applayout.xml.hpp | 7 ++- src/tools/ecode/projectdirectorytree.cpp | 62 ++++++++++++++++--- src/tools/ecode/projectdirectorytree.hpp | 10 +-- .../ecode/statusbuildoutputcontroller.cpp | 14 +++-- src/tools/ecode/universallocator.cpp | 33 ++++++++-- src/tools/ecode/universallocator.hpp | 4 +- 8 files changed, 116 insertions(+), 26 deletions(-) diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index dfdcda462..3cb03f7e3 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -254,6 +254,13 @@ class EE_API String { */ static bool startsWith( const char* haystack, const char* needle ); + /** Compare two strings from its beginning. + * @param haystack The string to search in. + * @param needle The searched string. + * @return true if string starts with the substring + */ + static bool startsWith( std::string_view haystack, std::string_view needle ); + /** Compare two strings from its end. * @param haystack The string to search in. * @param needle The searched string. diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 5bdcac79b..cc19c7884 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -884,6 +884,11 @@ bool String::startsWith( const char* haystack, const char* needle ) { return strncmp( needle, haystack, strlen( needle ) ) == 0; } +bool String::startsWith( std::string_view haystack, std::string_view needle ) { + return needle.length() <= haystack.length() && + std::equal( needle.begin(), needle.end(), haystack.begin() ); +} + bool String::endsWith( const std::string& haystack, const std::string& needle ) { return needle.length() <= haystack.length() && haystack.compare( haystack.size() - needle.size(), needle.size(), needle ) == 0; diff --git a/src/tools/ecode/applayout.xml.hpp b/src/tools/ecode/applayout.xml.hpp index f95f4d51b..1eaa77d15 100644 --- a/src/tools/ecode/applayout.xml.hpp +++ b/src/tools/ecode/applayout.xml.hpp @@ -11,7 +11,6 @@ TextInput.small_input, padding-bottom: 0; font-family: monospace; } -#search_bar, #global_search_bar, #locate_bar { padding-left: 4dp; @@ -20,6 +19,12 @@ TextInput.small_input, margin-bottom: 2dp; margin-top: 2dp; } +#search_bar { + padding-left: 4dp; + padding-right: 4dp; + padding-bottom: 1dp; + margin-top: 2dp; +} .close_button { width: 12dp; height: 12dp; diff --git a/src/tools/ecode/projectdirectorytree.cpp b/src/tools/ecode/projectdirectorytree.cpp index 9944ff942..3f5862785 100644 --- a/src/tools/ecode/projectdirectorytree.cpp +++ b/src/tools/ecode/projectdirectorytree.cpp @@ -122,6 +122,8 @@ ProjectDirectoryTree::fuzzyMatchTree( const std::vector& matches, c if ( names.size() < max ) { names.emplace_back( mNames[res.second] ); files.emplace_back( mFiles[res.second] ); + } else { + break; } } auto model = std::make_shared( files, names ); @@ -145,6 +147,8 @@ ProjectDirectoryTree::fuzzyMatchTree( const std::string& match, const size_t& ma if ( names.size() < max ) { names.emplace_back( mNames[res.second] ); files.emplace_back( mFiles[res.second] ); + } else { + break; } } auto model = std::make_shared( files, names ); @@ -163,8 +167,11 @@ ProjectDirectoryTree::matchTree( const std::string& match, const size_t& max, if ( String::toLower( mNames[i] ).find( lowerMatch ) != std::string::npos ) { names.emplace_back( mNames[i] ); files.emplace_back( mFiles[i] ); - if ( max == names.size() ) - return std::make_shared( files, names ); + if ( max == names.size() ) { + auto res = std::make_shared( files, names ); + res->setBasePath( basePath ); + return res; + } } } auto model = std::make_shared( files, names ); @@ -172,17 +179,52 @@ ProjectDirectoryTree::matchTree( const std::string& match, const size_t& max, return model; } -void ProjectDirectoryTree::asyncFuzzyMatchTree( const std::string& match, const size_t& max, - ProjectDirectoryTree::MatchResultCb res, - const std::string& basePath ) const { - mPool->run( - [this, match, max, res, basePath]() { res( fuzzyMatchTree( match, max, basePath ) ); } ); +std::shared_ptr +ProjectDirectoryTree::globMatchTree( const std::string& match, const size_t& max, + const std::string& basePath ) const { + Lock rl( mMatchingMutex ); + std::vector files; + std::vector names; + for ( size_t i = 0; i < mNames.size(); i++ ) { + std::string_view file( mFiles[i] ); + if ( !match.empty() && !basePath.empty() && file.size() >= basePath.size() && + String::startsWith( file, std::string_view{ basePath } ) ) { + file = file.substr( basePath.size() ); + } + + if ( match.empty() || String::globMatch( file, match ) ) { + names.emplace_back( mNames[i] ); + files.emplace_back( mFiles[i] ); + if ( max == names.size() ) { + auto res = std::make_shared( files, names ); + res->setBasePath( basePath ); + return res; + } + } + } + auto model = std::make_shared( files, names ); + model->setBasePath( basePath ); + return model; } -void ProjectDirectoryTree::asyncMatchTree( const std::string& match, const size_t& max, - ProjectDirectoryTree::MatchResultCb res, +void ProjectDirectoryTree::asyncMatchTree( MatchType type, const std::string& match, + const size_t& max, MatchResultCb res, const std::string& basePath ) const { - mPool->run( [this, match, max, res, basePath]() { res( matchTree( match, max, basePath ) ); } ); + mPool->run( [this, match, max, res, basePath, type]() { + std::shared_ptr result; + switch ( type ) { + case MatchType::Substring: + result = matchTree( match, max, basePath ); + break; + case MatchType::Fuzzy: + result = fuzzyMatchTree( match, max, basePath ); + break; + case MatchType::Glob: + result = globMatchTree( match, max, basePath ); + break; + } + res( result ); + } ); } std::shared_ptr diff --git a/src/tools/ecode/projectdirectorytree.hpp b/src/tools/ecode/projectdirectorytree.hpp index e7ad624cd..1e2fc2402 100644 --- a/src/tools/ecode/projectdirectorytree.hpp +++ b/src/tools/ecode/projectdirectorytree.hpp @@ -126,11 +126,13 @@ class ProjectDirectoryTree { std::shared_ptr matchTree( const std::string& match, const size_t& max, const std::string& basePath = "" ) const; - void asyncFuzzyMatchTree( const std::string& match, const size_t& max, MatchResultCb res, - const std::string& basePath = "" ) const; + std::shared_ptr globMatchTree( const std::string& match, const size_t& max, + const std::string& basePath = "" ) const; - void asyncMatchTree( const std::string& match, const size_t& max, MatchResultCb res, - const std::string& basePath = "" ) const; + enum class MatchType { Substring, Fuzzy, Glob }; + + void asyncMatchTree( MatchType type, const std::string& match, const size_t& max, + MatchResultCb res, const std::string& basePath = "" ) const; struct CommandInfo { std::string name; diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp index f856c3395..c91f6454b 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.cpp +++ b/src/tools/ecode/statusbuildoutputcontroller.cpp @@ -377,10 +377,11 @@ void StatusBuildOutputController::onLoadDone( const Variant& lineNum, const Vari } void StatusBuildOutputController::setHeaderWidth() { - auto totWidth = eefloor( mTableIssues->getPixelsSize().getWidth() - - ( mTableIssues->getVerticalScrollBar()->isVisible() - ? mTableIssues->getVerticalScrollBar()->getPixelsSize().getWidth() - : 0.f ) ); + auto totWidth = + eefloor( mTableIssues->getPixelsSize().getWidth() - + ( mTableIssues->getVerticalScrollBar()->isVisible() + ? mTableIssues->getVerticalScrollBar()->getPixelsSize().getWidth() + : 0.f ) ); Float col1 = eefloor( totWidth * 0.80f ); Float col2 = eefloor( totWidth * 0.15f ); Float col3 = totWidth - col1 - col2; @@ -457,8 +458,9 @@ void StatusBuildOutputController::createContainer() { } ); } else { #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) - mApp->getDirTree()->asyncFuzzyMatchTree( - path, 1, [this, colNum, lineNum]( std::shared_ptr res ) { + mApp->getDirTree()->asyncMatchTree( + ProjectDirectoryTree::MatchType::Fuzzy, path, 1, + [this, colNum, lineNum]( std::shared_ptr res ) { if ( res->rowCount( {} ) == 0 ) return; auto data = res->data( res->index( 0, 1 ) ); diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp index 8a21c02bc..79278829d 100644 --- a/src/tools/ecode/universallocator.cpp +++ b/src/tools/ecode/universallocator.cpp @@ -226,6 +226,15 @@ UniversalLocator::UniversalLocator( UICodeEditorSplitter* editorSplitter, UIScen }, nullptr, nullptr, false } ); + mLocatorProviders.push_back( + { "g", + mUISceneNode->i18n( "search_files_with_glob_match", "Search files with glob matching" ), + [this]( auto ) { + showLocateTableGlob(); + return true; + }, + nullptr, nullptr, false } ); + // clang-format off mLocatorProviders.push_back( { "sb", mUISceneNode->i18n( "switch_build", "Switch Build" ), [this](auto) { @@ -330,7 +339,7 @@ void UniversalLocator::toggleLocateBar() { } } -void UniversalLocator::updateFilesTable() { +void UniversalLocator::updateFilesTable( bool useGlob ) { auto text = mLocateInput->getText(); if ( pathHasPosition( text ) ) { @@ -338,13 +347,18 @@ void UniversalLocator::updateFilesTable() { text = pathAndPos.first; } + if ( useGlob && String::startsWith( text, "g " ) ) + text = text.substr( 2 ); + if ( !mApp->isDirTreeReady() ) { mLocateTable->setModel( ProjectDirectoryTree::emptyModel( getLocatorCommands(), mApp->getCurrentProject() ) ); mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); } else if ( !mLocateInput->getText().empty() ) { #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) - mApp->getDirTree()->asyncFuzzyMatchTree( + mApp->getDirTree()->asyncMatchTree( + useGlob ? ProjectDirectoryTree::MatchType::Glob + : ProjectDirectoryTree::MatchType::Fuzzy, text, LOCATEBAR_MAX_RESULTS, [this, text]( auto res ) { mUISceneNode->runOnMainThread( [this, res] { @@ -356,8 +370,11 @@ void UniversalLocator::updateFilesTable() { }, mApp->getCurrentProject() ); #else - mLocateTable->setModel( mApp->getDirTree()->fuzzyMatchTree( text, LOCATEBAR_MAX_RESULTS, - mApp->getCurrentProject() ) ); + mLocateTable->setModel( + useGlob ? mApp->getDirTree()->globMatchTree( text, LOCATEBAR_MAX_RESULTS, + mApp->getCurrentProject() ) + : mApp->getDirTree()->fuzzyMatchTree( text, LOCATEBAR_MAX_RESULTS, + mApp->getCurrentProject() ) ); mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); mLocateTable->scrollToTop(); #endif @@ -412,6 +429,14 @@ void UniversalLocator::showLocateTable() { updateFilesTable(); } +void UniversalLocator::showLocateTableGlob() { + mLocateTable->setVisible( true ); + Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) ); + pos.y -= mLocateTable->getPixelsSize().getHeight(); + mLocateTable->setPixelsPosition( pos ); + updateFilesTable( true ); +} + void UniversalLocator::goToLine() { showLocateBar(); mLocateInput->setText( "l " ); diff --git a/src/tools/ecode/universallocator.hpp b/src/tools/ecode/universallocator.hpp index d7d6f5b3f..a6883936d 100644 --- a/src/tools/ecode/universallocator.hpp +++ b/src/tools/ecode/universallocator.hpp @@ -52,12 +52,14 @@ class UniversalLocator { void goToLine(); - void updateFilesTable(); + void updateFilesTable( bool useGlob = false ); void updateCommandPaletteTable(); void showLocateTable(); + void showLocateTableGlob(); + void showWorkspaceSymbol(); void showDocumentSymbol();