mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-05-30 02:49:12 -04:00
refactor: Improvement code completion auto trigger
This commit is contained in:
@@ -54,6 +54,90 @@ using namespace Core;
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
namespace {
|
||||
Utils::Text::Position toTextPos(const Utils::Text::Position &pos)
|
||||
{
|
||||
return Utils::Text::Position{pos.line, pos.column};
|
||||
}
|
||||
|
||||
bool isIdentifierChar(QChar c)
|
||||
{
|
||||
return c.isLetterOrNumber() || c == QLatin1Char('_');
|
||||
}
|
||||
|
||||
bool isInsideIdentifier(const QTextCursor &cursor)
|
||||
{
|
||||
const QTextBlock block = cursor.block();
|
||||
const int col = cursor.positionInBlock();
|
||||
const QString text = block.text();
|
||||
if (col <= 0 || col > text.size())
|
||||
return false;
|
||||
if (!isIdentifierChar(text.at(col - 1)))
|
||||
return false;
|
||||
return col < text.size() && isIdentifierChar(text.at(col));
|
||||
}
|
||||
|
||||
bool isAfterMemberAccess(const QTextCursor &cursor)
|
||||
{
|
||||
const QTextBlock block = cursor.block();
|
||||
const int col = cursor.positionInBlock();
|
||||
const QString text = block.text();
|
||||
if (col <= 0)
|
||||
return false;
|
||||
|
||||
int i = col - 1;
|
||||
while (i >= 0 && isIdentifierChar(text.at(i)))
|
||||
--i;
|
||||
|
||||
if (i < 0)
|
||||
return false;
|
||||
|
||||
const QChar c = text.at(i);
|
||||
if (c == QLatin1Char('.'))
|
||||
return true;
|
||||
if (c == QLatin1Char('>') && i >= 1 && text.at(i - 1) == QLatin1Char('-'))
|
||||
return true;
|
||||
if (c == QLatin1Char(':') && i >= 1 && text.at(i - 1) == QLatin1Char(':'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isFreshIndentedLine(const QTextCursor &cursor)
|
||||
{
|
||||
const QTextBlock block = cursor.block();
|
||||
const int col = cursor.positionInBlock();
|
||||
if (col == 0)
|
||||
return false;
|
||||
const QString leftText = block.text().left(col);
|
||||
for (const QChar &ch : leftText) {
|
||||
if (!ch.isSpace())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isAfterEagerTrigger(const QTextCursor &cursor)
|
||||
{
|
||||
const QTextBlock block = cursor.block();
|
||||
const int col = cursor.positionInBlock();
|
||||
const QString text = block.text();
|
||||
int i = col - 1;
|
||||
while (i >= 0 && text.at(i).isSpace())
|
||||
--i;
|
||||
if (i < 0)
|
||||
return false;
|
||||
const QChar c = text.at(i);
|
||||
return c == QLatin1Char('{') || c == QLatin1Char('(') || c == QLatin1Char(',')
|
||||
|| c == QLatin1Char('=') || c == QLatin1Char('[') || c == QLatin1Char(';')
|
||||
|| c == QLatin1Char(':') || c == QLatin1Char('>');
|
||||
}
|
||||
|
||||
bool isManualMode()
|
||||
{
|
||||
return Settings::codeCompletionSettings().completionMode.stringValue() == "Manual";
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
||||
: LanguageClient::Client(clientInterface)
|
||||
, m_llmClient(clientInterface)
|
||||
@@ -69,10 +153,6 @@ QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
|
||||
|
||||
m_typingTimer.start();
|
||||
|
||||
m_hintHideTimer.setSingleShot(true);
|
||||
m_hintHideTimer.setInterval(Settings::codeCompletionSettings().hintHideTimeout());
|
||||
connect(&m_hintHideTimer, &QTimer::timeout, this, [this]() { m_hintHandler.hideHint(); });
|
||||
|
||||
m_refactorHoverHandler = new RefactorSuggestionHoverHandler();
|
||||
m_refactorWidgetHandler = new RefactorWidgetHandler(this);
|
||||
}
|
||||
@@ -108,6 +188,9 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
||||
if (!Settings::codeCompletionSettings().autoCompletion())
|
||||
return;
|
||||
|
||||
if (isManualMode())
|
||||
return;
|
||||
|
||||
auto project = ProjectManager::projectForFile(document->filePath());
|
||||
if (!isEnabled(project))
|
||||
return;
|
||||
@@ -131,38 +214,29 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
||||
if (charsRemoved > 0 || charsAdded <= 0) {
|
||||
m_recentCharCount = 0;
|
||||
m_typingTimer.restart();
|
||||
// 0 = Hint-based, 1 = Automatic
|
||||
const int triggerMode = Settings::codeCompletionSettings().completionTriggerMode();
|
||||
if (triggerMode != 1) {
|
||||
m_hintHideTimer.stop();
|
||||
m_hintHandler.hideHint();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QTextCursor cursor = widget->textCursor();
|
||||
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
|
||||
QString lastChar = cursor.selectedText();
|
||||
const QString lastChar = cursor.selectedText();
|
||||
if (lastChar.isEmpty())
|
||||
return;
|
||||
|
||||
if (lastChar.isEmpty() || lastChar[0].isPunct()) {
|
||||
const QChar lastCh = lastChar[0];
|
||||
if (lastCh == QLatin1Char('\n') || lastCh == QChar::ParagraphSeparator
|
||||
|| lastCh == QChar::LineSeparator) {
|
||||
m_recentCharCount = 0;
|
||||
m_typingTimer.restart();
|
||||
// 0 = Hint-based, 1 = Automatic
|
||||
const int triggerMode = Settings::codeCompletionSettings().completionTriggerMode();
|
||||
if (triggerMode != 1) {
|
||||
m_hintHideTimer.stop();
|
||||
m_hintHandler.hideHint();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool isSpaceOrTab = lastChar[0].isSpace();
|
||||
bool ignoreWhitespace
|
||||
const bool isSpaceOrTab = lastCh.isSpace();
|
||||
const bool ignoreWhitespace
|
||||
= Settings::codeCompletionSettings().ignoreWhitespaceInCharCount();
|
||||
|
||||
if (!ignoreWhitespace || !isSpaceOrTab) {
|
||||
if (!ignoreWhitespace || !isSpaceOrTab)
|
||||
m_recentCharCount += charsAdded;
|
||||
}
|
||||
|
||||
if (m_typingTimer.elapsed()
|
||||
> Settings::codeCompletionSettings().autoCompletionTypingInterval()) {
|
||||
@@ -170,13 +244,7 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
|
||||
m_typingTimer.restart();
|
||||
}
|
||||
|
||||
// 0 = Hint-based, 1 = Automatic
|
||||
const int triggerMode = Settings::codeCompletionSettings().completionTriggerMode();
|
||||
if (triggerMode == 1) {
|
||||
handleAutoRequestTrigger(widget, charsAdded, isSpaceOrTab);
|
||||
} else {
|
||||
handleHintBasedTrigger(widget, charsAdded, isSpaceOrTab, cursor);
|
||||
}
|
||||
handleAutoRequestTrigger(widget);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,11 +273,9 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor)
|
||||
if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
|
||||
return;
|
||||
|
||||
const int triggerMode = Settings::codeCompletionSettings().completionTriggerMode();
|
||||
|
||||
if (Settings::codeCompletionSettings().abortAssistOnRequest() && triggerMode == 0) {
|
||||
const auto &settings = Settings::codeCompletionSettings();
|
||||
if (settings.abortAssistOnRequest() && !settings.respectQtcPopup())
|
||||
editor->abortAssist();
|
||||
}
|
||||
|
||||
const FilePath filePath = editor->textDocument()->filePath();
|
||||
GetCompletionRequest request{
|
||||
@@ -270,33 +336,25 @@ void QodeAssistClient::requestQuickRefactor(
|
||||
|
||||
void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor)
|
||||
{
|
||||
cancelRunningRequest(editor);
|
||||
if (m_runningRequests.contains(editor))
|
||||
return;
|
||||
|
||||
auto it = m_scheduledRequests.find(editor);
|
||||
if (it == m_scheduledRequests.end()) {
|
||||
auto timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
connect(timer, &QTimer::timeout, this, [this, editor]() {
|
||||
if (editor
|
||||
&& editor->textCursor().position()
|
||||
== m_scheduledRequests[editor]->property("cursorPosition").toInt()
|
||||
&& m_recentCharCount
|
||||
> Settings::codeCompletionSettings().autoCompletionCharThreshold())
|
||||
requestCompletions(editor);
|
||||
if (!editor || m_runningRequests.contains(editor))
|
||||
return;
|
||||
if (editor->textCursor().position()
|
||||
!= m_scheduledRequests[editor]->property("cursorPosition").toInt())
|
||||
return;
|
||||
requestCompletions(editor);
|
||||
});
|
||||
connect(editor, &TextEditorWidget::destroyed, this, [this, editor]() {
|
||||
delete m_scheduledRequests.take(editor);
|
||||
cancelRunningRequest(editor);
|
||||
});
|
||||
connect(editor, &TextEditorWidget::cursorPositionChanged, this, [this, editor] {
|
||||
cancelRunningRequest(editor);
|
||||
// 0 = Hint-based, 1 = Automatic
|
||||
const int triggerMode = Settings::codeCompletionSettings().completionTriggerMode();
|
||||
if (triggerMode != 1) {
|
||||
m_hintHideTimer.stop();
|
||||
m_hintHandler.hideHint();
|
||||
}
|
||||
});
|
||||
it = m_scheduledRequests.insert(editor, timer);
|
||||
}
|
||||
|
||||
@@ -307,11 +365,9 @@ void QodeAssistClient::handleCompletions(
|
||||
const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor)
|
||||
{
|
||||
m_progressHandler.hideProgress();
|
||||
const int triggerMode = Settings::codeCompletionSettings().completionTriggerMode();
|
||||
|
||||
if (Settings::codeCompletionSettings().abortAssistOnRequest() && triggerMode == 1) {
|
||||
const auto &settings = Settings::codeCompletionSettings();
|
||||
if (settings.abortAssistOnRequest() && !settings.respectQtcPopup())
|
||||
editor->abortAssist();
|
||||
}
|
||||
|
||||
if (response.error()) {
|
||||
log(*response.error());
|
||||
@@ -325,12 +381,25 @@ void QodeAssistClient::handleCompletions(
|
||||
requestPosition = requestParams->position().toPositionInDocument(editor->document());
|
||||
|
||||
const MultiTextCursor cursors = editor->multiTextCursor();
|
||||
if (cursors.hasMultipleCursors())
|
||||
if (cursors.hasMultipleCursors() || cursors.hasSelection())
|
||||
return;
|
||||
|
||||
if (cursors.hasSelection() || cursors.mainCursor().position() != requestPosition)
|
||||
const int currentPosition = cursors.mainCursor().position();
|
||||
if (requestPosition < 0 || currentPosition < requestPosition)
|
||||
return;
|
||||
|
||||
QString typedSinceRequest;
|
||||
if (currentPosition > requestPosition) {
|
||||
QTextCursor diffCursor(editor->document());
|
||||
diffCursor.setPosition(requestPosition);
|
||||
diffCursor.setPosition(currentPosition, QTextCursor::KeepAnchor);
|
||||
typedSinceRequest = diffCursor.selectedText();
|
||||
if (typedSinceRequest.contains(QChar::ParagraphSeparator)
|
||||
|| typedSinceRequest.contains(QLatin1Char('\n'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (const std::optional<GetCompletionResponse> result = response.result()) {
|
||||
auto isValidCompletion = [](const Completion &completion) {
|
||||
return completion.isValid() && !completion.text().trimmed().isEmpty();
|
||||
@@ -338,34 +407,58 @@ void QodeAssistClient::handleCompletions(
|
||||
QList<Completion> completions
|
||||
= Utils::filtered(result->completions().toListOrEmpty(), isValidCompletion);
|
||||
|
||||
QList<Completion> matchedCompletions;
|
||||
matchedCompletions.reserve(completions.size());
|
||||
for (Completion &completion : completions) {
|
||||
const LanguageServerProtocol::Range range = completion.range();
|
||||
if (range.start().line() != range.end().line())
|
||||
continue;
|
||||
|
||||
const QString completionText = completion.text();
|
||||
QString completionText = completion.text();
|
||||
const int end = int(completionText.size()) - 1;
|
||||
int delta = 0;
|
||||
while (delta <= end && completionText[end - delta].isSpace())
|
||||
++delta;
|
||||
|
||||
if (delta > 0)
|
||||
completion.setText(completionText.chopped(delta));
|
||||
completionText.chop(delta);
|
||||
|
||||
if (!typedSinceRequest.isEmpty()) {
|
||||
if (!completionText.startsWith(typedSinceRequest))
|
||||
continue;
|
||||
completionText = completionText.mid(typedSinceRequest.size());
|
||||
if (completionText.isEmpty())
|
||||
continue;
|
||||
}
|
||||
|
||||
completion.setText(completionText);
|
||||
matchedCompletions.append(completion);
|
||||
}
|
||||
auto suggestions = Utils::transform(completions, [](const Completion &c) {
|
||||
|
||||
if (matchedCompletions.isEmpty()) {
|
||||
LOG_MESSAGE("No valid completions received");
|
||||
return;
|
||||
}
|
||||
|
||||
const Text::Position anchor = typedSinceRequest.isEmpty()
|
||||
? Text::Position{}
|
||||
: Text::Position::fromPositionInDocument(editor->document(), currentPosition);
|
||||
const bool useAnchor = !typedSinceRequest.isEmpty();
|
||||
|
||||
auto suggestions = Utils::transform(matchedCompletions,
|
||||
[useAnchor, &anchor](const Completion &c) {
|
||||
auto toTextPos = [](const LanguageServerProtocol::Position pos) {
|
||||
return Text::Position{pos.line() + 1, pos.character()};
|
||||
};
|
||||
|
||||
if (useAnchor) {
|
||||
return TextSuggestion::Data{Text::Range{anchor, anchor}, anchor, c.text()};
|
||||
}
|
||||
|
||||
Text::Range range{toTextPos(c.range().start()), toTextPos(c.range().end())};
|
||||
Text::Position pos{toTextPos(c.position())};
|
||||
return TextSuggestion::Data{range, pos, c.text()};
|
||||
});
|
||||
|
||||
if (completions.isEmpty()) {
|
||||
LOG_MESSAGE("No valid completions received");
|
||||
return;
|
||||
}
|
||||
editor->insertSuggestion(std::make_unique<LLMSuggestion>(suggestions, editor->document()));
|
||||
}
|
||||
}
|
||||
@@ -376,12 +469,6 @@ void QodeAssistClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor
|
||||
if (it == m_runningRequests.constEnd())
|
||||
return;
|
||||
m_progressHandler.hideProgress();
|
||||
// 0 = Hint-based, 1 = Automatic
|
||||
const int triggerMode = Settings::codeCompletionSettings().completionTriggerMode();
|
||||
if (triggerMode != 1) {
|
||||
m_hintHideTimer.stop();
|
||||
m_hintHandler.hideHint();
|
||||
}
|
||||
cancelRequest(it->id());
|
||||
m_runningRequests.erase(it);
|
||||
}
|
||||
@@ -423,17 +510,6 @@ void QodeAssistClient::cleanupConnections()
|
||||
m_scheduledRequests.clear();
|
||||
}
|
||||
|
||||
bool QodeAssistClient::isHintVisible() const
|
||||
{
|
||||
return m_hintHandler.isHintVisible();
|
||||
}
|
||||
|
||||
void QodeAssistClient::hideHintAndRequestCompletion(TextEditor::TextEditorWidget *editor)
|
||||
{
|
||||
m_hintHandler.hideHint();
|
||||
requestCompletions(editor);
|
||||
}
|
||||
|
||||
void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
|
||||
{
|
||||
m_progressHandler.hideProgress();
|
||||
@@ -465,13 +541,6 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
Utils::Text::Position toTextPos(const Utils::Text::Position &pos)
|
||||
{
|
||||
return Utils::Text::Position{pos.line, pos.column};
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
void QodeAssistClient::displayRefactoringSuggestion(const RefactorResult &result)
|
||||
{
|
||||
TextEditorWidget *editorWidget = result.editor;
|
||||
@@ -604,58 +673,20 @@ void QodeAssistClient::applyRefactoringEdit(TextEditor::TextEditorWidget *editor
|
||||
editCursor.endEditBlock();
|
||||
}
|
||||
|
||||
void QodeAssistClient::handleAutoRequestTrigger(TextEditor::TextEditorWidget *widget,
|
||||
int charsAdded,
|
||||
bool isSpaceOrTab)
|
||||
void QodeAssistClient::handleAutoRequestTrigger(TextEditor::TextEditorWidget *widget)
|
||||
{
|
||||
Q_UNUSED(isSpaceOrTab);
|
||||
const QTextCursor cursor = widget->textCursor();
|
||||
const auto &settings = Settings::codeCompletionSettings();
|
||||
const bool smart = settings.smartContextTrigger();
|
||||
|
||||
if (m_recentCharCount
|
||||
> Settings::codeCompletionSettings().autoCompletionCharThreshold()) {
|
||||
if (smart && (isInsideIdentifier(cursor) || isAfterMemberAccess(cursor)))
|
||||
return;
|
||||
|
||||
const bool eager = smart && (isFreshIndentedLine(cursor) || isAfterEagerTrigger(cursor));
|
||||
const int charThreshold = settings.autoCompletionCharThreshold();
|
||||
|
||||
if (eager || m_recentCharCount > charThreshold)
|
||||
scheduleRequest(widget);
|
||||
}
|
||||
}
|
||||
|
||||
void QodeAssistClient::handleHintBasedTrigger(TextEditor::TextEditorWidget *widget,
|
||||
int charsAdded,
|
||||
bool isSpaceOrTab,
|
||||
QTextCursor &cursor)
|
||||
{
|
||||
Q_UNUSED(charsAdded);
|
||||
|
||||
const int hintThreshold = Settings::codeCompletionSettings().hintCharThreshold();
|
||||
if (m_recentCharCount >= hintThreshold && !isSpaceOrTab) {
|
||||
const QRect cursorRect = widget->cursorRect(cursor);
|
||||
QPoint globalPos = widget->viewport()->mapToGlobal(cursorRect.topLeft());
|
||||
QPoint localPos = widget->mapFromGlobal(globalPos);
|
||||
|
||||
int fontSize = widget->font().pixelSize();
|
||||
if (fontSize <= 0) {
|
||||
fontSize = widget->fontMetrics().height();
|
||||
}
|
||||
|
||||
QTextCursor textCursor = widget->textCursor();
|
||||
|
||||
if (m_recentCharCount <= hintThreshold) {
|
||||
textCursor
|
||||
.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, m_recentCharCount);
|
||||
} else {
|
||||
textCursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, hintThreshold);
|
||||
}
|
||||
|
||||
int x = localPos.x() + cursorRect.height();
|
||||
int y = localPos.y() + cursorRect.height() / 4;
|
||||
|
||||
QPoint hintPos(x, y);
|
||||
|
||||
if (!m_hintHandler.isHintVisible()) {
|
||||
m_hintHandler.showHint(widget, hintPos, fontSize);
|
||||
} else {
|
||||
m_hintHandler.updateHintPosition(widget, hintPos);
|
||||
}
|
||||
|
||||
m_hintHideTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
bool QodeAssistClient::eventFilter(QObject *watched, QEvent *event)
|
||||
@@ -667,46 +698,6 @@ bool QodeAssistClient::eventFilter(QObject *watched, QEvent *event)
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
auto *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
|
||||
// Check hint trigger key (0=Space, 1=Ctrl+Space, 2=Alt+Space, 3=Ctrl+Enter, 4=Tab, 5=Enter)
|
||||
if (m_hintHandler.isHintVisible()) {
|
||||
const int triggerKeyIndex = Settings::codeCompletionSettings().hintTriggerKey();
|
||||
bool isMatchingKey = false;
|
||||
const Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
|
||||
|
||||
switch (triggerKeyIndex) {
|
||||
case 0: // Space
|
||||
isMatchingKey = (keyEvent->key() == Qt::Key_Space
|
||||
&& (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier));
|
||||
break;
|
||||
case 1: // Ctrl+Space
|
||||
isMatchingKey = (keyEvent->key() == Qt::Key_Space
|
||||
&& (modifiers & Qt::ControlModifier));
|
||||
break;
|
||||
case 2: // Alt+Space
|
||||
isMatchingKey = (keyEvent->key() == Qt::Key_Space
|
||||
&& (modifiers & Qt::AltModifier));
|
||||
break;
|
||||
case 3: // Ctrl+Enter
|
||||
isMatchingKey = ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)
|
||||
&& (modifiers & Qt::ControlModifier));
|
||||
break;
|
||||
case 4: // Tab
|
||||
isMatchingKey = (keyEvent->key() == Qt::Key_Tab);
|
||||
break;
|
||||
case 5: // Enter
|
||||
isMatchingKey = ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)
|
||||
&& (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier));
|
||||
break;
|
||||
}
|
||||
|
||||
if (isMatchingKey) {
|
||||
m_hintHideTimer.stop();
|
||||
m_hintHandler.hideHint();
|
||||
requestCompletions(editor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyEvent->key() == Qt::Key_Escape) {
|
||||
if (m_runningRequests.contains(editor)) {
|
||||
cancelRunningRequest(editor);
|
||||
@@ -724,8 +715,6 @@ bool QodeAssistClient::eventFilter(QObject *watched, QEvent *event)
|
||||
}
|
||||
|
||||
m_progressHandler.hideProgress();
|
||||
m_hintHideTimer.stop();
|
||||
m_hintHandler.hideHint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "RefactorSuggestionHoverHandler.hpp"
|
||||
#include "widgets/CompletionProgressHandler.hpp"
|
||||
#include "widgets/CompletionErrorHandler.hpp"
|
||||
#include "widgets/CompletionHintHandler.hpp"
|
||||
#include "widgets/EditorChatButtonHandler.hpp"
|
||||
#include "widgets/RefactorWidgetHandler.hpp"
|
||||
#include <languageclient/client.h>
|
||||
@@ -35,9 +34,6 @@ public:
|
||||
void requestQuickRefactor(
|
||||
TextEditor::TextEditorWidget *editor, const QString &instructions = QString());
|
||||
|
||||
bool isHintVisible() const;
|
||||
void hideHintAndRequestCompletion(TextEditor::TextEditorWidget *editor);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
@@ -55,8 +51,7 @@ private:
|
||||
void displayRefactoringWidget(const RefactorResult &result);
|
||||
void applyRefactoringEdit(TextEditor::TextEditorWidget *editor, const Utils::Text::Range &range, const QString &text);
|
||||
|
||||
void handleAutoRequestTrigger(TextEditor::TextEditorWidget *widget, int charsAdded, bool isSpaceOrTab);
|
||||
void handleHintBasedTrigger(TextEditor::TextEditorWidget *widget, int charsAdded, bool isSpaceOrTab, QTextCursor &cursor);
|
||||
void handleAutoRequestTrigger(TextEditor::TextEditorWidget *widget);
|
||||
|
||||
QHash<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
|
||||
QHash<TextEditor::TextEditorWidget *, QTimer *> m_scheduledRequests;
|
||||
@@ -65,10 +60,8 @@ private:
|
||||
|
||||
QElapsedTimer m_typingTimer;
|
||||
int m_recentCharCount;
|
||||
QTimer m_hintHideTimer;
|
||||
CompletionProgressHandler m_progressHandler;
|
||||
CompletionErrorHandler m_errorHandler;
|
||||
CompletionHintHandler m_hintHandler;
|
||||
EditorChatButtonHandler m_chatButtonHandler;
|
||||
QuickRefactorHandler *m_refactorHandler{nullptr};
|
||||
RefactorSuggestionHoverHandler *m_refactorHoverHandler{nullptr};
|
||||
|
||||
@@ -136,11 +136,7 @@ public:
|
||||
requestAction.addOnTriggered(this, [this] {
|
||||
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
|
||||
if (m_qodeAssistClient && m_qodeAssistClient->reachable()) {
|
||||
if (m_qodeAssistClient->isHintVisible()) {
|
||||
m_qodeAssistClient->hideHintAndRequestCompletion(editor);
|
||||
} else {
|
||||
m_qodeAssistClient->requestCompletions(editor);
|
||||
}
|
||||
m_qodeAssistClient->requestCompletions(editor);
|
||||
} else
|
||||
qWarning() << "The QodeAssist is not ready. Please check your connection and "
|
||||
"settings.";
|
||||
|
||||
@@ -59,6 +59,34 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
Tr::tr("Hint-based: Shows a hint when typing, press Tab to request completion\n"
|
||||
"Automatic: Automatically requests completion after typing threshold"));
|
||||
|
||||
completionMode.setLabelText(Tr::tr("Completion mode:"));
|
||||
completionMode.setSettingsKey(Constants::CC_COMPLETION_MODE);
|
||||
completionMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox);
|
||||
completionMode.addOption("Automatic");
|
||||
completionMode.addOption("Manual");
|
||||
completionMode.setDefaultValue("Automatic");
|
||||
completionMode.setToolTip(
|
||||
Tr::tr("Automatic: requests completion while typing (with smart context gates).\n"
|
||||
"Manual: no auto-triggering; invoke via the 'Request QodeAssist Suggestion' "
|
||||
"shortcut (default Ctrl+Alt+Q, reconfigurable in Preferences > Keyboard)."));
|
||||
|
||||
smartContextTrigger.setSettingsKey(Constants::CC_SMART_CONTEXT_TRIGGER);
|
||||
smartContextTrigger.setLabelText(Tr::tr("Smart context-aware triggering"));
|
||||
smartContextTrigger.setDefaultValue(true);
|
||||
smartContextTrigger.setToolTip(
|
||||
Tr::tr("When enabled, auto-completion is suppressed in places where Qt Creator's built-in "
|
||||
"completion is usually stronger (middle of an identifier, right after '.', '->', "
|
||||
"'::') and is triggered more eagerly after structural characters like '(', ',', "
|
||||
"'{', '=' and on fresh indented lines."));
|
||||
|
||||
respectQtcPopup.setSettingsKey(Constants::CC_RESPECT_QTC_POPUP);
|
||||
respectQtcPopup.setLabelText(Tr::tr("Don't dismiss Qt Creator's completion popup"));
|
||||
respectQtcPopup.setDefaultValue(true);
|
||||
respectQtcPopup.setToolTip(
|
||||
Tr::tr("When enabled, an AI completion arriving while Qt Creator's own completion popup "
|
||||
"is already visible will not force it closed. The LLM suggestion still appears "
|
||||
"inline."));
|
||||
|
||||
startSuggestionTimer.setSettingsKey(Constants::СС_START_SUGGESTION_TIMER);
|
||||
startSuggestionTimer.setLabelText(Tr::tr("with delay(ms)"));
|
||||
startSuggestionTimer.setToolTip(
|
||||
@@ -308,6 +336,8 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
|
||||
readSettings();
|
||||
|
||||
migrateCompletionMode();
|
||||
|
||||
readFileParts.setValue(!readFullFile.value());
|
||||
|
||||
setupConnections();
|
||||
@@ -354,30 +384,26 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
autoCompletion,
|
||||
multiLineCompletion,
|
||||
Row{modelOutputHandler, Stretch{1}},
|
||||
Row{completionTriggerMode, Stretch{1}},
|
||||
Row{completionMode, Stretch{1}},
|
||||
showProgressWidget,
|
||||
useOpenFilesContext,
|
||||
respectQtcPopup,
|
||||
abortAssistOnRequest,
|
||||
ignoreWhitespaceInCharCount};
|
||||
|
||||
auto autoTriggerSettings = Column{
|
||||
smartContextTrigger,
|
||||
Row{autoCompletionCharThreshold,
|
||||
autoCompletionTypingInterval,
|
||||
startSuggestionTimer,
|
||||
Stretch{1}}};
|
||||
|
||||
auto hintTriggerSettings = Column{
|
||||
Row{hintCharThreshold, hintHideTimeout, Stretch{1}},
|
||||
Row{hintTriggerKey, Stretch{1}}};
|
||||
|
||||
return Column{Row{Stretch{1}, resetToDefaults},
|
||||
Space{8},
|
||||
Group{title(TrConstants::AUTO_COMPLETION_SETTINGS),
|
||||
Column{Group{title(Tr::tr("General Settings")), generalSettings},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Automatic Trigger Mode")), autoTriggerSettings},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Hint-based Trigger Mode")), hintTriggerSettings}}},
|
||||
Group{title(Tr::tr("Automatic Trigger Mode")), autoTriggerSettings}}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("General Parameters")),
|
||||
Column{
|
||||
@@ -460,6 +486,9 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
||||
resetAspect(useOpenFilesContext);
|
||||
resetAspect(modelOutputHandler);
|
||||
resetAspect(completionTriggerMode);
|
||||
resetAspect(completionMode);
|
||||
resetAspect(smartContextTrigger);
|
||||
resetAspect(respectQtcPopup);
|
||||
resetAspect(hintCharThreshold);
|
||||
resetAspect(hintHideTimeout);
|
||||
resetAspect(hintTriggerKey);
|
||||
@@ -469,6 +498,21 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCompletionSettings::migrateCompletionMode()
|
||||
{
|
||||
auto *qtcSettings = Core::ICore::settings();
|
||||
if (!qtcSettings || qtcSettings->contains(Constants::CC_COMPLETION_MODE))
|
||||
return;
|
||||
|
||||
const QString oldMode = completionTriggerMode.stringValue();
|
||||
if (oldMode.startsWith("Hint"))
|
||||
completionMode.setStringValue("Manual");
|
||||
else
|
||||
completionMode.setStringValue("Automatic");
|
||||
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
QString CodeCompletionSettings::processMessageToFIM(const QString &prefix, const QString &suffix) const
|
||||
{
|
||||
QString result = userMessageTemplateForCC();
|
||||
|
||||
@@ -21,6 +21,9 @@ public:
|
||||
Utils::BoolAspect multiLineCompletion{this};
|
||||
Utils::SelectionAspect modelOutputHandler{this};
|
||||
Utils::SelectionAspect completionTriggerMode{this};
|
||||
Utils::SelectionAspect completionMode{this};
|
||||
Utils::BoolAspect smartContextTrigger{this};
|
||||
Utils::BoolAspect respectQtcPopup{this};
|
||||
|
||||
Utils::IntegerAspect startSuggestionTimer{this};
|
||||
Utils::IntegerAspect autoCompletionCharThreshold{this};
|
||||
@@ -78,6 +81,7 @@ public:
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetSettingsToDefaults();
|
||||
void migrateCompletionMode();
|
||||
};
|
||||
|
||||
CodeCompletionSettings &codeCompletionSettings();
|
||||
|
||||
@@ -65,6 +65,9 @@ const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
||||
const char СС_AUTO_COMPLETION_CHAR_THRESHOLD[] = "QodeAssist.autoCompletionCharThreshold";
|
||||
const char СС_AUTO_COMPLETION_TYPING_INTERVAL[] = "QodeAssist.autoCompletionTypingInterval";
|
||||
const char CC_COMPLETION_TRIGGER_MODE[] = "QodeAssist.ccCompletionTriggerMode";
|
||||
const char CC_COMPLETION_MODE[] = "QodeAssist.ccCompletionMode";
|
||||
const char CC_SMART_CONTEXT_TRIGGER[] = "QodeAssist.ccSmartContextTrigger";
|
||||
const char CC_RESPECT_QTC_POPUP[] = "QodeAssist.ccRespectQtcPopup";
|
||||
const char CC_HINT_CHAR_THRESHOLD[] = "QodeAssist.ccHintCharThreshold";
|
||||
const char CC_HINT_HIDE_TIMEOUT[] = "QodeAssist.ccHintHideTimeout";
|
||||
const char CC_HINT_TRIGGER_KEY[] = "QodeAssist.ccHintTriggerKey";
|
||||
|
||||
Reference in New Issue
Block a user