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

View File

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