"Fossies" - the Fresh Open Source Software Archive

Member "qt-creator-opensource-src-4.15.1/src/plugins/texteditor/codeassist/codeassistant.cpp" (8 Jun 2021, 20898 Bytes) of package /linux/misc/qt-creator-opensource-src-4.15.1.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "codeassistant.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: opensource-src-4.15.0_vs_opensource-src-4.15.1.

    1 /****************************************************************************
    2 **
    3 ** Copyright (C) 2016 The Qt Company Ltd.
    4 ** Contact: https://www.qt.io/licensing/
    5 **
    6 ** This file is part of Qt Creator.
    7 **
    8 ** Commercial License Usage
    9 ** Licensees holding valid commercial Qt licenses may use this file in
   10 ** accordance with the commercial license agreement provided with the
   11 ** Software or, alternatively, in accordance with the terms contained in
   12 ** a written agreement between you and The Qt Company. For licensing terms
   13 ** and conditions see https://www.qt.io/terms-conditions. For further
   14 ** information use the contact form at https://www.qt.io/contact-us.
   15 **
   16 ** GNU General Public License Usage
   17 ** Alternatively, this file may be used under the terms of the GNU
   18 ** General Public License version 3 as published by the Free Software
   19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
   20 ** included in the packaging of this file. Please review the following
   21 ** information to ensure the GNU General Public License requirements will
   22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
   23 **
   24 ****************************************************************************/
   25 
   26 #include "codeassistant.h"
   27 #include "completionassistprovider.h"
   28 #include "iassistprocessor.h"
   29 #include "iassistproposal.h"
   30 #include "iassistproposalmodel.h"
   31 #include "iassistproposalwidget.h"
   32 #include "assistinterface.h"
   33 #include "assistproposalitem.h"
   34 #include "runner.h"
   35 #include "textdocumentmanipulator.h"
   36 
   37 #include <texteditor/textdocument.h>
   38 #include <texteditor/texteditor.h>
   39 #include <texteditor/texteditorsettings.h>
   40 #include <texteditor/completionsettings.h>
   41 #include <coreplugin/editormanager/editormanager.h>
   42 #include <extensionsystem/pluginmanager.h>
   43 #include <utils/algorithm.h>
   44 #include <utils/qtcassert.h>
   45 
   46 #include <QKeyEvent>
   47 #include <QList>
   48 #include <QObject>
   49 #include <QScopedPointer>
   50 #include <QTimer>
   51 
   52 using namespace TextEditor::Internal;
   53 
   54 namespace TextEditor {
   55 
   56 class CodeAssistantPrivate : public QObject
   57 {
   58 public:
   59     CodeAssistantPrivate(CodeAssistant *assistant);
   60 
   61     void configure(TextEditorWidget *editorWidget);
   62     bool isConfigured() const;
   63 
   64     void invoke(AssistKind kind, IAssistProvider *provider = nullptr);
   65     void process();
   66     void requestProposal(AssistReason reason, AssistKind kind, IAssistProvider *provider = nullptr);
   67     void cancelCurrentRequest();
   68     void invalidateCurrentRequestData();
   69     void displayProposal(IAssistProposal *newProposal, AssistReason reason);
   70     bool isDisplayingProposal() const;
   71     bool isWaitingForProposal() const;
   72 
   73     void notifyChange();
   74     bool hasContext() const;
   75     void destroyContext();
   76 
   77     QVariant userData() const;
   78     void setUserData(const QVariant &data);
   79 
   80     CompletionAssistProvider *identifyActivationSequence();
   81 
   82     void stopAutomaticProposalTimer();
   83     void startAutomaticProposalTimer();
   84     void automaticProposalTimeout();
   85     void clearAbortedPosition();
   86     void updateFromCompletionSettings(const TextEditor::CompletionSettings &settings);
   87 
   88     bool eventFilter(QObject *o, QEvent *e) override;
   89 
   90 private:
   91     bool requestActivationCharProposal();
   92     void processProposalItem(AssistProposalItemInterface *proposalItem);
   93     void handlePrefixExpansion(const QString &newPrefix);
   94     void finalizeProposal();
   95     void explicitlyAborted();
   96     bool isDestroyEvent(int key, const QString &keyText);
   97 
   98 private:
   99     CodeAssistant *q = nullptr;
  100     TextEditorWidget *m_editorWidget = nullptr;
  101     Internal::ProcessorRunner *m_requestRunner = nullptr;
  102     QMetaObject::Connection m_runnerConnection;
  103     IAssistProvider *m_requestProvider = nullptr;
  104     IAssistProcessor *m_asyncProcessor = nullptr;
  105     AssistKind m_assistKind = TextEditor::Completion;
  106     IAssistProposalWidget *m_proposalWidget = nullptr;
  107     QScopedPointer<IAssistProposal> m_proposal;
  108     bool m_receivedContentWhileWaiting = false;
  109     QTimer m_automaticProposalTimer;
  110     CompletionSettings m_settings;
  111     int m_abortedBasePosition = -1;
  112     static const QChar m_null;
  113     QVariant m_userData;
  114 };
  115 
  116 // --------------------
  117 // CodeAssistantPrivate
  118 // --------------------
  119 const QChar CodeAssistantPrivate::m_null;
  120 
  121 CodeAssistantPrivate::CodeAssistantPrivate(CodeAssistant *assistant)
  122     : q(assistant)
  123 {
  124     m_automaticProposalTimer.setSingleShot(true);
  125     connect(&m_automaticProposalTimer, &QTimer::timeout,
  126             this, &CodeAssistantPrivate::automaticProposalTimeout);
  127 
  128     m_settings = TextEditorSettings::completionSettings();
  129     connect(TextEditorSettings::instance(), &TextEditorSettings::completionSettingsChanged,
  130             this, &CodeAssistantPrivate::updateFromCompletionSettings);
  131 
  132     connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
  133             this, &CodeAssistantPrivate::clearAbortedPosition);
  134 }
  135 
  136 void CodeAssistantPrivate::configure(TextEditorWidget *editorWidget)
  137 {
  138     m_editorWidget = editorWidget;
  139     m_editorWidget->installEventFilter(this);
  140 }
  141 
  142 bool CodeAssistantPrivate::isConfigured() const
  143 {
  144     return m_editorWidget != nullptr;
  145 }
  146 
  147 void CodeAssistantPrivate::invoke(AssistKind kind, IAssistProvider *provider)
  148 {
  149     if (!isConfigured())
  150         return;
  151 
  152     stopAutomaticProposalTimer();
  153 
  154     if (isDisplayingProposal() && m_assistKind == kind && !m_proposal->isFragile()) {
  155         m_proposalWidget->setReason(ExplicitlyInvoked);
  156         m_proposalWidget->updateProposal(m_editorWidget->textAt(
  157                         m_proposal->basePosition(),
  158                         m_editorWidget->position() - m_proposal->basePosition()));
  159     } else {
  160         destroyContext();
  161         requestProposal(ExplicitlyInvoked, kind, provider);
  162     }
  163 }
  164 
  165 bool CodeAssistantPrivate::requestActivationCharProposal()
  166 {
  167     if (m_assistKind == Completion && m_settings.m_completionTrigger != ManualCompletion) {
  168         if (CompletionAssistProvider *provider = identifyActivationSequence()) {
  169             if (isWaitingForProposal())
  170                 cancelCurrentRequest();
  171             requestProposal(ActivationCharacter, Completion, provider);
  172             return true;
  173         }
  174     }
  175     return false;
  176 }
  177 
  178 void CodeAssistantPrivate::process()
  179 {
  180     if (!isConfigured())
  181         return;
  182 
  183     stopAutomaticProposalTimer();
  184 
  185     if (m_assistKind == TextEditor::Completion) {
  186         if (!requestActivationCharProposal())
  187             startAutomaticProposalTimer();
  188     } else if (m_assistKind != FunctionHint){
  189         m_assistKind = TextEditor::Completion;
  190     }
  191 }
  192 
  193 void CodeAssistantPrivate::requestProposal(AssistReason reason,
  194                                            AssistKind kind,
  195                                            IAssistProvider *provider)
  196 {
  197     QTC_ASSERT(!isWaitingForProposal(), return);
  198 
  199     if (m_editorWidget->hasBlockSelection())
  200         return; // TODO
  201 
  202     if (!provider) {
  203         if (kind == Completion)
  204             provider = m_editorWidget->textDocument()->completionAssistProvider();
  205         else if (kind == FunctionHint)
  206             provider = m_editorWidget->textDocument()->functionHintAssistProvider();
  207         else
  208             provider = m_editorWidget->textDocument()->quickFixAssistProvider();
  209 
  210         if (!provider)
  211             return;
  212     }
  213 
  214     AssistInterface *assistInterface = m_editorWidget->createAssistInterface(kind, reason);
  215     if (!assistInterface)
  216         return;
  217 
  218     m_assistKind = kind;
  219     m_requestProvider = provider;
  220     IAssistProcessor *processor = provider->createProcessor();
  221 
  222     switch (provider->runType()) {
  223     case IAssistProvider::Synchronous: {
  224         if (IAssistProposal *newProposal = processor->perform(assistInterface))
  225             displayProposal(newProposal, reason);
  226         delete processor;
  227         break;
  228     }
  229     case IAssistProvider::AsynchronousWithThread: {
  230         if (IAssistProposal *newProposal = processor->immediateProposal(assistInterface))
  231             displayProposal(newProposal, reason);
  232 
  233         m_requestRunner = new ProcessorRunner;
  234         m_runnerConnection = connect(m_requestRunner, &ProcessorRunner::finished,
  235                                      this, [this, reason](){
  236             // Since the request runner is a different thread, there's still a gap in which the
  237             // queued signal could be processed after an invalidation of the current request.
  238             if (!m_requestRunner || m_requestRunner != sender())
  239                 return;
  240 
  241             IAssistProposal *proposal = m_requestRunner->proposal();
  242             invalidateCurrentRequestData();
  243             displayProposal(proposal, reason);
  244             emit q->finished();
  245         });
  246         connect(m_requestRunner, &ProcessorRunner::finished,
  247                 m_requestRunner, &ProcessorRunner::deleteLater);
  248         assistInterface->prepareForAsyncUse();
  249         m_requestRunner->setProcessor(processor);
  250         m_requestRunner->setAssistInterface(assistInterface);
  251         m_requestRunner->start();
  252         break;
  253     }
  254     case IAssistProvider::Asynchronous: {
  255         processor->setAsyncCompletionAvailableHandler([this, reason, processor](IAssistProposal *newProposal) {
  256             // do not delete this processor directly since this function is called from within the processor
  257             QMetaObject::invokeMethod(QCoreApplication::instance(), [processor]() {
  258                 delete processor;
  259             }, Qt::QueuedConnection);
  260             if (processor != m_asyncProcessor)
  261                 return;
  262             invalidateCurrentRequestData();
  263             if (processor && processor->needsRestart() && m_receivedContentWhileWaiting) {
  264                 delete newProposal;
  265                 m_receivedContentWhileWaiting = false;
  266                 requestProposal(reason, m_assistKind, m_requestProvider);
  267             } else {
  268                 displayProposal(newProposal, reason);
  269                 emit q->finished();
  270             }
  271         });
  272 
  273         // If there is a proposal, nothing asynchronous happened...
  274         if (IAssistProposal *newProposal = processor->perform(assistInterface)) {
  275             displayProposal(newProposal, reason);
  276             delete processor;
  277         } else if (!processor->running()) {
  278             delete processor;
  279         } else { // ...async request was triggered
  280             QTC_CHECK(!m_asyncProcessor);
  281             m_asyncProcessor = processor;
  282         }
  283 
  284         break;
  285     }
  286     } // switch
  287 }
  288 
  289 void CodeAssistantPrivate::cancelCurrentRequest()
  290 {
  291     if (m_requestRunner) {
  292         m_requestRunner->setDiscardProposal(true);
  293         disconnect(m_runnerConnection);
  294     }
  295     if (m_asyncProcessor) {
  296         m_asyncProcessor->cancel();
  297         delete m_asyncProcessor;
  298     }
  299     invalidateCurrentRequestData();
  300 }
  301 
  302 void CodeAssistantPrivate::displayProposal(IAssistProposal *newProposal, AssistReason reason)
  303 {
  304     if (!newProposal)
  305         return;
  306 
  307     // TODO: The proposal should own the model until someone takes it explicitly away.
  308     QScopedPointer<IAssistProposal> proposalCandidate(newProposal);
  309 
  310     if (isDisplayingProposal() && !m_proposal->isFragile())
  311         return;
  312 
  313     int basePosition = proposalCandidate->basePosition();
  314     if (m_editorWidget->position() < basePosition) {
  315         destroyContext();
  316         return;
  317     }
  318 
  319     if (m_abortedBasePosition == basePosition && reason != ExplicitlyInvoked) {
  320         destroyContext();
  321         return;
  322     }
  323 
  324     const QString prefix = m_editorWidget->textAt(basePosition,
  325                                                   m_editorWidget->position() - basePosition);
  326     if (!newProposal->hasItemsToPropose(prefix, reason)) {
  327         if (newProposal->isCorrective(m_editorWidget))
  328             newProposal->makeCorrection(m_editorWidget);
  329         return;
  330     }
  331 
  332     destroyContext();
  333 
  334     clearAbortedPosition();
  335     m_proposal.reset(proposalCandidate.take());
  336 
  337     if (m_proposal->isCorrective(m_editorWidget))
  338         m_proposal->makeCorrection(m_editorWidget);
  339 
  340     m_editorWidget->keepAutoCompletionHighlight(true);
  341     basePosition = m_proposal->basePosition();
  342     m_proposalWidget = m_proposal->createWidget();
  343     connect(m_proposalWidget, &QObject::destroyed,
  344             this, &CodeAssistantPrivate::finalizeProposal);
  345     connect(m_proposalWidget, &IAssistProposalWidget::prefixExpanded,
  346             this, &CodeAssistantPrivate::handlePrefixExpansion);
  347     connect(m_proposalWidget, &IAssistProposalWidget::proposalItemActivated,
  348             this, &CodeAssistantPrivate::processProposalItem);
  349     connect(m_proposalWidget, &IAssistProposalWidget::explicitlyAborted,
  350             this, &CodeAssistantPrivate::explicitlyAborted);
  351     m_proposalWidget->setAssistant(q);
  352     m_proposalWidget->setReason(reason);
  353     m_proposalWidget->setKind(m_assistKind);
  354     m_proposalWidget->setBasePosition(basePosition);
  355     m_proposalWidget->setUnderlyingWidget(m_editorWidget);
  356     m_proposalWidget->setModel(m_proposal->model());
  357     m_proposalWidget->setDisplayRect(m_editorWidget->cursorRect(basePosition));
  358     m_proposalWidget->setIsSynchronized(!m_receivedContentWhileWaiting);
  359     m_proposalWidget->showProposal(prefix);
  360 }
  361 
  362 void CodeAssistantPrivate::processProposalItem(AssistProposalItemInterface *proposalItem)
  363 {
  364     QTC_ASSERT(m_proposal, return);
  365     TextDocumentManipulator manipulator(m_editorWidget);
  366     proposalItem->apply(manipulator, m_proposal->basePosition());
  367     destroyContext();
  368     m_editorWidget->encourageApply();
  369     if (!proposalItem->isSnippet())
  370         requestActivationCharProposal();
  371 }
  372 
  373 void CodeAssistantPrivate::handlePrefixExpansion(const QString &newPrefix)
  374 {
  375     QTC_ASSERT(m_proposal, return);
  376 
  377     QTextCursor cursor(m_editorWidget->document());
  378     cursor.setPosition(m_proposal->basePosition());
  379     cursor.movePosition(QTextCursor::EndOfWord);
  380 
  381     int currentPosition = m_editorWidget->position();
  382     const QString textAfterCursor = m_editorWidget->textAt(currentPosition,
  383                                                        cursor.position() - currentPosition);
  384     if (!textAfterCursor.startsWith(newPrefix)) {
  385         if (newPrefix.indexOf(textAfterCursor, currentPosition - m_proposal->basePosition()) >= 0)
  386             currentPosition = cursor.position();
  387         const QStringView prefixAddition = QStringView(newPrefix).mid(currentPosition
  388                                                                       - m_proposal->basePosition());
  389         // If remaining string starts with the prefix addition
  390         if (textAfterCursor.startsWith(prefixAddition))
  391             currentPosition += prefixAddition.size();
  392     }
  393 
  394     m_editorWidget->setCursorPosition(m_proposal->basePosition());
  395     m_editorWidget->replace(currentPosition - m_proposal->basePosition(), newPrefix);
  396     notifyChange();
  397 }
  398 
  399 void CodeAssistantPrivate::finalizeProposal()
  400 {
  401     stopAutomaticProposalTimer();
  402     m_proposal.reset();
  403     m_proposalWidget = nullptr;
  404     if (m_receivedContentWhileWaiting)
  405         m_receivedContentWhileWaiting = false;
  406 }
  407 
  408 bool CodeAssistantPrivate::isDisplayingProposal() const
  409 {
  410     return m_proposalWidget != nullptr && m_proposalWidget->proposalIsVisible();
  411 }
  412 
  413 bool CodeAssistantPrivate::isWaitingForProposal() const
  414 {
  415     return m_requestRunner != nullptr || m_asyncProcessor != nullptr;
  416 }
  417 
  418 void CodeAssistantPrivate::invalidateCurrentRequestData()
  419 {
  420     m_asyncProcessor = nullptr;
  421     m_requestRunner = nullptr;
  422     m_requestProvider = nullptr;
  423 }
  424 
  425 CompletionAssistProvider *CodeAssistantPrivate::identifyActivationSequence()
  426 {
  427     auto checkActivationSequence = [this](CompletionAssistProvider *provider) {
  428         if (!provider)
  429             return false;
  430         const int length = provider->activationCharSequenceLength();
  431         if (!length)
  432             return false;
  433         QString sequence = m_editorWidget->textAt(m_editorWidget->position() - length, length);
  434         // In pretty much all cases the sequence will have the appropriate length. Only in the
  435         // case of typing the very first characters in the document for providers that request a
  436         // length greater than 1 (currently only C++, which specifies 3), the sequence needs to
  437         // be prepended so it has the expected length.
  438         const int lengthDiff = length - sequence.length();
  439         for (int j = 0; j < lengthDiff; ++j)
  440             sequence.prepend(m_null);
  441         return provider->isActivationCharSequence(sequence);
  442     };
  443 
  444     auto provider = {
  445         m_editorWidget->textDocument()->completionAssistProvider(),
  446         m_editorWidget->textDocument()->functionHintAssistProvider()
  447     };
  448     return Utils::findOrDefault(provider, checkActivationSequence);
  449 }
  450 
  451 void CodeAssistantPrivate::notifyChange()
  452 {
  453     stopAutomaticProposalTimer();
  454 
  455     if (isDisplayingProposal()) {
  456         QTC_ASSERT(m_proposal, return);
  457         if (m_editorWidget->position() < m_proposal->basePosition()) {
  458             destroyContext();
  459         } else if (m_proposal->supportsPrefix()) {
  460             m_proposalWidget->updateProposal(
  461                 m_editorWidget->textAt(m_proposal->basePosition(),
  462                                      m_editorWidget->position() - m_proposal->basePosition()));
  463             if (!isDisplayingProposal())
  464                 requestActivationCharProposal();
  465         } else {
  466             destroyContext();
  467             requestProposal(ExplicitlyInvoked, m_assistKind, m_requestProvider);
  468         }
  469     }
  470 }
  471 
  472 bool CodeAssistantPrivate::hasContext() const
  473 {
  474     return m_requestRunner || m_asyncProcessor || m_proposalWidget;
  475 }
  476 
  477 void CodeAssistantPrivate::destroyContext()
  478 {
  479     stopAutomaticProposalTimer();
  480 
  481     if (isWaitingForProposal()) {
  482         cancelCurrentRequest();
  483     } else if (m_proposalWidget) {
  484         m_editorWidget->keepAutoCompletionHighlight(false);
  485         if (m_proposalWidget->proposalIsVisible())
  486             m_proposalWidget->closeProposal();
  487         disconnect(m_proposalWidget, &QObject::destroyed,
  488                    this, &CodeAssistantPrivate::finalizeProposal);
  489         finalizeProposal();
  490     }
  491 }
  492 
  493 QVariant CodeAssistantPrivate::userData() const
  494 {
  495     return m_userData;
  496 }
  497 
  498 void CodeAssistantPrivate::setUserData(const QVariant &data)
  499 {
  500     m_userData = data;
  501 }
  502 
  503 void CodeAssistantPrivate::startAutomaticProposalTimer()
  504 {
  505     if (m_settings.m_completionTrigger == AutomaticCompletion)
  506         m_automaticProposalTimer.start();
  507 }
  508 
  509 void CodeAssistantPrivate::automaticProposalTimeout()
  510 {
  511     if (isWaitingForProposal() || (isDisplayingProposal() && !m_proposal->isFragile()))
  512         return;
  513 
  514     requestProposal(IdleEditor, Completion);
  515 }
  516 
  517 void CodeAssistantPrivate::stopAutomaticProposalTimer()
  518 {
  519     if (m_automaticProposalTimer.isActive())
  520         m_automaticProposalTimer.stop();
  521 }
  522 
  523 void CodeAssistantPrivate::updateFromCompletionSettings(
  524         const TextEditor::CompletionSettings &settings)
  525 {
  526     m_settings = settings;
  527     m_automaticProposalTimer.setInterval(m_settings.m_automaticProposalTimeoutInMs);
  528 }
  529 
  530 void CodeAssistantPrivate::explicitlyAborted()
  531 {
  532     QTC_ASSERT(m_proposal, return);
  533     m_abortedBasePosition = m_proposal->basePosition();
  534 }
  535 
  536 void CodeAssistantPrivate::clearAbortedPosition()
  537 {
  538     m_abortedBasePosition = -1;
  539 }
  540 
  541 bool CodeAssistantPrivate::isDestroyEvent(int key, const QString &keyText)
  542 {
  543     if (keyText.isEmpty())
  544         return key != Qt::LeftArrow && key != Qt::RightArrow && key != Qt::Key_Shift;
  545     if (auto provider = qobject_cast<CompletionAssistProvider *>(m_requestProvider))
  546         return !provider->isContinuationChar(keyText.at(0));
  547     return false;
  548 }
  549 
  550 bool CodeAssistantPrivate::eventFilter(QObject *o, QEvent *e)
  551 {
  552     Q_UNUSED(o)
  553 
  554     if (isWaitingForProposal()) {
  555         QEvent::Type type = e->type();
  556         if (type == QEvent::FocusOut) {
  557             destroyContext();
  558         } else if (type == QEvent::KeyPress) {
  559             auto keyEvent = static_cast<QKeyEvent *>(e);
  560             const QString &keyText = keyEvent->text();
  561 
  562             if (isDestroyEvent(keyEvent->key(), keyText))
  563                 destroyContext();
  564             else if (!keyText.isEmpty() && !m_receivedContentWhileWaiting)
  565                 m_receivedContentWhileWaiting = true;
  566         }
  567     }
  568 
  569     return false;
  570 }
  571 
  572 // -------------
  573 // CodeAssistant
  574 // -------------
  575 CodeAssistant::CodeAssistant() : d(new CodeAssistantPrivate(this))
  576 {
  577 }
  578 
  579 CodeAssistant::~CodeAssistant()
  580 {
  581     destroyContext();
  582     delete d;
  583 }
  584 
  585 void CodeAssistant::configure(TextEditorWidget *editorWidget)
  586 {
  587     d->configure(editorWidget);
  588 }
  589 
  590 void CodeAssistant::process()
  591 {
  592     d->process();
  593 }
  594 
  595 void CodeAssistant::notifyChange()
  596 {
  597     d->notifyChange();
  598 }
  599 
  600 bool CodeAssistant::hasContext() const
  601 {
  602     return d->hasContext();
  603 }
  604 
  605 void CodeAssistant::destroyContext()
  606 {
  607     d->destroyContext();
  608 }
  609 
  610 QVariant CodeAssistant::userData() const
  611 {
  612     return d->userData();
  613 }
  614 
  615 void CodeAssistant::setUserData(const QVariant &data)
  616 {
  617     d->setUserData(data);
  618 }
  619 
  620 void CodeAssistant::invoke(AssistKind kind, IAssistProvider *provider)
  621 {
  622     d->invoke(kind, provider);
  623 }
  624 
  625 } // namespace TextEditor