mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
Implemented the live-indexing fix + Fixed opening and closing the context menu no longer changes the scrollback position.
- Centralized .gitignore, .prjallowed, and .prjdisallowed filtering in src/tools/ecode/projectdirectorytree.cpp:392. - Live directories now require an already-admitted parent and use (parentDirectory, filename) matching before insertion. - Live file additions use the same shared filtering. - Newly admitted directories still load their own nested .gitignore before recursive scanning. - Added eight direct regression tests in src/tests/unit_tests/projectdirectorytree_tests.cpp:84. - Added the required ecode sources to both Premake unit-test targets. - No FileSystemListener changes; directory moves remain outside this Add-focused patch. - Context menu issue cause was terminal focus reporting: Codex enables focus events, and opening the popup sent ESC [ O through the ordinary input path, which scrolls to the bottom. Focus reports now use the non-scrolling protocol-write path while still reaching the application correctly.
This commit is contained in:
@@ -2019,7 +2019,9 @@ solution "eepp"
|
|||||||
links { "bsd", "network" }
|
links { "bsd", "network" }
|
||||||
end
|
end
|
||||||
files { "src/tests/unit_tests/*.cpp",
|
files { "src/tests/unit_tests/*.cpp",
|
||||||
|
"src/tools/ecode/ignorematcher.cpp",
|
||||||
"src/tools/ecode/jsonhelper.cpp",
|
"src/tools/ecode/jsonhelper.cpp",
|
||||||
|
"src/tools/ecode/projectdirectorytree.cpp",
|
||||||
"src/tools/ecode/plugins/git/git.cpp",
|
"src/tools/ecode/plugins/git/git.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
||||||
|
|||||||
@@ -2034,7 +2034,9 @@ workspace "eepp"
|
|||||||
incdirs { "src/modules/eterm/include/", "src/thirdparty" }
|
incdirs { "src/modules/eterm/include/", "src/thirdparty" }
|
||||||
language "C++"
|
language "C++"
|
||||||
files { "src/tests/unit_tests/*.cpp",
|
files { "src/tests/unit_tests/*.cpp",
|
||||||
|
"src/tools/ecode/ignorematcher.cpp",
|
||||||
"src/tools/ecode/jsonhelper.cpp",
|
"src/tools/ecode/jsonhelper.cpp",
|
||||||
|
"src/tools/ecode/projectdirectorytree.cpp",
|
||||||
"src/tools/ecode/plugins/git/git.cpp",
|
"src/tools/ecode/plugins/git/git.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ class TerminalEmulator final {
|
|||||||
|
|
||||||
void clearPendingKeyboardInput();
|
void clearPendingKeyboardInput();
|
||||||
|
|
||||||
|
void reportFocus( bool focused );
|
||||||
|
|
||||||
int tisaltscr();
|
int tisaltscr();
|
||||||
|
|
||||||
int scrollSize() const;
|
int scrollSize() const;
|
||||||
|
|||||||
@@ -1300,6 +1300,13 @@ void TerminalEmulator::clearPendingKeyboardInput() {
|
|||||||
mHasPendingTextKey = false;
|
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 ) {
|
void TerminalEmulator::ttywriteraw( const char* s, size_t n ) {
|
||||||
if ( mPty->write( s, n ) < (int)n ) {
|
if ( mPty->write( s, n ) < (int)n ) {
|
||||||
_die( "Failed to write to TTY" );
|
_die( "Failed to write to TTY" );
|
||||||
|
|||||||
@@ -583,10 +583,7 @@ void TerminalSession::processCommand( Command&& command ) {
|
|||||||
mEmulator->mousereport( value.type, value.cellPosition, value.pixelPosition,
|
mEmulator->mousereport( value.type, value.cellPosition, value.pixelPosition,
|
||||||
value.flags, value.modifiers );
|
value.flags, value.modifiers );
|
||||||
} else if constexpr ( std::is_same_v<T, FocusCommand> ) {
|
} else if constexpr ( std::is_same_v<T, FocusCommand> ) {
|
||||||
if ( !value.value )
|
mEmulator->reportFocus( value.value );
|
||||||
mEmulator->clearPendingKeyboardInput();
|
|
||||||
if ( mWorkerDisplay->getMode( MODE_FOCUS ) )
|
|
||||||
mEmulator->ttywrite( value.value ? "\033[I" : "\033[O", 3, false );
|
|
||||||
mWorkerDisplay->setFocused( value.value );
|
mWorkerDisplay->setFocused( value.value );
|
||||||
mEmulator->redraw();
|
mEmulator->redraw();
|
||||||
} else if constexpr ( std::is_same_v<T, CursorModeCommand> ) {
|
} else if constexpr ( std::is_same_v<T, CursorModeCommand> ) {
|
||||||
|
|||||||
@@ -267,28 +267,39 @@ UTEST( eterm_session, presentation_rate_is_applied_on_the_worker ) {
|
|||||||
UTEST( eterm_session, focus_reporting_is_ordered_on_worker ) {
|
UTEST( eterm_session, focus_reporting_is_ordered_on_worker ) {
|
||||||
auto pty = std::make_unique<MockPty>();
|
auto pty = std::make_unique<MockPty>();
|
||||||
pty->mBuffer = "\033[?1004h";
|
pty->mBuffer = "\033[?1004h";
|
||||||
|
for ( int line = 0; line < 40; ++line )
|
||||||
|
pty->mBuffer += "Line " + std::to_string( line ) + "\r\n";
|
||||||
pty->mLoopWrites = false;
|
pty->mLoopWrites = false;
|
||||||
MockPty* ptyPtr = pty.get();
|
MockPty* ptyPtr = pty.get();
|
||||||
auto process = std::make_unique<MockProcess>();
|
auto process = std::make_unique<MockProcess>();
|
||||||
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
|
auto session = TerminalSession::create( std::move( pty ), std::move( process ), 100 );
|
||||||
auto enabled = waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
|
auto enabled = waitForSnapshot( session, []( const TerminalSnapshot& snapshot ) {
|
||||||
return snapshot.windowMode & MODE_FOCUS;
|
return snapshot.windowMode & MODE_FOCUS && snapshot.historyLength >= 5;
|
||||||
} );
|
} );
|
||||||
ASSERT_TRUE( enabled != nullptr );
|
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 );
|
session->setFocus( false );
|
||||||
auto unfocused = waitForSnapshot( session, [enabled]( const TerminalSnapshot& snapshot ) {
|
auto unfocused = waitForSnapshot( session, [scrolled]( const TerminalSnapshot& snapshot ) {
|
||||||
return snapshot.generation > enabled->generation && !( snapshot.windowMode & MODE_FOCUSED );
|
return snapshot.generation > scrolled->generation &&
|
||||||
|
!( snapshot.windowMode & MODE_FOCUSED );
|
||||||
} );
|
} );
|
||||||
ASSERT_TRUE( unfocused != nullptr );
|
ASSERT_TRUE( unfocused != nullptr );
|
||||||
|
EXPECT_EQ( 5, unfocused->scrollPosition );
|
||||||
ASSERT_TRUE( ptyPtr->mWrites.size() >= 3 );
|
ASSERT_TRUE( ptyPtr->mWrites.size() >= 3 );
|
||||||
EXPECT_STDSTREQ( "\033[O", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) );
|
EXPECT_STDSTREQ( "\033[O", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) );
|
||||||
|
|
||||||
session->setFocus( true );
|
session->setFocus( true );
|
||||||
ASSERT_TRUE( waitForSnapshot( session, [unfocused]( const TerminalSnapshot& snapshot ) {
|
auto refocused = waitForSnapshot( session, [unfocused]( const TerminalSnapshot& snapshot ) {
|
||||||
return snapshot.generation > unfocused->generation &&
|
return snapshot.generation > unfocused->generation && snapshot.windowMode & MODE_FOCUSED;
|
||||||
snapshot.windowMode & MODE_FOCUSED;
|
} );
|
||||||
} ) != nullptr );
|
ASSERT_TRUE( refocused != nullptr );
|
||||||
|
EXPECT_EQ( 5, refocused->scrollPosition );
|
||||||
ASSERT_TRUE( ptyPtr->mWrites.size() >= 3 );
|
ASSERT_TRUE( ptyPtr->mWrites.size() >= 3 );
|
||||||
EXPECT_STDSTREQ( "\033[I", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) );
|
EXPECT_STDSTREQ( "\033[I", ptyPtr->mWrites.substr( ptyPtr->mWrites.size() - 3 ) );
|
||||||
}
|
}
|
||||||
|
|||||||
220
src/tests/unit_tests/projectdirectorytree_tests.cpp
Normal file
220
src/tests/unit_tests/projectdirectorytree_tests.cpp
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#include "utest.h"
|
||||||
|
|
||||||
|
#include "../../tools/ecode/projectdirectorytree.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <eepp/system/filesystem.hpp>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
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<PluginRequestHandle( const PluginMessage& )> ) {}
|
||||||
|
|
||||||
|
void PluginManager::unsubscribeMessages( const std::string& ) {}
|
||||||
|
|
||||||
|
} // namespace ecode
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class ProjectDirectoryTreeTestDirectory {
|
||||||
|
public:
|
||||||
|
ProjectDirectoryTreeTestDirectory() {
|
||||||
|
static std::atomic<Uint64> 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<ThreadPool>& pool ) {
|
||||||
|
tree.scan( {} );
|
||||||
|
std::promise<void> 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" ) ) );
|
||||||
|
}
|
||||||
@@ -70,8 +70,7 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent&
|
|||||||
for ( const auto& strPattern : acceptedPatterns )
|
for ( const auto& strPattern : acceptedPatterns )
|
||||||
mAcceptedPatterns.emplace_back( std::string{ strPattern } );
|
mAcceptedPatterns.emplace_back( std::string{ strPattern } );
|
||||||
std::set<std::string> info;
|
std::set<std::string> info;
|
||||||
getDirectoryFiles( files, names, mPath, info, false, mIgnoreMatcher,
|
getDirectoryFiles( files, names, mPath, info, false, mIgnoreMatcher );
|
||||||
mAllowedMatcher.get(), mDisallowedMatcher.get() );
|
|
||||||
size_t namesCount = names.size();
|
size_t namesCount = names.size();
|
||||||
bool found;
|
bool found;
|
||||||
for ( size_t i = 0; i < namesCount; i++ ) {
|
for ( size_t i = 0; i < namesCount; i++ ) {
|
||||||
@@ -101,8 +100,7 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent&
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::set<std::string> info;
|
std::set<std::string> info;
|
||||||
getDirectoryFiles( mFiles, mNames, mPath, info, ignoreHidden, mIgnoreMatcher,
|
getDirectoryFiles( mFiles, mNames, mPath, info, ignoreHidden, mIgnoreMatcher );
|
||||||
mAllowedMatcher.get(), mDisallowedMatcher.get() );
|
|
||||||
}
|
}
|
||||||
mIsReady = true;
|
mIsReady = true;
|
||||||
if ( mPluginManager ) {
|
if ( mPluginManager ) {
|
||||||
@@ -342,8 +340,7 @@ bool ProjectDirectoryTree::isDirInTree( const std::string& dirTree ) const {
|
|||||||
void ProjectDirectoryTree::getDirectoryFiles(
|
void ProjectDirectoryTree::getDirectoryFiles(
|
||||||
std::vector<std::string>& files, std::vector<std::string>& names, std::string directory,
|
std::vector<std::string>& files, std::vector<std::string>& names, std::string directory,
|
||||||
std::set<std::string> currentDirs, const bool& ignoreHidden,
|
std::set<std::string> currentDirs, const bool& ignoreHidden,
|
||||||
IgnoreMatcherManager& ignoreMatcher, GitIgnoreMatcher* allowedMatcher,
|
IgnoreMatcherManager& ignoreMatcher, bool initialScan ) {
|
||||||
GitIgnoreMatcher* disallowedMatcher, bool initialScan ) {
|
|
||||||
if ( mClosing || ( initialScan && !mRunning ) )
|
if ( mClosing || ( initialScan && !mRunning ) )
|
||||||
return;
|
return;
|
||||||
currentDirs.insert( directory );
|
currentDirs.insert( directory );
|
||||||
@@ -351,23 +348,8 @@ void ProjectDirectoryTree::getDirectoryFiles(
|
|||||||
FileSystem::filesGetInPath( directory, false, false, ignoreHidden );
|
FileSystem::filesGetInPath( directory, false, false, ignoreHidden );
|
||||||
for ( auto& file : pathFiles ) {
|
for ( auto& file : pathFiles ) {
|
||||||
std::string fullpath( directory + file );
|
std::string fullpath( directory + file );
|
||||||
if ( ignoreMatcher.foundMatch() && ignoreMatcher.match( directory, file ) ) {
|
if ( shouldIgnoreEntry( directory, file, ignoreMatcher ) )
|
||||||
if ( !allowedMatcher || !allowedMatcher->hasPatterns() )
|
continue;
|
||||||
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 ( FileSystem::isDirectory( fullpath ) ) {
|
if ( FileSystem::isDirectory( fullpath ) ) {
|
||||||
fullpath += FileSystem::getOSSlash();
|
fullpath += FileSystem::getOSSlash();
|
||||||
@@ -395,7 +377,7 @@ void ProjectDirectoryTree::getDirectoryFiles(
|
|||||||
ignoreMatcher.addChild( childMatch );
|
ignoreMatcher.addChild( childMatch );
|
||||||
}
|
}
|
||||||
getDirectoryFiles( files, names, fullpath, currentDirs, ignoreHidden, ignoreMatcher,
|
getDirectoryFiles( files, names, fullpath, currentDirs, ignoreHidden, ignoreMatcher,
|
||||||
allowedMatcher, disallowedMatcher, initialScan );
|
initialScan );
|
||||||
if ( childMatch ) {
|
if ( childMatch ) {
|
||||||
ignoreMatcher.removeChild( childMatch );
|
ignoreMatcher.removeChild( childMatch );
|
||||||
eeSAFE_DELETE( 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,
|
void ProjectDirectoryTree::onChange( const ProjectDirectoryTree::Action& action,
|
||||||
const FileInfo& file, const std::string& oldFilename ) {
|
const FileInfo& file, const std::string& oldFilename ) {
|
||||||
if ( !file.isDirectory() && !isDirInTree( file.getFilepath() ) )
|
if ( !file.isDirectory() && !isDirInTree( file.getFilepath() ) )
|
||||||
@@ -433,24 +438,25 @@ void ProjectDirectoryTree::resetPluginManager() {
|
|||||||
void ProjectDirectoryTree::tryAddFile( const FileInfo& file ) {
|
void ProjectDirectoryTree::tryAddFile( const FileInfo& file ) {
|
||||||
if ( mIgnoreHidden && file.isHidden() )
|
if ( mIgnoreHidden && file.isHidden() )
|
||||||
return;
|
return;
|
||||||
|
std::string directory( file.getDirectoryPath() );
|
||||||
|
FileSystem::dirAddSlashAtEnd( directory );
|
||||||
IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( file.getFilepath() ) );
|
IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( file.getFilepath() ) );
|
||||||
if ( !matcher.foundMatch() || !matcher.match( file ) ) {
|
if ( shouldIgnoreEntry( directory, file.getFileName(), matcher ) )
|
||||||
bool foundPattern = mAcceptedPatterns.empty();
|
return;
|
||||||
for ( auto& pattern : mAcceptedPatterns ) {
|
bool foundPattern = mAcceptedPatterns.empty();
|
||||||
if ( pattern.matches( file.getFilepath() ) ) {
|
for ( auto& pattern : mAcceptedPatterns ) {
|
||||||
foundPattern = true;
|
if ( pattern.matches( file.getFilepath() ) ) {
|
||||||
break;
|
foundPattern = true;
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
if ( foundPattern ) {
|
}
|
||||||
Lock rl( mMatchingMutex );
|
if ( foundPattern ) {
|
||||||
Lock l( mFilesMutex );
|
Lock rl( mMatchingMutex );
|
||||||
auto exists =
|
Lock l( mFilesMutex );
|
||||||
std::find( mFiles.begin(), mFiles.end(), file.getFilepath() ) != mFiles.end();
|
auto exists = std::find( mFiles.begin(), mFiles.end(), file.getFilepath() ) != mFiles.end();
|
||||||
if ( !exists ) {
|
if ( !exists ) {
|
||||||
mFiles.emplace_back( file.getFilepath() );
|
mFiles.emplace_back( file.getFilepath() );
|
||||||
mNames.emplace_back( file.getFileName() );
|
mNames.emplace_back( file.getFileName() );
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,25 +468,27 @@ void ProjectDirectoryTree::addFile( const FileInfo& file ) {
|
|||||||
if ( mIgnoreHidden && file.isHidden() )
|
if ( mIgnoreHidden && file.isHidden() )
|
||||||
return;
|
return;
|
||||||
Lock rl( mMatchingMutex );
|
Lock rl( mMatchingMutex );
|
||||||
const std::string& directoryEntry = file.getFilepath();
|
std::string directoryEntry( file.getFilepath() );
|
||||||
IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( directoryEntry ) );
|
FileSystem::dirRemoveSlashAtEnd( directoryEntry );
|
||||||
if ( matcher.foundMatch() && matcher.match( file ) ) {
|
std::string parentDirectory( FileSystem::fileRemoveFileName( directoryEntry ) );
|
||||||
if ( !mAllowedMatcher || !mAllowedMatcher->hasPatterns() )
|
FileSystem::dirAddSlashAtEnd( parentDirectory );
|
||||||
return;
|
{
|
||||||
std::string_view localPath( directoryEntry );
|
Lock ld( mDirectoriesMutex );
|
||||||
if ( String::startsWith( directoryEntry, mAllowedMatcher->getPath() ) )
|
if ( std::find( mDirectories.begin(), mDirectories.end(), parentDirectory ) ==
|
||||||
localPath.remove_prefix( mAllowedMatcher->getPath().size() );
|
mDirectories.end() )
|
||||||
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 ) )
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( directoryEntry ) );
|
||||||
|
if ( shouldIgnoreEntry( parentDirectory, file.getFileName(), matcher ) )
|
||||||
|
return;
|
||||||
std::string directory( directoryEntry );
|
std::string directory( directoryEntry );
|
||||||
FileSystem::dirAddSlashAtEnd( directory );
|
FileSystem::dirAddSlashAtEnd( directory );
|
||||||
|
IgnoreMatcherManager directoryMatcher( directory );
|
||||||
|
IgnoreMatcher* childMatch = nullptr;
|
||||||
|
if ( directoryMatcher.foundMatch() ) {
|
||||||
|
childMatch = directoryMatcher.popMatcher( 0 );
|
||||||
|
matcher.addChild( childMatch );
|
||||||
|
}
|
||||||
Lock l( mFilesMutex );
|
Lock l( mFilesMutex );
|
||||||
std::vector<std::string> files;
|
std::vector<std::string> files;
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
@@ -489,8 +497,11 @@ void ProjectDirectoryTree::addFile( const FileInfo& file ) {
|
|||||||
Lock ld( mDirectoriesMutex );
|
Lock ld( mDirectoriesMutex );
|
||||||
mDirectories.emplace_back( directory );
|
mDirectories.emplace_back( directory );
|
||||||
}
|
}
|
||||||
getDirectoryFiles( files, names, directory, info, mIgnoreHidden, matcher,
|
getDirectoryFiles( files, names, directory, info, mIgnoreHidden, matcher, false );
|
||||||
mAllowedMatcher.get(), mDisallowedMatcher.get(), false );
|
if ( childMatch ) {
|
||||||
|
matcher.removeChild( childMatch );
|
||||||
|
eeSAFE_DELETE( childMatch );
|
||||||
|
}
|
||||||
for ( size_t i = 0; i < files.size(); ++i ) {
|
for ( size_t i = 0; i < files.size(); ++i ) {
|
||||||
bool accepted = mAcceptedPatterns.empty();
|
bool accepted = mAcceptedPatterns.empty();
|
||||||
for ( const auto& pattern : mAcceptedPatterns ) {
|
for ( const auto& pattern : mAcceptedPatterns ) {
|
||||||
|
|||||||
@@ -195,9 +195,11 @@ class ProjectDirectoryTree {
|
|||||||
void getDirectoryFiles( std::vector<std::string>& files, std::vector<std::string>& names,
|
void getDirectoryFiles( std::vector<std::string>& files, std::vector<std::string>& names,
|
||||||
std::string directory, std::set<std::string> currentDirs,
|
std::string directory, std::set<std::string> currentDirs,
|
||||||
const bool& ignoreHidden, IgnoreMatcherManager& ignoreMatcher,
|
const bool& ignoreHidden, IgnoreMatcherManager& ignoreMatcher,
|
||||||
GitIgnoreMatcher* allowedMatcher, GitIgnoreMatcher* disallowedMatcher,
|
|
||||||
bool initialScan = true );
|
bool initialScan = true );
|
||||||
|
|
||||||
|
bool shouldIgnoreEntry( const std::string& directory, const std::string& filename,
|
||||||
|
IgnoreMatcherManager& ignoreMatcher ) const;
|
||||||
|
|
||||||
void addFile( const FileInfo& file );
|
void addFile( const FileInfo& file );
|
||||||
|
|
||||||
void tryAddFile( const FileInfo& file );
|
void tryAddFile( const FileInfo& file );
|
||||||
|
|||||||
Reference in New Issue
Block a user