From 63c6a3838485829d74ac6c122001441f161a0a88 Mon Sep 17 00:00:00 2001 From: Gres Date: Wed, 30 Apr 2025 10:09:22 +0300 Subject: [PATCH] feat: Merge suggestion with existing code if looks similar --- LLMSuggestion.cpp | 126 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 6 deletions(-) diff --git a/LLMSuggestion.cpp b/LLMSuggestion.cpp index 20e16f9..a52bb99 100644 --- a/LLMSuggestion.cpp +++ b/LLMSuggestion.cpp @@ -59,6 +59,100 @@ QString mergeWithRightText(const QString &suggestion, const QString &rightText) return suggestion; } +static int levenshteinDistance(const QString &source, const QString &target) +{ + if (source == target) { + return 0; + } + + int sourceCount = source.size(); + int targetCount = target.size(); + + if (sourceCount == 0) { + return targetCount; + } + + if (targetCount == 0) { + return sourceCount; + } + + if (sourceCount > targetCount) { + return levenshteinDistance(target, source); + } + + QVector previousColumn; + previousColumn.reserve(targetCount + 1); + for (int i = 0; i < targetCount + 1; ++i) { + previousColumn.append(i); + } + + QVector column(targetCount + 1, 0); + for (int i = 0; i < sourceCount; ++i) { + column[0] = i + 1; + for (int j = 0; j < targetCount; ++j) { + column[j + 1] = std::min( + {1 + column.at(j), + 1 + previousColumn.at(1 + j), + previousColumn.at(j) + ((source.at(i) == target.at(j)) ? 0 : 1)}); + } + column.swap(previousColumn); + } + + return previousColumn.at(targetCount); +} + +static int linesToReplace(const QString &suggestion, const QTextCursor &cursor) +{ + int suggestedLineCount = 0; + for (int idx = suggestion.indexOf('\n'); idx != -1; idx = suggestion.indexOf('\n', idx + 1)) { + ++suggestedLineCount; + } + if (suggestedLineCount == 0) { + return 0; + } + + QTextCursor lineReadCursor = cursor; + lineReadCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + int minDifference = 1e6; + int bestLineCount = 0; + + for (int i = 0; i < suggestedLineCount; ++i) { + lineReadCursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); + lineReadCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + QString curText = lineReadCursor.selectedText(); + + int difference = levenshteinDistance(curText, suggestion); + if (difference > minDifference) { + continue; + } + + bestLineCount = i + 1; + minDifference = difference; + } + + double minSimilarity = 0.4; // configurable? + int threshold = suggestion.length() * minSimilarity; + + return minDifference < threshold ? bestLineCount : 0; +} + +static QString existingTailToKeep(const QString &suggestedLine, const QString &existingLine) +{ + int minDifference = 1e6; + int bestLength = 0; + + for (int i = 0; i <= existingLine.length(); ++i) { + int difference = levenshteinDistance(suggestedLine, existingLine.left(i)); + if (difference > minDifference) { + continue; + } + + minDifference = difference; + bestLength = i; + } + return existingLine.mid(bestLength); +} + LLMSuggestion::LLMSuggestion( const QList &suggestions, QTextDocument *sourceDocument, int currentCompletion) : TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion) @@ -173,14 +267,34 @@ bool LLMSuggestion::apply() int firstLineEnd = text.indexOf('\n'); if (firstLineEnd != -1) { - QString firstLine = text.left(firstLineEnd); - QString restOfText = text.mid(firstLineEnd); + int toReplace = linesToReplace(text, cursor); - editCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); - editCursor.removeSelectedText(); + if (toReplace == 0) { + editCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + editCursor.removeSelectedText(); - QString mergedFirstLine = mergeWithRightText(firstLine, textAfterCursor); - editCursor.insertText(mergedFirstLine + restOfText); + QString firstLine = text.left(firstLineEnd); + QString mergedFirstLine = mergeWithRightText(firstLine, textAfterCursor); + QString restOfText = text.mid(firstLineEnd); + editCursor.insertText(mergedFirstLine + restOfText); + } else { + for (int i = 0; i < toReplace; ++i) + editCursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); + editCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + + QTextCursor lastLineCursor = editCursor; + lastLineCursor.setPosition(lastLineCursor.position(), QTextCursor::MoveAnchor); + lastLineCursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + QString lastExistingLine = lastLineCursor.selectedText(); + + QString lastSuggestedLine = text.mid(text.lastIndexOf('\n') + 1); + qDebug() << lastExistingLine; + qDebug() << lastSuggestedLine; + + QString tail = existingTailToKeep(lastSuggestedLine, lastExistingLine); + qDebug() << tail; + editCursor.insertText(text + tail); + } } else { editCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); editCursor.removeSelectedText();