diff --git a/premake4.lua b/premake4.lua index 448e4c693..9957c8196 100644 --- a/premake4.lua +++ b/premake4.lua @@ -2019,7 +2019,9 @@ solution "eepp" links { "bsd", "network" } end files { "src/tests/unit_tests/*.cpp", + "src/tools/ecode/ignorematcher.cpp", "src/tools/ecode/jsonhelper.cpp", + "src/tools/ecode/projectdirectorytree.cpp", "src/tools/ecode/plugins/git/git.cpp", "src/tools/ecode/plugins/autocomplete/snippetparser.cpp", "src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" } diff --git a/premake5.lua b/premake5.lua index 1406e47ea..05f456a74 100644 --- a/premake5.lua +++ b/premake5.lua @@ -2034,7 +2034,9 @@ workspace "eepp" incdirs { "src/modules/eterm/include/", "src/thirdparty" } language "C++" files { "src/tests/unit_tests/*.cpp", + "src/tools/ecode/ignorematcher.cpp", "src/tools/ecode/jsonhelper.cpp", + "src/tools/ecode/projectdirectorytree.cpp", "src/tools/ecode/plugins/git/git.cpp", "src/tools/ecode/plugins/autocomplete/snippetparser.cpp", "src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" } diff --git a/src/modules/eterm/include/eterm/terminal/terminalemulator.hpp b/src/modules/eterm/include/eterm/terminal/terminalemulator.hpp index 9a97cd610..2b711d635 100644 --- a/src/modules/eterm/include/eterm/terminal/terminalemulator.hpp +++ b/src/modules/eterm/include/eterm/terminal/terminalemulator.hpp @@ -253,6 +253,8 @@ class TerminalEmulator final { void clearPendingKeyboardInput(); + void reportFocus( bool focused ); + int tisaltscr(); int scrollSize() const; diff --git a/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp b/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp index f07b83472..1391bbb88 100644 --- a/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp +++ b/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp @@ -1300,6 +1300,13 @@ void TerminalEmulator::clearPendingKeyboardInput() { mHasPendingTextKey = false; } +void TerminalEmulator::reportFocus( bool focused ) { + if ( !focused ) + clearPendingKeyboardInput(); + if ( xgetmode( MODE_FOCUS ) ) + ttywriteInternal( focused ? "\033[I" : "\033[O", 3, false, false ); +} + void TerminalEmulator::ttywriteraw( const char* s, size_t n ) { if ( mPty->write( s, n ) < (int)n ) { _die( "Failed to write to TTY" ); diff --git a/src/modules/eterm/src/eterm/terminal/terminalsession.cpp b/src/modules/eterm/src/eterm/terminal/terminalsession.cpp index 13ac96f79..95a61b1cb 100644 --- a/src/modules/eterm/src/eterm/terminal/terminalsession.cpp +++ b/src/modules/eterm/src/eterm/terminal/terminalsession.cpp @@ -583,10 +583,7 @@ void TerminalSession::processCommand( Command&& command ) { mEmulator->mousereport( value.type, value.cellPosition, value.pixelPosition, value.flags, value.modifiers ); } else if constexpr ( std::is_same_v ) { - if ( !value.value ) - mEmulator->clearPendingKeyboardInput(); - if ( mWorkerDisplay->getMode( MODE_FOCUS ) ) - mEmulator->ttywrite( value.value ? "\033[I" : "\033[O", 3, false ); + mEmulator->reportFocus( value.value ); mWorkerDisplay->setFocused( value.value ); mEmulator->redraw(); } else if constexpr ( std::is_same_v ) { diff --git a/src/tests/unit_tests/eterm_tests.cpp b/src/tests/unit_tests/eterm_tests.cpp index e18d4622e..00e0ceab7 100644 --- a/src/tests/unit_tests/eterm_tests.cpp +++ b/src/tests/unit_tests/eterm_tests.cpp @@ -267,28 +267,39 @@ UTEST( eterm_session, presentation_rate_is_applied_on_the_worker ) { UTEST( eterm_session, focus_reporting_is_ordered_on_worker ) { auto pty = std::make_unique(); pty->mBuffer = "\033[?1004h"; + for ( int line = 0; line < 40; ++line ) + pty->mBuffer += "Line " + std::to_string( line ) + "\r\n"; pty->mLoopWrites = false; MockPty* ptyPtr = pty.get(); auto process = std::make_unique(); auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 ); auto enabled = waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) { - return snapshot.windowMode & MODE_FOCUS; + return snapshot.windowMode & MODE_FOCUS && snapshot.historyLength >= 5; } ); ASSERT_TRUE( enabled != nullptr ); + const Uint64 scrollCommand = session->scrollTo( 5 ); + auto scrolled = waitForSnapshot( session, [scrollCommand]( const TerminalSnapshot& snapshot ) { + return snapshot.lastAppliedScrollCommand == scrollCommand; + } ); + ASSERT_TRUE( scrolled != nullptr ); + ASSERT_EQ( 5, scrolled->scrollPosition ); session->setFocus( false ); - auto unfocused = waitForSnapshot( session, [enabled]( const TerminalSnapshot& snapshot ) { - return snapshot.generation > enabled->generation && !( snapshot.windowMode & MODE_FOCUSED ); + auto unfocused = waitForSnapshot( session, [scrolled]( const TerminalSnapshot& snapshot ) { + return snapshot.generation > scrolled->generation && + !( snapshot.windowMode & MODE_FOCUSED ); } ); ASSERT_TRUE( unfocused != nullptr ); + EXPECT_EQ( 5, unfocused->scrollPosition ); ASSERT_TRUE( ptyPtr->mWrites.size() >= 3 ); EXPECT_STDSTREQ( "\033[O", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) ); session->setFocus( true ); - ASSERT_TRUE( waitForSnapshot( session, [unfocused]( const TerminalSnapshot& snapshot ) { - return snapshot.generation > unfocused->generation && - snapshot.windowMode & MODE_FOCUSED; - } ) != nullptr ); + auto refocused = waitForSnapshot( session, [unfocused]( const TerminalSnapshot& snapshot ) { + return snapshot.generation > unfocused->generation && snapshot.windowMode & MODE_FOCUSED; + } ); + ASSERT_TRUE( refocused != nullptr ); + EXPECT_EQ( 5, refocused->scrollPosition ); ASSERT_TRUE( ptyPtr->mWrites.size() >= 3 ); EXPECT_STDSTREQ( "\033[I", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) ); } diff --git a/src/tests/unit_tests/projectdirectorytree_tests.cpp b/src/tests/unit_tests/projectdirectorytree_tests.cpp new file mode 100644 index 000000000..0c5953c51 --- /dev/null +++ b/src/tests/unit_tests/projectdirectorytree_tests.cpp @@ -0,0 +1,220 @@ +#include "utest.h" + +#include "../../tools/ecode/projectdirectorytree.hpp" +#include +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::System; +using namespace ecode; + +namespace ecode { + +// ProjectDirectoryTree only uses PluginManager when one is supplied. The unit-test target does not +// link the ecode application, so provide the null-manager path's unused symbols here. +UISceneNode* PluginManager::getUISceneNode() const { + return nullptr; +} + +void PluginManager::subscribeMessages( + const std::string&, std::function ) {} + +void PluginManager::unsubscribeMessages( const std::string& ) {} + +} // namespace ecode + +namespace { + +class ProjectDirectoryTreeTestDirectory { + public: + ProjectDirectoryTreeTestDirectory() { + static std::atomic counter{ 0 }; + mPath = std::filesystem::temp_directory_path() / + ( "eepp-project-directory-tree-" + + std::to_string( std::chrono::steady_clock::now().time_since_epoch().count() ) + + "-" + std::to_string( ++counter ) ); + std::filesystem::create_directories( mPath / ".git" ); + } + + ~ProjectDirectoryTreeTestDirectory() { FileSystem::dirRemoveAll( mPath.string() ); } + + bool makeDirectory( const std::string& relativePath ) const { + return std::filesystem::create_directories( mPath / relativePath ) || + std::filesystem::is_directory( mPath / relativePath ); + } + + bool writeFile( const std::string& relativePath, std::string_view contents = {} ) const { + const std::filesystem::path path( mPath / relativePath ); + std::filesystem::create_directories( path.parent_path() ); + return FileSystem::fileWrite( path.string(), contents ); + } + + std::string path( const std::string& relativePath = {} ) const { + return relativePath.empty() ? mPath.string() : ( mPath / relativePath ).string(); + } + + private: + std::filesystem::path mPath; +}; + +void scanAndWait( ProjectDirectoryTree& tree, const std::shared_ptr& pool ) { + tree.scan( {} ); + std::promise barrier; + auto done = barrier.get_future(); + pool->run( [&barrier] { barrier.set_value(); } ); + done.wait(); +} + +void sendAdd( ProjectDirectoryTree& tree, const std::string& path ) { + tree.onChange( ProjectDirectoryTree::Add, FileInfo( path ), {} ); +} + +bool hasDirectory( const ProjectDirectoryTree& tree, std::string directory ) { + FileSystem::dirAddSlashAtEnd( directory ); + const auto directories = tree.getDirectories(); + return std::find( directories.begin(), directories.end(), directory ) != directories.end(); +} + +} // namespace + +UTEST( ProjectDirectoryTree, RejectsIgnoredDirectoryCreatedAfterScan ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.writeFile( ".gitignore", "obj/\n" ) ); + ASSERT_TRUE( project.makeDirectory( "src" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + + ASSERT_TRUE( project.makeDirectory( "obj/linux/release" ) ); + ASSERT_TRUE( project.writeFile( "obj/linux/release/file.d" ) ); + sendAdd( tree, project.path( "obj" ) ); + sendAdd( tree, project.path( "obj/linux" ) ); + sendAdd( tree, project.path( "obj/linux/release" ) ); + sendAdd( tree, project.path( "obj/linux/release/file.d" ) ); + + EXPECT_FALSE( hasDirectory( tree, project.path( "obj" ) ) ); + EXPECT_FALSE( hasDirectory( tree, project.path( "obj/linux" ) ) ); + EXPECT_FALSE( hasDirectory( tree, project.path( "obj/linux/release" ) ) ); + EXPECT_FALSE( tree.isFileInTree( project.path( "obj/linux/release/file.d" ) ) ); +} + +UTEST( ProjectDirectoryTree, RejectsNewDescendantsOfInitiallyIgnoredDirectory ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.writeFile( ".gitignore", "obj/\n" ) ); + ASSERT_TRUE( project.makeDirectory( "obj" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + ASSERT_FALSE( hasDirectory( tree, project.path( "obj" ) ) ); + + ASSERT_TRUE( project.makeDirectory( "obj/linux/release" ) ); + ASSERT_TRUE( project.writeFile( "obj/linux/release/file.d" ) ); + sendAdd( tree, project.path( "obj/linux" ) ); + sendAdd( tree, project.path( "obj/linux/release" ) ); + sendAdd( tree, project.path( "obj/linux/release/file.d" ) ); + + EXPECT_FALSE( hasDirectory( tree, project.path( "obj/linux" ) ) ); + EXPECT_FALSE( hasDirectory( tree, project.path( "obj/linux/release" ) ) ); + EXPECT_FALSE( tree.isFileInTree( project.path( "obj/linux/release/file.d" ) ) ); +} + +UTEST( ProjectDirectoryTree, RejectsDynamicallyCreatedPathSpecificIgnoredDirectory ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.writeFile( ".gitignore", "build/generated/\n" ) ); + ASSERT_TRUE( project.makeDirectory( "build" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + + ASSERT_TRUE( project.makeDirectory( "build/generated" ) ); + ASSERT_TRUE( project.writeFile( "build/generated/foo.cpp" ) ); + sendAdd( tree, project.path( "build/generated" ) ); + sendAdd( tree, project.path( "build/generated/foo.cpp" ) ); + + EXPECT_FALSE( hasDirectory( tree, project.path( "build/generated" ) ) ); + EXPECT_FALSE( tree.isFileInTree( project.path( "build/generated/foo.cpp" ) ) ); +} + +UTEST( ProjectDirectoryTree, AdmitsLegitimateDynamicallyCreatedDirectory ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.makeDirectory( "src" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + + ASSERT_TRUE( project.makeDirectory( "src/newmodule" ) ); + ASSERT_TRUE( project.writeFile( "src/newmodule/foo.cpp" ) ); + sendAdd( tree, project.path( "src/newmodule" ) ); + sendAdd( tree, project.path( "src/newmodule/foo.cpp" ) ); + + EXPECT_TRUE( hasDirectory( tree, project.path( "src/newmodule" ) ) ); + EXPECT_TRUE( tree.isFileInTree( project.path( "src/newmodule/foo.cpp" ) ) ); +} + +UTEST( ProjectDirectoryTree, RespectsNestedIgnoreFileForDynamicDirectory ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.makeDirectory( "src" ) ); + ASSERT_TRUE( project.writeFile( "src/.gitignore", "generated/\n" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + + ASSERT_TRUE( project.makeDirectory( "src/generated" ) ); + ASSERT_TRUE( project.writeFile( "src/generated/foo.cpp" ) ); + sendAdd( tree, project.path( "src/generated" ) ); + sendAdd( tree, project.path( "src/generated/foo.cpp" ) ); + + EXPECT_FALSE( hasDirectory( tree, project.path( "src/generated" ) ) ); + EXPECT_FALSE( tree.isFileInTree( project.path( "src/generated/foo.cpp" ) ) ); +} + +UTEST( ProjectDirectoryTree, ScansNewDirectoryWithItsOwnIgnoreFile ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.makeDirectory( "src" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + + ASSERT_TRUE( project.writeFile( "src/newmodule/.gitignore", "generated/\n" ) ); + ASSERT_TRUE( project.writeFile( "src/newmodule/generated/foo.cpp" ) ); + sendAdd( tree, project.path( "src/newmodule" ) ); + + EXPECT_TRUE( hasDirectory( tree, project.path( "src/newmodule" ) ) ); + EXPECT_FALSE( hasDirectory( tree, project.path( "src/newmodule/generated" ) ) ); + EXPECT_FALSE( tree.isFileInTree( project.path( "src/newmodule/generated/foo.cpp" ) ) ); +} + +UTEST( ProjectDirectoryTree, RespectsProjectDisallowedForDynamicFile ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.makeDirectory( "src" ) ); + ASSERT_TRUE( project.writeFile( ".ecode/.prjdisallowed", "src/private.cpp\n" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + + ASSERT_TRUE( project.writeFile( "src/private.cpp" ) ); + sendAdd( tree, project.path( "src/private.cpp" ) ); + + EXPECT_FALSE( tree.isFileInTree( project.path( "src/private.cpp" ) ) ); +} + +UTEST( ProjectDirectoryTree, RespectsProjectAllowedForDynamicDirectory ) { + ProjectDirectoryTreeTestDirectory project; + ASSERT_TRUE( project.writeFile( ".gitignore", "vendor/\n" ) ); + ASSERT_TRUE( project.writeFile( ".ecode/.prjallowed", "vendor/\n" ) ); + auto pool = ThreadPool::createShared( 1 ); + ProjectDirectoryTree tree( project.path(), pool ); + scanAndWait( tree, pool ); + + ASSERT_TRUE( project.makeDirectory( "vendor" ) ); + ASSERT_TRUE( project.writeFile( "vendor/library.cpp" ) ); + sendAdd( tree, project.path( "vendor" ) ); + sendAdd( tree, project.path( "vendor/library.cpp" ) ); + + EXPECT_TRUE( hasDirectory( tree, project.path( "vendor" ) ) ); + EXPECT_TRUE( tree.isFileInTree( project.path( "vendor/library.cpp" ) ) ); +} diff --git a/src/tools/ecode/projectdirectorytree.cpp b/src/tools/ecode/projectdirectorytree.cpp index 870f64e9a..4640632c8 100644 --- a/src/tools/ecode/projectdirectorytree.cpp +++ b/src/tools/ecode/projectdirectorytree.cpp @@ -70,8 +70,7 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent& for ( const auto& strPattern : acceptedPatterns ) mAcceptedPatterns.emplace_back( std::string{ strPattern } ); std::set info; - getDirectoryFiles( files, names, mPath, info, false, mIgnoreMatcher, - mAllowedMatcher.get(), mDisallowedMatcher.get() ); + getDirectoryFiles( files, names, mPath, info, false, mIgnoreMatcher ); size_t namesCount = names.size(); bool found; for ( size_t i = 0; i < namesCount; i++ ) { @@ -101,8 +100,7 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent& } } else { std::set info; - getDirectoryFiles( mFiles, mNames, mPath, info, ignoreHidden, mIgnoreMatcher, - mAllowedMatcher.get(), mDisallowedMatcher.get() ); + getDirectoryFiles( mFiles, mNames, mPath, info, ignoreHidden, mIgnoreMatcher ); } mIsReady = true; if ( mPluginManager ) { @@ -342,8 +340,7 @@ bool ProjectDirectoryTree::isDirInTree( const std::string& dirTree ) const { void ProjectDirectoryTree::getDirectoryFiles( std::vector& files, std::vector& names, std::string directory, std::set currentDirs, const bool& ignoreHidden, - IgnoreMatcherManager& ignoreMatcher, GitIgnoreMatcher* allowedMatcher, - GitIgnoreMatcher* disallowedMatcher, bool initialScan ) { + IgnoreMatcherManager& ignoreMatcher, bool initialScan ) { if ( mClosing || ( initialScan && !mRunning ) ) return; currentDirs.insert( directory ); @@ -351,23 +348,8 @@ void ProjectDirectoryTree::getDirectoryFiles( FileSystem::filesGetInPath( directory, false, false, ignoreHidden ); for ( auto& file : pathFiles ) { std::string fullpath( directory + file ); - if ( ignoreMatcher.foundMatch() && ignoreMatcher.match( directory, file ) ) { - if ( !allowedMatcher || !allowedMatcher->hasPatterns() ) - continue; - std::string_view localPath( fullpath ); - if ( String::startsWith( directory, allowedMatcher->getPath() ) ) - localPath = std::string_view{ fullpath }.substr( allowedMatcher->getPath().size() ); - if ( !allowedMatcher->match( localPath ) ) - continue; - } else if ( disallowedMatcher && disallowedMatcher->hasPatterns() ) { - 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; - } + if ( shouldIgnoreEntry( directory, file, ignoreMatcher ) ) + continue; if ( FileSystem::isDirectory( fullpath ) ) { fullpath += FileSystem::getOSSlash(); @@ -395,7 +377,7 @@ void ProjectDirectoryTree::getDirectoryFiles( ignoreMatcher.addChild( childMatch ); } getDirectoryFiles( files, names, fullpath, currentDirs, ignoreHidden, ignoreMatcher, - allowedMatcher, disallowedMatcher, initialScan ); + initialScan ); if ( childMatch ) { ignoreMatcher.removeChild( childMatch ); eeSAFE_DELETE( childMatch ); @@ -407,6 +389,29 @@ void ProjectDirectoryTree::getDirectoryFiles( } } +bool ProjectDirectoryTree::shouldIgnoreEntry( const std::string& directory, + const std::string& filename, + IgnoreMatcherManager& ignoreMatcher ) const { + if ( ignoreMatcher.foundMatch() && ignoreMatcher.match( directory, filename ) ) { + if ( !mAllowedMatcher || !mAllowedMatcher->hasPatterns() ) + return true; + std::string fullpath( directory + filename ); + std::string_view localPath( fullpath ); + if ( String::startsWith( directory, mAllowedMatcher->getPath() ) ) + localPath.remove_prefix( mAllowedMatcher->getPath().size() ); + if ( !mAllowedMatcher->match( localPath ) ) + return true; + } else if ( mDisallowedMatcher && mDisallowedMatcher->hasPatterns() ) { + std::string fullpath( directory + filename ); + std::string_view localPath( fullpath ); + if ( String::startsWith( directory, mDisallowedMatcher->getPath() ) ) + localPath.remove_prefix( mDisallowedMatcher->getPath().size() ); + if ( mDisallowedMatcher->match( localPath ) ) + return true; + } + return false; +} + void ProjectDirectoryTree::onChange( const ProjectDirectoryTree::Action& action, const FileInfo& file, const std::string& oldFilename ) { if ( !file.isDirectory() && !isDirInTree( file.getFilepath() ) ) @@ -433,24 +438,25 @@ void ProjectDirectoryTree::resetPluginManager() { void ProjectDirectoryTree::tryAddFile( const FileInfo& file ) { if ( mIgnoreHidden && file.isHidden() ) return; + std::string directory( file.getDirectoryPath() ); + FileSystem::dirAddSlashAtEnd( directory ); IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( file.getFilepath() ) ); - if ( !matcher.foundMatch() || !matcher.match( file ) ) { - bool foundPattern = mAcceptedPatterns.empty(); - for ( auto& pattern : mAcceptedPatterns ) { - if ( pattern.matches( file.getFilepath() ) ) { - foundPattern = true; - break; - } + if ( shouldIgnoreEntry( directory, file.getFileName(), matcher ) ) + return; + bool foundPattern = mAcceptedPatterns.empty(); + for ( auto& pattern : mAcceptedPatterns ) { + if ( pattern.matches( file.getFilepath() ) ) { + foundPattern = true; + break; } - if ( foundPattern ) { - Lock rl( mMatchingMutex ); - Lock l( mFilesMutex ); - auto exists = - std::find( mFiles.begin(), mFiles.end(), file.getFilepath() ) != mFiles.end(); - if ( !exists ) { - mFiles.emplace_back( file.getFilepath() ); - mNames.emplace_back( file.getFileName() ); - } + } + if ( foundPattern ) { + Lock rl( mMatchingMutex ); + Lock l( mFilesMutex ); + auto exists = std::find( mFiles.begin(), mFiles.end(), file.getFilepath() ) != mFiles.end(); + if ( !exists ) { + mFiles.emplace_back( file.getFilepath() ); + mNames.emplace_back( file.getFileName() ); } } } @@ -462,25 +468,27 @@ void ProjectDirectoryTree::addFile( const FileInfo& file ) { if ( mIgnoreHidden && file.isHidden() ) return; Lock rl( mMatchingMutex ); - const std::string& directoryEntry = file.getFilepath(); - IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( directoryEntry ) ); - if ( matcher.foundMatch() && matcher.match( file ) ) { - if ( !mAllowedMatcher || !mAllowedMatcher->hasPatterns() ) - return; - std::string_view localPath( directoryEntry ); - if ( String::startsWith( directoryEntry, mAllowedMatcher->getPath() ) ) - localPath.remove_prefix( mAllowedMatcher->getPath().size() ); - if ( !mAllowedMatcher->match( localPath ) ) - return; - } else if ( mDisallowedMatcher && mDisallowedMatcher->hasPatterns() ) { - std::string_view localPath( directoryEntry ); - if ( String::startsWith( directoryEntry, mDisallowedMatcher->getPath() ) ) - localPath.remove_prefix( mDisallowedMatcher->getPath().size() ); - if ( mDisallowedMatcher->match( localPath ) ) + std::string directoryEntry( file.getFilepath() ); + FileSystem::dirRemoveSlashAtEnd( directoryEntry ); + std::string parentDirectory( FileSystem::fileRemoveFileName( directoryEntry ) ); + FileSystem::dirAddSlashAtEnd( parentDirectory ); + { + Lock ld( mDirectoriesMutex ); + if ( std::find( mDirectories.begin(), mDirectories.end(), parentDirectory ) == + mDirectories.end() ) return; } + IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( directoryEntry ) ); + if ( shouldIgnoreEntry( parentDirectory, file.getFileName(), matcher ) ) + return; std::string directory( directoryEntry ); FileSystem::dirAddSlashAtEnd( directory ); + IgnoreMatcherManager directoryMatcher( directory ); + IgnoreMatcher* childMatch = nullptr; + if ( directoryMatcher.foundMatch() ) { + childMatch = directoryMatcher.popMatcher( 0 ); + matcher.addChild( childMatch ); + } Lock l( mFilesMutex ); std::vector files; std::vector names; @@ -489,8 +497,11 @@ void ProjectDirectoryTree::addFile( const FileInfo& file ) { Lock ld( mDirectoriesMutex ); mDirectories.emplace_back( directory ); } - getDirectoryFiles( files, names, directory, info, mIgnoreHidden, matcher, - mAllowedMatcher.get(), mDisallowedMatcher.get(), false ); + getDirectoryFiles( files, names, directory, info, mIgnoreHidden, matcher, false ); + if ( childMatch ) { + matcher.removeChild( childMatch ); + eeSAFE_DELETE( childMatch ); + } for ( size_t i = 0; i < files.size(); ++i ) { bool accepted = mAcceptedPatterns.empty(); for ( const auto& pattern : mAcceptedPatterns ) { diff --git a/src/tools/ecode/projectdirectorytree.hpp b/src/tools/ecode/projectdirectorytree.hpp index bf9f68ce9..aeb8f1e00 100644 --- a/src/tools/ecode/projectdirectorytree.hpp +++ b/src/tools/ecode/projectdirectorytree.hpp @@ -195,9 +195,11 @@ class ProjectDirectoryTree { void getDirectoryFiles( std::vector& files, std::vector& names, std::string directory, std::set currentDirs, const bool& ignoreHidden, IgnoreMatcherManager& ignoreMatcher, - GitIgnoreMatcher* allowedMatcher, GitIgnoreMatcher* disallowedMatcher, bool initialScan = true ); + bool shouldIgnoreEntry( const std::string& directory, const std::string& filename, + IgnoreMatcherManager& ignoreMatcher ) const; + void addFile( const FileInfo& file ); void tryAddFile( const FileInfo& file );