2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
#include "codeassistant.h"
|
|
|
|
|
#include "completionassistprovider.h"
|
|
|
|
|
#include "iassistprocessor.h"
|
|
|
|
|
#include "iassistproposal.h"
|
2015-07-29 10:58:34 +02:00
|
|
|
#include "iassistproposalmodel.h"
|
2011-04-15 16:19:23 +02:00
|
|
|
#include "iassistproposalwidget.h"
|
2014-09-04 00:04:18 +02:00
|
|
|
#include "assistinterface.h"
|
|
|
|
|
#include "assistproposalitem.h"
|
2016-01-19 14:54:59 +01:00
|
|
|
#include "textdocumentmanipulator.h"
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2018-11-08 11:39:48 +01:00
|
|
|
#include <texteditor/textdocument.h>
|
2014-09-26 09:14:03 +02:00
|
|
|
#include <texteditor/texteditor.h>
|
2011-04-15 16:19:23 +02:00
|
|
|
#include <texteditor/texteditorsettings.h>
|
|
|
|
|
#include <texteditor/completionsettings.h>
|
2013-09-12 00:21:12 +03:00
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
2011-04-15 16:19:23 +02:00
|
|
|
#include <extensionsystem/pluginmanager.h>
|
2019-05-17 14:37:06 +02:00
|
|
|
#include <utils/algorithm.h>
|
2021-08-13 10:49:30 +02:00
|
|
|
#include <utils/executeondestruction.h>
|
2013-03-05 13:47:29 +01:00
|
|
|
#include <utils/qtcassert.h>
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2013-12-22 08:08:59 +02:00
|
|
|
#include <QKeyEvent>
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QList>
|
2013-12-22 08:08:59 +02:00
|
|
|
#include <QObject>
|
|
|
|
|
#include <QScopedPointer>
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QTimer>
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2014-09-19 14:08:11 +02:00
|
|
|
using namespace TextEditor::Internal;
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
namespace TextEditor {
|
|
|
|
|
|
|
|
|
|
class CodeAssistantPrivate : public QObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-11-10 14:38:34 +01:00
|
|
|
CodeAssistantPrivate(CodeAssistant *assistant, TextEditorWidget *editorWidget);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2018-02-14 12:25:15 +01:00
|
|
|
void invoke(AssistKind kind, IAssistProvider *provider = nullptr);
|
2011-04-15 16:19:23 +02:00
|
|
|
void process();
|
2022-06-24 13:06:05 +02:00
|
|
|
void requestProposal(AssistReason reason,
|
|
|
|
|
AssistKind kind,
|
|
|
|
|
IAssistProvider *provider = nullptr,
|
|
|
|
|
bool isUpdate = false);
|
2011-04-15 16:19:23 +02:00
|
|
|
void cancelCurrentRequest();
|
|
|
|
|
void invalidateCurrentRequestData();
|
|
|
|
|
void displayProposal(IAssistProposal *newProposal, AssistReason reason);
|
|
|
|
|
bool isDisplayingProposal() const;
|
|
|
|
|
bool isWaitingForProposal() const;
|
2022-11-10 13:33:25 +01:00
|
|
|
QString proposalPrefix() const;
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
void notifyChange();
|
|
|
|
|
bool hasContext() const;
|
|
|
|
|
void destroyContext();
|
|
|
|
|
|
2017-08-18 12:21:45 +02:00
|
|
|
QVariant userData() const;
|
|
|
|
|
void setUserData(const QVariant &data);
|
|
|
|
|
|
2022-11-10 15:10:12 +01:00
|
|
|
QList<CompletionAssistProvider *> identifyActivationSequence();
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
void stopAutomaticProposalTimer();
|
|
|
|
|
void startAutomaticProposalTimer();
|
2014-09-19 14:08:11 +02:00
|
|
|
void automaticProposalTimeout();
|
|
|
|
|
void clearAbortedPosition();
|
2014-12-05 09:59:15 +01:00
|
|
|
void updateFromCompletionSettings(const TextEditor::CompletionSettings &settings);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2018-07-11 07:31:38 +02:00
|
|
|
bool eventFilter(QObject *o, QEvent *e) override;
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2014-09-12 01:25:42 +02:00
|
|
|
private:
|
2020-09-23 12:48:17 +02:00
|
|
|
bool requestActivationCharProposal();
|
2016-02-01 14:51:01 +01:00
|
|
|
void processProposalItem(AssistProposalItemInterface *proposalItem);
|
2011-04-15 16:19:23 +02:00
|
|
|
void handlePrefixExpansion(const QString &newPrefix);
|
|
|
|
|
void finalizeProposal();
|
2013-09-12 00:21:12 +03:00
|
|
|
void explicitlyAborted();
|
2018-03-02 13:32:34 +01:00
|
|
|
bool isDestroyEvent(int key, const QString &keyText);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
private:
|
2018-02-14 12:25:15 +01:00
|
|
|
CodeAssistant *q = nullptr;
|
|
|
|
|
TextEditorWidget *m_editorWidget = nullptr;
|
|
|
|
|
IAssistProvider *m_requestProvider = nullptr;
|
2022-11-09 15:38:22 +01:00
|
|
|
IAssistProcessor *m_processor = nullptr;
|
2018-02-14 12:25:15 +01:00
|
|
|
AssistKind m_assistKind = TextEditor::Completion;
|
|
|
|
|
IAssistProposalWidget *m_proposalWidget = nullptr;
|
2023-05-10 15:23:24 +02:00
|
|
|
TextEditorWidget::SuggestionBlocker m_suggestionBlocker;
|
2018-02-14 12:25:15 +01:00
|
|
|
bool m_receivedContentWhileWaiting = false;
|
2011-04-15 16:19:23 +02:00
|
|
|
QTimer m_automaticProposalTimer;
|
|
|
|
|
CompletionSettings m_settings;
|
2018-02-14 12:25:15 +01:00
|
|
|
int m_abortedBasePosition = -1;
|
2011-05-20 14:08:34 +02:00
|
|
|
static const QChar m_null;
|
2017-08-18 12:21:45 +02:00
|
|
|
QVariant m_userData;
|
2011-04-15 16:19:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --------------------
|
|
|
|
|
// CodeAssistantPrivate
|
|
|
|
|
// --------------------
|
2011-05-20 14:08:34 +02:00
|
|
|
const QChar CodeAssistantPrivate::m_null;
|
|
|
|
|
|
2022-11-10 14:38:34 +01:00
|
|
|
CodeAssistantPrivate::CodeAssistantPrivate(CodeAssistant *assistant, TextEditorWidget *editorWidget)
|
2014-09-19 14:08:11 +02:00
|
|
|
: q(assistant)
|
2022-11-10 14:38:34 +01:00
|
|
|
, m_editorWidget(editorWidget)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
|
|
|
|
m_automaticProposalTimer.setSingleShot(true);
|
2014-09-19 14:08:11 +02:00
|
|
|
connect(&m_automaticProposalTimer, &QTimer::timeout,
|
|
|
|
|
this, &CodeAssistantPrivate::automaticProposalTimeout);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2021-12-09 11:44:09 +01:00
|
|
|
updateFromCompletionSettings(TextEditorSettings::completionSettings());
|
2014-09-19 14:08:11 +02:00
|
|
|
connect(TextEditorSettings::instance(), &TextEditorSettings::completionSettingsChanged,
|
2014-12-05 09:59:15 +01:00
|
|
|
this, &CodeAssistantPrivate::updateFromCompletionSettings);
|
2014-09-19 14:08:11 +02:00
|
|
|
|
|
|
|
|
connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
|
|
|
|
|
this, &CodeAssistantPrivate::clearAbortedPosition);
|
2014-09-03 22:45:33 +02:00
|
|
|
m_editorWidget->installEventFilter(this);
|
2013-08-30 12:55:06 +02:00
|
|
|
}
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
void CodeAssistantPrivate::invoke(AssistKind kind, IAssistProvider *provider)
|
|
|
|
|
{
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
2022-11-17 14:53:47 +01:00
|
|
|
if (isDisplayingProposal() && m_assistKind == kind && !m_proposalWidget->isFragile()) {
|
2011-04-15 16:19:23 +02:00
|
|
|
m_proposalWidget->setReason(ExplicitlyInvoked);
|
2022-11-17 14:53:47 +01:00
|
|
|
m_proposalWidget->filterProposal(m_editorWidget->textAt(
|
|
|
|
|
m_proposalWidget->basePosition(),
|
|
|
|
|
m_editorWidget->position() - m_proposalWidget->basePosition()));
|
2011-04-15 16:19:23 +02:00
|
|
|
} else {
|
|
|
|
|
requestProposal(ExplicitlyInvoked, kind, provider);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-23 12:48:17 +02:00
|
|
|
bool CodeAssistantPrivate::requestActivationCharProposal()
|
|
|
|
|
{
|
2021-06-28 09:13:57 +02:00
|
|
|
if (m_editorWidget->multiTextCursor().hasMultipleCursors())
|
|
|
|
|
return false;
|
2020-09-23 12:48:17 +02:00
|
|
|
if (m_assistKind == Completion && m_settings.m_completionTrigger != ManualCompletion) {
|
2022-11-10 15:10:12 +01:00
|
|
|
for (CompletionAssistProvider *provider : identifyActivationSequence()) {
|
2020-09-23 12:48:17 +02:00
|
|
|
requestProposal(ActivationCharacter, Completion, provider);
|
2022-11-10 15:10:12 +01:00
|
|
|
if (isDisplayingProposal() || isWaitingForProposal())
|
|
|
|
|
return true;
|
2020-09-23 12:48:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
void CodeAssistantPrivate::process()
|
|
|
|
|
{
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
2013-10-24 14:32:14 +02:00
|
|
|
if (m_assistKind == TextEditor::Completion) {
|
2020-09-23 12:48:17 +02:00
|
|
|
if (!requestActivationCharProposal())
|
|
|
|
|
startAutomaticProposalTimer();
|
2020-07-31 16:50:03 +02:00
|
|
|
} else if (m_assistKind != FunctionHint){
|
2013-10-24 14:32:14 +02:00
|
|
|
m_assistKind = TextEditor::Completion;
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::requestProposal(AssistReason reason,
|
|
|
|
|
AssistKind kind,
|
2022-06-24 13:06:05 +02:00
|
|
|
IAssistProvider *provider,
|
|
|
|
|
bool isUpdate)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
2021-08-13 10:49:30 +02:00
|
|
|
// make sure to cleanup old proposals if we cannot find a new assistant
|
2022-07-19 23:36:11 +02:00
|
|
|
Utils::ExecuteOnDestruction earlyReturnContextClear([this] { destroyContext(); });
|
2021-08-13 10:49:30 +02:00
|
|
|
if (isWaitingForProposal())
|
|
|
|
|
cancelCurrentRequest();
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
if (!provider) {
|
2013-08-30 12:55:06 +02:00
|
|
|
if (kind == Completion)
|
2014-10-01 22:39:47 +02:00
|
|
|
provider = m_editorWidget->textDocument()->completionAssistProvider();
|
2020-07-31 16:50:03 +02:00
|
|
|
else if (kind == FunctionHint)
|
|
|
|
|
provider = m_editorWidget->textDocument()->functionHintAssistProvider();
|
2016-03-16 13:04:05 +01:00
|
|
|
else
|
|
|
|
|
provider = m_editorWidget->textDocument()->quickFixAssistProvider();
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
if (!provider)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-15 14:19:06 +01:00
|
|
|
std::unique_ptr<AssistInterface> assistInterface =
|
|
|
|
|
m_editorWidget->createAssistInterface(kind, reason);
|
2023-04-28 14:04:50 +02:00
|
|
|
QTC_ASSERT(assistInterface, return);
|
2016-03-14 13:10:19 +01:00
|
|
|
|
2021-08-13 10:49:30 +02:00
|
|
|
// We got an assist provider and interface so no need to reset the current context anymore
|
|
|
|
|
earlyReturnContextClear.reset({});
|
|
|
|
|
|
2016-03-14 13:10:19 +01:00
|
|
|
m_assistKind = kind;
|
2018-07-13 12:33:46 +02:00
|
|
|
m_requestProvider = provider;
|
2022-11-15 14:19:06 +01:00
|
|
|
IAssistProcessor *processor = provider->createProcessor(assistInterface.get());
|
2022-11-09 15:38:22 +01:00
|
|
|
processor->setAsyncCompletionAvailableHandler([this, reason, processor](
|
|
|
|
|
IAssistProposal *newProposal) {
|
2023-05-22 10:12:23 +02:00
|
|
|
if (processor == m_processor) {
|
|
|
|
|
invalidateCurrentRequestData();
|
|
|
|
|
if (processor->needsRestart() && m_receivedContentWhileWaiting) {
|
|
|
|
|
delete newProposal;
|
|
|
|
|
m_receivedContentWhileWaiting = false;
|
|
|
|
|
requestProposal(reason, m_assistKind, m_requestProvider);
|
|
|
|
|
} else {
|
|
|
|
|
displayProposal(newProposal, reason);
|
|
|
|
|
if (processor->running())
|
|
|
|
|
m_processor = processor;
|
|
|
|
|
else
|
|
|
|
|
emit q->finished();
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-11-09 15:38:22 +01:00
|
|
|
if (!processor->running()) {
|
|
|
|
|
// do not delete this processor directly since this function is called from within the processor
|
|
|
|
|
QMetaObject::invokeMethod(QCoreApplication::instance(), [processor] {
|
|
|
|
|
delete processor;
|
|
|
|
|
}, Qt::QueuedConnection);
|
|
|
|
|
}
|
|
|
|
|
});
|
2015-04-30 11:58:20 +02:00
|
|
|
|
2022-11-15 14:19:06 +01:00
|
|
|
if (IAssistProposal *newProposal = processor->start(std::move(assistInterface)))
|
2022-11-09 15:38:22 +01:00
|
|
|
displayProposal(newProposal, reason);
|
|
|
|
|
if (!processor->running()) {
|
|
|
|
|
if (isUpdate)
|
|
|
|
|
destroyContext();
|
|
|
|
|
delete processor;
|
|
|
|
|
} else {
|
|
|
|
|
QTC_CHECK(!m_processor);
|
|
|
|
|
m_processor = processor;
|
2015-04-30 11:58:20 +02:00
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::cancelCurrentRequest()
|
|
|
|
|
{
|
2022-11-09 15:38:22 +01:00
|
|
|
if (m_processor) {
|
|
|
|
|
m_processor->cancel();
|
|
|
|
|
if (!m_processor->running())
|
|
|
|
|
delete m_processor;
|
2020-03-26 09:21:57 +01:00
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
invalidateCurrentRequestData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::displayProposal(IAssistProposal *newProposal, AssistReason reason)
|
|
|
|
|
{
|
|
|
|
|
if (!newProposal)
|
|
|
|
|
return;
|
|
|
|
|
|
2017-06-06 13:29:51 +02:00
|
|
|
// TODO: The proposal should own the model until someone takes it explicitly away.
|
2022-11-17 14:53:47 +01:00
|
|
|
QScopedPointer<IAssistProposal> deleter(newProposal);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2022-11-17 14:53:47 +01:00
|
|
|
if (isDisplayingProposal() && !m_proposalWidget->isFragile())
|
2020-10-23 10:49:37 +02:00
|
|
|
return;
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2022-11-17 14:53:47 +01:00
|
|
|
int basePosition = newProposal->basePosition();
|
2017-04-27 15:44:37 +02:00
|
|
|
if (m_editorWidget->position() < basePosition) {
|
2020-10-23 10:49:37 +02:00
|
|
|
destroyContext();
|
2011-04-15 16:19:23 +02:00
|
|
|
return;
|
2017-04-27 15:44:37 +02:00
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2017-04-27 15:44:37 +02:00
|
|
|
if (m_abortedBasePosition == basePosition && reason != ExplicitlyInvoked) {
|
2020-10-23 10:49:37 +02:00
|
|
|
destroyContext();
|
2013-09-12 00:21:12 +03:00
|
|
|
return;
|
2017-04-27 15:44:37 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-16 10:39:37 +01:00
|
|
|
if (m_editorWidget->suggestionVisible()) {
|
|
|
|
|
if (reason != ExplicitlyInvoked) {
|
|
|
|
|
destroyContext();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_editorWidget->clearSuggestion();
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-27 15:44:37 +02:00
|
|
|
const QString prefix = m_editorWidget->textAt(basePosition,
|
|
|
|
|
m_editorWidget->position() - basePosition);
|
2017-06-08 16:14:41 +02:00
|
|
|
if (!newProposal->hasItemsToPropose(prefix, reason)) {
|
|
|
|
|
if (newProposal->isCorrective(m_editorWidget))
|
|
|
|
|
newProposal->makeCorrection(m_editorWidget);
|
2021-10-28 13:29:03 +02:00
|
|
|
destroyContext();
|
2017-04-27 15:44:37 +02:00
|
|
|
return;
|
2017-06-08 16:14:41 +02:00
|
|
|
}
|
2017-04-27 15:44:37 +02:00
|
|
|
|
2020-10-23 10:49:37 +02:00
|
|
|
destroyContext();
|
2013-09-12 00:21:12 +03:00
|
|
|
|
|
|
|
|
clearAbortedPosition();
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2022-11-17 14:53:47 +01:00
|
|
|
if (newProposal->isCorrective(m_editorWidget))
|
|
|
|
|
newProposal->makeCorrection(m_editorWidget);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2016-06-01 10:19:59 +02:00
|
|
|
m_editorWidget->keepAutoCompletionHighlight(true);
|
2022-11-17 14:53:47 +01:00
|
|
|
basePosition = newProposal->basePosition();
|
|
|
|
|
m_proposalWidget = newProposal->createWidget();
|
2014-09-12 01:25:42 +02:00
|
|
|
connect(m_proposalWidget, &QObject::destroyed,
|
|
|
|
|
this, &CodeAssistantPrivate::finalizeProposal);
|
|
|
|
|
connect(m_proposalWidget, &IAssistProposalWidget::prefixExpanded,
|
|
|
|
|
this, &CodeAssistantPrivate::handlePrefixExpansion);
|
|
|
|
|
connect(m_proposalWidget, &IAssistProposalWidget::proposalItemActivated,
|
|
|
|
|
this, &CodeAssistantPrivate::processProposalItem);
|
|
|
|
|
connect(m_proposalWidget, &IAssistProposalWidget::explicitlyAborted,
|
|
|
|
|
this, &CodeAssistantPrivate::explicitlyAborted);
|
2014-09-19 14:08:11 +02:00
|
|
|
m_proposalWidget->setAssistant(q);
|
2011-04-15 16:19:23 +02:00
|
|
|
m_proposalWidget->setReason(reason);
|
2011-06-23 15:04:01 +02:00
|
|
|
m_proposalWidget->setKind(m_assistKind);
|
2017-08-18 12:21:45 +02:00
|
|
|
m_proposalWidget->setBasePosition(basePosition);
|
2014-09-03 22:45:33 +02:00
|
|
|
m_proposalWidget->setUnderlyingWidget(m_editorWidget);
|
2022-11-17 14:53:47 +01:00
|
|
|
m_proposalWidget->setModel(newProposal->model());
|
2014-09-03 22:45:33 +02:00
|
|
|
m_proposalWidget->setDisplayRect(m_editorWidget->cursorRect(basePosition));
|
2016-03-16 13:45:36 +01:00
|
|
|
m_proposalWidget->setIsSynchronized(!m_receivedContentWhileWaiting);
|
2017-04-27 15:44:37 +02:00
|
|
|
m_proposalWidget->showProposal(prefix);
|
2023-05-10 15:23:24 +02:00
|
|
|
m_suggestionBlocker = m_editorWidget->blockSuggestions();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2016-02-01 14:51:01 +01:00
|
|
|
void CodeAssistantPrivate::processProposalItem(AssistProposalItemInterface *proposalItem)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
2022-11-17 14:53:47 +01:00
|
|
|
QTC_ASSERT(m_proposalWidget, return);
|
2016-01-19 14:54:59 +01:00
|
|
|
TextDocumentManipulator manipulator(m_editorWidget);
|
2022-11-17 14:53:47 +01:00
|
|
|
proposalItem->apply(manipulator, m_proposalWidget->basePosition());
|
2011-04-15 16:19:23 +02:00
|
|
|
destroyContext();
|
2018-08-15 09:39:22 +02:00
|
|
|
m_editorWidget->encourageApply();
|
2020-09-23 12:48:17 +02:00
|
|
|
if (!proposalItem->isSnippet())
|
|
|
|
|
requestActivationCharProposal();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::handlePrefixExpansion(const QString &newPrefix)
|
|
|
|
|
{
|
2022-11-17 14:53:47 +01:00
|
|
|
QTC_ASSERT(m_proposalWidget, return);
|
2017-05-17 15:53:47 +02:00
|
|
|
|
|
|
|
|
QTextCursor cursor(m_editorWidget->document());
|
2022-11-17 14:53:47 +01:00
|
|
|
cursor.setPosition(m_proposalWidget->basePosition());
|
2017-05-17 15:53:47 +02:00
|
|
|
cursor.movePosition(QTextCursor::EndOfWord);
|
|
|
|
|
|
2017-06-28 09:51:53 +02:00
|
|
|
int currentPosition = m_editorWidget->position();
|
|
|
|
|
const QString textAfterCursor = m_editorWidget->textAt(currentPosition,
|
|
|
|
|
cursor.position() - currentPosition);
|
|
|
|
|
if (!textAfterCursor.startsWith(newPrefix)) {
|
2022-11-17 14:53:47 +01:00
|
|
|
if (newPrefix.indexOf(textAfterCursor, currentPosition - m_proposalWidget->basePosition()) >= 0)
|
2017-06-28 09:51:53 +02:00
|
|
|
currentPosition = cursor.position();
|
2020-09-07 14:44:41 +02:00
|
|
|
const QStringView prefixAddition = QStringView(newPrefix).mid(currentPosition
|
2022-11-17 14:53:47 +01:00
|
|
|
- m_proposalWidget->basePosition());
|
2017-06-28 09:51:53 +02:00
|
|
|
// If remaining string starts with the prefix addition
|
|
|
|
|
if (textAfterCursor.startsWith(prefixAddition))
|
|
|
|
|
currentPosition += prefixAddition.size();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 14:53:47 +01:00
|
|
|
m_editorWidget->setCursorPosition(m_proposalWidget->basePosition());
|
|
|
|
|
m_editorWidget->replace(currentPosition - m_proposalWidget->basePosition(), newPrefix);
|
2011-04-15 16:19:23 +02:00
|
|
|
notifyChange();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::finalizeProposal()
|
|
|
|
|
{
|
2013-08-21 23:22:11 +03:00
|
|
|
stopAutomaticProposalTimer();
|
2023-05-10 15:23:24 +02:00
|
|
|
m_suggestionBlocker.reset();
|
2018-02-14 12:25:15 +01:00
|
|
|
m_proposalWidget = nullptr;
|
2011-04-15 16:19:23 +02:00
|
|
|
if (m_receivedContentWhileWaiting)
|
|
|
|
|
m_receivedContentWhileWaiting = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeAssistantPrivate::isDisplayingProposal() const
|
|
|
|
|
{
|
2021-05-04 11:21:13 +02:00
|
|
|
return m_proposalWidget != nullptr && m_proposalWidget->proposalIsVisible();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeAssistantPrivate::isWaitingForProposal() const
|
|
|
|
|
{
|
2022-11-09 15:38:22 +01:00
|
|
|
return m_processor != nullptr;
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2022-11-10 13:33:25 +01:00
|
|
|
QString CodeAssistantPrivate::proposalPrefix() const
|
|
|
|
|
{
|
|
|
|
|
if (!isDisplayingProposal())
|
|
|
|
|
return {};
|
2022-11-17 14:53:47 +01:00
|
|
|
return m_editorWidget->textAt(m_proposalWidget->basePosition(),
|
|
|
|
|
m_editorWidget->position() - m_proposalWidget->basePosition());
|
2022-11-10 13:33:25 +01:00
|
|
|
}
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
void CodeAssistantPrivate::invalidateCurrentRequestData()
|
|
|
|
|
{
|
2022-11-09 15:38:22 +01:00
|
|
|
m_processor = nullptr;
|
2018-02-14 12:25:15 +01:00
|
|
|
m_requestProvider = nullptr;
|
2021-07-16 08:49:02 +02:00
|
|
|
m_receivedContentWhileWaiting = false;
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2022-11-10 15:10:12 +01:00
|
|
|
QList<CompletionAssistProvider *> CodeAssistantPrivate::identifyActivationSequence()
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
2019-05-17 14:37:06 +02:00
|
|
|
auto checkActivationSequence = [this](CompletionAssistProvider *provider) {
|
|
|
|
|
if (!provider)
|
|
|
|
|
return false;
|
|
|
|
|
const int length = provider->activationCharSequenceLength();
|
|
|
|
|
if (!length)
|
|
|
|
|
return false;
|
|
|
|
|
QString sequence = m_editorWidget->textAt(m_editorWidget->position() - length, length);
|
|
|
|
|
// In pretty much all cases the sequence will have the appropriate length. Only in the
|
|
|
|
|
// case of typing the very first characters in the document for providers that request a
|
|
|
|
|
// length greater than 1 (currently only C++, which specifies 3), the sequence needs to
|
|
|
|
|
// be prepended so it has the expected length.
|
|
|
|
|
const int lengthDiff = length - sequence.length();
|
|
|
|
|
for (int j = 0; j < lengthDiff; ++j)
|
|
|
|
|
sequence.prepend(m_null);
|
|
|
|
|
return provider->isActivationCharSequence(sequence);
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-10 15:10:12 +01:00
|
|
|
QList<CompletionAssistProvider *> provider = {
|
2019-05-17 14:37:06 +02:00
|
|
|
m_editorWidget->textDocument()->completionAssistProvider(),
|
|
|
|
|
m_editorWidget->textDocument()->functionHintAssistProvider()
|
|
|
|
|
};
|
2022-11-10 15:10:12 +01:00
|
|
|
return Utils::filtered(provider, checkActivationSequence);
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::notifyChange()
|
|
|
|
|
{
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
|
|
|
|
if (isDisplayingProposal()) {
|
2022-11-17 14:53:47 +01:00
|
|
|
QTC_ASSERT(m_proposalWidget, return);
|
|
|
|
|
if (m_editorWidget->position() < m_proposalWidget->basePosition()) {
|
2011-04-15 16:19:23 +02:00
|
|
|
destroyContext();
|
2018-07-13 12:33:46 +02:00
|
|
|
} else {
|
2023-04-28 14:04:50 +02:00
|
|
|
std::unique_ptr<AssistInterface> assistInterface
|
|
|
|
|
= m_editorWidget->createAssistInterface(m_assistKind, m_proposalWidget->reason());
|
|
|
|
|
QTC_ASSERT(assistInterface, destroyContext(); return);
|
|
|
|
|
m_proposalWidget->updateProposal(std::move(assistInterface));
|
2022-11-17 14:53:47 +01:00
|
|
|
if (!isDisplayingProposal())
|
|
|
|
|
requestActivationCharProposal();
|
2012-04-13 21:47:11 +04:00
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeAssistantPrivate::hasContext() const
|
|
|
|
|
{
|
2022-11-09 15:38:22 +01:00
|
|
|
return m_processor || m_proposalWidget;
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::destroyContext()
|
|
|
|
|
{
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
|
|
|
|
if (isWaitingForProposal()) {
|
|
|
|
|
cancelCurrentRequest();
|
2020-10-19 13:47:27 +02:00
|
|
|
} else if (m_proposalWidget) {
|
2016-06-01 10:19:59 +02:00
|
|
|
m_editorWidget->keepAutoCompletionHighlight(false);
|
2021-05-17 10:13:31 +02:00
|
|
|
if (m_proposalWidget->proposalIsVisible())
|
2020-10-19 13:47:27 +02:00
|
|
|
m_proposalWidget->closeProposal();
|
2014-09-12 01:25:42 +02:00
|
|
|
disconnect(m_proposalWidget, &QObject::destroyed,
|
|
|
|
|
this, &CodeAssistantPrivate::finalizeProposal);
|
2011-04-15 16:19:23 +02:00
|
|
|
finalizeProposal();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 12:21:45 +02:00
|
|
|
QVariant CodeAssistantPrivate::userData() const
|
|
|
|
|
{
|
|
|
|
|
return m_userData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::setUserData(const QVariant &data)
|
|
|
|
|
{
|
|
|
|
|
m_userData = data;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
void CodeAssistantPrivate::startAutomaticProposalTimer()
|
|
|
|
|
{
|
|
|
|
|
if (m_settings.m_completionTrigger == AutomaticCompletion)
|
|
|
|
|
m_automaticProposalTimer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::automaticProposalTimeout()
|
|
|
|
|
{
|
2021-06-28 09:13:57 +02:00
|
|
|
if (isWaitingForProposal()
|
|
|
|
|
|| m_editorWidget->multiTextCursor().hasMultipleCursors()
|
2023-03-16 07:13:42 +01:00
|
|
|
|| m_editorWidget->suggestionVisible()
|
2022-11-17 14:53:47 +01:00
|
|
|
|| (isDisplayingProposal() && !m_proposalWidget->isFragile())) {
|
2011-04-15 16:19:23 +02:00
|
|
|
return;
|
2021-06-28 09:13:57 +02:00
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
requestProposal(IdleEditor, Completion);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::stopAutomaticProposalTimer()
|
|
|
|
|
{
|
|
|
|
|
if (m_automaticProposalTimer.isActive())
|
|
|
|
|
m_automaticProposalTimer.stop();
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-05 09:59:15 +01:00
|
|
|
void CodeAssistantPrivate::updateFromCompletionSettings(
|
|
|
|
|
const TextEditor::CompletionSettings &settings)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
|
|
|
|
m_settings = settings;
|
2014-12-05 09:59:15 +01:00
|
|
|
m_automaticProposalTimer.setInterval(m_settings.m_automaticProposalTimeoutInMs);
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2013-09-12 00:21:12 +03:00
|
|
|
void CodeAssistantPrivate::explicitlyAborted()
|
|
|
|
|
{
|
2022-11-17 14:53:47 +01:00
|
|
|
QTC_ASSERT(m_proposalWidget, return);
|
|
|
|
|
m_abortedBasePosition = m_proposalWidget->basePosition();
|
2013-09-12 00:21:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::clearAbortedPosition()
|
|
|
|
|
{
|
|
|
|
|
m_abortedBasePosition = -1;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-02 13:32:34 +01:00
|
|
|
bool CodeAssistantPrivate::isDestroyEvent(int key, const QString &keyText)
|
|
|
|
|
{
|
|
|
|
|
if (keyText.isEmpty())
|
|
|
|
|
return key != Qt::LeftArrow && key != Qt::RightArrow && key != Qt::Key_Shift;
|
2018-09-20 01:16:01 +03:00
|
|
|
if (auto provider = qobject_cast<CompletionAssistProvider *>(m_requestProvider))
|
2018-03-02 13:32:34 +01:00
|
|
|
return !provider->isContinuationChar(keyText.at(0));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
bool CodeAssistantPrivate::eventFilter(QObject *o, QEvent *e)
|
|
|
|
|
{
|
2019-07-23 10:58:00 +02:00
|
|
|
Q_UNUSED(o)
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
if (isWaitingForProposal()) {
|
|
|
|
|
QEvent::Type type = e->type();
|
|
|
|
|
if (type == QEvent::FocusOut) {
|
|
|
|
|
destroyContext();
|
|
|
|
|
} else if (type == QEvent::KeyPress) {
|
2018-09-20 01:16:01 +03:00
|
|
|
auto keyEvent = static_cast<QKeyEvent *>(e);
|
2011-04-15 16:19:23 +02:00
|
|
|
const QString &keyText = keyEvent->text();
|
2013-09-17 13:25:39 +02:00
|
|
|
|
2018-03-02 13:32:34 +01:00
|
|
|
if (isDestroyEvent(keyEvent->key(), keyText))
|
2011-04-15 16:19:23 +02:00
|
|
|
destroyContext();
|
2018-03-02 13:32:34 +01:00
|
|
|
else if (!keyText.isEmpty() && !m_receivedContentWhileWaiting)
|
2011-04-15 16:19:23 +02:00
|
|
|
m_receivedContentWhileWaiting = true;
|
2021-05-31 11:21:30 +02:00
|
|
|
} else if (type == QEvent::KeyRelease
|
|
|
|
|
&& static_cast<QKeyEvent *>(e)->key() == Qt::Key_Escape) {
|
|
|
|
|
destroyContext();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------
|
|
|
|
|
// CodeAssistant
|
|
|
|
|
// -------------
|
2022-11-10 14:38:34 +01:00
|
|
|
CodeAssistant::CodeAssistant(TextEditorWidget *editorWidget)
|
|
|
|
|
: d(new CodeAssistantPrivate(this, editorWidget))
|
2013-03-19 11:41:40 +01:00
|
|
|
{
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
CodeAssistant::~CodeAssistant()
|
2011-09-16 13:10:06 +02:00
|
|
|
{
|
2015-09-01 15:40:04 +02:00
|
|
|
destroyContext();
|
2011-09-16 13:10:06 +02:00
|
|
|
delete d;
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
void CodeAssistant::process()
|
|
|
|
|
{
|
2011-09-16 13:10:06 +02:00
|
|
|
d->process();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistant::notifyChange()
|
|
|
|
|
{
|
2011-09-16 13:10:06 +02:00
|
|
|
d->notifyChange();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeAssistant::hasContext() const
|
|
|
|
|
{
|
2011-09-16 13:10:06 +02:00
|
|
|
return d->hasContext();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistant::destroyContext()
|
|
|
|
|
{
|
2011-09-16 13:10:06 +02:00
|
|
|
d->destroyContext();
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2017-08-18 12:21:45 +02:00
|
|
|
QVariant CodeAssistant::userData() const
|
|
|
|
|
{
|
|
|
|
|
return d->userData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistant::setUserData(const QVariant &data)
|
|
|
|
|
{
|
|
|
|
|
d->setUserData(data);
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
void CodeAssistant::invoke(AssistKind kind, IAssistProvider *provider)
|
|
|
|
|
{
|
2011-09-16 13:10:06 +02:00
|
|
|
d->invoke(kind, provider);
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2014-09-19 14:08:11 +02:00
|
|
|
} // namespace TextEditor
|