From 42ba58e29d930ec8a1dae667376e8b88852bfb63 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 13 Sep 2024 15:19:45 +0200 Subject: [PATCH] ProjectExplorer: improve treescanner scan result Allow returning all folders scanned by the treescanner. This will be used in the WorkspaceProject to also show empty folders in the project tree. Additionally we are now able to iterate over the complete tree of folder nodes and add the paths to the watcher instead of walking the directory tree again to collect all directories to watch. Change-Id: Ibe7bed9ccee8317918e06fc78ca85f74102d46fc Reviewed-by: Marcus Tillmanns --- .../cmakeprojectmanager/cmakebuildsystem.cpp | 5 +- .../compilationdbparser.cpp | 4 +- src/plugins/haskell/haskellproject.cpp | 2 +- src/plugins/nim/project/nimbuildsystem.cpp | 4 +- src/plugins/projectexplorer/treescanner.cpp | 101 ++++++++++++------ src/plugins/projectexplorer/treescanner.h | 10 +- .../projectexplorer/workspaceproject.cpp | 52 +++++---- 7 files changed, 108 insertions(+), 70 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index d9ca5a69672..e90e8ba2c18 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -1505,8 +1505,9 @@ void CMakeBuildSystem::updateProjectData() void CMakeBuildSystem::handleTreeScanningFinished() { TreeScanner::Result result = m_treeScanner.release(); - m_allFiles = result.folderNode; - qDeleteAll(result.allFiles); + m_allFiles = std::make_shared(projectDirectory()); + for (auto node : result.takeFirstLevelNodes()) + m_allFiles->addNode(std::unique_ptr(node)); updateFileSystemNodes(); } diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp index 97ed389bc2b..db38787b5a9 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp @@ -221,8 +221,8 @@ void CompilationDbParser::stop() QList CompilationDbParser::scannedFiles() const { const bool canceled = m_treeScanner->future().isCanceled(); - const TreeScanner::Result result = m_treeScanner->release(); - return !canceled ? result.allFiles : QList(); + TreeScanner::Result result = m_treeScanner->release(); + return !canceled ? result.takeAllFiles() : QList(); } void CompilationDbParser::parserJobFinished() diff --git a/src/plugins/haskell/haskellproject.cpp b/src/plugins/haskell/haskellproject.cpp index bb586c7eacb..c9378ac317c 100644 --- a/src/plugins/haskell/haskellproject.cpp +++ b/src/plugins/haskell/haskellproject.cpp @@ -67,7 +67,7 @@ HaskellBuildSystem::HaskellBuildSystem(Target *t) auto root = std::make_unique(projectDirectory()); root->setDisplayName(target()->project()->displayName()); std::vector> nodePtrs - = Utils::transform(m_scanner.release().allFiles, [](FileNode *fn) { + = Utils::transform(m_scanner.release().takeAllFiles(), [](FileNode *fn) { return std::unique_ptr(fn); }); root->addNestedNodes(std::move(nodePtrs)); diff --git a/src/plugins/nim/project/nimbuildsystem.cpp b/src/plugins/nim/project/nimbuildsystem.cpp index 0458d847bd1..5ffd852c41a 100644 --- a/src/plugins/nim/project/nimbuildsystem.cpp +++ b/src/plugins/nim/project/nimbuildsystem.cpp @@ -36,8 +36,8 @@ NimProjectScanner::NimProjectScanner(Project *project) connect(&m_scanner, &TreeScanner::finished, this, [this] { // Collect scanned nodes std::vector> nodes; - const TreeScanner::Result scanResult = m_scanner.release(); - for (FileNode *node : scanResult.allFiles) { + TreeScanner::Result scanResult = m_scanner.release(); + for (FileNode *node : scanResult.takeAllFiles()) { if (!node->path().endsWith(".nim") && !node->path().endsWith(".nimble")) node->setEnabled(false); // Disable files that do not end in .nim nodes.emplace_back(node); diff --git a/src/plugins/projectexplorer/treescanner.cpp b/src/plugins/projectexplorer/treescanner.cpp index 4587d913961..371d4946ae6 100644 --- a/src/plugins/projectexplorer/treescanner.cpp +++ b/src/plugins/projectexplorer/treescanner.cpp @@ -133,35 +133,23 @@ FileType TreeScanner::genericFileType(const Utils::MimeType &mimeType, const Uti return Node::fileTypeForMimeType(mimeType); } -static std::unique_ptr createFolderNode(const Utils::FilePath &directory, - const QList &allFiles) -{ - auto fileSystemNode = std::make_unique(directory); - for (const FileNode *fn : allFiles) { - if (!fn->filePath().isChildOf(directory)) - continue; - - std::unique_ptr node(fn->clone()); - fileSystemNode->addNestedNode(std::move(node)); - } - ProjectTree::applyTreeManager(fileSystemNode.get(), ProjectTree::AsyncPhase); // QRC nodes - return fileSystemNode; -} - struct DirectoryScanResult { QList nodes; - Utils::FilePaths subDirectories; + QList subDirectories; + FolderNode *parentNode; }; static DirectoryScanResult scanForFilesImpl( const QFuture &future, const Utils::FilePath &directory, + FolderNode *parent, QDir::Filters filter, const std::function &factory, const QList &versionControls) { DirectoryScanResult result; + result.parentNode = parent; const Utils::FilePaths entries = directory.dirEntries(filter); for (const Utils::FilePath &entry : entries) { @@ -175,7 +163,7 @@ static DirectoryScanResult scanForFilesImpl( } if (entry.isDir()) - result.subDirectories.append(entry); + result.subDirectories.append(new FolderNode(entry)); else if (FileNode *node = factory(entry)) result.nodes.append(node); } @@ -188,7 +176,7 @@ static const Utils::MimeType &directoryMimeType() return mimeType; } -static QList scanForFilesHelper( +static TreeScanner::Result scanForFilesHelper( TreeScanner::Promise &promise, const Utils::FilePath &directory, QDir::Filters dirfilter, @@ -202,23 +190,32 @@ static QList scanForFilesHelper( promise.setProgressRange(0, progressRange); QSet visited; - const DirectoryScanResult result = scanForFilesImpl(future, directory, dirfilter, factory, versionControls); + const DirectoryScanResult result + = scanForFilesImpl(future, directory, nullptr, dirfilter, factory, versionControls); QList fileNodes = result.nodes; + QList firstLevelNodes; + for (auto fileNode : fileNodes) + firstLevelNodes.append(fileNode->clone()); const int progressIncrement = int( progressRange / static_cast(fileNodes.count() + result.subDirectories.count())); promise.setProgressValue(int(fileNodes.count() * progressIncrement)); - QList> subDirectories; - auto addSubDirectories = [&](const Utils::FilePaths &subdirs, int progressIncrement) { - for (const Utils::FilePath &subdir : subdirs) { - if (Utils::insert(visited, subdir.canonicalPath()) - && !(filter && filter(directoryMimeType(), subdir))) { + QList> subDirectories; + auto addSubDirectories = [&](const QList &subdirs, FolderNode * parent, int progressIncrement) { + for (FolderNode *subdir : subdirs) { + if (Utils::insert(visited, subdir->filePath().canonicalPath()) + && !(filter && filter(directoryMimeType(), subdir->filePath()))) { subDirectories.append(qMakePair(subdir, progressIncrement)); + subdir->setDisplayName(subdir->filePath().fileName()); + if (parent) + parent->addNode(std::unique_ptr(subdir)); + else + firstLevelNodes << subdir; } else { promise.setProgressValue(future.progressValue() + progressIncrement); } } }; - addSubDirectories(result.subDirectories, progressIncrement); + addSubDirectories(result.subDirectories, nullptr, progressIncrement); while (!subDirectories.isEmpty()) { using namespace Tasking; @@ -227,7 +224,13 @@ static QList scanForFilesHelper( auto onSetup = [&, iterator](Utils::Async &task) { task.setConcurrentCallData( - scanForFilesImpl, future, iterator->first, dirfilter, factory, versionControls); + scanForFilesImpl, + future, + iterator->first->filePath(), + iterator->first, + dirfilter, + factory, + versionControls); }; auto onDone = [&, iterator](const Utils::Async &task) { @@ -235,6 +238,10 @@ static QList scanForFilesHelper( const DirectoryScanResult result = task.result(); fileNodes.append(result.nodes); const qsizetype subDirCount = result.subDirectories.count(); + if (iterator->first) { + for (auto fn : result.nodes) + iterator->first->addNode(std::unique_ptr(fn->clone())); + } if (subDirCount == 0) { promise.setProgressValue(future.progressValue() + progressRange); } else { @@ -242,7 +249,7 @@ static QList scanForFilesHelper( const int increment = int( progressRange / static_cast(fileCount + subDirCount)); promise.setProgressValue(future.progressValue() + increment * fileCount); - addSubDirectories(result.subDirectories, increment); + addSubDirectories(result.subDirectories, result.parentNode, increment); } }; @@ -252,7 +259,10 @@ static QList scanForFilesHelper( }; TaskTree::runBlocking(recipe); } - return fileNodes; + + Utils::sort(fileNodes, ProjectExplorer::Node::sortByPath); + + return {fileNodes, firstLevelNodes}; } void TreeScanner::scanForFiles( @@ -262,8 +272,12 @@ void TreeScanner::scanForFiles( QDir::Filters dirFilter, const FileTypeFactory &factory) { - QList nodes = scanForFilesHelper( - promise, directory, dirFilter, filter, [&filter, &factory](const Utils::FilePath &fn) -> FileNode * { + Result result = scanForFilesHelper( + promise, + directory, + dirFilter, + filter, + [&filter, &factory](const Utils::FilePath &fn) -> FileNode * { const Utils::MimeType mimeType = Utils::mimeTypesForFileName(fn.path()).value(0); // Skip some files during scan. @@ -278,12 +292,31 @@ void TreeScanner::scanForFiles( return new FileNode(fn, type); }); - Utils::sort(nodes, ProjectExplorer::Node::sortByPath); - promise.setProgressValue(promise.future().progressMaximum()); - Result result{createFolderNode(directory, nodes), nodes}; - promise.addResult(result); } +TreeScanner::Result::Result(QList files, QList nodes) + : allFiles(files) + , firstLevelNodes(nodes) +{} + +QList TreeScanner::Result::takeAllFiles() +{ + qDeleteAll(firstLevelNodes); + firstLevelNodes.clear(); + QList result = allFiles; + allFiles.clear(); + return result; +} + +QList TreeScanner::Result::takeFirstLevelNodes() +{ + qDeleteAll(allFiles); + allFiles.clear(); + QList result = firstLevelNodes; + firstLevelNodes.clear(); + return result; +} + } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/treescanner.h b/src/plugins/projectexplorer/treescanner.h index ca98b2a1e26..cf94e83531b 100644 --- a/src/plugins/projectexplorer/treescanner.h +++ b/src/plugins/projectexplorer/treescanner.h @@ -24,10 +24,16 @@ class PROJECTEXPLORER_EXPORT TreeScanner : public QObject Q_OBJECT public: - struct Result + class PROJECTEXPLORER_EXPORT Result { - std::shared_ptr folderNode; + public: + Result() = default; + Result(QList files, QList nodes); + QList takeAllFiles(); + QList takeFirstLevelNodes(); + private: QList allFiles; + QList firstLevelNodes; }; using Future = QFuture; using FutureWatcher = QFutureWatcher; diff --git a/src/plugins/projectexplorer/workspaceproject.cpp b/src/plugins/projectexplorer/workspaceproject.cpp index 7909a56e4f4..042cbfd9346 100644 --- a/src/plugins/projectexplorer/workspaceproject.cpp +++ b/src/plugins/projectexplorer/workspaceproject.cpp @@ -70,8 +70,6 @@ public: bool renameFiles(Node *context, const FilePairs &filesToRename, FilePaths *notRenamed) final; bool supportsAction(Node *context, ProjectAction action, const Node *node) const final; - void watchFolder(const FilePath &path, const QList &versionControls); - void handleDirectoryChanged(const FilePath &directory); void scan(const FilePath &path); @@ -108,13 +106,28 @@ WorkspaceBuildSystem::WorkspaceBuildSystem(Target *t) QTC_ASSERT(!m_scanQueue.isEmpty(), return); const FilePath scannedDir = m_scanQueue.takeFirst(); - std::vector> nodePtrs - = Utils::transform(m_scanner.release().allFiles, [](FileNode *fn) { - return std::unique_ptr(fn); - }); + TreeScanner::Result result = m_scanner.release(); + auto addNodes = [this, &result](FolderNode *parent) { + QElapsedTimer timer; + timer.start(); + const QList versionControls = VcsManager::versionControls(); + for (auto node : result.takeFirstLevelNodes()) + parent->addNode(std::unique_ptr(node)); + qCDebug(wsbs) << "Added nodes in" << timer.elapsed() << "ms"; + FilePaths toWatch; + auto collectWatchFolders = [this, &toWatch, &versionControls](FolderNode *fn) { + if (!isFiltered(fn->path(), versionControls)) + toWatch << fn->path(); + }; + collectWatchFolders(parent); + parent->forEachNode({}, collectWatchFolders); + qCDebug(wsbs) << "Added and collected nodes in" << timer.elapsed() << "ms" << toWatch.size() << "dirs"; + m_watcher->addDirectories(toWatch, FileSystemWatcher::WatchAllChanges); + qCDebug(wsbs) << "Added and and collected and watched nodes in" << timer.elapsed() << "ms"; + }; + if (scannedDir == projectDirectory()) { - qCDebug(wsbs) << "Finished scanning new root" << scannedDir << "found" - << nodePtrs.size() << "file entries"; + qCDebug(wsbs) << "Finished scanning new root" << scannedDir; auto root = std::make_unique(scannedDir); root->setDisplayName(target()->project()->displayName()); m_watcher.reset(new FileSystemWatcher); @@ -123,24 +136,22 @@ WorkspaceBuildSystem::WorkspaceBuildSystem(Target *t) &FileSystemWatcher::directoryChanged, this, [this](const QString &path) { - handleDirectoryChanged(Utils::FilePath::fromPathPart(path)); + handleDirectoryChanged(FilePath::fromPathPart(path)); }); - root->addNestedNodes(std::move(nodePtrs)); + addNodes(root.get()); setRootProjectNode(std::move(root)); } else { - qCDebug(wsbs) << "Finished scanning subdir" << scannedDir << "found" - << nodePtrs.size() << "file entries"; + qCDebug(wsbs) << "Finished scanning subdir" << scannedDir; FolderNode *parent = findAvailableParent(project()->rootProjectNode(), scannedDir); const FilePath relativePath = scannedDir.relativeChildPath(parent->filePath()); const QString firstRelativeFolder = relativePath.path().left(relativePath.path().indexOf('/')); const FilePath nodePath = parent->filePath() / firstRelativeFolder; auto newNode = std::make_unique(nodePath); newNode->setDisplayName(firstRelativeFolder); - newNode->addNestedNodes(std::move(nodePtrs)); + addNodes(newNode.get()); parent->replaceSubtree(nullptr, std::move(newNode)); } - watchFolder(scannedDir, VcsManager::versionControls()); scanNext(); }); @@ -258,19 +269,6 @@ bool WorkspaceBuildSystem::supportsAction(Node *, ProjectAction action, const No || action == ProjectAction::EraseFile; } -void WorkspaceBuildSystem::watchFolder( - const FilePath &path, const QList &versionControls) -{ - if (!m_watcher->watchesDirectory(path)) { - qCDebug(wsbs) << "Adding watch for " << path; - m_watcher->addDirectory(path, FileSystemWatcher::WatchAllChanges); - } - for (auto entry : path.dirEntries(QDir::NoDotAndDotDot | QDir::Hidden | QDir::Dirs)) { - if (!isFiltered(entry, versionControls)) - watchFolder(entry, versionControls); - } -} - void WorkspaceBuildSystem::handleDirectoryChanged(const FilePath &directory) { ProjectNode *root = project()->rootProjectNode();