mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-29 17:49:12 -04:00
refactor: Move to agent architecture
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
add_library(Context STATIC
|
||||
DocumentContextReader.hpp DocumentContextReader.cpp
|
||||
EnvBlockFormatter.hpp EnvBlockFormatter.cpp
|
||||
ChangesManager.h ChangesManager.cpp
|
||||
ContextManager.hpp ContextManager.cpp
|
||||
IProjectScanner.hpp
|
||||
ProjectScannerQtCreator.hpp ProjectScannerQtCreator.cpp
|
||||
ContentFile.hpp
|
||||
DocumentReaderQtCreator.hpp
|
||||
IDocumentReader.hpp
|
||||
@@ -21,7 +24,7 @@ target_link_libraries(Context
|
||||
QtCreator::Utils
|
||||
QtCreator::ProjectExplorer
|
||||
PRIVATE
|
||||
PluginLLMCore
|
||||
Common
|
||||
QodeAssistSettings
|
||||
)
|
||||
|
||||
|
||||
@@ -282,175 +282,6 @@ ChangesManager::FileEdit ChangesManager::getFileEdit(const QString &editId) cons
|
||||
return m_fileEdits.value(editId);
|
||||
}
|
||||
|
||||
QList<ChangesManager::FileEdit> ChangesManager::getPendingEdits() const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
QList<FileEdit> pendingEdits;
|
||||
for (const auto &edit : m_fileEdits.values()) {
|
||||
if (edit.status == Pending) {
|
||||
pendingEdits.append(edit);
|
||||
}
|
||||
}
|
||||
return pendingEdits;
|
||||
}
|
||||
|
||||
bool ChangesManager::performFileEdit(
|
||||
const QString &filePath, const QString &oldContent, const QString &newContent, QString *errorMsg)
|
||||
{
|
||||
auto setError = [errorMsg](const QString &msg) {
|
||||
if (errorMsg) *errorMsg = msg;
|
||||
};
|
||||
|
||||
auto editors = Core::EditorManager::visibleEditors();
|
||||
for (auto *editor : editors) {
|
||||
if (!editor || !editor->document()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString editorPath = editor->document()->filePath().toFSPathString();
|
||||
if (editorPath == filePath) {
|
||||
QByteArray contentBytes = editor->document()->contents();
|
||||
QString currentContent = QString::fromUtf8(contentBytes);
|
||||
|
||||
if (oldContent.isEmpty()) {
|
||||
if (auto *textEditor
|
||||
= qobject_cast<TextEditor::TextDocument *>(editor->document())) {
|
||||
QTextDocument *doc = textEditor->document();
|
||||
|
||||
QTextCursor cursor(doc);
|
||||
cursor.beginEditBlock();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.insertText(newContent);
|
||||
cursor.endEditBlock();
|
||||
|
||||
LOG_MESSAGE(QString("Appended to open editor: %1").arg(filePath));
|
||||
setError("Applied successfully (appended to end of file)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int matchPos = currentContent.indexOf(oldContent);
|
||||
if (matchPos != -1) {
|
||||
if (auto *textEditor
|
||||
= qobject_cast<TextEditor::TextDocument *>(editor->document())) {
|
||||
QTextDocument *doc = textEditor->document();
|
||||
|
||||
QTextCursor cursor(doc);
|
||||
cursor.beginEditBlock();
|
||||
cursor.setPosition(matchPos);
|
||||
cursor.setPosition(matchPos + oldContent.length(), QTextCursor::KeepAnchor);
|
||||
cursor.removeSelectedText();
|
||||
cursor.insertText(newContent);
|
||||
cursor.endEditBlock();
|
||||
|
||||
LOG_MESSAGE(QString("Updated open editor (exact match): %1").arg(filePath));
|
||||
setError("Applied successfully (exact match)");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
double similarity = 0.0;
|
||||
QString matchedContent = findBestMatch(currentContent, oldContent, 0.82, &similarity);
|
||||
if (!matchedContent.isEmpty()) {
|
||||
matchPos = currentContent.indexOf(matchedContent);
|
||||
if (matchPos != -1) {
|
||||
if (auto *textEditor
|
||||
= qobject_cast<TextEditor::TextDocument *>(editor->document())) {
|
||||
QTextDocument *doc = textEditor->document();
|
||||
|
||||
QTextCursor cursor(doc);
|
||||
cursor.beginEditBlock();
|
||||
cursor.setPosition(matchPos);
|
||||
cursor.setPosition(matchPos + matchedContent.length(), QTextCursor::KeepAnchor);
|
||||
cursor.removeSelectedText();
|
||||
cursor.insertText(newContent);
|
||||
cursor.endEditBlock();
|
||||
|
||||
LOG_MESSAGE(QString("Updated open editor (fuzzy match %1%%): %2")
|
||||
.arg(qRound(similarity * 100)).arg(filePath));
|
||||
setError(QString("Applied with fuzzy match (%1%% similarity)").arg(qRound(similarity * 100)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_MESSAGE(QString("Old content not found in open editor (best similarity: %1%%): %2")
|
||||
.arg(qRound(similarity * 100)).arg(filePath));
|
||||
setError(QString("Content not found. Best match: %1%% (threshold: 82%%). "
|
||||
"File may have changed.").arg(qRound(similarity * 100)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString msg = QString("Cannot open file: %1").arg(file.errorString());
|
||||
LOG_MESSAGE(QString("Failed to open file for reading: %1 - %2").arg(filePath, file.errorString()));
|
||||
setError(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString currentContent = QString::fromUtf8(file.readAll());
|
||||
file.close();
|
||||
|
||||
QString updatedContent;
|
||||
|
||||
if (oldContent.isEmpty()) {
|
||||
updatedContent = currentContent + newContent;
|
||||
LOG_MESSAGE(QString("Appending to file: %1").arg(filePath));
|
||||
setError("Applied successfully (appended to end of file)");
|
||||
}
|
||||
else if (currentContent.contains(oldContent)) {
|
||||
int matchPos = currentContent.indexOf(oldContent);
|
||||
updatedContent = currentContent.left(matchPos)
|
||||
+ newContent
|
||||
+ currentContent.mid(matchPos + oldContent.length());
|
||||
LOG_MESSAGE(QString("Using exact match for file update: %1 at position %2")
|
||||
.arg(filePath).arg(matchPos));
|
||||
setError("Applied successfully (exact match)");
|
||||
} else {
|
||||
double similarity = 0.0;
|
||||
QString matchedContent = findBestMatch(currentContent, oldContent, 0.82, &similarity);
|
||||
if (!matchedContent.isEmpty()) {
|
||||
int matchPos = currentContent.indexOf(matchedContent);
|
||||
if (matchPos == -1) {
|
||||
QString msg = "Internal error: matched content not found in file";
|
||||
LOG_MESSAGE(QString("Internal error: matched content disappeared: %1").arg(filePath));
|
||||
setError(msg);
|
||||
return false;
|
||||
}
|
||||
updatedContent = currentContent.left(matchPos)
|
||||
+ newContent
|
||||
+ currentContent.mid(matchPos + matchedContent.length());
|
||||
LOG_MESSAGE(QString("Using fuzzy match (%1%%) for file update: %2 at position %3")
|
||||
.arg(qRound(similarity * 100)).arg(filePath).arg(matchPos));
|
||||
setError(QString("Applied with fuzzy match (%1%% similarity)").arg(qRound(similarity * 100)));
|
||||
} else {
|
||||
QString msg = QString("Content not found. Best match: %1%% (threshold: 82%%). "
|
||||
"File may have changed.").arg(qRound(similarity * 100));
|
||||
LOG_MESSAGE(QString("Old content not found in file (best similarity: %1%%): %2")
|
||||
.arg(qRound(similarity * 100)).arg(filePath));
|
||||
setError(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
QString msg = QString("Cannot write file: %1").arg(file.errorString());
|
||||
LOG_MESSAGE(QString("Failed to open file for writing: %1 - %2").arg(filePath, file.errorString()));
|
||||
setError(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&file);
|
||||
out << updatedContent;
|
||||
file.close();
|
||||
|
||||
LOG_MESSAGE(QString("File updated: %1").arg(filePath));
|
||||
return true;
|
||||
}
|
||||
|
||||
int ChangesManager::levenshteinDistance(const QString &s1, const QString &s2) const
|
||||
{
|
||||
const int len1 = s1.length();
|
||||
@@ -1112,138 +943,6 @@ QString ChangesManager::readFileContent(const QString &filePath) const
|
||||
return content;
|
||||
}
|
||||
|
||||
bool ChangesManager::performFileEditWithDiff(
|
||||
const QString &filePath,
|
||||
const DiffInfo &diffInfo,
|
||||
bool reverse,
|
||||
QString *errorMsg)
|
||||
{
|
||||
LOG_MESSAGE(QString("=== performFileEditWithDiff: %1 (reverse: %2) ===")
|
||||
.arg(filePath).arg(reverse ? "yes" : "no"));
|
||||
|
||||
auto setError = [errorMsg](const QString &msg) {
|
||||
if (errorMsg) *errorMsg = msg;
|
||||
};
|
||||
|
||||
auto editors = Core::EditorManager::visibleEditors();
|
||||
LOG_MESSAGE(QString(" Checking %1 visible editor(s)").arg(editors.size()));
|
||||
|
||||
for (auto *editor : editors) {
|
||||
if (!editor || !editor->document()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString editorPath = editor->document()->filePath().toFSPathString();
|
||||
if (editorPath == filePath) {
|
||||
LOG_MESSAGE(QString(" Found open editor for: %1").arg(filePath));
|
||||
|
||||
QByteArray contentBytes = editor->document()->contents();
|
||||
QString currentContent = QString::fromUtf8(contentBytes);
|
||||
|
||||
LOG_MESSAGE(QString(" Current content size: %1 bytes").arg(currentContent.size()));
|
||||
|
||||
QString modifiedContent = currentContent;
|
||||
QString diffErrorMsg;
|
||||
bool diffSuccess = applyDiffToContent(modifiedContent, diffInfo, reverse, &diffErrorMsg);
|
||||
|
||||
if (!diffSuccess) {
|
||||
LOG_MESSAGE(QString(" Failed to apply diff: %1").arg(diffErrorMsg));
|
||||
setError(diffErrorMsg);
|
||||
|
||||
LOG_MESSAGE(" Attempting fallback to old content-based method...");
|
||||
QString oldContent = reverse ? diffInfo.modifiedContent : diffInfo.originalContent;
|
||||
QString newContent = reverse ? diffInfo.originalContent : diffInfo.modifiedContent;
|
||||
|
||||
return performFileEdit(filePath, oldContent, newContent, errorMsg);
|
||||
}
|
||||
|
||||
if (auto *textEditor = qobject_cast<TextEditor::TextDocument *>(editor->document())) {
|
||||
QTextDocument *doc = textEditor->document();
|
||||
|
||||
LOG_MESSAGE(" Applying changes to text editor document...");
|
||||
|
||||
if (!doc) {
|
||||
LOG_MESSAGE(" Document is invalid");
|
||||
setError("Document pointer is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
QTextCursor cursor(doc);
|
||||
|
||||
if (cursor.isNull()) {
|
||||
LOG_MESSAGE(" Cursor is invalid");
|
||||
setError("Cannot create text cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
cursor.beginEditBlock();
|
||||
cursor.select(QTextCursor::Document);
|
||||
cursor.removeSelectedText();
|
||||
cursor.insertText(modifiedContent);
|
||||
cursor.endEditBlock();
|
||||
|
||||
LOG_MESSAGE(QString(" ✓ Successfully applied diff to open editor: %1").arg(filePath));
|
||||
setError(diffErrorMsg);
|
||||
return true;
|
||||
} catch (...) {
|
||||
LOG_MESSAGE(" Exception during document modification");
|
||||
setError("Exception during document modification");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_MESSAGE(" File not open in editor, modifying file directly...");
|
||||
LOG_MESSAGE(" Note: Undo (Ctrl+Z) will not be available for this file until it is opened");
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString msg = QString("Cannot open file: %1").arg(file.errorString());
|
||||
LOG_MESSAGE(QString(" Failed to open file for reading: %1 - %2")
|
||||
.arg(filePath, file.errorString()));
|
||||
setError(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString currentContent = QString::fromUtf8(file.readAll());
|
||||
file.close();
|
||||
|
||||
LOG_MESSAGE(QString(" File read successfully (%1 bytes)").arg(currentContent.size()));
|
||||
|
||||
QString modifiedContent = currentContent;
|
||||
QString diffErrorMsg;
|
||||
bool diffSuccess = applyDiffToContent(modifiedContent, diffInfo, reverse, &diffErrorMsg);
|
||||
|
||||
if (!diffSuccess) {
|
||||
LOG_MESSAGE(QString(" Failed to apply diff to file: %1").arg(diffErrorMsg));
|
||||
setError(diffErrorMsg);
|
||||
|
||||
LOG_MESSAGE(" Attempting fallback to old content-based method...");
|
||||
QString oldContent = reverse ? diffInfo.modifiedContent : diffInfo.originalContent;
|
||||
QString newContent = reverse ? diffInfo.originalContent : diffInfo.modifiedContent;
|
||||
|
||||
return performFileEdit(filePath, oldContent, newContent, errorMsg);
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
QString msg = QString("Cannot write file: %1").arg(file.errorString());
|
||||
LOG_MESSAGE(QString(" Failed to open file for writing: %1 - %2")
|
||||
.arg(filePath, file.errorString()));
|
||||
setError(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&file);
|
||||
out << modifiedContent;
|
||||
file.close();
|
||||
|
||||
LOG_MESSAGE(QString(" ✓ Successfully wrote modified content to file: %1").arg(filePath));
|
||||
setError(diffErrorMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
ChangesManager::DiffInfo ChangesManager::createDiffInfo(
|
||||
const QString &originalContent,
|
||||
const QString &modifiedContent,
|
||||
@@ -1390,263 +1089,4 @@ ChangesManager::DiffInfo ChangesManager::createDiffInfo(
|
||||
return diffInfo;
|
||||
}
|
||||
|
||||
bool ChangesManager::findHunkLocation(
|
||||
const QStringList &fileLines,
|
||||
const DiffHunk &hunk,
|
||||
int &actualStartLine,
|
||||
QString *debugInfo) const
|
||||
{
|
||||
LOG_MESSAGE(QString(" Searching for hunk location (expected line: %1)").arg(hunk.oldStartLine));
|
||||
|
||||
QString debug;
|
||||
|
||||
int expectedIdx = hunk.oldStartLine - 1;
|
||||
|
||||
if (expectedIdx >= 0 && expectedIdx < fileLines.size()) {
|
||||
bool exactMatch = true;
|
||||
|
||||
int checkIdx = expectedIdx - hunk.contextBefore.size();
|
||||
if (checkIdx < 0) {
|
||||
exactMatch = false;
|
||||
debug += QString(" Context before out of bounds (need %1 lines before line %2)\n")
|
||||
.arg(hunk.contextBefore.size()).arg(expectedIdx + 1);
|
||||
} else {
|
||||
for (int i = 0; i < hunk.contextBefore.size(); ++i) {
|
||||
if (fileLines[checkIdx + i] != hunk.contextBefore[i]) {
|
||||
exactMatch = false;
|
||||
debug += QString(" Context before mismatch at offset %1: expected '%2', got '%3'\n")
|
||||
.arg(i).arg(hunk.contextBefore[i]).arg(fileLines[checkIdx + i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exactMatch) {
|
||||
for (int i = 0; i < hunk.removedLines.size(); ++i) {
|
||||
int lineIdx = expectedIdx + i;
|
||||
if (lineIdx >= fileLines.size() || fileLines[lineIdx] != hunk.removedLines[i]) {
|
||||
exactMatch = false;
|
||||
debug += QString(" Removed line mismatch at offset %1: expected '%2', got '%3'\n")
|
||||
.arg(i)
|
||||
.arg(hunk.removedLines[i])
|
||||
.arg(lineIdx < fileLines.size() ? fileLines[lineIdx] : "<EOF>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exactMatch && !hunk.contextAfter.isEmpty()) {
|
||||
int afterIdx = expectedIdx + hunk.removedLines.size();
|
||||
for (int i = 0; i < hunk.contextAfter.size(); ++i) {
|
||||
int lineIdx = afterIdx + i;
|
||||
if (lineIdx >= fileLines.size() || fileLines[lineIdx] != hunk.contextAfter[i]) {
|
||||
exactMatch = false;
|
||||
debug += QString(" Context after mismatch at offset %1: expected '%2', got '%3'\n")
|
||||
.arg(i)
|
||||
.arg(hunk.contextAfter[i])
|
||||
.arg(lineIdx < fileLines.size() ? fileLines[lineIdx] : "<EOF>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (exactMatch) {
|
||||
actualStartLine = expectedIdx;
|
||||
LOG_MESSAGE(QString(" ✓ Found exact match at expected line %1").arg(hunk.oldStartLine));
|
||||
if (debugInfo) *debugInfo = "Exact match at expected location";
|
||||
return true;
|
||||
} else {
|
||||
debug += " Exact match at expected location failed, trying fuzzy search...\n";
|
||||
}
|
||||
} else {
|
||||
debug += QString(" Expected location %1 is out of bounds (file has %2 lines)\n")
|
||||
.arg(hunk.oldStartLine).arg(fileLines.size());
|
||||
}
|
||||
|
||||
LOG_MESSAGE(" Trying fuzzy search within ±20 lines...");
|
||||
|
||||
int searchStart = qMax(0, expectedIdx - 20);
|
||||
int searchEnd = qMin(fileLines.size(), expectedIdx + 20);
|
||||
|
||||
int bestMatchLine = -1;
|
||||
int bestMatchScore = 0;
|
||||
|
||||
for (int searchIdx = searchStart; searchIdx < searchEnd; ++searchIdx) {
|
||||
int matchScore = 0;
|
||||
int totalChecks = 0;
|
||||
|
||||
int checkIdx = searchIdx - hunk.contextBefore.size();
|
||||
if (checkIdx >= 0) {
|
||||
for (int i = 0; i < hunk.contextBefore.size(); ++i) {
|
||||
totalChecks++;
|
||||
if (fileLines[checkIdx + i] == hunk.contextBefore[i]) {
|
||||
matchScore++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hunk.removedLines.size(); ++i) {
|
||||
int lineIdx = searchIdx + i;
|
||||
if (lineIdx < fileLines.size()) {
|
||||
totalChecks++;
|
||||
if (fileLines[lineIdx] == hunk.removedLines[i]) {
|
||||
matchScore++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int afterIdx = searchIdx + hunk.removedLines.size();
|
||||
for (int i = 0; i < hunk.contextAfter.size(); ++i) {
|
||||
int lineIdx = afterIdx + i;
|
||||
if (lineIdx < fileLines.size()) {
|
||||
totalChecks++;
|
||||
if (fileLines[lineIdx] == hunk.contextAfter[i]) {
|
||||
matchScore++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchScore > bestMatchScore) {
|
||||
bestMatchScore = matchScore;
|
||||
bestMatchLine = searchIdx;
|
||||
}
|
||||
}
|
||||
|
||||
int totalPossibleScore = hunk.contextBefore.size() + hunk.removedLines.size() + hunk.contextAfter.size();
|
||||
double matchPercentage = totalPossibleScore > 0 ? (double)bestMatchScore / totalPossibleScore * 100.0 : 0.0;
|
||||
|
||||
if (bestMatchLine != -1 && matchPercentage >= 70.0) {
|
||||
actualStartLine = bestMatchLine;
|
||||
debug += QString(" ✓ Found fuzzy match at line %1 (score: %2/%3 = %4%%)\n")
|
||||
.arg(bestMatchLine + 1)
|
||||
.arg(bestMatchScore)
|
||||
.arg(totalPossibleScore)
|
||||
.arg(matchPercentage, 0, 'f', 1);
|
||||
LOG_MESSAGE(QString(" ✓ Found fuzzy match at line %1 (%2%% confidence)")
|
||||
.arg(bestMatchLine + 1).arg(matchPercentage, 0, 'f', 1));
|
||||
if (debugInfo) *debugInfo = debug;
|
||||
return true;
|
||||
}
|
||||
|
||||
debug += QString(" ✗ No suitable match found (best: %1%% at line %2)\n")
|
||||
.arg(matchPercentage, 0, 'f', 1)
|
||||
.arg(bestMatchLine != -1 ? bestMatchLine + 1 : -1);
|
||||
LOG_MESSAGE(QString(" ✗ Hunk location not found (best match: %1%%)").arg(matchPercentage, 0, 'f', 1));
|
||||
|
||||
if (debugInfo) *debugInfo = debug;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChangesManager::applyDiffToContent(
|
||||
QString &content,
|
||||
const DiffInfo &diffInfo,
|
||||
bool reverse,
|
||||
QString *errorMsg)
|
||||
{
|
||||
LOG_MESSAGE(QString("=== Applying %1 to content ===").arg(reverse ? "REVERSE diff" : "diff"));
|
||||
|
||||
auto setError = [errorMsg](const QString &msg) {
|
||||
if (errorMsg) *errorMsg = msg;
|
||||
};
|
||||
|
||||
if (diffInfo.useFallback) {
|
||||
LOG_MESSAGE(" Using fallback mode (direct content replacement)");
|
||||
|
||||
QString searchContent = reverse ? diffInfo.modifiedContent : diffInfo.originalContent;
|
||||
QString replaceContent = reverse ? diffInfo.originalContent : diffInfo.modifiedContent;
|
||||
|
||||
int matchPos = content.indexOf(searchContent);
|
||||
if (matchPos != -1) {
|
||||
content = content.left(matchPos)
|
||||
+ replaceContent
|
||||
+ content.mid(matchPos + searchContent.length());
|
||||
setError("Applied using fallback mode (direct replacement)");
|
||||
LOG_MESSAGE(QString(" ✓ Fallback: Direct replacement successful at position %1").arg(matchPos));
|
||||
return true;
|
||||
} else {
|
||||
setError("Fallback failed: Original content not found in file");
|
||||
LOG_MESSAGE(" ✗ Fallback: Content not found");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (diffInfo.hunks.isEmpty()) {
|
||||
LOG_MESSAGE(" No hunks to apply (content unchanged)");
|
||||
setError("No changes to apply");
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList fileLines = content.split('\n');
|
||||
LOG_MESSAGE(QString(" File has %1 lines, applying %2 hunk(s)")
|
||||
.arg(fileLines.size()).arg(diffInfo.hunks.size()));
|
||||
|
||||
QList<DiffHunk> hunksToApply = diffInfo.hunks;
|
||||
|
||||
std::sort(hunksToApply.begin(), hunksToApply.end(),
|
||||
[](const DiffHunk &a, const DiffHunk &b) {
|
||||
return a.oldStartLine > b.oldStartLine;
|
||||
});
|
||||
|
||||
LOG_MESSAGE(" Hunks sorted in descending order for application");
|
||||
|
||||
int appliedHunks = 0;
|
||||
int failedHunks = 0;
|
||||
|
||||
for (int hunkIdx = 0; hunkIdx < hunksToApply.size(); ++hunkIdx) {
|
||||
const DiffHunk &hunk = hunksToApply[hunkIdx];
|
||||
|
||||
LOG_MESSAGE(QString(" --- Applying hunk %1/%2 ---")
|
||||
.arg(hunkIdx + 1).arg(hunksToApply.size()));
|
||||
|
||||
int actualStartLine = -1;
|
||||
QString debugInfo;
|
||||
|
||||
if (!findHunkLocation(fileLines, hunk, actualStartLine, &debugInfo)) {
|
||||
LOG_MESSAGE(QString(" ✗ Failed to locate hunk %1:\n%2")
|
||||
.arg(hunkIdx + 1).arg(debugInfo));
|
||||
failedHunks++;
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_MESSAGE(QString(" Applying hunk at line %1 (remove %2 lines, add %3 lines)")
|
||||
.arg(actualStartLine + 1)
|
||||
.arg(hunk.removedLines.size())
|
||||
.arg(hunk.addedLines.size()));
|
||||
|
||||
for (int i = 0; i < hunk.removedLines.size(); ++i) {
|
||||
if (actualStartLine < fileLines.size()) {
|
||||
LOG_MESSAGE(QString(" Removing line %1: '%2'")
|
||||
.arg(actualStartLine + 1)
|
||||
.arg(fileLines[actualStartLine]));
|
||||
fileLines.removeAt(actualStartLine);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hunk.addedLines.size(); ++i) {
|
||||
LOG_MESSAGE(QString(" Inserting line %1: '%2'")
|
||||
.arg(actualStartLine + i + 1)
|
||||
.arg(hunk.addedLines[i]));
|
||||
fileLines.insert(actualStartLine + i, hunk.addedLines[i]);
|
||||
}
|
||||
|
||||
appliedHunks++;
|
||||
LOG_MESSAGE(QString(" ✓ Hunk %1 applied successfully").arg(hunkIdx + 1));
|
||||
}
|
||||
|
||||
if (failedHunks > 0) {
|
||||
QString msg = QString("Partially applied: %1 of %2 hunks succeeded")
|
||||
.arg(appliedHunks).arg(hunksToApply.size());
|
||||
setError(msg);
|
||||
LOG_MESSAGE(QString(" ⚠ %1").arg(msg));
|
||||
|
||||
content = fileLines.join('\n');
|
||||
return false;
|
||||
}
|
||||
|
||||
content = fileLines.join('\n');
|
||||
setError(QString("Successfully applied %1 hunk(s)").arg(appliedHunks));
|
||||
LOG_MESSAGE(QString("=== All %1 hunk(s) applied successfully ===").arg(appliedHunks));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@@ -81,8 +81,7 @@ public:
|
||||
bool rejectFileEdit(const QString &editId);
|
||||
bool undoFileEdit(const QString &editId);
|
||||
FileEdit getFileEdit(const QString &editId) const;
|
||||
QList<FileEdit> getPendingEdits() const;
|
||||
|
||||
|
||||
bool applyPendingEditsForRequest(const QString &requestId, QString *errorMsg = nullptr);
|
||||
|
||||
QList<FileEdit> getEditsForRequest(const QString &requestId) const;
|
||||
@@ -106,13 +105,9 @@ private:
|
||||
ChangesManager(const ChangesManager &) = delete;
|
||||
ChangesManager &operator=(const ChangesManager &) = delete;
|
||||
|
||||
bool performFileEdit(const QString &filePath, const QString &oldContent, const QString &newContent, QString *errorMsg = nullptr);
|
||||
bool performFileEditWithDiff(const QString &filePath, const DiffInfo &diffInfo, bool reverse, QString *errorMsg = nullptr);
|
||||
QString readFileContent(const QString &filePath) const;
|
||||
|
||||
DiffInfo createDiffInfo(const QString &originalContent, const QString &modifiedContent, const QString &filePath);
|
||||
bool applyDiffToContent(QString &content, const DiffInfo &diffInfo, bool reverse, QString *errorMsg = nullptr);
|
||||
bool findHunkLocation(const QStringList &fileLines, const DiffHunk &hunk, int &actualStartLine, QString *debugInfo = nullptr) const;
|
||||
|
||||
// Helper method for fragment-based apply/undo operations
|
||||
bool performFragmentReplacement(
|
||||
|
||||
@@ -6,25 +6,24 @@
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonObject>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/projectnodes.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include "Logger.hpp"
|
||||
#include "ProjectScannerQtCreator.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
ContextManager::ContextManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_ignoreManager(new IgnoreManager(this))
|
||||
: ContextManager(std::make_unique<ProjectScannerQtCreator>(), parent)
|
||||
{}
|
||||
|
||||
ContextManager::ContextManager(std::unique_ptr<IProjectScanner> scanner, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_scanner(std::move(scanner))
|
||||
{}
|
||||
|
||||
ContextManager::~ContextManager() = default;
|
||||
|
||||
QString ContextManager::readFile(const QString &filePath) const
|
||||
{
|
||||
QFile file(filePath);
|
||||
@@ -37,7 +36,7 @@ QString ContextManager::readFile(const QString &filePath) const
|
||||
QTextStream in(&file);
|
||||
QString content = in.readAll();
|
||||
file.close();
|
||||
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -45,9 +44,7 @@ QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths)
|
||||
{
|
||||
QList<ContentFile> files;
|
||||
for (const QString &path : filePaths) {
|
||||
auto project = ProjectExplorer::ProjectManager::projectForFile(
|
||||
Utils::FilePath::fromString(path));
|
||||
if (project && m_ignoreManager->shouldIgnore(path, project)) {
|
||||
if (m_scanner->shouldIgnore(path)) {
|
||||
LOG_MESSAGE(QString("Ignoring file in context due to .qodeassistignore: %1").arg(path));
|
||||
continue;
|
||||
}
|
||||
@@ -58,27 +55,6 @@ QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths)
|
||||
return files;
|
||||
}
|
||||
|
||||
QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *project) const
|
||||
{
|
||||
QStringList sourceFiles;
|
||||
if (!project)
|
||||
return sourceFiles;
|
||||
|
||||
auto projectNode = project->rootProjectNode();
|
||||
if (!projectNode)
|
||||
return sourceFiles;
|
||||
|
||||
projectNode->forEachNode(
|
||||
[&sourceFiles, this](ProjectExplorer::FileNode *fileNode) {
|
||||
if (fileNode /*&& shouldProcessFile(fileNode->filePath().toString())*/) {
|
||||
sourceFiles.append(fileNode->filePath().toUrlishString());
|
||||
}
|
||||
},
|
||||
nullptr);
|
||||
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
ContentFile ContextManager::createContentFile(const QString &filePath) const
|
||||
{
|
||||
ContentFile contentFile;
|
||||
@@ -100,77 +76,26 @@ ProgrammingLanguage ContextManager::getDocumentLanguage(const DocumentInfo &docu
|
||||
|
||||
bool ContextManager::isSpecifyCompletion(const DocumentInfo &documentInfo) const
|
||||
{
|
||||
const auto &generalSettings = Settings::generalSettings();
|
||||
|
||||
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(documentInfo);
|
||||
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
||||
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
||||
|
||||
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
||||
Q_UNUSED(documentInfo)
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> ContextManager::openedFiles(const QStringList excludeFiles) const
|
||||
{
|
||||
auto documents = Core::DocumentModel::openedDocuments();
|
||||
|
||||
QList<QPair<QString, QString>> files;
|
||||
|
||||
for (const auto *document : std::as_const(documents)) {
|
||||
auto textDocument = qobject_cast<const TextEditor::TextDocument *>(document);
|
||||
if (!textDocument)
|
||||
continue;
|
||||
|
||||
auto filePath = textDocument->filePath().toUrlishString();
|
||||
|
||||
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
|
||||
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
||||
LOG_MESSAGE(
|
||||
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!excludeFiles.contains(filePath)) {
|
||||
files.append({filePath, textDocument->plainText()});
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
QString ContextManager::openedFilesContext(const QStringList excludeFiles)
|
||||
QString ContextManager::openedFilesContext(const QStringList &excludeFiles) const
|
||||
{
|
||||
QString context = "User files context:\n";
|
||||
|
||||
auto documents = Core::DocumentModel::openedDocuments();
|
||||
|
||||
for (const auto *document : documents) {
|
||||
auto textDocument = qobject_cast<const TextEditor::TextDocument *>(document);
|
||||
if (!textDocument)
|
||||
continue;
|
||||
|
||||
auto filePath = textDocument->filePath().toUrlishString();
|
||||
if (excludeFiles.contains(filePath))
|
||||
continue;
|
||||
|
||||
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
|
||||
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
|
||||
LOG_MESSAGE(
|
||||
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
|
||||
continue;
|
||||
}
|
||||
|
||||
context += QString("File: %1\n").arg(filePath);
|
||||
context += textDocument->plainText();
|
||||
|
||||
for (const auto &file : m_scanner->openedTextFiles(excludeFiles)) {
|
||||
context += QString("File: %1\n").arg(file.filePath);
|
||||
context += file.content;
|
||||
context += "\n";
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
IgnoreManager *ContextManager::ignoreManager() const
|
||||
bool ContextManager::shouldIgnore(const QString &filePath) const
|
||||
{
|
||||
return m_ignoreManager;
|
||||
return m_scanner->shouldIgnore(filePath);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@@ -4,18 +4,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "ContentFile.hpp"
|
||||
#include "IContextManager.hpp"
|
||||
#include "IgnoreManager.hpp"
|
||||
#include "IProjectScanner.hpp"
|
||||
#include "ProgrammingLanguage.hpp"
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Project;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class ContextManager : public QObject, public IContextManager
|
||||
@@ -24,22 +22,22 @@ class ContextManager : public QObject, public IContextManager
|
||||
|
||||
public:
|
||||
explicit ContextManager(QObject *parent = nullptr);
|
||||
~ContextManager() override = default;
|
||||
ContextManager(std::unique_ptr<IProjectScanner> scanner, QObject *parent = nullptr);
|
||||
~ContextManager() override;
|
||||
|
||||
QString readFile(const QString &filePath) const override;
|
||||
QList<ContentFile> getContentFiles(const QStringList &filePaths) const override;
|
||||
QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const override;
|
||||
ContentFile createContentFile(const QString &filePath) const override;
|
||||
|
||||
ProgrammingLanguage getDocumentLanguage(const DocumentInfo &documentInfo) const override;
|
||||
bool isSpecifyCompletion(const DocumentInfo &documentInfo) const override;
|
||||
QList<QPair<QString, QString>> openedFiles(const QStringList excludeFiles = QStringList{}) const;
|
||||
QString openedFilesContext(const QStringList excludeFiles = QStringList{});
|
||||
|
||||
IgnoreManager *ignoreManager() const;
|
||||
QString openedFilesContext(const QStringList &excludeFiles = QStringList{}) const;
|
||||
|
||||
bool shouldIgnore(const QString &filePath) const;
|
||||
|
||||
private:
|
||||
IgnoreManager *m_ignoreManager;
|
||||
std::unique_ptr<IProjectScanner> m_scanner;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
|
||||
#include "DocumentContextReader.hpp"
|
||||
|
||||
#include <languageserverprotocol/lsptypes.h>
|
||||
#include <QFileInfo>
|
||||
#include <QTextBlock>
|
||||
|
||||
#include "CodeCompletionSettings.hpp"
|
||||
|
||||
#include "ChangesManager.h"
|
||||
#include "EnvBlockFormatter.hpp"
|
||||
|
||||
const QRegularExpression &getYearRegex()
|
||||
{
|
||||
@@ -108,15 +107,6 @@ QString DocumentContextReader::readWholeFileAfter(int lineNumber, int cursorPosi
|
||||
return getContextBetween(lineNumber, cursorPosition, endLine, -1);
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getLanguageAndFileInfo() const
|
||||
{
|
||||
QString language = LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(m_mimeType);
|
||||
QString fileExtension = QFileInfo(m_filePath).suffix();
|
||||
|
||||
return QString("Language: %1 (MIME: %2) filepath: %3(%4)\n\n")
|
||||
.arg(language, m_mimeType, m_filePath, fileExtension);
|
||||
}
|
||||
|
||||
CopyrightInfo DocumentContextReader::findCopyright()
|
||||
{
|
||||
CopyrightInfo result = {-1, -1, false};
|
||||
@@ -249,12 +239,7 @@ QString DocumentContextReader::getContextBetween(
|
||||
return context;
|
||||
}
|
||||
|
||||
CopyrightInfo DocumentContextReader::copyrightInfo() const
|
||||
{
|
||||
return m_copyrightInfo;
|
||||
}
|
||||
|
||||
PluginLLMCore::ContextData DocumentContextReader::prepareContext(
|
||||
Templates::ContextData DocumentContextReader::prepareContext(
|
||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const
|
||||
{
|
||||
QString contextBefore;
|
||||
@@ -272,7 +257,9 @@ PluginLLMCore::ContextData DocumentContextReader::prepareContext(
|
||||
}
|
||||
|
||||
QString fileContext;
|
||||
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
||||
fileContext.append("\n")
|
||||
.append(EnvBlockFormatter::formatFile({m_filePath, m_mimeType}))
|
||||
.append("\n");
|
||||
|
||||
if (settings.useProjectChangesCache())
|
||||
fileContext.append("Recent Project Changes Context:\n ")
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include <pluginllmcore/ContextData.hpp>
|
||||
#include <sources/common/ContextData.hpp>
|
||||
#include <settings/CodeCompletionSettings.hpp>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
@@ -51,14 +51,11 @@ public:
|
||||
*/
|
||||
QString readWholeFileAfter(int lineNumber, int cursorPosition) const;
|
||||
|
||||
QString getLanguageAndFileInfo() const;
|
||||
CopyrightInfo findCopyright();
|
||||
QString getContextBetween(
|
||||
int startLine, int startCursorPosition, int endLine, int endCursorPosition) const;
|
||||
|
||||
CopyrightInfo copyrightInfo() const;
|
||||
|
||||
PluginLLMCore::ContextData prepareContext(
|
||||
Templates::ContextData prepareContext(
|
||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const;
|
||||
|
||||
private:
|
||||
|
||||
66
context/EnvBlockFormatter.cpp
Normal file
66
context/EnvBlockFormatter.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "EnvBlockFormatter.hpp"
|
||||
|
||||
#include <languageserverprotocol/lsptypes.h>
|
||||
#include <projectexplorer/buildconfiguration.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/target.h>
|
||||
|
||||
namespace QodeAssist::Context::EnvBlockFormatter {
|
||||
|
||||
ProjectEnv currentProject()
|
||||
{
|
||||
ProjectEnv env;
|
||||
auto *project = ProjectExplorer::ProjectManager::startupProject();
|
||||
if (!project)
|
||||
return env;
|
||||
|
||||
env.name = project->displayName();
|
||||
env.sourceRoot = project->projectDirectory().toUrlishString();
|
||||
if (auto *target = project->activeTarget()) {
|
||||
if (auto *buildConfig = target->activeBuildConfiguration())
|
||||
env.buildDir = buildConfig->buildDirectory().toUrlishString();
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
QString formatProject(const ProjectEnv &env)
|
||||
{
|
||||
if (env.name.isEmpty() && env.sourceRoot.isEmpty())
|
||||
return QStringLiteral("# No active project in IDE");
|
||||
|
||||
QString out = QStringLiteral("# Active project: %1").arg(env.name);
|
||||
out += QStringLiteral(
|
||||
"\n# Project source root: %1"
|
||||
"\n# All new source files, headers, QML and CMake edits MUST be "
|
||||
"created or modified under this directory. Use absolute paths "
|
||||
"rooted here, or project-relative paths.")
|
||||
.arg(env.sourceRoot);
|
||||
if (!env.buildDir.isEmpty()) {
|
||||
out += QStringLiteral(
|
||||
"\n# Build output directory (compiler artifacts only — do NOT "
|
||||
"create or edit source files here): %1")
|
||||
.arg(env.buildDir);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString formatFile(const FileEnv &env)
|
||||
{
|
||||
const QString language
|
||||
= LanguageServerProtocol::TextDocumentItem::mimeTypeToLanguageId(env.mimeType);
|
||||
|
||||
QString out = QStringLiteral("File information:");
|
||||
if (!language.isEmpty())
|
||||
out += QStringLiteral("\nLanguage: %1 (MIME: %2)").arg(language, env.mimeType);
|
||||
else if (!env.mimeType.isEmpty())
|
||||
out += QStringLiteral("\nMIME type: %1").arg(env.mimeType);
|
||||
out += QStringLiteral("\nFile path: %1\n").arg(env.filePath);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context::EnvBlockFormatter
|
||||
29
context/EnvBlockFormatter.hpp
Normal file
29
context/EnvBlockFormatter.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Context::EnvBlockFormatter {
|
||||
|
||||
struct ProjectEnv
|
||||
{
|
||||
QString name;
|
||||
QString sourceRoot;
|
||||
QString buildDir;
|
||||
};
|
||||
|
||||
struct FileEnv
|
||||
{
|
||||
QString filePath;
|
||||
QString mimeType;
|
||||
};
|
||||
|
||||
ProjectEnv currentProject();
|
||||
|
||||
QString formatProject(const ProjectEnv &env);
|
||||
QString formatFile(const FileEnv &env);
|
||||
|
||||
} // namespace QodeAssist::Context::EnvBlockFormatter
|
||||
@@ -11,10 +11,6 @@
|
||||
#include "IDocumentReader.hpp"
|
||||
#include "ProgrammingLanguage.hpp"
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Project;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class IContextManager
|
||||
@@ -24,7 +20,6 @@ public:
|
||||
|
||||
virtual QString readFile(const QString &filePath) const = 0;
|
||||
virtual QList<ContentFile> getContentFiles(const QStringList &filePaths) const = 0;
|
||||
virtual QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const = 0;
|
||||
virtual ContentFile createContentFile(const QString &filePath) const = 0;
|
||||
|
||||
virtual ProgrammingLanguage getDocumentLanguage(const DocumentInfo &documentInfo) const = 0;
|
||||
|
||||
28
context/IProjectScanner.hpp
Normal file
28
context/IProjectScanner.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
struct OpenedTextFile
|
||||
{
|
||||
QString filePath;
|
||||
QString content;
|
||||
};
|
||||
|
||||
class IProjectScanner
|
||||
{
|
||||
public:
|
||||
virtual ~IProjectScanner() = default;
|
||||
|
||||
virtual QList<OpenedTextFile> openedTextFiles(const QStringList &excludeFiles = {}) const = 0;
|
||||
virtual bool shouldIgnore(const QString &filePath) const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
@@ -234,19 +234,6 @@ void IgnoreManager::removeIgnorePatterns(ProjectExplorer::Project *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
|
||||
{
|
||||
if (!project) {
|
||||
|
||||
@@ -27,8 +27,6 @@ public:
|
||||
void reloadIgnorePatterns(ProjectExplorer::Project *project);
|
||||
void removeIgnorePatterns(ProjectExplorer::Project *project);
|
||||
|
||||
void reloadAllPatterns();
|
||||
|
||||
private slots:
|
||||
void cleanupConnections();
|
||||
|
||||
|
||||
53
context/ProjectScannerQtCreator.cpp
Normal file
53
context/ProjectScannerQtCreator.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "ProjectScannerQtCreator.hpp"
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#include "IgnoreManager.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
ProjectScannerQtCreator::ProjectScannerQtCreator()
|
||||
: m_ignoreManager(std::make_unique<IgnoreManager>())
|
||||
{}
|
||||
|
||||
ProjectScannerQtCreator::~ProjectScannerQtCreator() = default;
|
||||
|
||||
QList<OpenedTextFile> ProjectScannerQtCreator::openedTextFiles(
|
||||
const QStringList &excludeFiles) const
|
||||
{
|
||||
QList<OpenedTextFile> files;
|
||||
|
||||
const auto documents = Core::DocumentModel::openedDocuments();
|
||||
for (const auto *document : documents) {
|
||||
const auto *textDocument = qobject_cast<const TextEditor::TextDocument *>(document);
|
||||
if (!textDocument)
|
||||
continue;
|
||||
|
||||
const QString filePath = textDocument->filePath().toUrlishString();
|
||||
if (excludeFiles.contains(filePath))
|
||||
continue;
|
||||
if (shouldIgnore(filePath))
|
||||
continue;
|
||||
|
||||
files.append({filePath, textDocument->plainText()});
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
bool ProjectScannerQtCreator::shouldIgnore(const QString &filePath) const
|
||||
{
|
||||
auto *project = ProjectExplorer::ProjectManager::projectForFile(
|
||||
Utils::FilePath::fromString(filePath));
|
||||
return project && m_ignoreManager->shouldIgnore(filePath, project);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
28
context/ProjectScannerQtCreator.hpp
Normal file
28
context/ProjectScannerQtCreator.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "IProjectScanner.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class IgnoreManager;
|
||||
|
||||
class ProjectScannerQtCreator : public IProjectScanner
|
||||
{
|
||||
public:
|
||||
ProjectScannerQtCreator();
|
||||
~ProjectScannerQtCreator() override;
|
||||
|
||||
QList<OpenedTextFile> openedTextFiles(const QStringList &excludeFiles = {}) const override;
|
||||
bool shouldIgnore(const QString &filePath) const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<IgnoreManager> m_ignoreManager;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
@@ -35,25 +35,6 @@ bool ProjectUtils::isFileInProject(const QString &filePath)
|
||||
return false;
|
||||
}
|
||||
|
||||
QString ProjectUtils::findFileInProject(const QString &filename)
|
||||
{
|
||||
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
|
||||
|
||||
for (auto project : projects) {
|
||||
if (!project)
|
||||
continue;
|
||||
|
||||
Utils::FilePaths projectFiles = project->files(ProjectExplorer::Project::SourceFiles);
|
||||
for (const auto &projectFile : std::as_const(projectFiles)) {
|
||||
if (projectFile.fileName() == filename) {
|
||||
return projectFile.toFSPathString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString ProjectUtils::getProjectRoot()
|
||||
{
|
||||
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
|
||||
|
||||
@@ -26,17 +26,6 @@ public:
|
||||
*/
|
||||
static bool isFileInProject(const QString &filePath);
|
||||
|
||||
/**
|
||||
* @brief Find a file in open projects by filename
|
||||
*
|
||||
* Searches all open projects for a file matching the given filename.
|
||||
* If multiple files with the same name exist, returns the first match.
|
||||
*
|
||||
* @param filename File name to search for (e.g., "main.cpp")
|
||||
* @return Absolute file path if found, empty string otherwise
|
||||
*/
|
||||
static QString findFileInProject(const QString &filename);
|
||||
|
||||
/**
|
||||
* @brief Get the project root directory
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user