-
-
Notifications
You must be signed in to change notification settings - Fork 58
Expand file tree
/
Copy pathLLMSuggestion.cpp
More file actions
223 lines (176 loc) · 7.65 KB
/
Copy pathLLMSuggestion.cpp
File metadata and controls
223 lines (176 loc) · 7.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Copyright (C) 2023 The Qt Company Ltd.
// 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 "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