fix: Check patterns and remove filewatcher (#166)

* fix: Check patterns and remove filewatcher
* fix: Don't show log for non-existent ignore file
This commit is contained in:
Petr Mironychev 2025-04-18 10:55:46 +02:00 committed by GitHub
parent 2f9622e23e
commit 8cb6a2f6d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 195 additions and 54 deletions

View File

@ -20,6 +20,8 @@
#include "IgnoreManager.hpp" #include "IgnoreManager.hpp"
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
@ -33,17 +35,52 @@ namespace QodeAssist::Context {
IgnoreManager::IgnoreManager(QObject *parent) IgnoreManager::IgnoreManager(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged, this, [this](const QString &path) { auto projectManager = ProjectExplorer::ProjectManager::instance();
for (auto it = m_projectIgnorePatterns.begin(); it != m_projectIgnorePatterns.end(); ++it) { if (projectManager) {
if (ignoreFilePath(it.key()) == path) { connect(
reloadIgnorePatterns(it.key()); projectManager,
break; &ProjectExplorer::ProjectManager::projectAdded,
this,
&IgnoreManager::reloadIgnorePatterns);
connect(
projectManager,
&ProjectExplorer::ProjectManager::projectRemoved,
this,
&IgnoreManager::removeIgnorePatterns);
const QList<ProjectExplorer::Project *> projects = projectManager->projects();
for (ProjectExplorer::Project *project : projects) {
if (project) {
reloadIgnorePatterns(project);
} }
} }
}); }
connect(
QCoreApplication::instance(),
&QCoreApplication::aboutToQuit,
this,
&IgnoreManager::cleanupConnections);
} }
IgnoreManager::~IgnoreManager() = default; IgnoreManager::~IgnoreManager()
{
cleanupConnections();
}
void IgnoreManager::cleanupConnections()
{
QList<ProjectExplorer::Project *> projects = m_projectConnections.keys();
for (ProjectExplorer::Project *project : projects) {
if (project) {
disconnect(m_projectConnections.take(project));
}
}
m_projectConnections.clear();
m_projectIgnorePatterns.clear();
m_ignoreCache.clear();
}
bool IgnoreManager::shouldIgnore(const QString &filePath, ProjectExplorer::Project *project) const bool IgnoreManager::shouldIgnore(const QString &filePath, ProjectExplorer::Project *project) const
{ {
@ -64,29 +101,71 @@ bool IgnoreManager::shouldIgnore(const QString &filePath, ProjectExplorer::Proje
return matchesIgnorePatterns(relativePath, patterns); return matchesIgnorePatterns(relativePath, patterns);
} }
void IgnoreManager::reloadIgnorePatterns(ProjectExplorer::Project *project) bool IgnoreManager::matchesIgnorePatterns(const QString &path, const QStringList &patterns) const
{ {
if (!project) QString cacheKey = path + ":" + patterns.join("|");
return; if (m_ignoreCache.contains(cacheKey))
return m_ignoreCache[cacheKey];
QStringList patterns = loadIgnorePatterns(project); bool result = isPathExcluded(path, patterns);
m_projectIgnorePatterns[project] = patterns; m_ignoreCache.insert(cacheKey, result);
return result;
}
QString ignoreFile = ignoreFilePath(project); bool IgnoreManager::isPathExcluded(const QString &path, const QStringList &patterns) const
if (QFileInfo::exists(ignoreFile)) { {
if (m_fileWatcher.files().contains(ignoreFile)) bool excluded = false;
m_fileWatcher.removePath(ignoreFile);
m_fileWatcher.addPath(ignoreFile); for (const QString &pattern : patterns) {
if (pattern.isEmpty() || pattern.startsWith('#'))
continue;
if (!m_projectConnections.contains(project)) { bool isNegative = pattern.startsWith('!');
auto connection = connect(project, &QObject::destroyed, this, [this, project]() { QString actualPattern = isNegative ? pattern.mid(1) : pattern;
m_projectIgnorePatterns.remove(project);
m_projectConnections.remove(project); bool matches = matchPathWithPattern(path, actualPattern);
});
m_projectConnections[project] = connection; if (matches) {
excluded = !isNegative;
} }
} }
return excluded;
}
bool IgnoreManager::matchPathWithPattern(const QString &path, const QString &pattern) const
{
QString adjustedPattern = pattern.trimmed();
bool matchFromRoot = adjustedPattern.startsWith('/');
if (matchFromRoot)
adjustedPattern = adjustedPattern.mid(1);
bool matchDirOnly = adjustedPattern.endsWith('/');
if (matchDirOnly)
adjustedPattern.chop(1);
QString regexPattern = QRegularExpression::escape(adjustedPattern);
regexPattern.replace("\\*\\*", ".*");
regexPattern.replace("\\*", "[^/]*");
regexPattern.replace("\\?", ".");
if (matchFromRoot)
regexPattern = QString("^%1").arg(regexPattern);
else
regexPattern = QString("(^|/)%1").arg(regexPattern);
if (matchDirOnly)
regexPattern = QString("%1$").arg(regexPattern);
else
regexPattern = QString("%1($|/)").arg(regexPattern);
QRegularExpression regex(regexPattern);
QRegularExpressionMatch match = regex.match(path);
return match.hasMatch();
} }
QStringList IgnoreManager::loadIgnorePatterns(ProjectExplorer::Project *project) QStringList IgnoreManager::loadIgnorePatterns(ProjectExplorer::Project *project)
@ -95,14 +174,16 @@ QStringList IgnoreManager::loadIgnorePatterns(ProjectExplorer::Project *project)
if (!project) if (!project)
return patterns; return patterns;
QString path = ignoreFilePath(project); QString ignoreFile = ignoreFilePath(project);
QFile file(path); if (ignoreFile.isEmpty() || !QFile::exists(ignoreFile)) {
// LOG_MESSAGE(
if (!file.exists()) // QString("No .qodeassistignore file found for project: %1").arg(project->displayName()));
return patterns; return patterns;
}
QFile file(ignoreFile);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_MESSAGE(QString("Could not open .qodeassistignore file: %1").arg(path)); LOG_MESSAGE(QString("Could not open .qodeassistignore file: %1").arg(ignoreFile));
return patterns; return patterns;
} }
@ -113,26 +194,85 @@ QStringList IgnoreManager::loadIgnorePatterns(ProjectExplorer::Project *project)
patterns << line; patterns << line;
} }
LOG_MESSAGE(QString("Successfully loaded .qodeassistignore file: %1 with %2 patterns")
.arg(ignoreFile)
.arg(patterns.size()));
return patterns; return patterns;
} }
bool IgnoreManager::matchesIgnorePatterns(const QString &path, const QStringList &patterns) const void IgnoreManager::reloadIgnorePatterns(ProjectExplorer::Project *project)
{ {
for (const QString &pattern : patterns) { if (!project)
QString regexPattern = QRegularExpression::escape(pattern); return;
regexPattern.replace("\\*\\*", ".*"); // ** matches any characters including / QStringList patterns = loadIgnorePatterns(project);
regexPattern.replace("\\*", "[^/]*"); // * matches any characters except / m_projectIgnorePatterns[project] = patterns;
regexPattern.replace("\\?", "."); // ? matches any single character
regexPattern = QString("^%1$").arg(regexPattern); QStringList keysToRemove;
QString projectPath = project->projectDirectory().toUrlishString();
QRegularExpression regex(regexPattern); for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
if (regex.match(path).hasMatch()) if (it.key().contains(projectPath))
return true; keysToRemove << it.key();
} }
return false; for (const QString &key : keysToRemove)
m_ignoreCache.remove(key);
if (!m_projectConnections.contains(project)) {
QPointer<ProjectExplorer::Project> projectPtr(project);
auto connection = connect(project, &QObject::destroyed, this, [this, projectPtr]() {
if (projectPtr) {
m_projectIgnorePatterns.remove(projectPtr);
m_projectConnections.remove(projectPtr);
QStringList keysToRemove;
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
if (it.key().contains(projectPtr->projectDirectory().toUrlishString()))
keysToRemove << it.key();
}
for (const QString &key : keysToRemove)
m_ignoreCache.remove(key);
}
});
m_projectConnections[project] = connection;
}
}
void IgnoreManager::removeIgnorePatterns(ProjectExplorer::Project *project)
{
m_projectIgnorePatterns.remove(project);
QStringList keysToRemove;
for (auto it = m_ignoreCache.begin(); it != m_ignoreCache.end(); ++it) {
if (it.key().contains(project->projectDirectory().toUrlishString()))
keysToRemove << it.key();
}
for (const QString &key : keysToRemove)
m_ignoreCache.remove(key);
if (m_projectConnections.contains(project)) {
disconnect(m_projectConnections[project]);
m_projectConnections.remove(project);
}
LOG_MESSAGE(QString("Removed ignore patterns for project: %1").arg(project->displayName()));
}
void IgnoreManager::reloadAllPatterns()
{
QList<ProjectExplorer::Project *> projects = m_projectIgnorePatterns.keys();
for (ProjectExplorer::Project *project : projects) {
if (project) {
reloadIgnorePatterns(project);
}
}
m_ignoreCache.clear();
} }
QString IgnoreManager::ignoreFilePath(ProjectExplorer::Project *project) const QString IgnoreManager::ignoreFilePath(ProjectExplorer::Project *project) const
@ -141,14 +281,7 @@ QString IgnoreManager::ignoreFilePath(ProjectExplorer::Project *project) const
return QString(); return QString();
} }
QString path = project->projectDirectory().toUrlishString() + "/.qodeassist/qodeassistignore"; return project->projectDirectory().toUrlishString() + "/.qodeassistignore";
QFileInfo fileInfo(path);
if (!fileInfo.exists() || !fileInfo.isFile()) {
LOG_MESSAGE(QString("File .qodeassistignore not found at path: %1").arg(path));
}
return path;
} }
} // namespace QodeAssist::Context } // namespace QodeAssist::Context

