From 8cb6a2f6d215c03e95e86e6c635891108dc483ff Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:55:46 +0200 Subject: [PATCH] fix: Check patterns and remove filewatcher (#166) * fix: Check patterns and remove filewatcher * fix: Don't show log for non-existent ignore file --- context/IgnoreManager.cpp | 229 ++++++++++++++++++++++++++++++-------- context/IgnoreManager.hpp | 20 +++- 2 files changed, 195 insertions(+), 54 deletions(-) diff --git a/context/IgnoreManager.cpp b/context/IgnoreManager.cpp index 9e82994..a4bd250 100644 --- a/context/IgnoreManager.cpp +++ b/context/IgnoreManager.cpp @@ -20,6 +20,8 @@ #include "IgnoreManager.hpp" #include +#include +#include #include #include #include @@ -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 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 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 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 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 diff --git a/context/IgnoreManager.hpp b/context/IgnoreManager.hpp index 3ca8e8d..d00623f 100644 --- a/context/IgnoreManager.hpp +++ b/context/IgnoreManager.hpp @@ -19,9 +19,9 @@ #pragma once -#include -#include +#include #include +#include #include 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 m_projectIgnorePatterns; - QFileSystemWatcher m_fileWatcher; - QMap m_projectConnections; + QHash m_projectIgnorePatterns; + mutable QHash m_ignoreCache; + QHash m_projectConnections; }; } // namespace QodeAssist::Context