diff options
author | David Schulz <david.schulz@qt.io> | 2022-11-09 15:38:22 +0100 |
---|---|---|
committer | David Schulz <david.schulz@qt.io> | 2022-11-14 13:02:51 +0000 |
commit | 09ee528c40de17401487974dae5ce708079ac8ad (patch) | |
tree | 6dae9a77cb60b6c3e16a1f0fbd99ad465b40b73b /src/plugins/texteditor/codeassist | |
parent | 33a33612c8ff92bb541d76cb338675f16beb4281 (diff) |
Editor: unify assist processor handling
Define the run type of the processor by its implementation instead of a
enum value of the provider. The execution of a processor inside the
assist now follows a unified procedure.
Change-Id: Ibe9fab324c6072e77702c2663946d7a9f562a085
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Diffstat (limited to 'src/plugins/texteditor/codeassist')
13 files changed, 163 insertions, 260 deletions
diff --git a/src/plugins/texteditor/codeassist/asyncprocessor.cpp b/src/plugins/texteditor/codeassist/asyncprocessor.cpp new file mode 100644 index 0000000000..2ee5ada35f --- /dev/null +++ b/src/plugins/texteditor/codeassist/asyncprocessor.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "asyncprocessor.h" + +#include "assistinterface.h" +#include "iassistproposal.h" + +#include <utils/runextensions.h> + +namespace TextEditor { + +AsyncProcessor::AsyncProcessor() +{ + QObject::connect(&m_watcher, &QFutureWatcher<IAssistProposal *>::finished, [this]() { + setAsyncProposalAvailable(m_watcher.result()); + }); +} + +IAssistProposal *AsyncProcessor::perform(AssistInterface *interface) +{ + IAssistProposal *result = immediateProposal(interface); + m_interface = interface; + m_interface->prepareForAsyncUse(); + m_watcher.setFuture(Utils::runAsync([this]() { + m_interface->recreateTextDocument(); + return performAsync(m_interface); + })); + return result; +} + +bool AsyncProcessor::running() +{ + return m_watcher.isRunning(); +} + +void AsyncProcessor::cancel() +{ + setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal){ + delete proposal; + QMetaObject::invokeMethod(QCoreApplication::instance(), [this] { + delete this; + }, Qt::QueuedConnection); + }); +} + +IAssistProposal *AsyncProcessor::immediateProposal(AssistInterface *interface) +{ + Q_UNUSED(interface) + return nullptr; +} + +bool AsyncProcessor::isCanceled() const +{ + return m_watcher.isCanceled(); +} + +} // namespace TextEditor diff --git a/src/plugins/texteditor/codeassist/asyncprocessor.h b/src/plugins/texteditor/codeassist/asyncprocessor.h new file mode 100644 index 0000000000..6890a721a0 --- /dev/null +++ b/src/plugins/texteditor/codeassist/asyncprocessor.h @@ -0,0 +1,32 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "iassistprocessor.h" + +#include <QFutureWatcher> + +namespace TextEditor { + +class TEXTEDITOR_EXPORT AsyncProcessor : public TextEditor::IAssistProcessor +{ +public: + AsyncProcessor(); + + IAssistProposal *perform(AssistInterface *interface) final; + bool running() override; + void cancel() override; + + virtual IAssistProposal *performAsync(AssistInterface *interface) = 0; + virtual IAssistProposal *immediateProposal(AssistInterface *interface); + +protected: + bool isCanceled() const; + +private: + AssistInterface *m_interface = nullptr; + QFutureWatcher<IAssistProposal *> m_watcher; +}; + +} // namespace TextEditor diff --git a/src/plugins/texteditor/codeassist/codeassistant.cpp b/src/plugins/texteditor/codeassist/codeassistant.cpp index c38c0a6724..75c81efb53 100644 --- a/src/plugins/texteditor/codeassist/codeassistant.cpp +++ b/src/plugins/texteditor/codeassist/codeassistant.cpp @@ -9,7 +9,6 @@ #include "iassistproposalwidget.h" #include "assistinterface.h" #include "assistproposalitem.h" -#include "runner.h" #include "textdocumentmanipulator.h" #include <texteditor/textdocument.h> @@ -81,10 +80,9 @@ private: private: CodeAssistant *q = nullptr; TextEditorWidget *m_editorWidget = nullptr; - Internal::ProcessorRunner *m_requestRunner = nullptr; QMetaObject::Connection m_runnerConnection; IAssistProvider *m_requestProvider = nullptr; - IAssistProcessor *m_asyncProcessor = nullptr; + IAssistProcessor *m_processor = nullptr; AssistKind m_assistKind = TextEditor::Completion; IAssistProposalWidget *m_proposalWidget = nullptr; QScopedPointer<IAssistProposal> m_proposal; @@ -207,92 +205,48 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason, m_requestProvider = provider; IAssistProcessor *processor = provider->createProcessor(assistInterface); - switch (provider->runType()) { - case IAssistProvider::Synchronous: { - if (IAssistProposal *newProposal = processor->perform(assistInterface)) - displayProposal(newProposal, reason); - delete processor; - break; - } - case IAssistProvider::AsynchronousWithThread: { - if (IAssistProposal *newProposal = processor->immediateProposal(assistInterface)) - displayProposal(newProposal, reason); - - m_requestRunner = new ProcessorRunner; - m_runnerConnection = connect(m_requestRunner, &ProcessorRunner::finished, - this, [this, reason, sender = m_requestRunner] { - // 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. - if (!m_requestRunner || m_requestRunner != sender) - return; - - IAssistProposal *proposal = m_requestRunner->proposal(); - invalidateCurrentRequestData(); - displayProposal(proposal, reason); - emit q->finished(); - }); - connect(m_requestRunner, &ProcessorRunner::finished, - m_requestRunner, &ProcessorRunner::deleteLater); - assistInterface->prepareForAsyncUse(); - m_requestRunner->setProcessor(processor); - m_requestRunner->setAssistInterface(assistInterface); - m_requestRunner->start(); - break; - } - case IAssistProvider::Asynchronous: { - processor->setAsyncCompletionAvailableHandler([this, reason, processor]( - IAssistProposal *newProposal) { - 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); - } - if (processor != m_asyncProcessor) - return; - 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_asyncProcessor = processor; - else - emit q->finished(); - } - }); - - // If there is a proposal, nothing asynchronous happened... - if (IAssistProposal *newProposal = processor->perform(assistInterface)) { + processor->setAsyncCompletionAvailableHandler([this, reason, processor]( + IAssistProposal *newProposal) { + 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); + } + if (processor != m_processor) + return; + invalidateCurrentRequestData(); + if (processor->needsRestart() && m_receivedContentWhileWaiting) { + delete newProposal; + m_receivedContentWhileWaiting = false; + requestProposal(reason, m_assistKind, m_requestProvider); + } else { displayProposal(newProposal, reason); - delete processor; - } else if (!processor->running()) { - if (isUpdate) - destroyContext(); - delete processor; - } else { // ...async request was triggered - if (IAssistProposal *newProposal = processor->immediateProposal(assistInterface)) - displayProposal(newProposal, reason); - QTC_CHECK(!m_asyncProcessor); - m_asyncProcessor = processor; + if (processor->running()) + m_processor = processor; + else + emit q->finished(); } + }); - break; + if (IAssistProposal *newProposal = processor->perform(assistInterface)) + displayProposal(newProposal, reason); + if (!processor->running()) { + if (isUpdate) + destroyContext(); + delete processor; + } else { + QTC_CHECK(!m_processor); + m_processor = processor; } - } // switch } void CodeAssistantPrivate::cancelCurrentRequest() { - if (m_requestRunner) { - m_requestRunner->setDiscardProposal(true); - disconnect(m_runnerConnection); - } - if (m_asyncProcessor) { - m_asyncProcessor->cancel(); - delete m_asyncProcessor; + if (m_processor) { + m_processor->cancel(); + if (!m_processor->running()) + delete m_processor; } invalidateCurrentRequestData(); } @@ -424,7 +378,7 @@ bool CodeAssistantPrivate::isDisplayingProposal() const bool CodeAssistantPrivate::isWaitingForProposal() const { - return m_requestRunner != nullptr || m_asyncProcessor != nullptr; + return m_processor != nullptr; } QString CodeAssistantPrivate::proposalPrefix() const @@ -437,8 +391,7 @@ QString CodeAssistantPrivate::proposalPrefix() const void CodeAssistantPrivate::invalidateCurrentRequestData() { - m_asyncProcessor = nullptr; - m_requestRunner = nullptr; + m_processor = nullptr; m_requestProvider = nullptr; m_receivedContentWhileWaiting = false; } @@ -492,7 +445,7 @@ void CodeAssistantPrivate::notifyChange() bool CodeAssistantPrivate::hasContext() const { - return m_requestRunner || m_asyncProcessor || m_proposalWidget; + return m_processor || m_proposalWidget; } void CodeAssistantPrivate::destroyContext() diff --git a/src/plugins/texteditor/codeassist/completionassistprovider.cpp b/src/plugins/texteditor/codeassist/completionassistprovider.cpp index 04e2e2d98c..4b74db1b49 100644 --- a/src/plugins/texteditor/codeassist/completionassistprovider.cpp +++ b/src/plugins/texteditor/codeassist/completionassistprovider.cpp @@ -13,11 +13,6 @@ CompletionAssistProvider::CompletionAssistProvider(QObject *parent) CompletionAssistProvider::~CompletionAssistProvider() = default; -IAssistProvider::RunType CompletionAssistProvider::runType() const -{ - return AsynchronousWithThread; -} - int CompletionAssistProvider::activationCharSequenceLength() const { return 0; diff --git a/src/plugins/texteditor/codeassist/completionassistprovider.h b/src/plugins/texteditor/codeassist/completionassistprovider.h index ab507275b1..8a3d13e149 100644 --- a/src/plugins/texteditor/codeassist/completionassistprovider.h +++ b/src/plugins/texteditor/codeassist/completionassistprovider.h @@ -16,7 +16,6 @@ public: CompletionAssistProvider(QObject *parent = nullptr); ~CompletionAssistProvider() override; - IAssistProvider::RunType runType() const override; virtual int activationCharSequenceLength() const; virtual bool isActivationCharSequence(const QString &sequence) const; virtual bool isContinuationChar(const QChar &c) const; diff --git a/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp b/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp index 323781100c..99e0824701 100644 --- a/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp +++ b/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp @@ -5,8 +5,8 @@ #include "assistinterface.h" #include "assistproposalitem.h" +#include "asyncprocessor.h" #include "genericproposal.h" -#include "genericproposalmodel.h" #include "iassistprocessor.h" #include "../snippets/snippetassistcollector.h" #include "../completionsettings.h" @@ -23,30 +23,22 @@ using namespace TextEditor; -class DocumentContentCompletionProcessor final : public IAssistProcessor +class DocumentContentCompletionProcessor final : public AsyncProcessor { public: DocumentContentCompletionProcessor(const QString &snippetGroupId); ~DocumentContentCompletionProcessor() final; - IAssistProposal *perform(const AssistInterface *interface) override; - bool running() final { return m_watcher.isRunning(); } - void cancel() final; + IAssistProposal *performAsync(AssistInterface *interface) override; private: QString m_snippetGroup; - QFutureWatcher<QStringList> m_watcher; }; DocumentContentCompletionProvider::DocumentContentCompletionProvider(const QString &snippetGroup) : m_snippetGroup(snippetGroup) { } -IAssistProvider::RunType DocumentContentCompletionProvider::runType() const -{ - return Asynchronous; -} - IAssistProcessor *DocumentContentCompletionProvider::createProcessor(const AssistInterface *) const { return new DocumentContentCompletionProcessor(m_snippetGroup); @@ -61,38 +53,9 @@ DocumentContentCompletionProcessor::~DocumentContentCompletionProcessor() cancel(); } -static void createProposal(QFutureInterface<QStringList> &future, const QString &text, - const QString &wordUnderCursor) -{ - const QRegularExpression wordRE("([\\p{L}_][\\p{L}0-9_]{2,})"); - - QSet<QString> words; - QRegularExpressionMatchIterator it = wordRE.globalMatch(text); - int wordUnderCursorFound = 0; - while (it.hasNext()) { - if (future.isCanceled()) - return; - QRegularExpressionMatch match = it.next(); - const QString &word = match.captured(); - if (word == wordUnderCursor) { - // Only add the word under cursor if it - // already appears elsewhere in the text - if (++wordUnderCursorFound < 2) - continue; - } - - if (!words.contains(word)) - words.insert(word); - } - - future.reportResult(Utils::toList(words)); -} - -IAssistProposal *DocumentContentCompletionProcessor::perform(const AssistInterface *interface) +IAssistProposal *DocumentContentCompletionProcessor::performAsync(AssistInterface *interface) { - QScopedPointer<const AssistInterface> assistInterface(interface); - if (running()) - return nullptr; + QScopedPointer<AssistInterface> interfaceDeleter(interface); int pos = interface->position(); @@ -113,27 +76,36 @@ IAssistProposal *DocumentContentCompletionProcessor::perform(const AssistInterfa } } + const TextEditor::SnippetAssistCollector snippetCollector( + m_snippetGroup, QIcon(":/texteditor/images/snippet.png")); + QList<AssistProposalItemInterface *> items = snippetCollector.collect(); + const QString wordUnderCursor = interface->textAt(pos, length); const QString text = interface->textDocument()->toPlainText(); - m_watcher.setFuture(Utils::runAsync(&createProposal, text, wordUnderCursor)); - QObject::connect(&m_watcher, &QFutureWatcher<QStringList>::resultReadyAt, - &m_watcher, [this, pos](int index){ - const TextEditor::SnippetAssistCollector snippetCollector( - m_snippetGroup, QIcon(":/texteditor/images/snippet.png")); - QList<AssistProposalItemInterface *> items = snippetCollector.collect(); - for (const QString &word : m_watcher.resultAt(index)) { + const QRegularExpression wordRE("([\\p{L}_][\\p{L}0-9_]{2,})"); + QSet<QString> words; + QRegularExpressionMatchIterator it = wordRE.globalMatch(text); + int wordUnderCursorFound = 0; + while (it.hasNext()) { + if (isCanceled()) + return nullptr; + QRegularExpressionMatch match = it.next(); + const QString &word = match.captured(); + if (word == wordUnderCursor) { + // Only add the word under cursor if it + // already appears elsewhere in the text + if (++wordUnderCursorFound < 2) + continue; + } + + if (!words.contains(word)) { auto item = new AssistProposalItem(); item->setText(word); items.append(item); + words.insert(word); } - setAsyncProposalAvailable(new GenericProposal(pos, items)); - }); - return nullptr; -} + } -void DocumentContentCompletionProcessor::cancel() -{ - if (running()) - m_watcher.cancel(); + return new GenericProposal(pos, items); } diff --git a/src/plugins/texteditor/codeassist/documentcontentcompletion.h b/src/plugins/texteditor/codeassist/documentcontentcompletion.h index 1acbbefc84..21d996356b 100644 --- a/src/plugins/texteditor/codeassist/documentcontentcompletion.h +++ b/src/plugins/texteditor/codeassist/documentcontentcompletion.h @@ -17,7 +17,6 @@ public: DocumentContentCompletionProvider( const QString &snippetGroup = QString(Constants::TEXT_SNIPPET_GROUP_ID)); - RunType runType() const override; IAssistProcessor *createProcessor(const AssistInterface *) const override; private: diff --git a/src/plugins/texteditor/codeassist/iassistprocessor.h b/src/plugins/texteditor/codeassist/iassistprocessor.h index 583f3ae973..dac8b88ecc 100644 --- a/src/plugins/texteditor/codeassist/iassistprocessor.h +++ b/src/plugins/texteditor/codeassist/iassistprocessor.h @@ -18,8 +18,7 @@ public: IAssistProcessor(); virtual ~IAssistProcessor(); - virtual IAssistProposal *immediateProposal(const AssistInterface *) { return nullptr; } - virtual IAssistProposal *perform(const AssistInterface *interface) = 0; // takes ownership + virtual IAssistProposal *perform(AssistInterface *interface) = 0; // takes ownership void setAsyncProposalAvailable(IAssistProposal *proposal); diff --git a/src/plugins/texteditor/codeassist/iassistprovider.h b/src/plugins/texteditor/codeassist/iassistprovider.h index aee54913c6..68fbf1172d 100644 --- a/src/plugins/texteditor/codeassist/iassistprovider.h +++ b/src/plugins/texteditor/codeassist/iassistprovider.h @@ -18,14 +18,6 @@ class TEXTEDITOR_EXPORT IAssistProvider : public QObject public: IAssistProvider(QObject *parent = nullptr) : QObject(parent) {} - - enum RunType { - Synchronous, - Asynchronous, - AsynchronousWithThread - }; - - virtual RunType runType() const = 0; virtual IAssistProcessor *createProcessor(const AssistInterface *assistInterface) const = 0; }; diff --git a/src/plugins/texteditor/codeassist/keywordscompletionassist.cpp b/src/plugins/texteditor/codeassist/keywordscompletionassist.cpp index 6a28877ec2..11bd9cbd37 100644 --- a/src/plugins/texteditor/codeassist/keywordscompletionassist.cpp +++ b/src/plugins/texteditor/codeassist/keywordscompletionassist.cpp @@ -150,7 +150,7 @@ KeywordsCompletionAssistProcessor::KeywordsCompletionAssistProcessor(const Keywo , m_keywords(keywords) {} -IAssistProposal *KeywordsCompletionAssistProcessor::perform(const AssistInterface *interface) +IAssistProposal *KeywordsCompletionAssistProcessor::performAsync(AssistInterface *interface) { QScopedPointer<const AssistInterface> assistInterface(interface); if (isInComment(interface)) @@ -257,11 +257,6 @@ void KeywordsCompletionAssistProvider::setDynamicCompletionFunction( m_completionFunc = func; } -IAssistProvider::RunType KeywordsCompletionAssistProvider::runType() const -{ - return Synchronous; -} - IAssistProcessor *KeywordsCompletionAssistProvider::createProcessor(const AssistInterface *) const { auto processor = new KeywordsCompletionAssistProcessor(m_keyWords); diff --git a/src/plugins/texteditor/codeassist/keywordscompletionassist.h b/src/plugins/texteditor/codeassist/keywordscompletionassist.h index 420f0371a4..85861af2dd 100644 --- a/src/plugins/texteditor/codeassist/keywordscompletionassist.h +++ b/src/plugins/texteditor/codeassist/keywordscompletionassist.h @@ -3,10 +3,10 @@ #pragma once -#include "iassistprocessor.h" #include "assistproposalitem.h" -#include "ifunctionhintproposalmodel.h" +#include "asyncprocessor.h" #include "completionassistprovider.h" +#include "ifunctionhintproposalmodel.h" #include "../snippets/snippetassistcollector.h" #include "texteditor/texteditorconstants.h" @@ -71,8 +71,6 @@ public: void setDynamicCompletionFunction(const DynamicCompletionFunction &func); - // IAssistProvider interface - RunType runType() const override; IAssistProcessor *createProcessor(const AssistInterface *) const override; private: @@ -81,13 +79,13 @@ private: DynamicCompletionFunction m_completionFunc; }; -class TEXTEDITOR_EXPORT KeywordsCompletionAssistProcessor : public IAssistProcessor +class TEXTEDITOR_EXPORT KeywordsCompletionAssistProcessor : public AsyncProcessor { public: KeywordsCompletionAssistProcessor(const Keywords &keywords); ~KeywordsCompletionAssistProcessor() override = default; - IAssistProposal *perform(const AssistInterface *interface) override; + IAssistProposal *performAsync(AssistInterface *interface) override; void setSnippetGroup(const QString &id); diff --git a/src/plugins/texteditor/codeassist/runner.cpp b/src/plugins/texteditor/codeassist/runner.cpp deleted file mode 100644 index 2f1f25220d..0000000000 --- a/src/plugins/texteditor/codeassist/runner.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "runner.h" -#include "iassistprocessor.h" -#include "iassistproposal.h" -#include "assistinterface.h" -#include "iassistproposalmodel.h" - -using namespace TextEditor; -using namespace Internal; - -ProcessorRunner::ProcessorRunner() = default; - -ProcessorRunner::~ProcessorRunner() -{ - delete m_processor; - if (m_discardProposal && m_proposal) - delete m_proposal; -} - -void ProcessorRunner::setProcessor(IAssistProcessor *computer) -{ - m_processor = computer; -} - -void ProcessorRunner::run() -{ - m_interface->recreateTextDocument(); - m_proposal = m_processor->perform(m_interface); -} - -IAssistProposal *ProcessorRunner::proposal() const -{ - return m_proposal; -} - -void ProcessorRunner::setDiscardProposal(bool discard) -{ - m_discardProposal = discard; -} - -void ProcessorRunner::setAssistInterface(AssistInterface *interface) -{ - m_interface = interface; -} diff --git a/src/plugins/texteditor/codeassist/runner.h b/src/plugins/texteditor/codeassist/runner.h deleted file mode 100644 index 33dd37255e..0000000000 --- a/src/plugins/texteditor/codeassist/runner.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "iassistproposalwidget.h" - -#include <QThread> - -namespace TextEditor { - -class IAssistProcessor; -class IAssistProposal; -class AssistInterface; - -namespace Internal { - -class ProcessorRunner : public QThread -{ - Q_OBJECT - -public: - ProcessorRunner(); - ~ProcessorRunner() override; - - void setProcessor(IAssistProcessor *processor); // Takes ownership of the processor. - void setAssistInterface(AssistInterface *interface); - void setDiscardProposal(bool discard); - - void run() override; - - IAssistProposal *proposal() const; - -private: - IAssistProcessor *m_processor = nullptr; - AssistInterface *m_interface = nullptr; - bool m_discardProposal = false; - IAssistProposal *m_proposal = nullptr; - AssistReason m_reason = IdleEditor; -}; - -} // Internal -} // TextEditor |