View File

@ -19,9 +19,9 @@
#pragma once #pragma once
#include <QFileSystemWatcher> #include <QHash>
#include <QMap>
#include <QObject> #include <QObject>
#include <QPointer>
#include <QStringList> #include <QStringList>
namespace ProjectExplorer { namespace ProjectExplorer {
@ -40,15 +40,23 @@ public:
bool shouldIgnore(const QString &filePath, ProjectExplorer::Project *project = nullptr) const; bool shouldIgnore(const QString &filePath, ProjectExplorer::Project *project = nullptr) const;
void reloadIgnorePatterns(ProjectExplorer::Project *project); void reloadIgnorePatterns(ProjectExplorer::Project *project);
void removeIgnorePatterns(ProjectExplorer::Project *project);
void reloadAllPatterns();
private slots:
void cleanupConnections();
private: private:
QStringList loadIgnorePatterns(ProjectExplorer::Project *project);
bool matchesIgnorePatterns(const QString &path, const QStringList &patterns) const; bool matchesIgnorePatterns(const QString &path, const QStringList &patterns) const;
bool isPathExcluded(const QString &path, const QStringList &patterns) const;
bool matchPathWithPattern(const QString &path, const QString &pattern) const;
QStringList loadIgnorePatterns(ProjectExplorer::Project *project);
QString ignoreFilePath(ProjectExplorer::Project *project) const; QString ignoreFilePath(ProjectExplorer::Project *project) const;
mutable QMap<ProjectExplorer::Project *, QStringList> m_projectIgnorePatterns; QHash<ProjectExplorer::Project *, QStringList> m_projectIgnorePatterns;
QFileSystemWatcher m_fileWatcher; mutable QHash<QString, bool> m_ignoreCache;
QMap<ProjectExplorer::Project *, QMetaObject::Connection> m_projectConnections; QHash<ProjectExplorer::Project *, QMetaObject::Connection> m_projectConnections;
}; };
} // namespace QodeAssist::Context } // namespace QodeAssist::Context