mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-05-30 19:09:14 -04:00
223 lines
7.6 KiB
C++
223 lines
7.6 KiB
C++
// Copyright (C) 2023 The Qt Company Ltd.
|
|
// Copyright (C) 2024-2026 Petr Mironychev
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "LLMSuggestion.hpp"
|
|
#include <texteditor/texteditor.h>
|
|
#include <utils/stringutils.h>
|
|
#include <utils/tooltip/tooltip.h>
|
|
|
|
namespace QodeAssist {
|
|
|
|
static bool isClosingTail(const QString &s, int from)
|
|
{
|
|
static const QString closeChars = QStringLiteral("(){}[];,");
|
|
for (int i = from; i < s.size(); ++i) {
|
|
const QChar c = s.at(i);
|
|
if (!c.isSpace() && !closeChars.contains(c))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int LLMSuggestion::calculateReplaceLength(const QString &suggestion, const QString &rightText)
|
|
{
|
|
if (rightText.isEmpty())
|
|
return 0;
|
|
|
|
const int maxN = qMin(suggestion.size(), rightText.size());
|
|
int lcp = 0;
|
|
while (lcp < maxN && suggestion.at(lcp) == rightText.at(lcp))
|
|
++lcp;
|
|
|
|
if (lcp > 0) {
|
|
if (isClosingTail(rightText, lcp))
|
|
return rightText.size();
|
|
return lcp;
|
|
}
|
|
|
|
if (!isClosingTail(rightText, 0))
|
|
return 0;
|
|
|
|
static const QString closeChars = QStringLiteral("(){}[];,");
|
|
int i = suggestion.size() - 1;
|
|
while (i >= 0 && suggestion.at(i).isSpace())
|
|
--i;
|
|
if (i >= 0 && closeChars.contains(suggestion.at(i)) && rightText.contains(suggestion.at(i)))
|
|
return rightText.size();
|
|
|
|
return 0;
|
|
}
|
|
|
|
LLMSuggestion::LLMSuggestion(
|
|
const QList<Data> &suggestions, QTextDocument *sourceDocument, int currentCompletion)
|
|
: TextEditor::CyclicSuggestion(suggestions, sourceDocument, currentCompletion)
|
|
{
|
|
const auto &data = suggestions[currentCompletion];
|
|
|
|
int startPos = data.range.begin.toPositionInDocument(sourceDocument);
|
|
|
|
startPos = qBound(0, startPos, sourceDocument->characterCount());
|
|
|
|
QTextCursor cursor(sourceDocument);
|
|
cursor.setPosition(startPos);
|
|
QTextBlock block = cursor.block();
|
|
QString blockText = block.text();
|
|
|
|
int cursorPositionInBlock = cursor.positionInBlock();
|
|
QString leftText = blockText.left(cursorPositionInBlock);
|
|
QString rightText = blockText.mid(cursorPositionInBlock);
|
|
|
|
QString suggestionText = data.text;
|
|
|
|
if (!suggestionText.contains('\n')) {
|
|
int replaceLength = calculateReplaceLength(suggestionText, rightText);
|
|
QString remainingRightText = (replaceLength > 0) ? rightText.mid(replaceLength) : rightText;
|
|
|
|
QString displayText = leftText + suggestionText + remainingRightText;
|
|
replacementDocument()->setPlainText(displayText);
|
|
} else {
|
|
int firstLineEnd = suggestionText.indexOf('\n');
|
|
QString firstLine = suggestionText.left(firstLineEnd);
|
|
QString restOfCompletion = suggestionText.mid(firstLineEnd);
|
|
|
|
int replaceLength = calculateReplaceLength(firstLine, rightText);
|
|
QString remainingRightText = (replaceLength > 0) ? rightText.mid(replaceLength) : rightText;
|
|
|
|
QString displayText = leftText + firstLine + remainingRightText + restOfCompletion;
|
|
replacementDocument()->setPlainText(displayText);
|
|
}
|
|
}
|
|
|
|
bool LLMSuggestion::applyWord(TextEditor::TextEditorWidget *widget)
|
|
{
|
|
return applyPart(Word, widget);
|
|
}
|
|
|
|
bool LLMSuggestion::applyLine(TextEditor::TextEditorWidget *widget)
|
|
{
|
|
return applyPart(Line, widget);
|
|
}
|
|
|
|
bool LLMSuggestion::applyPart(Part part, TextEditor::TextEditorWidget *widget)
|
|
{
|
|
const auto ¤tSuggestions = suggestions();
|
|
const auto ¤tData = currentSuggestions[currentSuggestion()];
|
|
const Utils::Text::Range range = currentData.range;
|
|
const QTextCursor cursor = range.begin.toTextCursor(sourceDocument());
|
|
QTextCursor currentCursor = widget->textCursor();
|
|
const QString text = currentData.text;
|
|
|
|
const int startPos = currentCursor.positionInBlock() - cursor.positionInBlock()
|
|
+ (cursor.selectionEnd() - cursor.selectionStart());
|
|
|
|
int next = part == Word ? Utils::endOfNextWord(text, startPos) : text.indexOf('\n', startPos);
|
|
|
|
if (next == -1) {
|
|
if (part == Line) {
|
|
next = text.length();
|
|
} else {
|
|
return apply();
|
|
}
|
|
}
|
|
|
|
if (part == Line)
|
|
++next;
|
|
|
|
QString subText = text.mid(startPos, next - startPos);
|
|
|
|
if (subText.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
if (startPos == 0) {
|
|
QTextBlock currentBlock = cursor.block();
|
|
QString textAfterCursor = currentBlock.text().mid(cursor.positionInBlock());
|
|
|
|
int replaceLength = calculateReplaceLength(text, textAfterCursor);
|
|
|
|
if (replaceLength > 0) {
|
|
currentCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, replaceLength);
|
|
currentCursor.removeSelectedText();
|
|
}
|
|
}
|
|
|
|
if (!subText.contains('\n')) {
|
|
currentCursor.insertText(subText);
|
|
|
|
const QString remainingText = text.mid(next);
|
|
if (!remainingText.isEmpty()) {
|
|
QTextCursor newCursor = widget->textCursor();
|
|
const Utils::Text::Position newStart = Utils::Text::Position::fromPositionInDocument(
|
|
newCursor.document(), newCursor.position());
|
|
const Utils::Text::Position
|
|
newEnd{newStart.line, newStart.column + int(remainingText.length())};
|
|
const Utils::Text::Range newRange{newStart, newEnd};
|
|
const QList<Data> newSuggestion{{newRange, newStart, remainingText}};
|
|
widget->insertSuggestion(
|
|
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
|
|
}
|
|
} else {
|
|
currentCursor.insertText(subText);
|
|
|
|
if (const int seperatorPos = subText.lastIndexOf('\n'); seperatorPos >= 0) {
|
|
const QString newCompletionText = text.mid(startPos + seperatorPos + 1);
|
|
if (!newCompletionText.isEmpty()) {
|
|
const Utils::Text::Position newStart{int(range.begin.line + subText.count('\n')), 0};
|
|
const Utils::Text::Position newEnd{newStart.line, int(newCompletionText.length())};
|
|
const Utils::Text::Range newRange{newStart, newEnd};
|
|
const QList<Data> newSuggestion{{newRange, newEnd, newCompletionText}};
|
|
widget->insertSuggestion(
|
|
std::make_unique<LLMSuggestion>(newSuggestion, widget->document(), 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLMSuggestion::apply()
|
|
{
|
|
const auto ¤tSuggestions = suggestions();
|
|
const auto ¤tData = currentSuggestions[currentSuggestion()];
|
|
const Utils::Text::Range range = currentData.range;
|
|
const QTextCursor cursor = range.begin.toTextCursor(sourceDocument());
|
|
QString text = currentData.text;
|
|
|
|
QTextBlock currentBlock = cursor.block();
|
|
QString textAfterCursor = currentBlock.text().mid(cursor.positionInBlock());
|
|
|
|
QTextCursor editCursor = cursor;
|
|
editCursor.beginEditBlock();
|
|
|
|
int firstLineEnd = text.indexOf('\n');
|
|
if (firstLineEnd != -1) {
|
|
QString firstLine = text.left(firstLineEnd);
|
|
QString restOfText = text.mid(firstLineEnd);
|
|
|
|
int replaceLength = calculateReplaceLength(firstLine, textAfterCursor);
|
|
|
|
if (replaceLength > 0) {
|
|
editCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, replaceLength);
|
|
editCursor.removeSelectedText();
|
|
}
|
|
|
|
editCursor.insertText(firstLine + restOfText);
|
|
} else {
|
|
int replaceLength = calculateReplaceLength(text, textAfterCursor);
|
|
|
|
if (replaceLength > 0) {
|
|
editCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, replaceLength);
|
|
editCursor.removeSelectedText();
|
|
}
|
|
|
|
editCursor.insertText(text);
|
|
}
|
|
|
|
editCursor.endEditBlock();
|
|
return true;
|
|
}
|
|
|
|
} // namespace QodeAssist
|
|
|