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 <marcus.tillmanns@qt.io>
This commit is contained in:
David Schulz
2024-09-13 15:19:45 +02:00
parent 00057a94d5
commit 42ba58e29d
7 changed files with 108 additions and 70 deletions

View File

@@ -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<ProjectExplorer::FolderNode>(projectDirectory());
for (auto node : result.takeFirstLevelNodes())
m_allFiles->addNode(std::unique_ptr<Node>(node));
updateFileSystemNodes();
}

View File

@@ -221,8 +221,8 @@ void CompilationDbParser::stop()
QList<FileNode *> CompilationDbParser::scannedFiles() const
{
const bool canceled = m_treeScanner->future().isCanceled();
const TreeScanner::Result result = m_treeScanner->release();
return !canceled ? result.allFiles : QList<FileNode *>();
TreeScanner::Result result = m_treeScanner->release();
return !canceled ? result.takeAllFiles() : QList<FileNode *>();
}
void CompilationDbParser::parserJobFinished()

View File

@@ -67,7 +67,7 @@ HaskellBuildSystem::HaskellBuildSystem(Target *t)
auto root = std::make_unique<ProjectNode>(projectDirectory());
root->setDisplayName(target()->project()->displayName());
std::vector<std::unique_ptr<FileNode>> nodePtrs
= Utils::transform<std::vector>(m_scanner.release().allFiles, [](FileNode *fn) {
= Utils::transform<std::vector>(m_scanner.release().takeAllFiles(), [](FileNode *fn) {
return std::unique_ptr<FileNode>(fn);
});
root->addNestedNodes(std::move(nodePtrs));

View File

@@ -36,8 +36,8 @@ NimProjectScanner::NimProjectScanner(Project *project)
connect(&m_scanner, &TreeScanner::finished, this, [this] {
// Collect scanned nodes
std::vector<std::unique_ptr<FileNode>> 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);

View File

@@ -133,35 +133,23 @@ FileType TreeScanner::genericFileType(const Utils::MimeType &mimeType, const Uti
return Node::fileTypeForMimeType(mimeType);
}
static std::unique_ptr<FolderNode> createFolderNode(const Utils::FilePath &directory,
const QList<FileNode *> &allFiles)
{
auto fileSystemNode = std::make_unique<FolderNode>(directory);
for (const FileNode *fn : allFiles) {
if (!fn->filePath().isChildOf(directory))
continue;
std::unique_ptr<FileNode> node(fn->clone());
fileSystemNode->addNestedNode(std::move(node));
}
ProjectTree::applyTreeManager(fileSystemNode.get(), ProjectTree::AsyncPhase); // QRC nodes
return fileSystemNode;
}
struct DirectoryScanResult
{
QList<FileNode *> nodes;
Utils::FilePaths subDirectories;
QList<FolderNode *> subDirectories;
FolderNode *parentNode;
};
static DirectoryScanResult scanForFilesImpl(
const QFuture<void> &future,
const Utils::FilePath &directory,
FolderNode *parent,
QDir::Filters filter,
const std::function<FileNode *(const Utils::FilePath &)> &factory,
const QList<Core::IVersionControl *> &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<FileNode *> scanForFilesHelper(
static TreeScanner::Result scanForFilesHelper(
TreeScanner::Promise &promise,
const Utils::FilePath &directory,
QDir::Filters dirfilter,
@@ -202,23 +190,32 @@ static QList<FileNode *> scanForFilesHelper(
promise.setProgressRange(0, progressRange);
QSet<Utils::FilePath> visited;
const DirectoryScanResult result = scanForFilesImpl(future, directory, dirfilter, factory, versionControls);
const DirectoryScanResult result
= scanForFilesImpl(future, directory, nullptr, dirfilter, factory, versionControls);
QList<FileNode *> fileNodes = result.nodes;
QList<Node *> firstLevelNodes;
for (auto fileNode : fileNodes)
firstLevelNodes.append(fileNode->clone());
const int progressIncrement = int(
progressRange / static_cast<double>(fileNodes.count() + result.subDirectories.count()));
promise.setProgressValue(int(fileNodes.count() * progressIncrement));
QList<QPair<Utils::FilePath, int>> 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<QPair<FolderNode *, int>> subDirectories;
auto addSubDirectories = [&](const QList<FolderNode *> &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<FolderNode>(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<FileNode *> scanForFilesHelper(
auto onSetup = [&, iterator](Utils::Async<DirectoryScanResult> &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<DirectoryScanResult> &task) {
@@ -235,6 +238,10 @@ static QList<FileNode *> 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<FileNode>(fn->clone()));
}
if (subDirCount == 0) {
promise.setProgressValue(future.progressValue() + progressRange);
} else {
@@ -242,7 +249,7 @@ static QList<FileNode *> scanForFilesHelper(
const int increment = int(
progressRange / static_cast<double>(fileCount + subDirCount));
promise.setProgressValue(future.progressValue() + increment * fileCount);
addSubDirectories(result.subDirectories, increment);
addSubDirectories(result.subDirectories, result.parentNode, increment);
}
};
@@ -252,7 +259,10 @@ static QList<FileNode *> 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<FileNode *> 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<FileNode *> files, QList<Node *> nodes)
: allFiles(files)
, firstLevelNodes(nodes)
{}
QList<FileNode *> TreeScanner::Result::takeAllFiles()
{
qDeleteAll(firstLevelNodes);
firstLevelNodes.clear();
QList<FileNode *> result = allFiles;
allFiles.clear();
return result;
}
QList<Node *> TreeScanner::Result::takeFirstLevelNodes()
{
qDeleteAll(allFiles);
allFiles.clear();
QList<Node *> result = firstLevelNodes;
firstLevelNodes.clear();
return result;
}
} // namespace ProjectExplorer

View File

@@ -24,10 +24,16 @@ class PROJECTEXPLORER_EXPORT TreeScanner : public QObject
Q_OBJECT
public:
struct Result
class PROJECTEXPLORER_EXPORT Result
{
std::shared_ptr<FolderNode> folderNode;
public:
Result() = default;
Result(QList<FileNode *> files, QList<Node *> nodes);
QList<FileNode *> takeAllFiles();
QList<Node *> takeFirstLevelNodes();
private:
QList<FileNode *> allFiles;
QList<Node *> firstLevelNodes;
};
using Future = QFuture<Result>;
using FutureWatcher = QFutureWatcher<Result>;

View File

@@ -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<IVersionControl *> &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<std::unique_ptr<FileNode>> nodePtrs
= Utils::transform<std::vector>(m_scanner.release().allFiles, [](FileNode *fn) {
return std::unique_ptr<FileNode>(fn);
});
TreeScanner::Result result = m_scanner.release();
auto addNodes = [this, &result](FolderNode *parent) {
QElapsedTimer timer;
timer.start();
const QList<IVersionControl *> versionControls = VcsManager::versionControls();
for (auto node : result.takeFirstLevelNodes())
parent->addNode(std::unique_ptr<Node>(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<ProjectNode>(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<FolderNode>(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<IVersionControl *> &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();