mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2025-11-12 13:02:54 -05:00
fix: Changes to top of file
This commit is contained in:
@ -269,7 +269,7 @@ bool ChangesManager::undoFileEdit(const QString &editId)
|
|||||||
QString errorMsg;
|
QString errorMsg;
|
||||||
bool isAppend = oldContentCopy.isEmpty();
|
bool isAppend = oldContentCopy.isEmpty();
|
||||||
bool success = performFragmentReplacement(
|
bool success = performFragmentReplacement(
|
||||||
filePathCopy, newContentCopy, oldContentCopy, isAppend, &errorMsg);
|
filePathCopy, newContentCopy, oldContentCopy, isAppend, &errorMsg, true);
|
||||||
|
|
||||||
locker.relock();
|
locker.relock();
|
||||||
|
|
||||||
@ -365,7 +365,7 @@ bool ChangesManager::performFileEdit(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
double similarity = 0.0;
|
double similarity = 0.0;
|
||||||
QString matchedContent = findBestMatch(currentContent, oldContent, 0.8, &similarity);
|
QString matchedContent = findBestMatch(currentContent, oldContent, 0.82, &similarity);
|
||||||
if (!matchedContent.isEmpty()) {
|
if (!matchedContent.isEmpty()) {
|
||||||
matchPos = currentContent.indexOf(matchedContent);
|
matchPos = currentContent.indexOf(matchedContent);
|
||||||
if (matchPos != -1) {
|
if (matchPos != -1) {
|
||||||
@ -391,7 +391,7 @@ bool ChangesManager::performFileEdit(
|
|||||||
|
|
||||||
LOG_MESSAGE(QString("Old content not found in open editor (best similarity: %1%%): %2")
|
LOG_MESSAGE(QString("Old content not found in open editor (best similarity: %1%%): %2")
|
||||||
.arg(qRound(similarity * 100)).arg(filePath));
|
.arg(qRound(similarity * 100)).arg(filePath));
|
||||||
setError(QString("Content not found. Best match: %1%% (threshold: 80%%). "
|
setError(QString("Content not found. Best match: %1%% (threshold: 82%%). "
|
||||||
"File may have changed.").arg(qRound(similarity * 100)));
|
"File may have changed.").arg(qRound(similarity * 100)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -417,19 +417,32 @@ bool ChangesManager::performFileEdit(
|
|||||||
setError("Applied successfully (appended to end of file)");
|
setError("Applied successfully (appended to end of file)");
|
||||||
}
|
}
|
||||||
else if (currentContent.contains(oldContent)) {
|
else if (currentContent.contains(oldContent)) {
|
||||||
updatedContent = currentContent.replace(oldContent, newContent);
|
int matchPos = currentContent.indexOf(oldContent);
|
||||||
LOG_MESSAGE(QString("Using exact match for file update: %1").arg(filePath));
|
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)");
|
setError("Applied successfully (exact match)");
|
||||||
} else {
|
} else {
|
||||||
double similarity = 0.0;
|
double similarity = 0.0;
|
||||||
QString matchedContent = findBestMatch(currentContent, oldContent, 0.8, &similarity);
|
QString matchedContent = findBestMatch(currentContent, oldContent, 0.82, &similarity);
|
||||||
if (!matchedContent.isEmpty()) {
|
if (!matchedContent.isEmpty()) {
|
||||||
updatedContent = currentContent.replace(matchedContent, newContent);
|
int matchPos = currentContent.indexOf(matchedContent);
|
||||||
LOG_MESSAGE(QString("Using fuzzy match (%1%%) for file update: %2")
|
if (matchPos == -1) {
|
||||||
.arg(qRound(similarity * 100)).arg(filePath));
|
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)));
|
setError(QString("Applied with fuzzy match (%1%% similarity)").arg(qRound(similarity * 100)));
|
||||||
} else {
|
} else {
|
||||||
QString msg = QString("Content not found. Best match: %1%% (threshold: 80%%). "
|
QString msg = QString("Content not found. Best match: %1%% (threshold: 82%%). "
|
||||||
"File may have changed.").arg(qRound(similarity * 100));
|
"File may have changed.").arg(qRound(similarity * 100));
|
||||||
LOG_MESSAGE(QString("Old content not found in file (best similarity: %1%%): %2")
|
LOG_MESSAGE(QString("Old content not found in file (best similarity: %1%%): %2")
|
||||||
.arg(qRound(similarity * 100)).arg(filePath));
|
.arg(qRound(similarity * 100)).arg(filePath));
|
||||||
@ -458,6 +471,11 @@ int ChangesManager::levenshteinDistance(const QString &s1, const QString &s2) co
|
|||||||
const int len1 = s1.length();
|
const int len1 = s1.length();
|
||||||
const int len2 = s2.length();
|
const int len2 = s2.length();
|
||||||
|
|
||||||
|
const int MAX_LENGTH = 10000;
|
||||||
|
if (len1 > MAX_LENGTH || len2 > MAX_LENGTH) {
|
||||||
|
return qAbs(len1 - len2) + qMin(len1, len2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
QVector<QVector<int>> d(len1 + 1, QVector<int>(len2 + 1));
|
QVector<QVector<int>> d(len1 + 1, QVector<int>(len2 + 1));
|
||||||
|
|
||||||
for (int i = 0; i <= len1; ++i) {
|
for (int i = 0; i <= len1; ++i) {
|
||||||
@ -481,6 +499,72 @@ int ChangesManager::levenshteinDistance(const QString &s1, const QString &s2) co
|
|||||||
return d[len1][len2];
|
return d[len1][len2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ChangesManager::findBestMatchLineBased(
|
||||||
|
const QString &fileContent,
|
||||||
|
const QString &searchContent,
|
||||||
|
double threshold,
|
||||||
|
double *outSimilarity) const
|
||||||
|
{
|
||||||
|
QStringList fileLines = fileContent.split('\n');
|
||||||
|
QStringList searchLines = searchContent.split('\n');
|
||||||
|
|
||||||
|
if (searchLines.isEmpty() || fileLines.isEmpty()) {
|
||||||
|
if (outSimilarity) *outSimilarity = 0.0;
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchLines.size() > fileLines.size()) {
|
||||||
|
if (outSimilarity) *outSimilarity = 0.0;
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString bestMatch;
|
||||||
|
double bestSimilarity = 0.0;
|
||||||
|
int searchLineCount = searchLines.size();
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("Line-based search: %1 search lines in %2 file lines")
|
||||||
|
.arg(searchLineCount).arg(fileLines.size()));
|
||||||
|
|
||||||
|
for (int i = 0; i <= fileLines.size() - searchLineCount; ++i) {
|
||||||
|
int matchingLines = 0;
|
||||||
|
int totalLines = searchLineCount;
|
||||||
|
|
||||||
|
for (int j = 0; j < searchLineCount; ++j) {
|
||||||
|
if (fileLines[i + j] == searchLines[j]) {
|
||||||
|
matchingLines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double similarity = static_cast<double>(matchingLines) / totalLines;
|
||||||
|
|
||||||
|
if (similarity > bestSimilarity) {
|
||||||
|
bestSimilarity = similarity;
|
||||||
|
if (similarity >= threshold) {
|
||||||
|
QStringList matchedLines;
|
||||||
|
for (int j = 0; j < searchLineCount; ++j) {
|
||||||
|
matchedLines.append(fileLines[i + j]);
|
||||||
|
}
|
||||||
|
bestMatch = matchedLines.join('\n');
|
||||||
|
|
||||||
|
if (similarity >= 0.99) {
|
||||||
|
if (outSimilarity) *outSimilarity = similarity;
|
||||||
|
LOG_MESSAGE(QString("Found exact line match at line %1").arg(i + 1));
|
||||||
|
return bestMatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outSimilarity) {
|
||||||
|
*outSimilarity = bestSimilarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("Line-based search complete, best similarity: %1%%")
|
||||||
|
.arg(qRound(bestSimilarity * 100)));
|
||||||
|
|
||||||
|
return bestMatch;
|
||||||
|
}
|
||||||
|
|
||||||
QString ChangesManager::findBestMatch(const QString &fileContent, const QString &searchContent, double threshold, double *outSimilarity) const
|
QString ChangesManager::findBestMatch(const QString &fileContent, const QString &searchContent, double threshold, double *outSimilarity) const
|
||||||
{
|
{
|
||||||
if (searchContent.isEmpty() || fileContent.isEmpty()) {
|
if (searchContent.isEmpty() || fileContent.isEmpty()) {
|
||||||
@ -496,11 +580,37 @@ QString ChangesManager::findBestMatch(const QString &fileContent, const QString
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int MAX_SEARCH_LENGTH = 50000;
|
||||||
|
if (searchLen > MAX_SEARCH_LENGTH) {
|
||||||
|
LOG_MESSAGE(QString("Search content too large (%1 chars), using line-based search").arg(searchLen));
|
||||||
|
return findBestMatchLineBased(fileContent, searchContent, threshold, outSimilarity);
|
||||||
|
}
|
||||||
|
|
||||||
QString bestMatch;
|
QString bestMatch;
|
||||||
double bestSimilarity = 0.0;
|
double bestSimilarity = 0.0;
|
||||||
|
|
||||||
for (int i = 0; i <= fileLen - searchLen; ++i) {
|
QChar firstChar = searchContent.at(0);
|
||||||
|
|
||||||
|
int step = 1;
|
||||||
|
if (fileLen > 100000 && searchLen > 1000) {
|
||||||
|
step = searchLen / 10;
|
||||||
|
if (step < 1) step = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int searchEnd = fileLen - searchLen + 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < searchEnd; i += step) {
|
||||||
|
if (step == 1 && fileContent.at(i) != firstChar) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
QString candidate = fileContent.mid(i, searchLen);
|
QString candidate = fileContent.mid(i, searchLen);
|
||||||
|
|
||||||
|
int lengthDiff = qAbs(candidate.length() - searchLen);
|
||||||
|
if (lengthDiff > searchLen * 0.3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int distance = levenshteinDistance(candidate, searchContent);
|
int distance = levenshteinDistance(candidate, searchContent);
|
||||||
double similarity = 1.0 - (static_cast<double>(distance) / searchLen);
|
double similarity = 1.0 - (static_cast<double>(distance) / searchLen);
|
||||||
|
|
||||||
@ -508,8 +618,19 @@ QString ChangesManager::findBestMatch(const QString &fileContent, const QString
|
|||||||
bestSimilarity = similarity;
|
bestSimilarity = similarity;
|
||||||
if (similarity >= threshold) {
|
if (similarity >= threshold) {
|
||||||
bestMatch = candidate;
|
bestMatch = candidate;
|
||||||
|
|
||||||
|
if (similarity >= 0.95) {
|
||||||
|
if (outSimilarity) *outSimilarity = bestSimilarity;
|
||||||
|
LOG_MESSAGE(QString("Found excellent match early (similarity: %1%%), stopping search").arg(qRound(similarity * 100)));
|
||||||
|
return bestMatch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i > searchLen * 3 && bestSimilarity < 0.5) {
|
||||||
|
LOG_MESSAGE("Early termination: no good matches found in first 3x search area");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outSimilarity) {
|
if (outSimilarity) {
|
||||||
@ -576,7 +697,8 @@ bool ChangesManager::performFragmentReplacement(
|
|||||||
const QString &searchContent,
|
const QString &searchContent,
|
||||||
const QString &replaceContent,
|
const QString &replaceContent,
|
||||||
bool isAppendOperation,
|
bool isAppendOperation,
|
||||||
QString *errorMsg)
|
QString *errorMsg,
|
||||||
|
bool isUndo)
|
||||||
{
|
{
|
||||||
QString currentContent = readFileContent(filePath);
|
QString currentContent = readFileContent(filePath);
|
||||||
if (currentContent.isNull()) {
|
if (currentContent.isNull()) {
|
||||||
@ -606,36 +728,120 @@ bool ChangesManager::performFragmentReplacement(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
double minThreshold = isUndo ? 0.70 : 0.85;
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("Fragment replacement: isUndo=%1, threshold=%2%%")
|
||||||
|
.arg(isUndo ? "yes" : "no")
|
||||||
|
.arg(qRound(minThreshold * 100)));
|
||||||
|
|
||||||
double similarity = 0.0;
|
double similarity = 0.0;
|
||||||
QString matchType;
|
QString matchType;
|
||||||
QString matchedContent = findBestMatchWithNormalization(
|
QString matchedContent = findBestMatchWithNormalization(
|
||||||
currentContent, searchContent, &similarity, &matchType);
|
currentContent, searchContent, &similarity, &matchType);
|
||||||
|
|
||||||
|
if (!matchedContent.isEmpty() && similarity < minThreshold) {
|
||||||
|
QString msg = QString("Cannot %1: similarity too low (%2%%, threshold: %3%%). %4")
|
||||||
|
.arg(isUndo ? "undo" : "apply")
|
||||||
|
.arg(qRound(similarity * 100))
|
||||||
|
.arg(qRound(minThreshold * 100))
|
||||||
|
.arg(isUndo ? "File may have been modified."
|
||||||
|
: "LLM may have provided incorrect oldContent.");
|
||||||
|
if (errorMsg) *errorMsg = msg;
|
||||||
|
LOG_MESSAGE(QString("Fragment replacement failed: %1").arg(msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!matchedContent.isEmpty()) {
|
if (!matchedContent.isEmpty()) {
|
||||||
resultContent = currentContent;
|
int matchPos = currentContent.indexOf(matchedContent);
|
||||||
resultContent.replace(matchedContent, replaceContent);
|
if (matchPos == -1) {
|
||||||
|
if (errorMsg) {
|
||||||
|
*errorMsg = "Internal error: matched content not found in file";
|
||||||
|
}
|
||||||
|
LOG_MESSAGE(QString("Internal error: matched content disappeared: %1").arg(filePath));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultContent = currentContent.left(matchPos)
|
||||||
|
+ replaceContent
|
||||||
|
+ currentContent.mid(matchPos + matchedContent.length());
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("Replaced content at position %1 (length: %2 -> %3)")
|
||||||
|
.arg(matchPos)
|
||||||
|
.arg(matchedContent.length())
|
||||||
|
.arg(replaceContent.length()));
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
if (matchType == "exact") {
|
if (matchType == "exact") {
|
||||||
*errorMsg = "Successfully applied";
|
*errorMsg = isUndo ? "Successfully undone" : "Successfully applied";
|
||||||
} else if (matchType.startsWith("fuzzy")) {
|
} else if (matchType.startsWith("fuzzy")) {
|
||||||
*errorMsg = QString("Applied (%1%% similarity)")
|
*errorMsg = QString("%1 (%2%% similarity)")
|
||||||
|
.arg(isUndo ? "Undone" : "Applied")
|
||||||
.arg(qRound(similarity * 100));
|
.arg(qRound(similarity * 100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
*errorMsg = QString("Cannot apply: similarity too low (%1%%). File may have been modified.")
|
*errorMsg = QString("Cannot %1: content not found in file.")
|
||||||
.arg(qRound(similarity * 100));
|
.arg(isUndo ? "undo" : "apply");
|
||||||
}
|
}
|
||||||
LOG_MESSAGE(QString("Failed to find content for fragment replacement: %1 (similarity: %2%%)")
|
LOG_MESSAGE(QString("Failed to find content for fragment replacement: %1").arg(filePath));
|
||||||
.arg(filePath).arg(qRound(similarity * 100)));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DiffInfo freshDiff = createDiffInfo(currentContent, resultContent, filePath);
|
auto editors = Core::EditorManager::visibleEditors();
|
||||||
return performFileEditWithDiff(filePath, freshDiff, false, errorMsg);
|
for (auto *editor : editors) {
|
||||||
|
if (!editor || !editor->document()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString editorPath = editor->document()->filePath().toFSPathString();
|
||||||
|
if (editorPath == filePath) {
|
||||||
|
if (auto *textEditor = qobject_cast<TextEditor::TextDocument *>(editor->document())) {
|
||||||
|
QTextDocument *doc = textEditor->document();
|
||||||
|
|
||||||
|
try {
|
||||||
|
QTextCursor cursor(doc);
|
||||||
|
if (!cursor.isNull()) {
|
||||||
|
cursor.beginEditBlock();
|
||||||
|
cursor.select(QTextCursor::Document);
|
||||||
|
cursor.removeSelectedText();
|
||||||
|
cursor.insertText(resultContent);
|
||||||
|
cursor.endEditBlock();
|
||||||
|
|
||||||
|
if (errorMsg && errorMsg->isEmpty()) {
|
||||||
|
*errorMsg = isUndo ? "Successfully undone" : "Successfully applied";
|
||||||
|
}
|
||||||
|
LOG_MESSAGE(QString("Applied fragment replacement to open editor: %1").arg(filePath));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
LOG_MESSAGE("Exception during document modification");
|
||||||
|
if (errorMsg) *errorMsg = "Exception during document modification";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(filePath);
|
||||||
|
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()));
|
||||||
|
if (errorMsg) *errorMsg = msg;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << resultContent;
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (errorMsg && errorMsg->isEmpty()) {
|
||||||
|
*errorMsg = isUndo ? "Successfully undone" : "Successfully applied";
|
||||||
|
}
|
||||||
|
LOG_MESSAGE(QString("Applied fragment replacement to file: %1").arg(filePath));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChangesManager::applyPendingEditsForRequest(const QString &requestId, QString *errorMsg)
|
bool ChangesManager::applyPendingEditsForRequest(const QString &requestId, QString *errorMsg)
|
||||||
@ -741,7 +947,7 @@ bool ChangesManager::undoAllEditsForRequest(const QString &requestId, QString *e
|
|||||||
QString errMsg;
|
QString errMsg;
|
||||||
bool isAppend = oldContentCopy.isEmpty();
|
bool isAppend = oldContentCopy.isEmpty();
|
||||||
bool success = performFragmentReplacement(
|
bool success = performFragmentReplacement(
|
||||||
filePathCopy, newContentCopy, oldContentCopy, isAppend, &errMsg);
|
filePathCopy, newContentCopy, oldContentCopy, isAppend, &errMsg, true);
|
||||||
|
|
||||||
locker.relock();
|
locker.relock();
|
||||||
|
|
||||||
@ -919,7 +1125,7 @@ QString ChangesManager::readFileContent(const QString &filePath) const
|
|||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
LOG_MESSAGE(QString(" Failed to read file: %1").arg(file.errorString()));
|
LOG_MESSAGE(QString(" Failed to read file: %1").arg(file.errorString()));
|
||||||
return QString(); // Return null QString on error
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString content = QString::fromUtf8(file.readAll());
|
QString content = QString::fromUtf8(file.readAll());
|
||||||
@ -985,14 +1191,10 @@ bool ChangesManager::performFileEditWithDiff(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool oldBlockState = doc->blockSignals(true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
QTextCursor cursor(doc);
|
QTextCursor cursor(doc);
|
||||||
|
|
||||||
if (cursor.isNull()) {
|
if (cursor.isNull()) {
|
||||||
doc->blockSignals(oldBlockState);
|
|
||||||
LOG_MESSAGE(" Cursor is invalid");
|
LOG_MESSAGE(" Cursor is invalid");
|
||||||
setError("Cannot create text cursor");
|
setError("Cannot create text cursor");
|
||||||
return false;
|
return false;
|
||||||
@ -1004,26 +1206,20 @@ bool ChangesManager::performFileEditWithDiff(
|
|||||||
cursor.insertText(modifiedContent);
|
cursor.insertText(modifiedContent);
|
||||||
cursor.endEditBlock();
|
cursor.endEditBlock();
|
||||||
|
|
||||||
doc->blockSignals(oldBlockState);
|
|
||||||
|
|
||||||
emit doc->contentsChange(0, doc->characterCount(), doc->characterCount());
|
|
||||||
|
|
||||||
LOG_MESSAGE(QString(" ✓ Successfully applied diff to open editor: %1").arg(filePath));
|
LOG_MESSAGE(QString(" ✓ Successfully applied diff to open editor: %1").arg(filePath));
|
||||||
setError(diffErrorMsg);
|
setError(diffErrorMsg);
|
||||||
return true;
|
return true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
doc->blockSignals(oldBlockState);
|
|
||||||
LOG_MESSAGE(" Exception during document modification");
|
LOG_MESSAGE(" Exception during document modification");
|
||||||
setError("Exception during document modification");
|
setError("Exception during document modification");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
doc->blockSignals(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_MESSAGE(" File not open in editor, modifying file directly...");
|
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);
|
QFile file(filePath);
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
@ -1382,10 +1578,13 @@ bool ChangesManager::applyDiffToContent(
|
|||||||
QString searchContent = reverse ? diffInfo.modifiedContent : diffInfo.originalContent;
|
QString searchContent = reverse ? diffInfo.modifiedContent : diffInfo.originalContent;
|
||||||
QString replaceContent = reverse ? diffInfo.originalContent : diffInfo.modifiedContent;
|
QString replaceContent = reverse ? diffInfo.originalContent : diffInfo.modifiedContent;
|
||||||
|
|
||||||
if (content.contains(searchContent)) {
|
int matchPos = content.indexOf(searchContent);
|
||||||
content.replace(searchContent, replaceContent);
|
if (matchPos != -1) {
|
||||||
|
content = content.left(matchPos)
|
||||||
|
+ replaceContent
|
||||||
|
+ content.mid(matchPos + searchContent.length());
|
||||||
setError("Applied using fallback mode (direct replacement)");
|
setError("Applied using fallback mode (direct replacement)");
|
||||||
LOG_MESSAGE(" ✓ Fallback: Direct replacement successful");
|
LOG_MESSAGE(QString(" ✓ Fallback: Direct replacement successful at position %1").arg(matchPos));
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
setError("Fallback failed: Original content not found in file");
|
setError("Fallback failed: Original content not found in file");
|
||||||
|
|||||||
@ -135,10 +135,12 @@ private:
|
|||||||
const QString &searchContent,
|
const QString &searchContent,
|
||||||
const QString &replaceContent,
|
const QString &replaceContent,
|
||||||
bool isAppendOperation,
|
bool isAppendOperation,
|
||||||
QString *errorMsg = nullptr);
|
QString *errorMsg = nullptr,
|
||||||
|
bool isUndo = false);
|
||||||
|
|
||||||
int levenshteinDistance(const QString &s1, const QString &s2) const;
|
int levenshteinDistance(const QString &s1, const QString &s2) const;
|
||||||
QString findBestMatch(const QString &fileContent, const QString &searchContent, double threshold = 0.8, double *outSimilarity = nullptr) const;
|
QString findBestMatch(const QString &fileContent, const QString &searchContent, double threshold = 0.82, double *outSimilarity = nullptr) const;
|
||||||
|
QString findBestMatchLineBased(const QString &fileContent, const QString &searchContent, double threshold = 0.82, double *outSimilarity = nullptr) const;
|
||||||
QString findBestMatchWithNormalization(const QString &fileContent, const QString &searchContent, double *outSimilarity = nullptr, QString *outMatchType = nullptr) const;
|
QString findBestMatchWithNormalization(const QString &fileContent, const QString &searchContent, double *outSimilarity = nullptr, QString *outMatchType = nullptr) const;
|
||||||
|
|
||||||
struct RequestEdits
|
struct RequestEdits
|
||||||
|
|||||||
@ -55,7 +55,15 @@ QString EditFileTool::description() const
|
|||||||
"Provide the filename (or absolute path), old_content to find and replace, "
|
"Provide the filename (or absolute path), old_content to find and replace, "
|
||||||
"and new_content to replace it with. Changes are applied immediately if auto-apply "
|
"and new_content to replace it with. Changes are applied immediately if auto-apply "
|
||||||
"is enabled in settings. The user can undo or reapply changes at any time. "
|
"is enabled in settings. The user can undo or reapply changes at any time. "
|
||||||
"If old_content is empty, new_content will be appended to the end of the file.";
|
"\n\nIMPORTANT:"
|
||||||
|
"\n- To insert at the BEGINNING of a file (e.g., copyright header), you MUST provide "
|
||||||
|
"the EXACT first few lines of the file as old_content (at least 3-5 lines), "
|
||||||
|
"then put those lines + new header in new_content."
|
||||||
|
"\n- To append at the END of file, use empty old_content."
|
||||||
|
"\n- For replacements in the middle, provide EXACT matching text with sufficient "
|
||||||
|
"context (at least 5-10 lines) to ensure correct placement."
|
||||||
|
"\n- The system requires 85% similarity for first-time edits. Provide accurate "
|
||||||
|
"old_content to avoid incorrect placement.";
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject EditFileTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
QJsonObject EditFileTool::getDefinition(LLMCore::ToolSchemaFormat format) const
|
||||||
@ -172,6 +180,22 @@ QFuture<QString> EditFileTool::executeAsync(const QJsonObject &input)
|
|||||||
QString editId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
QString editId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
bool autoApply = Settings::toolsSettings().autoApplyFileEdits();
|
bool autoApply = Settings::toolsSettings().autoApplyFileEdits();
|
||||||
|
|
||||||
|
LOG_MESSAGE(QString("EditFileTool: Edit details for %1:").arg(filePath));
|
||||||
|
LOG_MESSAGE(QString(" oldContent length: %1 chars").arg(oldContent.length()));
|
||||||
|
LOG_MESSAGE(QString(" newContent length: %1 chars").arg(newContent.length()));
|
||||||
|
if (oldContent.length() <= 200) {
|
||||||
|
LOG_MESSAGE(QString(" oldContent: '%1'").arg(oldContent));
|
||||||
|
} else {
|
||||||
|
LOG_MESSAGE(QString(" oldContent (first 200 chars): '%1...'")
|
||||||
|
.arg(oldContent.left(200)));
|
||||||
|
}
|
||||||
|
if (newContent.length() <= 200) {
|
||||||
|
LOG_MESSAGE(QString(" newContent: '%1'").arg(newContent));
|
||||||
|
} else {
|
||||||
|
LOG_MESSAGE(QString(" newContent (first 200 chars): '%1...'")
|
||||||
|
.arg(newContent.left(200)));
|
||||||
|
}
|
||||||
|
|
||||||
Context::ChangesManager::instance().addFileEdit(
|
Context::ChangesManager::instance().addFileEdit(
|
||||||
editId,
|
editId,
|
||||||
filePath,
|
filePath,
|
||||||
|
|||||||
Reference in New Issue
Block a user