diff --git a/.gitignore b/.gitignore index 2a50c2bb9..6c160d6f9 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ ecode.dmg /projects/**/*.7z /projects/**/*.tar.gz /projects/**/*.zip +/.ecode/.fstreeviewignore diff --git a/include/eepp/ui/models/filesystemmodel.hpp b/include/eepp/ui/models/filesystemmodel.hpp index 341c204cf..c387b9f2d 100644 --- a/include/eepp/ui/models/filesystemmodel.hpp +++ b/include/eepp/ui/models/filesystemmodel.hpp @@ -37,16 +37,20 @@ class EE_API FileSystemModel : public Model { struct DisplayConfig { DisplayConfig() {} - DisplayConfig( bool sortByName, bool foldersFirst, bool ignoreHidden, - std::vector acceptedExtensions = {} ) : + DisplayConfig( + bool sortByName, bool foldersFirst, bool ignoreHidden, + std::vector acceptedExtensions = {}, + std::function fileIsVisibleFn = nullptr ) : sortByName( sortByName ), foldersFirst( foldersFirst ), ignoreHidden( ignoreHidden ), - acceptedExtensions( acceptedExtensions ) {} + acceptedExtensions( acceptedExtensions ), + fileIsVisibleFn( fileIsVisibleFn ) {} bool sortByName{ true }; bool foldersFirst{ true }; bool ignoreHidden{ false }; std::vector acceptedExtensions; + std::function fileIsVisibleFn; bool operator==( const DisplayConfig& other ) { return sortByName == other.sortByName && foldersFirst == other.foldersFirst && ignoreHidden == other.ignoreHidden && diff --git a/include/eepp/version.hpp b/include/eepp/version.hpp index 498755e57..ecc9b951a 100644 --- a/include/eepp/version.hpp +++ b/include/eepp/version.hpp @@ -6,7 +6,7 @@ #define EEPP_MAJOR_VERSION 2 #define EEPP_MINOR_VERSION 5 -#define EEPP_PATCH_LEVEL 5 +#define EEPP_PATCH_LEVEL 6 #define EEPP_CODENAME "Bindu" /** The compiled version of the library */ diff --git a/src/eepp/ui/models/filesystemmodel.cpp b/src/eepp/ui/models/filesystemmodel.cpp index 1c9aa689b..a472ba9d3 100644 --- a/src/eepp/ui/models/filesystemmodel.cpp +++ b/src/eepp/ui/models/filesystemmodel.cpp @@ -138,25 +138,47 @@ FileSystemModel::Node* FileSystemModel::Node::childWithPathExists( const std::st return nullptr; } +static bool isAcceptedExtension( const std::vector& acceptedExtensions, + const FileInfo& file ) { + if ( !acceptedExtensions.empty() && file.isRegularFile() ) { + for ( size_t z = 0; z < acceptedExtensions.size(); z++ ) + if ( acceptedExtensions[z] == FileSystem::fileExtension( file.getFilepath() ) ) + return true; + return false; + } + return true; +} + void FileSystemModel::Node::refresh( const FileSystemModel& model ) { if ( !mInfo.isDirectory() ) return; auto oldFiles = mChildren; - auto files = FileSystem::filesInfoGetInPath( - mInfo.getFilepath(), false, model.getDisplayConfig().sortByName, - model.getDisplayConfig().foldersFirst, model.getDisplayConfig().ignoreHidden ); + + const auto& displayCfg = model.getDisplayConfig(); + + auto files = FileSystem::filesInfoGetInPath( mInfo.getFilepath(), false, displayCfg.sortByName, + displayCfg.foldersFirst, displayCfg.ignoreHidden ); std::vector newChildren; Node* node = nullptr; for ( auto file : files ) { - if ( ( node = childWithPathExists( file.getFilepath() ) ) ) { - newChildren.emplace_back( node ); + node = childWithPathExists( file.getFilepath() ); + + if ( !isAcceptedExtension( displayCfg.acceptedExtensions, file ) ) + continue; + + if ( displayCfg.fileIsVisibleFn && !displayCfg.fileIsVisibleFn( file.getFilepath() ) ) + continue; + + if ( node ) { auto it = std::find( oldFiles.begin(), oldFiles.end(), node ); if ( it != oldFiles.end() ) oldFiles.erase( it ); + newChildren.emplace_back( node ); + if ( node->info().isDirectory() && node->mHasTraversed ) node->refresh( model ); } else { @@ -182,17 +204,21 @@ void FileSystemModel::Node::traverseIfNeeded( const FileSystemModel& model ) { mHasTraversed = true; cleanChildren(); - auto files = FileSystem::filesInfoGetInPath( - mInfo.getFilepath(), false, model.getDisplayConfig().sortByName, - model.getDisplayConfig().foldersFirst, model.getDisplayConfig().ignoreHidden ); + const auto& displayCfg = model.getDisplayConfig(); - const auto& patterns = model.getDisplayConfig().acceptedExtensions; + auto files = FileSystem::filesInfoGetInPath( mInfo.getFilepath(), false, displayCfg.sortByName, + displayCfg.foldersFirst, displayCfg.ignoreHidden ); + + const auto& patterns = displayCfg.acceptedExtensions; bool accepted; for ( auto file : files ) { if ( ( model.getMode() == Mode::DirectoriesOnly && ( file.isDirectory() || file.linksToDirectory() ) ) || model.getMode() == Mode::FilesAndDirectories ) { if ( file.isDirectory() || file.linksToDirectory() || patterns.empty() ) { + if ( displayCfg.fileIsVisibleFn && + !displayCfg.fileIsVisibleFn( file.getFilepath() ) ) + continue; mChildren.emplace_back( eeNew( Node, ( std::move( file ), this ) ) ); } else { accepted = false; @@ -204,8 +230,14 @@ void FileSystemModel::Node::traverseIfNeeded( const FileSystemModel& model ) { } } } else { - accepted = true; + if ( displayCfg.fileIsVisibleFn ) { + if ( displayCfg.fileIsVisibleFn( file.getFilepath() ) ) + accepted = true; + } else { + accepted = true; + } } + if ( accepted ) mChildren.emplace_back( eeNew( Node, ( std::move( file ), this ) ) ); } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 8033d76fe..daae8d567 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1430,7 +1430,8 @@ std::vector App::getUnlockedCommands() { "serif-font", "monospace-font", "terminal-font", - "fallback-font" }; + "fallback-font", + "tree-view-configure-ignore-files" }; } bool App::isUnlockedCommand( const std::string& command ) { @@ -1728,6 +1729,11 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { ed->getKeyBindings().reset(); ed->getKeyBindings().addKeybindsStringUnordered( mKeybindings ); } ); + } else if ( mFileSystemMatcher && mFileSystemMatcher->getIgnoreFilePath() == + editor->getDocument().getFilePath() ) { + loadFileSystemMatcher( mFileSystemMatcher ? mFileSystemMatcher->getPath() + : mCurrentProject ); + refreshFolderView(); } if ( !editor->getDocument().hasSyntaxDefinition() ) { editor->getDocument().resetSyntax(); @@ -1966,9 +1972,15 @@ void App::renameFile( const FileInfo& file ) { } void App::toggleHiddenFiles() { - mFileSystemModel = FileSystemModel::New( - mFileSystemModel->getRootPath(), FileSystemModel::Mode::FilesAndDirectories, - { true, true, !mFileSystemModel->getDisplayConfig().ignoreHidden } ); + mFileSystemModel = FileSystemModel::New( mFileSystemModel->getRootPath(), + FileSystemModel::Mode::FilesAndDirectories, + { true, + true, + !mFileSystemModel->getDisplayConfig().ignoreHidden, + {}, + [&]( const std::string& filePath ) -> bool { + return isFileVisibleInTreeView( filePath ); + } } ); if ( mProjectTreeView ) mProjectTreeView->setModel( mFileSystemModel ); if ( mFileSystemListener ) @@ -2121,8 +2133,13 @@ void App::initProjectTreeView( const std::string& path ) { std::string folderPath( FileSystem::fileRemoveFileName( rpath ) ); if ( FileSystem::isDirectory( folderPath ) ) { + loadFileSystemMatcher( folderPath ); + mFileSystemModel = FileSystemModel::New( - folderPath, FileSystemModel::Mode::FilesAndDirectories, { true, true, true } ); + folderPath, FileSystemModel::Mode::FilesAndDirectories, + { true, true, true, {}, [&]( const std::string& filePath ) -> bool { + return isFileVisibleInTreeView( filePath ); + } } ); mProjectTreeView->setModel( mFileSystemModel ); @@ -2152,6 +2169,61 @@ void App::initImageView() { } ); } +bool App::isFileVisibleInTreeView( const std::string& filePath ) { + if ( !mFileSystemMatcher || !mFileSystemMatcher->matcherReady() ) + return true; + auto fpath( filePath ); + FileSystem::filePathRemoveBasePath( mCurrentProject, fpath ); + FileSystem::dirRemoveSlashAtEnd( fpath ); + return !mFileSystemMatcher->match( fpath ); +} + +void App::treeViewConfigureIgnoreFiles() { + if ( !mFileSystemModel || mFileSystemModel->getRootPath().empty() ) { + errorMsgBox( i18n( "open_first_a_folder", "You must first open a folder." ) ); + return; + } + + std::string ignoreFilePath; + + if ( mFileSystemMatcher && !mFileSystemMatcher->getIgnoreFilePath().empty() ) { + ignoreFilePath = mFileSystemMatcher->getIgnoreFilePath(); + } else { + ignoreFilePath = mFileSystemModel->getRootPath(); + FileSystem::dirAddSlashAtEnd( ignoreFilePath ); + ignoreFilePath += ".ecode/.fstreeviewignore"; + } + + if ( !FileSystem::fileExists( ignoreFilePath ) ) { + bool dirExists = true; + std::string dirPath( FileSystem::fileRemoveFileName( ignoreFilePath ) ); + if ( !FileSystem::fileExists( dirPath ) ) + dirExists = FileSystem::makeDir( dirPath ); + + if ( !dirExists ) { + errorMsgBox( i18n( "couldnt_create_ecode_dir", + "Couldn't create .ecode directory in current folder." ) ); + return; + } + + if ( !FileSystem::fileWrite( ignoreFilePath, "\n" ) ) { + errorMsgBox( i18n( "couldnt_write_file", "Couldn't write file on disk." ) ); + return; + } + + loadFileSystemMatcher( mFileSystemModel->getRootPath() ); + } + + loadFileFromPath( ignoreFilePath ); +} + +void App::loadFileSystemMatcher( const std::string& folderPath ) { + if ( folderPath.empty() ) + return; + mFileSystemMatcher = + std::make_shared( folderPath, ".ecode/.fstreeviewignore" ); +} + void App::loadFolder( const std::string& path ) { if ( !mCurrentProject.empty() ) closeEditors(); @@ -2164,8 +2236,13 @@ void App::loadFolder( const std::string& path ) { mConfig.loadProject( rpath, mSplitter, mConfigPath, mProjectDocConfig, mThreadPool, this ); - mFileSystemModel = FileSystemModel::New( rpath, FileSystemModel::Mode::FilesAndDirectories, - { true, true, true } ); + loadFileSystemMatcher( rpath ); + + mFileSystemModel = + FileSystemModel::New( rpath, FileSystemModel::Mode::FilesAndDirectories, + { true, true, true, {}, [&]( const std::string& filePath ) -> bool { + return isFileVisibleInTreeView( filePath ); + } } ); if ( mProjectTreeView ) mProjectTreeView->setModel( mFileSystemModel ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index c4271e6dd..88b52fb3b 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -227,6 +227,7 @@ class App : public UICodeEditorSplitter::Client { t.setCommand( "monospace-font", [&] { openFontDialog( mConfig.ui.monospaceFont, true ); } ); t.setCommand( "terminal-font", [&] { openFontDialog( mConfig.ui.terminalFont, false ); } ); t.setCommand( "fallback-font", [&] { openFontDialog( mConfig.ui.fallbackFont, false ); } ); + t.setCommand( "tree-view-configure-ignore-files", [&] { treeViewConfigureIgnoreFiles(); } ); mSplitter->registerSplitterCommands( t ); } @@ -297,6 +298,12 @@ class App : public UICodeEditorSplitter::Client { void refreshFolderView(); + bool isFileVisibleInTreeView( const std::string& filePath ); + + void treeViewConfigureIgnoreFiles(); + + void loadFileSystemMatcher( const std::string& folderPath ); + protected: EE::Window::Window* mWindow{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; @@ -329,6 +336,7 @@ class App : public UICodeEditorSplitter::Client { std::shared_ptr mDirTree; UITreeView* mProjectTreeView{ nullptr }; std::shared_ptr mFileSystemModel; + std::shared_ptr mFileSystemMatcher; size_t mMenuIconSize{ 16 }; bool mDirTreeReady{ false }; bool mIsBundledApp{ false }; diff --git a/src/tools/ecode/ignorematcher.cpp b/src/tools/ecode/ignorematcher.cpp index 2f416d523..2fc37c6be 100644 --- a/src/tools/ecode/ignorematcher.cpp +++ b/src/tools/ecode/ignorematcher.cpp @@ -148,18 +148,29 @@ IgnoreMatcher::IgnoreMatcher( const std::string& rootPath ) : mPath( rootPath ) IgnoreMatcher::~IgnoreMatcher() {} -GitIgnoreMatcher::GitIgnoreMatcher( const std::string& rootPath ) : IgnoreMatcher( rootPath ) { - if ( canMatch() ) - parse(); +GitIgnoreMatcher::GitIgnoreMatcher( const std::string& rootPath, + const std::string& ignoreFileName ) : + IgnoreMatcher( rootPath ), + mIgnoreFileName( ignoreFileName ), + mIgnoreFilePath( mPath + mIgnoreFileName ) { + if ( canMatch() ) { + if ( parse() ) { + mMatcherReady = true; + } + } } bool GitIgnoreMatcher::canMatch() { - return FileSystem::fileExists( mPath + ".gitignore" ); + return FileSystem::fileExists( mIgnoreFilePath ); +} + +const std::string& GitIgnoreMatcher::getIgnoreFilePath() const { + return mIgnoreFilePath; } bool GitIgnoreMatcher::parse() { std::string patternFile; - FileSystem::fileGet( mPath + ".gitignore", patternFile ); + FileSystem::fileGet( mPath + mIgnoreFileName, patternFile ); std::vector patterns = String::split( patternFile ); for ( auto& pattern : patterns ) { bool negates = false; @@ -182,13 +193,11 @@ bool GitIgnoreMatcher::parse() { } bool GitIgnoreMatcher::match( const std::string& value ) const { + if ( mPatterns.empty() ) + return false; bool match = false; for ( size_t i = 0; i < mPatterns.size(); i++ ) { auto& pattern = mPatterns[i]; - if ( String::contains( pattern.first, "Tarballs" ) && - String::contains( value, "Tarballs" ) ) { - String::toLower( pattern.first ); - } match = gitignore_glob_match( value, pattern.first ); if ( pattern.second ) match = !match; diff --git a/src/tools/ecode/ignorematcher.hpp b/src/tools/ecode/ignorematcher.hpp index 6671e8bba..b74243d5b 100644 --- a/src/tools/ecode/ignorematcher.hpp +++ b/src/tools/ecode/ignorematcher.hpp @@ -23,28 +23,38 @@ class IgnoreMatcher { virtual std::string findRepositoryRootPath() const = 0; + virtual const std::string& getIgnoreFilePath() const = 0; + const std::string& getPath() const { return mPath; } + const bool& matcherReady() const { return mMatcherReady; } + protected: std::string mPath; + bool mMatcherReady{ false }; virtual bool parse() = 0; }; class GitIgnoreMatcher : public IgnoreMatcher { public: - GitIgnoreMatcher( const std::string& rootPath ); + GitIgnoreMatcher( const std::string& rootPath, + const std::string& ignoreFileName = ".gitignore" ); bool canMatch() override; + const std::string& getIgnoreFilePath() const override; + bool match( const std::string& value ) const override; std::string findRepositoryRootPath() const override; protected: - bool parse() override; - + std::string mIgnoreFileName; + std::string mIgnoreFilePath; std::vector> mPatterns; + + bool parse() override; }; class IgnoreMatcherManager { @@ -80,8 +90,6 @@ class IgnoreMatcherManager { protected: std::string mRootPath; std::vector mMatchers; - - }; } // namespace ecode diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index df6a7ba28..e0f17184f 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -1404,10 +1404,13 @@ void SettingsMenu::createProjectTreeMenu() { mProjectTreeMenu ->addCheckBox( i18n( "show_hidden_files", "Show hidden files" ), !mApp->getFileSystemModel()->getDisplayConfig().ignoreHidden ) - ->setId( "show_hidden_fileƱs" ); + ->setId( "show-hidden-files" ); mProjectTreeMenu->addSeparator(); mProjectTreeMenu->add( i18n( "refresh_view", "Refresh View..." ), findIcon( "refresh" ) ) ->setId( "refresh-view" ); + mProjectTreeMenu->addSeparator(); + mProjectTreeMenu->add( i18n( "configure_ignore_files", "Configure Ignore Files..." ) ) + ->setId( "configure-ignore-files" ); } else if ( !mApp->getFileSystemModel() ) { mProjectTreeMenu->add( i18n( "open_folder", "Open Folder..." ), findIcon( "folder-open" ) ) ->setId( "open-folder" ); @@ -1426,7 +1429,7 @@ void SettingsMenu::createProjectTreeMenu() { Engine::instance()->openURI( mApp->getCurrentProject() ); } else if ( "execute_dir_in_terminal" == id ) { mApp->getTerminalManager()->createNewTerminal( "", nullptr, mApp->getCurrentProject() ); - } else if ( "show_hidden_files" == id ) { + } else if ( "show-hidden-files" == id ) { mApp->toggleHiddenFiles(); } else if ( "collapse-all" == id ) { mApp->getProjectTreeView()->collapseAll(); @@ -1436,6 +1439,8 @@ void SettingsMenu::createProjectTreeMenu() { mApp->openFolderDialog(); } else if ( "refresh-view" == id ) { mApp->refreshFolderView(); + } else if ( "configure-ignore-files" == id ) { + mApp->treeViewConfigureIgnoreFiles(); } } ); @@ -1493,7 +1498,7 @@ void SettingsMenu::createProjectTreeMenu( const FileInfo& file ) { mProjectTreeMenu ->addCheckBox( i18n( "show_hidden_files", "Show hidden files" ), !mApp->getFileSystemModel()->getDisplayConfig().ignoreHidden ) - ->setId( "show_hidden_files" ); + ->setId( "show-hidden-files" ); mProjectTreeMenu->addSeparator(); mProjectTreeMenu->add( i18n( "collapse_all", "Collapse All" ), findIcon( "collapse-all" ) ) @@ -1504,6 +1509,12 @@ void SettingsMenu::createProjectTreeMenu( const FileInfo& file ) { mProjectTreeMenu->add( i18n( "refresh_view", "Refresh View..." ), findIcon( "refresh" ) ) ->setId( "refresh-view" ); + if ( !mApp->getCurrentProject().empty() ) { + mProjectTreeMenu->addSeparator(); + mProjectTreeMenu->add( i18n( "configure_ignore_files", "Configure Ignore Files..." ) ) + ->setId( "configure-ignore-files" ); + } + mProjectTreeMenu->addEventListener( Event::OnItemClicked, [&, file]( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) return; @@ -1571,7 +1582,7 @@ void SettingsMenu::createProjectTreeMenu( const FileInfo& file ) { Engine::instance()->openURI( file.getDirectoryPath() ); } else if ( "open_folder" == id ) { Engine::instance()->openURI( file.getFilepath() ); - } else if ( "show_hidden_files" == id ) { + } else if ( "show-hidden-files" == id ) { mApp->toggleHiddenFiles(); } else if ( "execute_in_terminal" == id ) { UITerminal* term = mApp->getTerminalManager()->createNewTerminal( @@ -1587,6 +1598,8 @@ void SettingsMenu::createProjectTreeMenu( const FileInfo& file ) { mApp->getProjectTreeView()->expandAll(); } else if ( "refresh-view" == id ) { mApp->refreshFolderView(); + } else if ( "configure-ignore-files" == id ) { + mApp->treeViewConfigureIgnoreFiles(); } } ); diff --git a/src/tools/ecode/version.hpp b/src/tools/ecode/version.hpp index bcce49853..2da276d38 100644 --- a/src/tools/ecode/version.hpp +++ b/src/tools/ecode/version.hpp @@ -8,7 +8,7 @@ using namespace EE; #define ECODE_MAJOR_VERSION 0 #define ECODE_MINOR_VERSION 4 -#define ECODE_PATCH_LEVEL 3 +#define ECODE_PATCH_LEVEL 4 #define ECODE_CODENAME "Vajra" /** The compiled version of the library */