ProjectExplorer: add watcher to WorkspaceProject directories

Change-Id: I6335515239e31239a32ca8c3e0155f161103aeab
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
David Schulz
2024-09-09 15:43:57 +02:00
parent 4248913f95
commit b76cfb1ea6

View File

@@ -18,8 +18,11 @@
#include "treescanner.h" #include "treescanner.h"
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/filesystemwatcher.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
@@ -32,6 +35,8 @@ using namespace Core;
namespace ProjectExplorer { namespace ProjectExplorer {
Q_LOGGING_CATEGORY(wsbs, "qtc.projectexplorer.workspacebuildsystem", QtWarningMsg);
const QLatin1StringView FOLDER_MIMETYPE{"inode/directory"}; const QLatin1StringView FOLDER_MIMETYPE{"inode/directory"};
const QLatin1StringView WORKSPACE_MIMETYPE{"text/x-workspace-project"}; const QLatin1StringView WORKSPACE_MIMETYPE{"text/x-workspace-project"};
const char WORKSPACE_PROJECT_ID[] = "ProjectExplorer.WorkspaceProject"; const char WORKSPACE_PROJECT_ID[] = "ProjectExplorer.WorkspaceProject";
@@ -49,28 +54,90 @@ const expected_str<QJsonObject> projectDefinition(const Project *project)
return {}; return {};
} }
static QFlags<QDir::Filter> workspaceDirFilter = QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden;
class WorkspaceBuildSystem final : public BuildSystem class WorkspaceBuildSystem final : public BuildSystem
{ {
public: public:
WorkspaceBuildSystem(Target *t) WorkspaceBuildSystem(Target *t);
void triggerParsing() final;
void watchFolder(const FilePath &path, const QList<IVersionControl *> &versionControls);
void handleDirectoryChanged(const FilePath &directory);
void scan(const FilePath &path);
void scanNext();
QString name() const final { return QLatin1String("Workspace"); }
private:
bool isFiltered(const FilePath &path, QList<IVersionControl *> versionControls) const;
QList<QRegularExpression> m_filters;
std::unique_ptr<FileSystemWatcher> m_watcher;
ParseGuard m_parseGuard;
FilePaths m_scanQueue;
TreeScanner m_scanner;
};
static FolderNode *findAvailableParent(ProjectNode *root, const FilePath &path)
{
FolderNode *result = nullptr;
FolderNode *child = root;
do {
result = child;
child = result->findChildFolderNode(
[&path](FolderNode *fn) { return path.isChildOf(fn->path()) || path == fn->path(); });
} while (child);
return result;
}
WorkspaceBuildSystem::WorkspaceBuildSystem(Target *t)
:BuildSystem(t) :BuildSystem(t)
{ {
connect(&m_scanner, &TreeScanner::finished, this, [this] { connect(&m_scanner, &TreeScanner::finished, this, [this] {
auto root = std::make_unique<ProjectNode>(projectDirectory()); QTC_ASSERT(!m_scanQueue.isEmpty(), return);
root->setDisplayName(target()->project()->displayName()); const FilePath scannedDir = m_scanQueue.takeFirst();
std::vector<std::unique_ptr<FileNode>> nodePtrs std::vector<std::unique_ptr<FileNode>> nodePtrs
= Utils::transform<std::vector>(m_scanner.release().allFiles, [](FileNode *fn) { = Utils::transform<std::vector>(m_scanner.release().allFiles, [](FileNode *fn) {
return std::unique_ptr<FileNode>(fn); return std::unique_ptr<FileNode>(fn);
}); });
if (scannedDir == projectDirectory()) {
qCDebug(wsbs) << "Finished scanning new root" << scannedDir << "found"
<< nodePtrs.size() << "file entries";
auto root = std::make_unique<ProjectNode>(scannedDir);
root->setDisplayName(target()->project()->displayName());
m_watcher.reset(new FileSystemWatcher);
connect(
m_watcher.get(),
&FileSystemWatcher::directoryChanged,
this,
[this](const QString &path) {
handleDirectoryChanged(Utils::FilePath::fromPathPart(path));
});
root->addNestedNodes(std::move(nodePtrs)); root->addNestedNodes(std::move(nodePtrs));
setRootProjectNode(std::move(root)); setRootProjectNode(std::move(root));
} else {
qCDebug(wsbs) << "Finished scanning subdir" << scannedDir << "found"
<< nodePtrs.size() << "file entries";
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));
parent->replaceSubtree(nullptr, std::move(newNode));
}
watchFolder(scannedDir, VcsManager::versionControls());
m_parseGuard.markAsSuccess(); scanNext();
m_parseGuard = {};
emitBuildSystemUpdated();
}); });
m_scanner.setDirFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden); m_scanner.setDirFilter(workspaceDirFilter);
m_scanner.setFilter([&](const Utils::MimeType &, const Utils::FilePath &filePath) { m_scanner.setFilter([&](const Utils::MimeType &, const Utils::FilePath &filePath) {
return Utils::anyOf(m_filters, [filePath](const QRegularExpression &filter) { return Utils::anyOf(m_filters, [filePath](const QRegularExpression &filter) {
return filter.match(filePath.path()).hasMatch(); return filter.match(filePath.path()).hasMatch();
@@ -83,10 +150,10 @@ public:
&BuildSystem::requestDelayedParse); &BuildSystem::requestDelayedParse);
requestDelayedParse(); requestDelayedParse();
} }
void triggerParsing() final void WorkspaceBuildSystem::triggerParsing()
{ {
m_filters.clear(); m_filters.clear();
FilePath projectPath = project()->projectDirectory(); FilePath projectPath = project()->projectDirectory();
@@ -144,17 +211,106 @@ public:
setApplicationTargets(targetInfos); setApplicationTargets(targetInfos);
m_parseGuard = guardParsingRun(); scan(target()->project()->projectDirectory());
m_scanner.asyncScanForFiles(target()->project()->projectDirectory()); }
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);
}
}
QString name() const final { return QLatin1String("Workspace"); } void WorkspaceBuildSystem::handleDirectoryChanged(const FilePath &directory)
{
ProjectNode *root = project()->rootProjectNode();
QTC_ASSERT(root, return);
auto node = root->findNode(
[&directory](Node *node) {
if (node->asFolderNode()) {
qCDebug(wsbs) << "comparing" << node->filePath() << directory;
return node->filePath() == directory;
}
return false;
});
if (!directory.exists()) {
m_watcher->removeDirectory(directory);
if (node)
node->parentFolderNode()->replaceSubtree(node, nullptr);
} else if (node) {
FolderNode *fn = node->asFolderNode();
QTC_ASSERT(fn, return);
const FilePaths entries = directory.dirEntries(workspaceDirFilter);
const QList<IVersionControl *> &versionControls = VcsManager::versionControls();
for (auto entry : entries) {
if (isFiltered(entry, versionControls))
continue;
if (entry.isDir()) {
if (!fn->folderNode(entry))
scan(entry);
} else if (!fn->fileNode(entry)) {
fn->replaceSubtree(
nullptr, std::make_unique<FileNode>(entry, Node::fileTypeForFileName(entry)));
}
}
QList<Node *> toRemove;
auto filter = [&entries, &toRemove](Node *n) {
if (!entries.contains(n->filePath()))
toRemove << n;
};
fn->forEachFileNode(filter);
fn->forEachFolderNode(filter);
for (auto n : toRemove)
fn->replaceSubtree(n, nullptr);
} else {
scan(directory);
}
}
private: void WorkspaceBuildSystem::scan(const FilePath &path)
QList<QRegularExpression> m_filters; {
ParseGuard m_parseGuard; m_scanQueue << path;
TreeScanner m_scanner; scanNext();
}; }
void WorkspaceBuildSystem::scanNext()
{
if (m_scanQueue.isEmpty()) {
qCDebug(wsbs) << "Scan done.";
m_parseGuard.markAsSuccess();
m_parseGuard = {};
emitBuildSystemUpdated();
} else {
if (!m_parseGuard.guardsProject())
m_parseGuard = guardParsingRun();
if (m_scanner.isFinished()) {
const FilePath next = m_scanQueue.first();
qCDebug(wsbs) << "Start scanning" << next;
m_scanner.asyncScanForFiles(next);
}
}
}
bool WorkspaceBuildSystem::isFiltered(const FilePath &path, QList<IVersionControl *> versionControls) const
{
const bool explicitlyExcluded = Utils::anyOf(
m_filters,
[path](const QRegularExpression &filter) {
return filter.match(path.path()).hasMatch();
});
if (explicitlyExcluded)
return true;
return Utils::anyOf(versionControls, [path](const IVersionControl *vc) {
return vc->isVcsFileOrDirectory(path);
});
}
class WorkspaceRunConfiguration : public RunConfiguration class WorkspaceRunConfiguration : public RunConfiguration
{ {