2018-07-13 12:33:46 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2018 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** 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 The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2019-01-29 13:20:58 +01:00
|
|
|
#include "languageclientcompletionassist.h"
|
2019-01-25 09:48:44 +01:00
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
#include "client.h"
|
2019-01-25 09:48:44 +01:00
|
|
|
#include "languageclientutils.h"
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
#include <languageserverprotocol/completion.h>
|
|
|
|
|
#include <texteditor/codeassist/assistinterface.h>
|
|
|
|
|
#include <texteditor/codeassist/assistproposalitem.h>
|
|
|
|
|
#include <texteditor/codeassist/iassistprocessor.h>
|
|
|
|
|
#include <texteditor/codeassist/genericproposal.h>
|
|
|
|
|
#include <texteditor/codeassist/genericproposalmodel.h>
|
|
|
|
|
#include <utils/algorithm.h>
|
|
|
|
|
#include <utils/textutils.h>
|
|
|
|
|
#include <utils/utilsicons.h>
|
|
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QLoggingCategory>
|
2019-05-08 12:50:08 +02:00
|
|
|
#include <QRegularExpression>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <QTextBlock>
|
|
|
|
|
#include <QTextDocument>
|
|
|
|
|
#include <QTime>
|
|
|
|
|
|
2018-10-12 09:33:30 +03:00
|
|
|
static Q_LOGGING_CATEGORY(LOGLSPCOMPLETION, "qtc.languageclient.completion", QtWarningMsg);
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
using namespace LanguageServerProtocol;
|
2019-01-29 11:23:59 +01:00
|
|
|
using namespace TextEditor;
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
namespace LanguageClient {
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
class LanguageClientCompletionItem : public AssistProposalItemInterface
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
LanguageClientCompletionItem(CompletionItem item);
|
|
|
|
|
|
|
|
|
|
// AssistProposalItemInterface interface
|
|
|
|
|
QString text() const override;
|
|
|
|
|
bool implicitlyApplies() const override;
|
|
|
|
|
bool prematurelyApplies(const QChar &typedCharacter) const override;
|
2019-01-29 11:23:59 +01:00
|
|
|
void apply(TextDocumentManipulatorInterface &manipulator, int basePosition) const override;
|
2018-07-13 12:33:46 +02:00
|
|
|
QIcon icon() const override;
|
|
|
|
|
QString detail() const override;
|
|
|
|
|
bool isSnippet() const override;
|
|
|
|
|
bool isValid() const override;
|
|
|
|
|
quint64 hash() const override;
|
|
|
|
|
|
|
|
|
|
const QString &sortText() const;
|
|
|
|
|
|
|
|
|
|
bool operator <(const LanguageClientCompletionItem &other) const;
|
|
|
|
|
|
2018-10-18 10:57:26 +02:00
|
|
|
bool isPerfectMatch(int pos, QTextDocument *doc) const;
|
|
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
private:
|
|
|
|
|
CompletionItem m_item;
|
2019-05-06 08:25:30 +02:00
|
|
|
mutable QChar m_triggeredCommitCharacter;
|
2018-07-13 12:33:46 +02:00
|
|
|
mutable QString m_sortText;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item)
|
|
|
|
|
: m_item(std::move(item))
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
QString LanguageClientCompletionItem::text() const
|
|
|
|
|
{ return m_item.label(); }
|
|
|
|
|
|
|
|
|
|
bool LanguageClientCompletionItem::implicitlyApplies() const
|
2018-09-10 12:58:53 +02:00
|
|
|
{ return false; }
|
2018-07-13 12:33:46 +02:00
|
|
|
|
2019-05-06 08:25:30 +02:00
|
|
|
bool LanguageClientCompletionItem::prematurelyApplies(const QChar &typedCharacter) const
|
|
|
|
|
{
|
|
|
|
|
if (m_item.commitCharacters().has_value() && m_item.commitCharacters().value().contains(typedCharacter)) {
|
|
|
|
|
m_triggeredCommitCharacter = typedCharacter;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
|
2018-07-13 12:33:46 +02:00
|
|
|
int /*basePosition*/) const
|
|
|
|
|
{
|
|
|
|
|
const int pos = manipulator.currentPosition();
|
|
|
|
|
if (auto edit = m_item.textEdit()) {
|
|
|
|
|
applyTextEdit(manipulator, *edit);
|
|
|
|
|
} else {
|
2019-05-08 12:50:08 +02:00
|
|
|
const QString textToInsert(m_item.insertText().value_or(text()));
|
|
|
|
|
int length = 0;
|
|
|
|
|
for (auto it = textToInsert.crbegin(), end = textToInsert.crend(); it != end; ++it) {
|
|
|
|
|
if (it->toLower() != manipulator.characterAt(pos - length - 1).toLower()) {
|
|
|
|
|
length = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
++length;
|
|
|
|
|
}
|
2019-05-03 15:04:40 +02:00
|
|
|
QTextCursor cursor = manipulator.textCursorAt(pos);
|
2019-05-08 12:50:08 +02:00
|
|
|
cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
|
|
|
|
|
const QString blockTextUntilPosition = cursor.selectedText();
|
|
|
|
|
static QRegularExpression identifier("[a-zA-Z_][a-zA-Z0-9_]*$");
|
|
|
|
|
QRegularExpressionMatch match = identifier.match(blockTextUntilPosition);
|
|
|
|
|
int matchLength = match.hasMatch() ? match.capturedLength(0) : 0;
|
|
|
|
|
length = qMax(length, matchLength);
|
|
|
|
|
manipulator.replace(pos - length, length, textToInsert);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (auto additionalEdits = m_item.additionalTextEdits()) {
|
|
|
|
|
for (const auto &edit : *additionalEdits)
|
|
|
|
|
applyTextEdit(manipulator, edit);
|
|
|
|
|
}
|
2019-05-06 08:25:30 +02:00
|
|
|
if (!m_triggeredCommitCharacter.isNull())
|
|
|
|
|
manipulator.insertCodeSnippet(manipulator.currentPosition(), m_triggeredCommitCharacter);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QIcon LanguageClientCompletionItem::icon() const
|
|
|
|
|
{
|
|
|
|
|
QIcon icon;
|
|
|
|
|
using namespace Utils::CodeModelIcon;
|
|
|
|
|
const int kind = m_item.kind().value_or(CompletionItemKind::Text);
|
|
|
|
|
switch (kind) {
|
|
|
|
|
case CompletionItemKind::Method:
|
|
|
|
|
case CompletionItemKind::Function:
|
|
|
|
|
case CompletionItemKind::Constructor: icon = iconForType(FuncPublic); break;
|
2019-10-31 16:20:45 +01:00
|
|
|
case CompletionItemKind::Field:
|
2018-07-13 12:33:46 +02:00
|
|
|
case CompletionItemKind::Variable: icon = iconForType(VarPublic); break;
|
|
|
|
|
case CompletionItemKind::Class: icon = iconForType(Class); break;
|
|
|
|
|
case CompletionItemKind::Module: icon = iconForType(Namespace); break;
|
|
|
|
|
case CompletionItemKind::Property: icon = iconForType(Property); break;
|
|
|
|
|
case CompletionItemKind::Enum: icon = iconForType(Enum); break;
|
|
|
|
|
case CompletionItemKind::Keyword: icon = iconForType(Keyword); break;
|
|
|
|
|
case CompletionItemKind::Snippet: icon = QIcon(":/texteditor/images/snippet.png"); break;
|
|
|
|
|
case CompletionItemKind::EnumMember: icon = iconForType(Enumerator); break;
|
|
|
|
|
case CompletionItemKind::Struct: icon = iconForType(Struct); break;
|
2019-04-24 12:19:03 +02:00
|
|
|
default: icon = iconForType(Unknown); break;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
return icon;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString LanguageClientCompletionItem::detail() const
|
|
|
|
|
{
|
|
|
|
|
if (auto _doc = m_item.documentation()) {
|
|
|
|
|
auto doc = *_doc;
|
|
|
|
|
QString detailDocText;
|
|
|
|
|
if (Utils::holds_alternative<QString>(doc)) {
|
|
|
|
|
detailDocText = Utils::get<QString>(doc);
|
|
|
|
|
} else if (Utils::holds_alternative<MarkupContent>(doc)) {
|
|
|
|
|
// TODO markdown parser?
|
|
|
|
|
detailDocText = Utils::get<MarkupContent>(doc).content();
|
|
|
|
|
}
|
|
|
|
|
if (!detailDocText.isEmpty())
|
|
|
|
|
return detailDocText;
|
|
|
|
|
}
|
|
|
|
|
return m_item.detail().value_or(text());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LanguageClientCompletionItem::isSnippet() const
|
|
|
|
|
{
|
|
|
|
|
// FIXME add lsp > creator snippet converter
|
|
|
|
|
// return m_item.insertTextFormat().value_or(CompletionItem::PlainText);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LanguageClientCompletionItem::isValid() const
|
|
|
|
|
{
|
|
|
|
|
return m_item.isValid(nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quint64 LanguageClientCompletionItem::hash() const
|
|
|
|
|
{
|
|
|
|
|
return qHash(m_item.label()); // TODO: naaaa
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString &LanguageClientCompletionItem::sortText() const
|
|
|
|
|
{
|
|
|
|
|
if (m_sortText.isEmpty())
|
|
|
|
|
m_sortText = m_item.sortText().has_value() ? *m_item.sortText() : m_item.label();
|
|
|
|
|
return m_sortText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LanguageClientCompletionItem::operator <(const LanguageClientCompletionItem &other) const
|
|
|
|
|
{
|
|
|
|
|
return sortText() < other.sortText();
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-18 10:57:26 +02:00
|
|
|
bool LanguageClientCompletionItem::isPerfectMatch(int pos, QTextDocument *doc) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(doc, return false);
|
|
|
|
|
using namespace Utils::Text;
|
|
|
|
|
if (auto additionalEdits = m_item.additionalTextEdits()) {
|
|
|
|
|
if (!additionalEdits.value().isEmpty())
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (auto edit = m_item.textEdit()) {
|
|
|
|
|
auto range = edit->range();
|
|
|
|
|
const int start = positionInText(doc, range.start().line() + 1, range.start().character() + 1);
|
|
|
|
|
const int end = positionInText(doc, range.end().line() + 1, range.end().character() + 1);
|
|
|
|
|
auto text = textAt(QTextCursor(doc), start, end - start);
|
|
|
|
|
return text == edit->newText();
|
|
|
|
|
}
|
|
|
|
|
const QString textToInsert(m_item.insertText().value_or(text()));
|
|
|
|
|
const int length = textToInsert.length();
|
|
|
|
|
return textToInsert == textAt(QTextCursor(doc), pos - length, length);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
class LanguageClientCompletionModel : public GenericProposalModel
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
// GenericProposalModel interface
|
|
|
|
|
bool isSortable(const QString &/*prefix*/) const override { return true; }
|
|
|
|
|
void sort(const QString &/*prefix*/) override;
|
|
|
|
|
bool supportsPrefixExpansion() const override { return false; }
|
2018-10-18 10:57:26 +02:00
|
|
|
|
|
|
|
|
QList<LanguageClientCompletionItem *> items() const
|
|
|
|
|
{ return Utils::static_container_cast<LanguageClientCompletionItem *>(m_currentItems); }
|
2018-07-13 12:33:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void LanguageClientCompletionModel::sort(const QString &/*prefix*/)
|
|
|
|
|
{
|
|
|
|
|
std::sort(m_currentItems.begin(), m_currentItems.end(),
|
|
|
|
|
[] (AssistProposalItemInterface *a, AssistProposalItemInterface *b){
|
|
|
|
|
return *(dynamic_cast<LanguageClientCompletionItem *>(a)) < *(
|
|
|
|
|
dynamic_cast<LanguageClientCompletionItem *>(b));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
class LanguageClientCompletionProposal : public GenericProposal
|
2018-10-18 10:57:26 +02:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
LanguageClientCompletionProposal(int cursorPos, LanguageClientCompletionModel *model)
|
2019-01-29 11:23:59 +01:00
|
|
|
: GenericProposal(cursorPos, GenericProposalModelPtr(model))
|
2018-10-18 10:57:26 +02:00
|
|
|
, m_model(model)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
// IAssistProposal interface
|
2019-01-29 11:23:59 +01:00
|
|
|
bool hasItemsToPropose(const QString &/*text*/, AssistReason reason) const override
|
2018-10-18 10:57:26 +02:00
|
|
|
{
|
|
|
|
|
if (m_model->size() <= 0 || m_document.isNull())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return m_model->keepPerfectMatch(reason)
|
|
|
|
|
|| !Utils::anyOf(m_model->items(), [this](LanguageClientCompletionItem *item){
|
|
|
|
|
return item->isPerfectMatch(m_pos, m_document);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LanguageClientCompletionModel *m_model;
|
|
|
|
|
QPointer<QTextDocument> m_document;
|
|
|
|
|
int m_pos = -1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
class LanguageClientCompletionAssistProcessor : public IAssistProcessor
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2019-01-31 12:15:43 +01:00
|
|
|
LanguageClientCompletionAssistProcessor(Client *client);
|
2020-06-17 14:15:13 +02:00
|
|
|
~LanguageClientCompletionAssistProcessor() override;
|
2019-01-29 11:23:59 +01:00
|
|
|
IAssistProposal *perform(const AssistInterface *interface) override;
|
2018-07-13 12:33:46 +02:00
|
|
|
bool running() override;
|
2018-09-20 14:31:50 +02:00
|
|
|
bool needsRestart() const override { return true; }
|
2020-02-12 14:06:45 +01:00
|
|
|
void cancel() override;
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
private:
|
2018-11-20 07:45:22 +01:00
|
|
|
void handleCompletionResponse(const CompletionRequest::Response &response);
|
2018-07-13 12:33:46 +02:00
|
|
|
|
2018-10-18 10:57:26 +02:00
|
|
|
QPointer<QTextDocument> m_document;
|
2019-01-31 12:15:43 +01:00
|
|
|
QPointer<Client> m_client;
|
2020-05-14 08:15:39 +02:00
|
|
|
Utils::optional<MessageId> m_currentRequest;
|
2020-06-17 14:15:13 +02:00
|
|
|
QMetaObject::Connection m_postponedUpdateConnection;
|
2018-07-13 12:33:46 +02:00
|
|
|
int m_pos = -1;
|
|
|
|
|
};
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(Client *client)
|
2018-07-13 12:33:46 +02:00
|
|
|
: m_client(client)
|
|
|
|
|
{ }
|
|
|
|
|
|
2020-06-17 14:15:13 +02:00
|
|
|
LanguageClientCompletionAssistProcessor::~LanguageClientCompletionAssistProcessor()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!running(), cancel());
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
static QString assistReasonString(AssistReason reason)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
switch (reason) {
|
2019-01-29 11:23:59 +01:00
|
|
|
case IdleEditor: return QString("idle editor");
|
|
|
|
|
case ActivationCharacter: return QString("activation character");
|
|
|
|
|
case ExplicitlyInvoked: return QString("explicitly invoking");
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
return QString("unknown reason");
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistInterface *interface)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_client, return nullptr);
|
|
|
|
|
m_pos = interface->position();
|
2019-01-29 11:23:59 +01:00
|
|
|
if (interface->reason() == IdleEditor) {
|
2018-10-09 14:30:41 +02:00
|
|
|
// Trigger an automatic completion request only when we are on a word with more than 2 "identifier" character
|
2020-06-18 13:06:27 +02:00
|
|
|
const QRegularExpression regexp("[_a-zA-Z0-9]+");
|
|
|
|
|
auto hasMatch = [®exp](const QString &txt) { return regexp.match(txt).hasMatch(); };
|
2018-10-09 14:30:41 +02:00
|
|
|
int delta = 0;
|
2020-06-18 13:06:27 +02:00
|
|
|
while (m_pos - delta > 0 && hasMatch(interface->textAt(m_pos - delta - 1, delta + 1)))
|
2018-10-09 14:30:41 +02:00
|
|
|
++delta;
|
|
|
|
|
if (delta < 3)
|
|
|
|
|
return nullptr;
|
2020-06-17 14:15:13 +02:00
|
|
|
if (m_client->documentUpdatePostponed(interface->fileName())) {
|
|
|
|
|
m_postponedUpdateConnection
|
|
|
|
|
= QObject::connect(m_client,
|
|
|
|
|
&Client::documentUpdated,
|
|
|
|
|
[this, interface](TextEditor::TextDocument *document) {
|
|
|
|
|
if (document->filePath()
|
|
|
|
|
== Utils::FilePath::fromString(interface->fileName()))
|
|
|
|
|
perform(interface);
|
|
|
|
|
});
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2018-10-09 14:30:41 +02:00
|
|
|
}
|
2020-06-17 14:15:13 +02:00
|
|
|
if (m_postponedUpdateConnection)
|
|
|
|
|
QObject::disconnect(m_postponedUpdateConnection);
|
2018-07-13 12:33:46 +02:00
|
|
|
CompletionRequest completionRequest;
|
|
|
|
|
CompletionParams::CompletionContext context;
|
2020-05-09 02:46:49 +02:00
|
|
|
if (interface->reason() == ActivationCharacter) {
|
|
|
|
|
context.setTriggerKind(CompletionParams::TriggerCharacter);
|
|
|
|
|
QChar triggerCharacter = interface->characterAt(interface->position() - 1);
|
|
|
|
|
if (!triggerCharacter.isNull())
|
|
|
|
|
context.setTriggerCharacter(triggerCharacter);
|
|
|
|
|
} else {
|
|
|
|
|
context.setTriggerKind(CompletionParams::Invoked);
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
auto params = completionRequest.params().value_or(CompletionParams());
|
|
|
|
|
int line;
|
|
|
|
|
int column;
|
|
|
|
|
if (!Utils::Text::convertPosition(interface->textDocument(), m_pos, &line, &column))
|
|
|
|
|
return nullptr;
|
|
|
|
|
--line; // line is 0 based in the protocol
|
2018-10-17 10:06:02 +02:00
|
|
|
--column; // column is 0 based in the protocol
|
2018-07-13 12:33:46 +02:00
|
|
|
params.setPosition({line, column});
|
2019-04-24 10:57:11 +02:00
|
|
|
params.setContext(context);
|
2018-07-13 12:33:46 +02:00
|
|
|
params.setTextDocument(
|
2019-09-11 14:34:20 +02:00
|
|
|
DocumentUri::fromFilePath(Utils::FilePath::fromString(interface->fileName())));
|
2018-07-13 12:33:46 +02:00
|
|
|
completionRequest.setResponseCallback([this](auto response) {
|
|
|
|
|
this->handleCompletionResponse(response);
|
|
|
|
|
});
|
|
|
|
|
completionRequest.setParams(params);
|
|
|
|
|
m_client->sendContent(completionRequest);
|
2020-03-26 09:21:57 +01:00
|
|
|
m_client->addAssistProcessor(this);
|
2020-02-12 14:06:45 +01:00
|
|
|
m_currentRequest = completionRequest.id();
|
2018-10-18 10:57:26 +02:00
|
|
|
m_document = interface->textDocument();
|
2018-07-13 12:33:46 +02:00
|
|
|
qCDebug(LOGLSPCOMPLETION) << QTime::currentTime()
|
|
|
|
|
<< " : request completions at " << m_pos
|
|
|
|
|
<< " by " << assistReasonString(interface->reason());
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LanguageClientCompletionAssistProcessor::running()
|
|
|
|
|
{
|
2020-06-17 14:15:13 +02:00
|
|
|
return m_currentRequest.has_value() || m_postponedUpdateConnection;
|
2020-02-12 14:06:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LanguageClientCompletionAssistProcessor::cancel()
|
|
|
|
|
{
|
2020-06-17 14:15:13 +02:00
|
|
|
if (m_currentRequest.has_value()) {
|
2020-05-14 08:15:39 +02:00
|
|
|
m_client->cancelRequest(m_currentRequest.value());
|
2020-03-26 09:21:57 +01:00
|
|
|
m_client->removeAssistProcessor(this);
|
2020-05-14 08:15:39 +02:00
|
|
|
m_currentRequest.reset();
|
2020-06-17 14:15:13 +02:00
|
|
|
} else if (m_postponedUpdateConnection) {
|
|
|
|
|
QObject::disconnect(m_postponedUpdateConnection);
|
2020-02-12 14:06:45 +01:00
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LanguageClientCompletionAssistProcessor::handleCompletionResponse(
|
2018-11-20 07:45:22 +01:00
|
|
|
const CompletionRequest::Response &response)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2020-02-26 08:50:43 +01:00
|
|
|
// We must report back to the code assistant under all circumstances
|
2018-07-13 12:33:46 +02:00
|
|
|
qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : got completions";
|
2020-05-14 08:15:39 +02:00
|
|
|
m_currentRequest.reset();
|
2020-03-02 14:19:44 +01:00
|
|
|
QTC_ASSERT(m_client, setAsyncProposalAvailable(nullptr); return);
|
|
|
|
|
if (auto error = response.error())
|
2018-11-20 09:44:22 +01:00
|
|
|
m_client->log(error.value());
|
2020-03-02 14:19:44 +01:00
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
const Utils::optional<CompletionResult> &result = response.result();
|
2020-03-02 14:19:44 +01:00
|
|
|
if (!result || Utils::holds_alternative<std::nullptr_t>(*result)) {
|
|
|
|
|
setAsyncProposalAvailable(nullptr);
|
2020-03-26 09:21:57 +01:00
|
|
|
m_client->removeAssistProcessor(this);
|
2018-07-13 12:33:46 +02:00
|
|
|
return;
|
2020-03-02 14:19:44 +01:00
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
QList<CompletionItem> items;
|
|
|
|
|
if (Utils::holds_alternative<CompletionList>(*result)) {
|
|
|
|
|
const auto &list = Utils::get<CompletionList>(*result);
|
|
|
|
|
items = list.items().value_or(QList<CompletionItem>());
|
|
|
|
|
} else if (Utils::holds_alternative<QList<CompletionItem>>(*result)) {
|
|
|
|
|
items = Utils::get<QList<CompletionItem>>(*result);
|
|
|
|
|
}
|
|
|
|
|
auto model = new LanguageClientCompletionModel();
|
|
|
|
|
model->loadContent(Utils::transform(items, [](const CompletionItem &item){
|
|
|
|
|
return static_cast<AssistProposalItemInterface *>(new LanguageClientCompletionItem(item));
|
|
|
|
|
}));
|
2020-03-02 14:19:44 +01:00
|
|
|
LanguageClientCompletionProposal *proposal = new LanguageClientCompletionProposal(m_pos, model);
|
2018-10-18 10:57:26 +02:00
|
|
|
proposal->m_document = m_document;
|
|
|
|
|
proposal->m_pos = m_pos;
|
2018-07-13 12:33:46 +02:00
|
|
|
proposal->setFragile(true);
|
2018-10-23 13:42:58 +02:00
|
|
|
proposal->setSupportsPrefix(false);
|
2020-03-02 14:19:44 +01:00
|
|
|
setAsyncProposalAvailable(proposal);
|
2020-03-26 09:21:57 +01:00
|
|
|
m_client->removeAssistProcessor(this);
|
2018-07-13 12:33:46 +02:00
|
|
|
qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : "
|
|
|
|
|
<< items.count() << " completions handled";
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
LanguageClientCompletionAssistProvider::LanguageClientCompletionAssistProvider(Client *client)
|
2019-09-10 08:03:36 +02:00
|
|
|
: CompletionAssistProvider(client)
|
|
|
|
|
, m_client(client)
|
2018-07-13 12:33:46 +02:00
|
|
|
{ }
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
IAssistProcessor *LanguageClientCompletionAssistProvider::createProcessor() const
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
return new LanguageClientCompletionAssistProcessor(m_client);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-29 11:23:59 +01:00
|
|
|
IAssistProvider::RunType LanguageClientCompletionAssistProvider::runType() const
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2019-01-29 11:23:59 +01:00
|
|
|
return IAssistProvider::Asynchronous;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int LanguageClientCompletionAssistProvider::activationCharSequenceLength() const
|
|
|
|
|
{
|
|
|
|
|
return m_activationCharSequenceLength;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool LanguageClientCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
|
|
|
|
|
{
|
|
|
|
|
return Utils::anyOf(m_triggerChars, [sequence](const QString &trigger){
|
|
|
|
|
return trigger.endsWith(sequence);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LanguageClientCompletionAssistProvider::setTriggerCharacters(QList<QString> triggerChars)
|
|
|
|
|
{
|
|
|
|
|
m_triggerChars = triggerChars;
|
|
|
|
|
for (const QString &trigger : triggerChars) {
|
|
|
|
|
if (trigger.length() > m_activationCharSequenceLength)
|
|
|
|
|
m_activationCharSequenceLength = trigger.length();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace LanguageClient
|