diff --git a/src/plugins/languageclient/languageclientcodeassist.cpp b/src/plugins/languageclient/languageclientcodeassist.cpp index 0096d9b454b..4a813889f9b 100644 --- a/src/plugins/languageclient/languageclientcodeassist.cpp +++ b/src/plugins/languageclient/languageclientcodeassist.cpp @@ -69,6 +69,8 @@ public: bool operator <(const LanguageClientCompletionItem &other) const; + bool isPerfectMatch(int pos, QTextDocument *doc) const; + private: CompletionItem m_item; mutable QString m_sortText; @@ -194,6 +196,26 @@ bool LanguageClientCompletionItem::operator <(const LanguageClientCompletionItem return sortText() < other.sortText(); } +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); +} + class LanguageClientCompletionModel : public TextEditor::GenericProposalModel { public: @@ -201,6 +223,9 @@ public: bool isSortable(const QString &/*prefix*/) const override { return true; } void sort(const QString &/*prefix*/) override; bool supportsPrefixExpansion() const override { return false; } + + QList items() const + { return Utils::static_container_cast(m_currentItems); } }; void LanguageClientCompletionModel::sort(const QString &/*prefix*/) @@ -213,6 +238,32 @@ void LanguageClientCompletionModel::sort(const QString &/*prefix*/) }); } +class LanguageClientCompletionProposal : public TextEditor::GenericProposal +{ +public: + LanguageClientCompletionProposal(int cursorPos, LanguageClientCompletionModel *model) + : TextEditor::GenericProposal(cursorPos, TextEditor::GenericProposalModelPtr(model)) + , m_model(model) + { } + + // IAssistProposal interface + bool hasItemsToPropose(const QString &/*text*/, TextEditor::AssistReason reason) const override + { + 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 m_document; + int m_pos = -1; +}; + + class LanguageClientCompletionAssistProcessor : public TextEditor::IAssistProcessor { public: @@ -224,6 +275,7 @@ public: private: void handleCompletionResponse(const Response &response); + QPointer m_document; QPointer m_client; bool m_running = false; int m_pos = -1; @@ -278,6 +330,7 @@ TextEditor::IAssistProposal *LanguageClientCompletionAssistProcessor::perform( completionRequest.setParams(params); m_client->sendContent(completionRequest); m_running = true; + m_document = interface->textDocument(); qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : request completions at " << m_pos << " by " << assistReasonString(interface->reason()); @@ -315,7 +368,9 @@ void LanguageClientCompletionAssistProcessor::handleCompletionResponse( model->loadContent(Utils::transform(items, [](const CompletionItem &item){ return static_cast(new LanguageClientCompletionItem(item)); })); - auto proposal = new GenericProposal(m_pos, GenericProposalModelPtr(model)); + auto proposal = new LanguageClientCompletionProposal(m_pos, model); + proposal->m_document = m_document; + proposal->m_pos = m_pos; proposal->setFragile(true); setAsyncProposalAvailable(proposal); qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : "