From 5b33d8ed5e80dca5b790597cacf53be4da29967b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 24 Jan 2021 01:06:45 -0300 Subject: [PATCH] ecode: detect file system changes in the project directory tree. --- include/eepp/system/fileinfo.hpp | 4 + src/eepp/system/fileinfo.cpp | 15 ++ src/tools/codeeditor/codeeditor.cpp | 9 +- src/tools/codeeditor/codeeditor.hpp | 2 +- src/tools/codeeditor/filesystemlistener.cpp | 9 +- src/tools/codeeditor/filesystemlistener.hpp | 6 +- src/tools/codeeditor/projectdirectorytree.cpp | 174 +++++++++++++++++- src/tools/codeeditor/projectdirectorytree.hpp | 29 ++- 8 files changed, 236 insertions(+), 12 deletions(-) diff --git a/include/eepp/system/fileinfo.hpp b/include/eepp/system/fileinfo.hpp index 5a8a6c296..3e1b3e1cf 100644 --- a/include/eepp/system/fileinfo.hpp +++ b/include/eepp/system/fileinfo.hpp @@ -54,6 +54,10 @@ class EE_API FileInfo { const std::string& getFilepath() const; + std::string getFileName() const; + + std::string getDirectoryPath() const; + const Uint64& getModificationTime() const; const Uint64& getSize() const; diff --git a/src/eepp/system/fileinfo.cpp b/src/eepp/system/fileinfo.cpp index 74996e3e9..3a966279b 100644 --- a/src/eepp/system/fileinfo.cpp +++ b/src/eepp/system/fileinfo.cpp @@ -148,6 +148,21 @@ const std::string& FileInfo::getFilepath() const { return mFilepath; } +std::string FileInfo::getFileName() const { + if ( !mFilepath.empty() && mFilepath[mFilepath.size() - 1] != '\\' && + mFilepath[mFilepath.size() - 1] != '/' ) + return FileSystem::fileNameFromPath( mFilepath ); + std::string path( mFilepath ); + FileSystem::dirRemoveSlashAtEnd( mFilepath ); + return FileSystem::fileNameFromPath( path ); +} + +std::string FileInfo::getDirectoryPath() const { + if ( isDirectory() ) + return mFilepath; + return FileSystem::fileRemoveFileName( mFilepath ); +} + const Uint64& FileInfo::getModificationTime() const { return mModificationTime; } diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 22dd65c97..1a1237f81 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -1126,6 +1126,9 @@ void App::closeEditors() { tabWidget->removeTab( (UITab*)editor->getData() ); } mCurrentProject = ""; + mDirTree = nullptr; + if ( mFileSystemListener ) + mFileSystemListener->setDirTree( mDirTree ); } void App::closeFolder() { @@ -1253,7 +1256,7 @@ void App::hideLocateBar() { } bool App::isDirTreeReady() const { - return mDirTreeReady; + return mDirTreeReady && mDirTree != nullptr; } NotificationCenter* App::getNotificationCenter() const { @@ -1650,7 +1653,8 @@ void App::removeFolderWatches() { void App::loadDirTree( const std::string& path ) { Clock* clock = eeNew( Clock, () ); - mDirTree = std::make_unique( path, mThreadPool ); + mDirTreeReady = false; + mDirTree = std::make_shared( path, mThreadPool ); Log::info( "Loading DirTree: %s", path.c_str() ); mDirTree->scan( [&, clock]( ProjectDirectoryTree& dirTree ) { @@ -1664,6 +1668,7 @@ void App::loadDirTree( const std::string& path ) { auto newDirs = dirTree.getDirectories(); for ( const auto& dir : newDirs ) mFolderWatches.insert( mFileWatcher->addWatch( dir, mFileSystemListener ) ); + mFileSystemListener->setDirTree( mDirTree ); } }, SyntaxDefinitionManager::instance()->getExtensionsPatternsSupported() ); diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index 89b3abc2c..ab8dd1d33 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -104,7 +104,7 @@ class App : public UICodeEditorSplitter::Client { LinterModule* mLinterModule{ nullptr }; FormatterModule* mFormatterModule{ nullptr }; std::shared_ptr mThreadPool; - std::unique_ptr mDirTree; + std::shared_ptr mDirTree; UITreeView* mProjectTreeView{ nullptr }; std::shared_ptr mFileSystemModel; size_t mMenuIconSize; diff --git a/src/tools/codeeditor/filesystemlistener.cpp b/src/tools/codeeditor/filesystemlistener.cpp index 6915d364c..367d8a228 100644 --- a/src/tools/codeeditor/filesystemlistener.cpp +++ b/src/tools/codeeditor/filesystemlistener.cpp @@ -6,7 +6,7 @@ FileSystemListener::FileSystemListener( UICodeEditorSplitter* splitter, void FileSystemListener::handleFileAction( efsw::WatchID, const std::string& dir, const std::string& filename, efsw::Action action, - std::string ) { + std::string oldFilename ) { FileInfo file( dir + filename ); switch ( action ) { @@ -18,6 +18,9 @@ void FileSystemListener::handleFileAction( efsw::WatchID, const std::string& dir node->invalidate(); mFileSystemModel.get()->invalidate(); } + + if ( mDirTree ) + mDirTree.get()->onChange( (ProjectDirectoryTree::Action)action, file, oldFilename ); } case efsw::Actions::Modified: { if ( file.isLink() ) @@ -28,6 +31,10 @@ void FileSystemListener::handleFileAction( efsw::WatchID, const std::string& dir } } +void FileSystemListener::setDirTree( const std::shared_ptr& dirTree ) { + mDirTree = dirTree; +} + bool FileSystemListener::isFileOpen( const FileInfo& file ) { bool found = false; mSplitter->forEachDocStoppable( [&]( TextDocument& doc ) { diff --git a/src/tools/codeeditor/filesystemlistener.hpp b/src/tools/codeeditor/filesystemlistener.hpp index 0e78b7a65..a7135fe3c 100644 --- a/src/tools/codeeditor/filesystemlistener.hpp +++ b/src/tools/codeeditor/filesystemlistener.hpp @@ -1,6 +1,7 @@ #ifndef FILESYSTEMLISTENER_HPP #define FILESYSTEMLISTENER_HPP +#include "projectdirectorytree.hpp" #include #include #include @@ -20,13 +21,16 @@ class FileSystemListener : public efsw::FileWatchListener { virtual ~FileSystemListener() {} void handleFileAction( efsw::WatchID, const std::string& dir, const std::string& filename, - efsw::Action action, std::string ); + efsw::Action action, std::string oldFilename ); void setFileSystemModel( std::shared_ptr model ) { mFileSystemModel = model; } + void setDirTree( const std::shared_ptr& dirTree ); + protected: UICodeEditorSplitter* mSplitter; std::shared_ptr mFileSystemModel; + std::shared_ptr mDirTree; bool isFileOpen( const FileInfo& file ); diff --git a/src/tools/codeeditor/projectdirectorytree.cpp b/src/tools/codeeditor/projectdirectorytree.cpp index 4fed68d7c..4830adeb8 100644 --- a/src/tools/codeeditor/projectdirectorytree.cpp +++ b/src/tools/codeeditor/projectdirectorytree.cpp @@ -1,7 +1,6 @@ #include "projectdirectorytree.hpp" #include #include -#include ProjectDirectoryTree::ProjectDirectoryTree( const std::string& path, std::shared_ptr threadPool ) : @@ -10,20 +9,22 @@ ProjectDirectoryTree::ProjectDirectoryTree( const std::string& path, } void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent& scanComplete, - const std::vector& acceptedPattern, + const std::vector& acceptedPatterns, const bool& ignoreHidden ) { #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) mPool->run( - [&, acceptedPattern, ignoreHidden] { + [&, acceptedPatterns, ignoreHidden] { #endif Lock l( mFilesMutex ); + mIgnoreHidden = ignoreHidden; mDirectories.push_back( mPath ); - if ( !acceptedPattern.empty() ) { + if ( !acceptedPatterns.empty() ) { std::vector files; std::vector names; std::vector patterns; - for ( auto& strPattern : acceptedPattern ) + for ( auto& strPattern : acceptedPatterns ) patterns.emplace_back( LuaPattern( strPattern ) ); + mAcceptedPatterns = patterns; std::set info; getDirectoryFiles( files, names, mPath, info, ignoreHidden, mIgnoreMatcher ); size_t namesCount = names.size(); @@ -134,7 +135,7 @@ bool ProjectDirectoryTree::isFileInTree( const std::string& filePath ) const { bool ProjectDirectoryTree::isDirInTree( const std::string& dirTree ) const { std::string dir( FileSystem::fileRemoveFileName( dirTree ) ); FileSystem::dirAddSlashAtEnd( dir ); - return std::find( mDirectories.begin(), mDirectories.end(), dirTree ) != mDirectories.end(); + return std::find( mDirectories.begin(), mDirectories.end(), dir ) != mDirectories.end(); } void ProjectDirectoryTree::getDirectoryFiles( std::vector& files, @@ -174,3 +175,164 @@ void ProjectDirectoryTree::getDirectoryFiles( std::vector& files, } } } + +void ProjectDirectoryTree::onChange( const ProjectDirectoryTree::Action& action, + const FileInfo& file, const std::string& oldFilename ) { + if ( !file.isDirectory() && !isDirInTree( file.getFilepath() ) ) + return; + switch ( action ) { + case ProjectDirectoryTree::Action::Add: + addFile( file ); + break; + case ProjectDirectoryTree::Action::Delete: + removeFile( file ); + break; + case ProjectDirectoryTree::Action::Moved: + moveFile( file, oldFilename ); + break; + case ProjectDirectoryTree::Action::Modified: + break; + } +} + +void ProjectDirectoryTree::addFile( const FileInfo& file ) { + if ( file.isDirectory() ) { + if ( !String::startsWith( file.getFilepath(), mPath ) || isDirInTree( file.getFilepath() ) ) + return; + Lock l( mFilesMutex ); + std::vector files; + std::vector names; + std::vector patterns; + std::set info; + if ( !mAcceptedPatterns.empty() ) { + getDirectoryFiles( files, names, mPath, info, mIgnoreHidden, mIgnoreMatcher ); + size_t namesCount = names.size(); + bool found; + for ( size_t i = 0; i < namesCount; i++ ) { + found = false; + for ( auto& pattern : patterns ) { + if ( pattern.matches( names[i] ) ) { + found = true; + break; + } + } + if ( found ) { + mFiles.emplace_back( std::move( files[i] ) ); + mNames.emplace_back( std::move( names[i] ) ); + } + } + } else { + getDirectoryFiles( mFiles, mNames, mPath, info, mIgnoreHidden, mIgnoreMatcher ); + } + } else { + IgnoreMatcherManager matcher( getIgnoreMatcherFromPath( file.getFilepath() ) ); + if ( !matcher.foundMatch() || + ( matcher.foundMatch() && !matcher.match( file.getFilepath() ) ) ) { + bool foundPattern = mAcceptedPatterns.empty(); + for ( auto& pattern : mAcceptedPatterns ) { + if ( pattern.matches( file.getFilepath() ) ) { + foundPattern = true; + break; + } + } + if ( foundPattern ) { + Lock l( mFilesMutex ); + mFiles.emplace_back( file.getFilepath() ); + mNames.emplace_back( file.getFileName() ); + } + } + } +} + +void ProjectDirectoryTree::moveFile( const FileInfo& file, const std::string& oldFilename ) { + Lock l( mFilesMutex ); + if ( file.isDirectory() ) { + std::string dir( file.getDirectoryPath() ); + FileSystem::dirRemoveSlashAtEnd( dir ); + std::string parentDir( FileSystem::fileRemoveFileName( dir ) ); + FileSystem::dirAddSlashAtEnd( parentDir ); + std::string oldDir( parentDir + oldFilename ); + FileSystem::dirAddSlashAtEnd( dir ); + FileSystem::dirAddSlashAtEnd( oldDir ); + std::vector files; + std::vector names; + for ( size_t i = 0; i < mFiles.size(); i++ ) { + if ( !String::startsWith( mFiles[i], oldDir ) ) { + files.emplace_back( mFiles[i] ); + names.emplace_back( mNames[i] ); + } else { + std::string newDir( dir + mFiles[i].substr( oldDir.size() ) ); + files.emplace_back( newDir ); + names.emplace_back( FileSystem::fileNameFromPath( newDir ) ); + } + } + mFiles = files; + mNames = names; + auto wasDirIt = std::find( mDirectories.begin(), mDirectories.end(), oldDir ); + if ( wasDirIt != mDirectories.end() ) + mDirectories.erase( wasDirIt ); + mDirectories.emplace_back( std::move( dir ) ); + } else { + std::string dir( file.getDirectoryPath() ); + FileSystem::dirAddSlashAtEnd( dir ); + size_t index = findFileIndex( dir + oldFilename ); + if ( index != std::string::npos ) { + mFiles[index] = file.getFilepath(); + mNames[index] = file.getFileName(); + } + } +} + +void ProjectDirectoryTree::removeFile( const FileInfo& file ) { + Lock l( mFilesMutex ); + std::string removedDir( file.getFilepath() ); + FileSystem::dirAddSlashAtEnd( removedDir ); + auto wasDirIt = std::find( mDirectories.begin(), mDirectories.end(), removedDir ); + if ( wasDirIt != mDirectories.end() ) { + std::vector files; + std::vector names; + for ( size_t i = 0; i < mFiles.size(); i++ ) { + if ( !String::startsWith( mFiles[i], removedDir ) ) { + files.emplace_back( mFiles[i] ); + names.emplace_back( mNames[i] ); + } + } + mFiles = files; + mNames = names; + mDirectories.erase( wasDirIt ); + } else { + size_t index = findFileIndex( file.getFilepath() ); + if ( index != std::string::npos ) { + mFiles.erase( mFiles.begin() + index ); + mNames.erase( mNames.begin() + index ); + } + } +} + +IgnoreMatcherManager ProjectDirectoryTree::getIgnoreMatcherFromPath( const std::string& path ) { + std::string dir( FileSystem::fileRemoveFileName( path ) ); + std::string ldir; + FileSystem::dirAddSlashAtEnd( dir ); + IgnoreMatcherManager dirMatcher( dir ); + while ( !dirMatcher.foundMatch() ) { + dirMatcher = IgnoreMatcherManager( dir ); + if ( !dirMatcher.foundMatch() ) { + if ( dir.empty() || dir.find_first_of( "/\\" ) == std::string::npos || dir == mPath || + dir == ldir ) + break; + ldir = dir; + FileSystem::dirRemoveSlashAtEnd( dir ); + dir = FileSystem::fileRemoveFileName( dir ); + FileSystem::dirAddSlashAtEnd( dir ); + } + } + return dirMatcher; +} + +size_t ProjectDirectoryTree::findFileIndex( const std::string& path ) { + for ( size_t i = 0; i < mFiles.size(); i++ ) { + if ( mFiles[i] == path ) + return i; + } + return std::string::npos; +} diff --git a/src/tools/codeeditor/projectdirectorytree.hpp b/src/tools/codeeditor/projectdirectorytree.hpp index e1281bc76..e0399b8f3 100644 --- a/src/tools/codeeditor/projectdirectorytree.hpp +++ b/src/tools/codeeditor/projectdirectorytree.hpp @@ -2,6 +2,7 @@ #define EE_TOOLS_PROJECTDIRECTORYTREE_HPP #include "ignorematcher.hpp" +#include #include #include #include @@ -45,13 +46,24 @@ class FileListModel : public Model { class ProjectDirectoryTree { public: + enum Action { + /// Sent when a file is created or renamed + Add = 1, + /// Sent when a file is deleted or renamed + Delete = 2, + /// Sent when a file is modified + Modified = 3, + /// Sent when a file is moved + Moved = 4 + }; + typedef std::function ScanCompleteEvent; typedef std::function )> MatchResultCb; ProjectDirectoryTree( const std::string& path, std::shared_ptr threadPool ); void scan( const ScanCompleteEvent& scanComplete, - const std::vector& acceptedPattern = {}, + const std::vector& acceptedPatterns = {}, const bool& ignoreHidden = true ); std::shared_ptr fuzzyMatchTree( const std::string& match, @@ -76,19 +88,34 @@ class ProjectDirectoryTree { bool isDirInTree( const std::string& dirTree ) const; + void onChange( const Action& action, const FileInfo& file, const std::string& oldFilename ); + protected: std::string mPath; std::shared_ptr mPool; std::vector mFiles; std::vector mNames; std::vector mDirectories; + std::vector mAcceptedPatterns; bool mIsReady; + bool mIgnoreHidden; Mutex mFilesMutex; IgnoreMatcherManager mIgnoreMatcher; void getDirectoryFiles( std::vector& files, std::vector& names, std::string directory, std::set currentDirs, const bool& ignoreHidden, const IgnoreMatcherManager& ignoreMatcher ); + + void addFile( const FileInfo& file ); + + void moveFile( const FileInfo& file, const std::string& oldFilename ); + + void removeFile( const FileInfo& file ); + + IgnoreMatcherManager getIgnoreMatcherFromPath( const std::string& path ); + + size_t findFileIndex( const std::string& path ); + }; #endif // EE_TOOLS_PROJECTDIRECTORYTREE_HPP