diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp index 4ba08a6e1..673eb2981 100644 --- a/include/eepp/ui/abstract/uiabstracttableview.hpp +++ b/include/eepp/ui/abstract/uiabstracttableview.hpp @@ -69,7 +69,7 @@ class EE_API UIAbstractTableView : public UIAbstractView { void moveSelection( int steps ); - void setSelection( const ModelIndex& index, bool scrollToSelection = true ); + virtual void setSelection( const ModelIndex& index, bool scrollToSelection = true ); const size_t& getIconSize() const; diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp index cb6784dba..8a9b1ae7f 100644 --- a/include/eepp/ui/uitreeview.hpp +++ b/include/eepp/ui/uitreeview.hpp @@ -129,6 +129,10 @@ class EE_API UITreeView : public UIAbstractTableView { virtual ModelIndex findRowWithText( const std::string& text, const bool& caseSensitive = false, const bool& exactMatch = false ) const; + virtual ModelIndex selectRowWithPath( std::string path ); + + virtual void setSelection( const ModelIndex& index, bool scrollToSelection = true ); + bool getFocusOnSelection() const; void setFocusOnSelection( bool focusOnSelection ); diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index aaffa9ace..c1e65e861 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -643,6 +643,81 @@ ModelIndex UITreeView::findRowWithText( const std::string& text, const bool& cas return foundIndex; } +ModelIndex UITreeView::selectRowWithPath( std::string path ) { + const Model* model = getModel(); + if ( !model || model->rowCount() == 0 ) + return {}; + String::replaceAll( path, "\\", "/" ); + auto pathPart = String::split( path, "/" ); + if ( pathPart.empty() ) + return {}; + + for ( size_t i = 0; i < pathPart.size(); i++ ) { + ModelIndex foundIndex = {}; + const auto& part = pathPart[i]; + + traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& ) { + Variant var = model->data( index ); + if ( var.isValid() && var.toString() == part ) { + foundIndex = index; + return IterationDecision::Stop; + } + return IterationDecision::Continue; + } ); + + if ( foundIndex == ModelIndex() ) + break; + + if ( getModel()->rowCount( foundIndex ) ) { + auto& data = getIndexMetadata( foundIndex ); + if ( !data.open ) { + data.open = true; + createOrUpdateColumns(); + onOpenTreeModelIndex( foundIndex, data.open ); + } + } + + if ( i == pathPart.size() - 1 ) { + setSelection( foundIndex ); + return foundIndex; + } + } + return {}; +} + +void UITreeView::setSelection( const ModelIndex& index, bool scrollToSelection ) { + if ( !getModel() ) + return; + auto& model = *this->getModel(); + if ( model.isValid( index ) && scrollToSelection ) { + getSelection().set( index ); + + ModelIndex prevIndex; + ModelIndex foundIndex; + Float curY = 0; + traverseTree( + [&]( const int&, const ModelIndex& _index, const size_t&, const Float& offsetY ) { + if ( index == _index ) { + foundIndex = prevIndex; + curY = offsetY; + return IterationDecision::Break; + } + prevIndex = index; + return IterationDecision::Continue; + } ); + + if ( foundIndex.isValid() ) { + if ( curY < mScrollOffset.y + getHeaderHeight() + getRowHeight() || + curY > mScrollOffset.y + getPixelsSize().getHeight() - mPaddingPx.Top - + mPaddingPx.Bottom - getRowHeight() ) { + curY -= getHeaderHeight() + getRowHeight(); + mVScroll->setValue( eemin( + 1.f, eemax( 0.f, curY / getScrollableArea().getHeight() ) ) ); + } + } + } +} + bool UITreeView::getFocusOnSelection() const { return mFocusOnSelection; } diff --git a/src/tools/codeeditor/appconfig.cpp b/src/tools/codeeditor/appconfig.cpp index 390a0236f..4612d1bab 100644 --- a/src/tools/codeeditor/appconfig.cpp +++ b/src/tools/codeeditor/appconfig.cpp @@ -75,6 +75,8 @@ void AppConfig::load( std::string& confPath, std::string& keybindingsPath, editor.hideTabBarOnSingleTab = ini.getValueB( "editor", "hide_tab_bar_on_single_tab", true ); editor.singleClickTreeNavigation = ini.getValueB( "editor", "single_click_tree_navigation", false ); + editor.syncProjectTreeWithEditor = + ini.getValueB( "editor", "sync_project_tree_with_editor", false ); iniInfo = FileInfo( ini.path() ); } @@ -130,6 +132,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValueB( "editor", "show_doc_info", editor.showDocInfo ); ini.setValueB( "editor", "hide_tab_bar_on_single_tab", editor.hideTabBarOnSingleTab ); ini.setValueB( "editor", "single_click_tree_navigation", editor.singleClickTreeNavigation ); + ini.setValueB( "editor", "sync_project_tree_with_editor", editor.syncProjectTreeWithEditor ); ini.writeFile(); iniState.writeFile(); } diff --git a/src/tools/codeeditor/appconfig.hpp b/src/tools/codeeditor/appconfig.hpp index 7e795b30e..fac4e3a10 100644 --- a/src/tools/codeeditor/appconfig.hpp +++ b/src/tools/codeeditor/appconfig.hpp @@ -54,6 +54,7 @@ struct CodeEditorConfig { bool formatter{ true }; bool hideTabBarOnSingleTab{ true }; bool singleClickTreeNavigation{ false }; + bool syncProjectTreeWithEditor{ true }; std::string autoCloseBrackets{ "" }; int indentWidth{ 4 }; int tabWidth{ 4 }; diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index bd85e2e69..f0c1e9445 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -625,6 +625,10 @@ UIMenu* App::createViewMenu() { ->setActive( mConfig.editor.singleClickTreeNavigation ) ->setTooltipText( "Uses single click to open files and expand subfolders in\nthe directory tree." ); + mViewMenu->addCheckBox( "Synchronize project tree with editor" ) + ->setActive( mConfig.editor.syncProjectTreeWithEditor ) + ->setTooltipText( "Syncronizes the current focused document as the selected\nfile in the " + "directory tree." ); mViewMenu->add( "Line Breaking Column" ); mViewMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { @@ -689,6 +693,8 @@ UIMenu* App::createViewMenu() { } else if ( item->getText() == "Single Click Navigation in Tree View" ) { mConfig.editor.singleClickTreeNavigation = item->asType()->isActive(); mProjectTreeView->setSingleClickNavigation( mConfig.editor.singleClickTreeNavigation ); + } else if ( item->getText() == "Synchronize project tree with editor" ) { + mConfig.editor.syncProjectTreeWithEditor = item->asType()->isActive(); } else if ( item->getText() == "Line Breaking Column" ) { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, "Set Line Breaking Column:\n" @@ -1042,10 +1048,21 @@ void App::updateDocInfo( TextDocument& doc ) { } } +void App::syncProjectTreeWithEditor( UICodeEditor* editor ) { + if ( mConfig.editor.syncProjectTreeWithEditor && editor->getDocument().hasFilepath() ) { + std::string path = editor->getDocument().getFilePath(); + if ( path.size() >= mCurrentProject.size() ) { + path = path.substr( mCurrentProject.size() ); + mProjectTreeView->selectRowWithPath( path ); + } + } +} + void App::onCodeEditorFocusChange( UICodeEditor* editor ) { updateDocInfo( editor->getDocument() ); updateDocumentMenu(); mDocSearchController->onCodeEditorFocusChange( editor ); + syncProjectTreeWithEditor( editor ); } void App::onColorSchemeChanged( const std::string& ) { @@ -1666,12 +1683,14 @@ void App::loadDirTree( const std::string& path ) { clock->getElapsedTime().asMilliseconds(), dirTree.getFilesCount() ); eeDelete( clock ); mDirTreeReady = true; - mUISceneNode->runOnMainThread( [&] { mFileLocator->updateLocateTable(); } ); + mUISceneNode->runOnMainThread( [&] { + mFileLocator->updateLocateTable(); + syncProjectTreeWithEditor( mEditorSplitter->getCurEditor() ); + } ); if ( mFileWatcher ) { removeFolderWatches(); - auto newDirs = dirTree.getDirectories(); - for ( const auto& dir : newDirs ) - mFolderWatches.insert( mFileWatcher->addWatch( dir, mFileSystemListener ) ); + mFolderWatches.insert( + mFileWatcher->addWatch( dirTree.getPath(), mFileSystemListener, true ) ); mFileSystemListener->setDirTree( mDirTree ); } }, diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index ab8dd1d33..e5f103458 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -236,6 +236,8 @@ class App : public UICodeEditorSplitter::Client { void removeFolderWatches(); void createDocAlert( UICodeEditor* editor ); + + void syncProjectTreeWithEditor( UICodeEditor* editor ); }; #endif // EE_TOOLS_CODEEDITOR_HPP diff --git a/src/tools/codeeditor/filesystemlistener.cpp b/src/tools/codeeditor/filesystemlistener.cpp index b22150fa5..166358caf 100644 --- a/src/tools/codeeditor/filesystemlistener.cpp +++ b/src/tools/codeeditor/filesystemlistener.cpp @@ -14,7 +14,7 @@ void FileSystemListener::handleFileAction( efsw::WatchID, const std::string& dir case efsw::Actions::Delete: case efsw::Actions::Moved: { auto* node = mFileSystemModel.get()->getNodeFromPath( file.getFilepath(), true, false ); - if ( node ) { + if ( node && mDirTree && mDirTree.get()->isDirInTree( file.getDirectoryPath() ) ) { if ( !mFileSystemModel.get()->getDisplayConfig().ignoreHidden || !file.isHidden() ) { node->invalidate(); diff --git a/src/tools/codeeditor/projectdirectorytree.hpp b/src/tools/codeeditor/projectdirectorytree.hpp index e0399b8f3..a9215d781 100644 --- a/src/tools/codeeditor/projectdirectorytree.hpp +++ b/src/tools/codeeditor/projectdirectorytree.hpp @@ -90,6 +90,8 @@ class ProjectDirectoryTree { void onChange( const Action& action, const FileInfo& file, const std::string& oldFilename ); + const std::string& getPath() const { return mPath; } + protected: std::string mPath; std::shared_ptr mPool; @@ -115,7 +117,6 @@ class ProjectDirectoryTree { IgnoreMatcherManager getIgnoreMatcherFromPath( const std::string& path ); size_t findFileIndex( const std::string& path ); - }; #endif // EE_TOOLS_PROJECTDIRECTORYTREE_HPP