2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2011-04-15 16:19:23 +02:00
|
|
|
**
|
2014-01-07 13:27:11 +01:00
|
|
|
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
2012-10-02 09:12:39 +02:00
|
|
|
** Contact: http://www.qt-project.org/legal
|
2011-04-15 16:19:23 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2011-04-15 16:19:23 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and Digia. For licensing terms and
|
|
|
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
|
|
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
2011-04-15 16:19:23 +02:00
|
|
|
**
|
|
|
|
|
** GNU Lesser General Public License Usage
|
2012-10-02 09:12:39 +02:00
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
|
|
|
** General Public License version 2.1 as published by the Free Software
|
|
|
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
|
|
|
** packaging of this file. Please review the following information to
|
|
|
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
|
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
|
|
|
**
|
|
|
|
|
** In addition, as a special exception, Digia gives you certain additional
|
|
|
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
2011-04-15 16:19:23 +02:00
|
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
#include "codeassistant.h"
|
|
|
|
|
#include "completionassistprovider.h"
|
|
|
|
|
#include "quickfixassistprovider.h"
|
|
|
|
|
#include "iassistprocessor.h"
|
|
|
|
|
#include "iassistproposal.h"
|
|
|
|
|
#include "iassistproposalwidget.h"
|
2014-09-04 00:04:18 +02:00
|
|
|
#include "assistinterface.h"
|
|
|
|
|
#include "assistproposalitem.h"
|
2011-04-15 16:19:23 +02:00
|
|
|
#include "runner.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>
|
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:
|
|
|
|
|
CodeAssistantPrivate(CodeAssistant *assistant);
|
|
|
|
|
|
2014-09-03 22:45:33 +02:00
|
|
|
void configure(BaseTextEditorWidget *editorWidget);
|
2011-04-15 16:19:23 +02:00
|
|
|
bool isConfigured() const;
|
|
|
|
|
|
|
|
|
|
void invoke(AssistKind kind, IAssistProvider *provider = 0);
|
|
|
|
|
void process();
|
|
|
|
|
void requestProposal(AssistReason reason, AssistKind kind, IAssistProvider *provider = 0);
|
|
|
|
|
void cancelCurrentRequest();
|
|
|
|
|
void invalidateCurrentRequestData();
|
|
|
|
|
void displayProposal(IAssistProposal *newProposal, AssistReason reason);
|
|
|
|
|
bool isDisplayingProposal() const;
|
|
|
|
|
bool isWaitingForProposal() const;
|
|
|
|
|
|
|
|
|
|
void notifyChange();
|
|
|
|
|
bool hasContext() const;
|
|
|
|
|
void destroyContext();
|
|
|
|
|
|
|
|
|
|
CompletionAssistProvider *identifyActivationSequence();
|
|
|
|
|
|
|
|
|
|
void stopAutomaticProposalTimer();
|
|
|
|
|
void startAutomaticProposalTimer();
|
2014-09-19 14:08:11 +02:00
|
|
|
void automaticProposalTimeout();
|
|
|
|
|
void clearAbortedPosition();
|
|
|
|
|
void updateCompletionSettings(const TextEditor::CompletionSettings &settings);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
virtual bool eventFilter(QObject *o, QEvent *e);
|
|
|
|
|
|
2014-09-12 01:25:42 +02:00
|
|
|
private:
|
2011-04-15 16:19:23 +02:00
|
|
|
void finalizeRequest();
|
|
|
|
|
void proposalComputed();
|
2014-09-04 00:04:18 +02:00
|
|
|
void processProposalItem(AssistProposalItem *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();
|
2011-04-15 16:19:23 +02:00
|
|
|
|
|
|
|
|
private:
|
2014-09-19 14:08:11 +02:00
|
|
|
CodeAssistant *q;
|
2014-09-03 22:45:33 +02:00
|
|
|
BaseTextEditorWidget *m_editorWidget;
|
2011-04-15 16:19:23 +02:00
|
|
|
QList<QuickFixAssistProvider *> m_quickFixProviders;
|
|
|
|
|
Internal::ProcessorRunner *m_requestRunner;
|
2013-09-17 13:25:39 +02:00
|
|
|
IAssistProvider *m_requestProvider;
|
2011-04-15 16:19:23 +02:00
|
|
|
AssistKind m_assistKind;
|
|
|
|
|
IAssistProposalWidget *m_proposalWidget;
|
|
|
|
|
QScopedPointer<IAssistProposal> m_proposal;
|
|
|
|
|
bool m_receivedContentWhileWaiting;
|
|
|
|
|
QTimer m_automaticProposalTimer;
|
|
|
|
|
CompletionSettings m_settings;
|
2013-09-12 00:21:12 +03:00
|
|
|
int m_abortedBasePosition;
|
2011-05-20 14:08:34 +02:00
|
|
|
static const QChar m_null;
|
2011-04-15 16:19:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --------------------
|
|
|
|
|
// CodeAssistantPrivate
|
|
|
|
|
// --------------------
|
2011-05-20 14:08:34 +02:00
|
|
|
const QChar CodeAssistantPrivate::m_null;
|
|
|
|
|
|
2013-02-26 11:15:38 +01:00
|
|
|
static const int AutomaticProposalTimerInterval = 400;
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
CodeAssistantPrivate::CodeAssistantPrivate(CodeAssistant *assistant)
|
2014-09-19 14:08:11 +02:00
|
|
|
: q(assistant)
|
2014-09-03 22:45:33 +02:00
|
|
|
, m_editorWidget(0)
|
2011-04-15 16:19:23 +02:00
|
|
|
, m_requestRunner(0)
|
|
|
|
|
, m_requestProvider(0)
|
2013-10-24 14:32:14 +02:00
|
|
|
, m_assistKind(TextEditor::Completion)
|
2011-04-15 16:19:23 +02:00
|
|
|
, m_proposalWidget(0)
|
|
|
|
|
, m_receivedContentWhileWaiting(false)
|
2013-09-19 17:59:27 +02:00
|
|
|
, m_settings(TextEditorSettings::completionSettings())
|
2013-09-12 00:21:12 +03:00
|
|
|
, m_abortedBasePosition(-1)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
|
|
|
|
m_automaticProposalTimer.setSingleShot(true);
|
2013-02-26 11:15:38 +01:00
|
|
|
m_automaticProposalTimer.setInterval(AutomaticProposalTimerInterval);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2014-09-19 14:08:11 +02:00
|
|
|
connect(&m_automaticProposalTimer, &QTimer::timeout,
|
|
|
|
|
this, &CodeAssistantPrivate::automaticProposalTimeout);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2014-09-19 14:08:11 +02:00
|
|
|
connect(TextEditorSettings::instance(), &TextEditorSettings::completionSettingsChanged,
|
|
|
|
|
this, &CodeAssistantPrivate::updateCompletionSettings);
|
|
|
|
|
|
|
|
|
|
connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
|
|
|
|
|
this, &CodeAssistantPrivate::clearAbortedPosition);
|
2011-09-16 13:10:06 +02:00
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2014-09-03 22:45:33 +02:00
|
|
|
void CodeAssistantPrivate::configure(BaseTextEditorWidget *editorWidget)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
|
|
|
|
// @TODO: There's a list of providers but currently only the first one is used. Perhaps we
|
|
|
|
|
// should implement a truly mechanism to support multiple providers for an editor (either
|
|
|
|
|
// merging or not proposals) or just leave it as not extensible and store directly the one
|
|
|
|
|
// completion and quick-fix provider (getting rid of the list).
|
|
|
|
|
|
2014-09-03 22:45:33 +02:00
|
|
|
m_editorWidget = editorWidget;
|
2014-09-19 14:08:11 +02:00
|
|
|
m_quickFixProviders = ExtensionSystem::PluginManager::getObjects<QuickFixAssistProvider>();
|
|
|
|
|
|
|
|
|
|
Core::Id editorId = m_editorWidget->textDocument()->id();
|
|
|
|
|
auto it = m_quickFixProviders.begin();
|
|
|
|
|
while (it != m_quickFixProviders.end()) {
|
|
|
|
|
if ((*it)->supportsEditor(editorId))
|
|
|
|
|
++it;
|
|
|
|
|
else
|
|
|
|
|
it = m_quickFixProviders.erase(it);
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
|
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
|
|
|
bool CodeAssistantPrivate::isConfigured() const
|
|
|
|
|
{
|
2014-09-03 22:45:33 +02:00
|
|
|
return m_editorWidget != 0;
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::invoke(AssistKind kind, IAssistProvider *provider)
|
|
|
|
|
{
|
|
|
|
|
if (!isConfigured())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
|
|
|
|
if (isDisplayingProposal() && m_assistKind == kind && !m_proposal->isFragile()) {
|
|
|
|
|
m_proposalWidget->setReason(ExplicitlyInvoked);
|
2014-09-19 14:08:11 +02:00
|
|
|
m_proposalWidget->updateProposal(m_editorWidget->textAt(
|
2013-04-18 18:21:17 +02:00
|
|
|
m_proposal->basePosition(),
|
2014-09-03 22:45:33 +02:00
|
|
|
m_editorWidget->position() - m_proposal->basePosition()));
|
2011-04-15 16:19:23 +02:00
|
|
|
} else {
|
|
|
|
|
destroyContext();
|
|
|
|
|
requestProposal(ExplicitlyInvoked, kind, provider);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::process()
|
|
|
|
|
{
|
|
|
|
|
if (!isConfigured())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
2013-10-24 14:32:14 +02:00
|
|
|
if (m_assistKind == TextEditor::Completion) {
|
|
|
|
|
if (m_settings.m_completionTrigger != ManualCompletion) {
|
|
|
|
|
if (CompletionAssistProvider *provider = identifyActivationSequence()) {
|
|
|
|
|
if (isWaitingForProposal())
|
|
|
|
|
cancelCurrentRequest();
|
|
|
|
|
requestProposal(ActivationCharacter, Completion, provider);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-24 14:32:14 +02:00
|
|
|
startAutomaticProposalTimer();
|
|
|
|
|
} else {
|
|
|
|
|
m_assistKind = TextEditor::Completion;
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::requestProposal(AssistReason reason,
|
|
|
|
|
AssistKind kind,
|
|
|
|
|
IAssistProvider *provider)
|
|
|
|
|
{
|
2013-03-05 13:47:29 +01:00
|
|
|
QTC_ASSERT(!isWaitingForProposal(), return);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2014-09-03 22:45:33 +02:00
|
|
|
if (m_editorWidget->hasBlockSelection())
|
2014-06-16 14:20:36 +02:00
|
|
|
return; // TODO
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
if (!provider) {
|
2013-08-30 12:55:06 +02:00
|
|
|
if (kind == Completion)
|
2014-09-12 01:25:42 +02:00
|
|
|
provider = m_editorWidget->completionAssistProvider();
|
2013-08-30 12:55:06 +02:00
|
|
|
else if (!m_quickFixProviders.isEmpty())
|
2011-04-15 16:19:23 +02:00
|
|
|
provider = m_quickFixProviders.at(0);
|
|
|
|
|
|
|
|
|
|
if (!provider)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_assistKind = kind;
|
|
|
|
|
IAssistProcessor *processor = provider->createProcessor();
|
2014-09-04 00:04:18 +02:00
|
|
|
AssistInterface *assistInterface = m_editorWidget->createAssistInterface(kind, reason);
|
2011-04-15 16:19:23 +02:00
|
|
|
if (!assistInterface)
|
|
|
|
|
return;
|
|
|
|
|
|
2013-09-17 13:25:39 +02:00
|
|
|
if (provider->isAsynchronous()) {
|
2013-09-18 09:07:21 +02:00
|
|
|
if (IAssistProposal *newProposal = processor->immediateProposal(assistInterface))
|
|
|
|
|
displayProposal(newProposal, reason);
|
|
|
|
|
|
2013-09-17 13:25:39 +02:00
|
|
|
m_requestProvider = provider;
|
|
|
|
|
m_requestRunner = new ProcessorRunner;
|
2014-09-12 01:25:42 +02:00
|
|
|
connect(m_requestRunner, &ProcessorRunner::finished,
|
|
|
|
|
this, &CodeAssistantPrivate::proposalComputed);
|
|
|
|
|
connect(m_requestRunner, &ProcessorRunner::finished,
|
|
|
|
|
this, &CodeAssistantPrivate::finalizeRequest);
|
|
|
|
|
connect(m_requestRunner, &ProcessorRunner::finished,
|
|
|
|
|
q, &CodeAssistant::finished);
|
2013-09-17 13:25:39 +02:00
|
|
|
assistInterface->prepareForAsyncUse();
|
|
|
|
|
m_requestRunner->setReason(reason);
|
|
|
|
|
m_requestRunner->setProcessor(processor);
|
|
|
|
|
m_requestRunner->setAssistInterface(assistInterface);
|
|
|
|
|
m_requestRunner->start();
|
|
|
|
|
return;
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2013-06-03 12:26:25 +02:00
|
|
|
if (IAssistProposal *newProposal = processor->perform(assistInterface))
|
2013-04-18 23:12:44 +02:00
|
|
|
displayProposal(newProposal, reason);
|
2011-04-15 16:19:23 +02:00
|
|
|
delete processor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::cancelCurrentRequest()
|
|
|
|
|
{
|
|
|
|
|
m_requestRunner->setDiscardProposal(true);
|
2014-09-12 01:25:42 +02:00
|
|
|
disconnect(m_requestRunner, &ProcessorRunner::finished,
|
|
|
|
|
this, &CodeAssistantPrivate::proposalComputed);
|
2011-04-15 16:19:23 +02:00
|
|
|
invalidateCurrentRequestData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::proposalComputed()
|
|
|
|
|
{
|
|
|
|
|
// Since the request runner is a different thread, there's still a gap in which the queued
|
|
|
|
|
// signal could be processed after an invalidation of the current request.
|
2011-11-23 12:09:16 +01:00
|
|
|
if (m_requestRunner != sender())
|
2011-04-15 16:19:23 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
IAssistProposal *newProposal = m_requestRunner->proposal();
|
|
|
|
|
AssistReason reason = m_requestRunner->reason();
|
|
|
|
|
invalidateCurrentRequestData();
|
|
|
|
|
displayProposal(newProposal, reason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::displayProposal(IAssistProposal *newProposal, AssistReason reason)
|
|
|
|
|
{
|
|
|
|
|
if (!newProposal)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
QScopedPointer<IAssistProposal> proposalCandidate(newProposal);
|
|
|
|
|
|
|
|
|
|
if (isDisplayingProposal()) {
|
2013-09-18 09:07:21 +02:00
|
|
|
if (!m_proposal->isFragile())
|
2011-04-15 16:19:23 +02:00
|
|
|
return;
|
|
|
|
|
destroyContext();
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-12 00:21:12 +03:00
|
|
|
int basePosition = proposalCandidate->basePosition();
|
2014-09-03 22:45:33 +02:00
|
|
|
if (m_editorWidget->position() < basePosition)
|
2011-04-15 16:19:23 +02:00
|
|
|
return;
|
|
|
|
|
|
2013-09-12 00:21:12 +03:00
|
|
|
if (m_abortedBasePosition == basePosition && reason != ExplicitlyInvoked)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
clearAbortedPosition();
|
2011-04-15 16:19:23 +02:00
|
|
|
m_proposal.reset(proposalCandidate.take());
|
|
|
|
|
|
|
|
|
|
if (m_proposal->isCorrective())
|
2014-09-03 22:45:33 +02:00
|
|
|
m_proposal->makeCorrection(m_editorWidget);
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2013-09-12 00:21:12 +03:00
|
|
|
basePosition = m_proposal->basePosition();
|
2011-04-15 16:19:23 +02:00
|
|
|
m_proposalWidget = m_proposal->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);
|
2014-09-03 22:45:33 +02:00
|
|
|
m_proposalWidget->setUnderlyingWidget(m_editorWidget);
|
2011-04-15 16:19:23 +02:00
|
|
|
m_proposalWidget->setModel(m_proposal->model());
|
2014-09-03 22:45:33 +02:00
|
|
|
m_proposalWidget->setDisplayRect(m_editorWidget->cursorRect(basePosition));
|
2011-04-15 16:19:23 +02:00
|
|
|
if (m_receivedContentWhileWaiting)
|
|
|
|
|
m_proposalWidget->setIsSynchronized(false);
|
|
|
|
|
else
|
|
|
|
|
m_proposalWidget->setIsSynchronized(true);
|
2014-09-12 01:25:42 +02:00
|
|
|
m_proposalWidget->showProposal(m_editorWidget->textAt(
|
2013-09-12 00:21:12 +03:00
|
|
|
basePosition,
|
2014-09-03 22:45:33 +02:00
|
|
|
m_editorWidget->position() - basePosition));
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
2014-09-04 00:04:18 +02:00
|
|
|
void CodeAssistantPrivate::processProposalItem(AssistProposalItem *proposalItem)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
2013-03-05 13:47:29 +01:00
|
|
|
QTC_ASSERT(m_proposal, return);
|
2014-09-03 22:45:33 +02:00
|
|
|
proposalItem->apply(m_editorWidget, m_proposal->basePosition());
|
2011-04-15 16:19:23 +02:00
|
|
|
destroyContext();
|
|
|
|
|
process();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::handlePrefixExpansion(const QString &newPrefix)
|
|
|
|
|
{
|
2013-03-05 13:47:29 +01:00
|
|
|
QTC_ASSERT(m_proposal, return);
|
2014-09-03 22:45:33 +02:00
|
|
|
const int currentPosition = m_editorWidget->position();
|
|
|
|
|
m_editorWidget->setCursorPosition(m_proposal->basePosition());
|
|
|
|
|
m_editorWidget->replace(currentPosition - m_proposal->basePosition(), newPrefix);
|
2011-04-15 16:19:23 +02:00
|
|
|
notifyChange();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::finalizeRequest()
|
|
|
|
|
{
|
|
|
|
|
if (ProcessorRunner *runner = qobject_cast<ProcessorRunner *>(sender()))
|
|
|
|
|
delete runner;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::finalizeProposal()
|
|
|
|
|
{
|
2013-08-21 23:22:11 +03:00
|
|
|
stopAutomaticProposalTimer();
|
2011-04-15 16:19:23 +02:00
|
|
|
m_proposal.reset();
|
|
|
|
|
m_proposalWidget = 0;
|
|
|
|
|
if (m_receivedContentWhileWaiting)
|
|
|
|
|
m_receivedContentWhileWaiting = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeAssistantPrivate::isDisplayingProposal() const
|
|
|
|
|
{
|
|
|
|
|
return m_proposalWidget != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeAssistantPrivate::isWaitingForProposal() const
|
|
|
|
|
{
|
|
|
|
|
return m_requestRunner != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::invalidateCurrentRequestData()
|
|
|
|
|
{
|
|
|
|
|
m_requestRunner = 0;
|
|
|
|
|
m_requestProvider = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CompletionAssistProvider *CodeAssistantPrivate::identifyActivationSequence()
|
|
|
|
|
{
|
2014-09-12 01:25:42 +02:00
|
|
|
CompletionAssistProvider *completionProvider = m_editorWidget->completionAssistProvider();
|
|
|
|
|
if (!completionProvider)
|
2013-08-30 12:55:06 +02:00
|
|
|
return 0;
|
|
|
|
|
|
2014-09-12 01:25:42 +02:00
|
|
|
const int length = completionProvider->activationCharSequenceLength();
|
2013-08-30 12:55:06 +02:00
|
|
|
if (length == 0)
|
|
|
|
|
return 0;
|
2014-09-19 14:08:11 +02:00
|
|
|
QString sequence = m_editorWidget->textAt(m_editorWidget->position() - length, length);
|
2013-08-30 12:55:06 +02:00
|
|
|
// 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
|
2013-09-11 23:22:21 +03:00
|
|
|
// be prepended so it has the expected length.
|
2013-08-30 12:55:06 +02:00
|
|
|
const int lengthDiff = length - sequence.length();
|
|
|
|
|
for (int j = 0; j < lengthDiff; ++j)
|
|
|
|
|
sequence.prepend(m_null);
|
2014-09-12 01:25:42 +02:00
|
|
|
return completionProvider->isActivationCharSequence(sequence) ? completionProvider : 0;
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::notifyChange()
|
|
|
|
|
{
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
|
|
|
|
if (isDisplayingProposal()) {
|
2013-03-05 13:47:29 +01:00
|
|
|
QTC_ASSERT(m_proposal, return);
|
2014-09-03 22:45:33 +02:00
|
|
|
if (m_editorWidget->position() < m_proposal->basePosition()) {
|
2011-04-15 16:19:23 +02:00
|
|
|
destroyContext();
|
2012-04-13 21:47:11 +04:00
|
|
|
} else {
|
2011-04-15 16:19:23 +02:00
|
|
|
m_proposalWidget->updateProposal(
|
2014-09-12 01:25:42 +02:00
|
|
|
m_editorWidget->textAt(m_proposal->basePosition(),
|
2014-09-03 22:45:33 +02:00
|
|
|
m_editorWidget->position() - m_proposal->basePosition()));
|
2012-04-13 21:47:11 +04:00
|
|
|
if (m_proposal->isFragile())
|
|
|
|
|
startAutomaticProposalTimer();
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeAssistantPrivate::hasContext() const
|
|
|
|
|
{
|
|
|
|
|
return m_requestRunner || m_proposalWidget;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::destroyContext()
|
|
|
|
|
{
|
|
|
|
|
stopAutomaticProposalTimer();
|
|
|
|
|
|
|
|
|
|
if (isWaitingForProposal()) {
|
|
|
|
|
cancelCurrentRequest();
|
|
|
|
|
} else if (isDisplayingProposal()) {
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::startAutomaticProposalTimer()
|
|
|
|
|
{
|
|
|
|
|
if (m_settings.m_completionTrigger == AutomaticCompletion)
|
|
|
|
|
m_automaticProposalTimer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::automaticProposalTimeout()
|
|
|
|
|
{
|
|
|
|
|
if (isWaitingForProposal() || (isDisplayingProposal() && !m_proposal->isFragile()))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
requestProposal(IdleEditor, Completion);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::stopAutomaticProposalTimer()
|
|
|
|
|
{
|
|
|
|
|
if (m_automaticProposalTimer.isActive())
|
|
|
|
|
m_automaticProposalTimer.stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::updateCompletionSettings(const TextEditor::CompletionSettings &settings)
|
|
|
|
|
{
|
|
|
|
|
m_settings = settings;
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-12 00:21:12 +03:00
|
|
|
void CodeAssistantPrivate::explicitlyAborted()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_proposal, return);
|
|
|
|
|
m_abortedBasePosition = m_proposal->basePosition();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CodeAssistantPrivate::clearAbortedPosition()
|
|
|
|
|
{
|
|
|
|
|
m_abortedBasePosition = -1;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-15 16:19:23 +02:00
|
|
|
bool CodeAssistantPrivate::eventFilter(QObject *o, QEvent *e)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(o);
|
|
|
|
|
|
|
|
|
|
if (isWaitingForProposal()) {
|
|
|
|
|
QEvent::Type type = e->type();
|
|
|
|
|
if (type == QEvent::FocusOut) {
|
|
|
|
|
destroyContext();
|
|
|
|
|
} else if (type == QEvent::KeyPress) {
|
|
|
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
|
|
|
|
|
const QString &keyText = keyEvent->text();
|
2013-09-17 13:25:39 +02:00
|
|
|
|
|
|
|
|
CompletionAssistProvider *completionProvider = 0;
|
2011-04-15 16:19:23 +02:00
|
|
|
if ((keyText.isEmpty()
|
|
|
|
|
&& keyEvent->key() != Qt::LeftArrow
|
|
|
|
|
&& keyEvent->key() != Qt::RightArrow
|
|
|
|
|
&& keyEvent->key() != Qt::Key_Shift)
|
2013-09-17 13:25:39 +02:00
|
|
|
|| (!keyText.isEmpty()
|
|
|
|
|
&& (((completionProvider = dynamic_cast<CompletionAssistProvider *>(m_requestProvider))
|
|
|
|
|
? !completionProvider->isContinuationChar(keyText.at(0))
|
|
|
|
|
: false)))) {
|
2011-04-15 16:19:23 +02:00
|
|
|
destroyContext();
|
|
|
|
|
} else if (!keyText.isEmpty() && !m_receivedContentWhileWaiting) {
|
|
|
|
|
m_receivedContentWhileWaiting = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------
|
|
|
|
|
// CodeAssistant
|
|
|
|
|
// -------------
|
2011-09-16 13:10:06 +02:00
|
|
|
CodeAssistant::CodeAssistant() : d(new CodeAssistantPrivate(this))
|
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
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
2011-04-15 16:19:23 +02:00
|
|
|
|
2014-09-03 22:45:33 +02:00
|
|
|
void CodeAssistant::configure(BaseTextEditorWidget *editorWidget)
|
2011-04-15 16:19:23 +02:00
|
|
|
{
|
2014-09-03 22:45:33 +02:00
|
|
|
d->configure(editorWidget);
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|