LanguageClient: filter completion results

Use the optional filter text or the label of a completion item to filter
out results that do not match the current completion prefix. Fixes
completing code with language servers that do not provide server side
filtering.

Done with: Thorbjørn Lindeijer

Change-Id: Id27b88bb4e8f0b8b68d6ee49bd1d41ec11d54c45
Reviewed-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2021-05-31 12:47:35 +02:00
parent f4a8c4590e
commit 14067cca86
3 changed files with 21 additions and 8 deletions

View File

@@ -61,6 +61,7 @@ public:
// AssistProposalItemInterface interface // AssistProposalItemInterface interface
QString text() const override; QString text() const override;
QString filterText() const override;
bool implicitlyApplies() const override; bool implicitlyApplies() const override;
bool prematurelyApplies(const QChar &typedCharacter) const override; bool prematurelyApplies(const QChar &typedCharacter) const override;
void apply(TextDocumentManipulatorInterface &manipulator, int basePosition) const override; void apply(TextDocumentManipulatorInterface &manipulator, int basePosition) const override;
@@ -80,6 +81,7 @@ private:
CompletionItem m_item; CompletionItem m_item;
mutable QChar m_triggeredCommitCharacter; mutable QChar m_triggeredCommitCharacter;
mutable QString m_sortText; mutable QString m_sortText;
mutable QString m_filterText;
}; };
LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item) LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item)
@@ -202,6 +204,15 @@ const QString &LanguageClientCompletionItem::sortText() const
return m_sortText; return m_sortText;
} }
QString LanguageClientCompletionItem::filterText() const
{
if (m_filterText.isEmpty()) {
const Utils::optional<QString> filterText = m_item.filterText();
m_filterText = filterText.has_value() ? filterText.value() : m_item.label();
}
return m_filterText;
}
bool LanguageClientCompletionItem::operator <(const LanguageClientCompletionItem &other) const bool LanguageClientCompletionItem::operator <(const LanguageClientCompletionItem &other) const
{ {
return sortText() < other.sortText(); return sortText() < other.sortText();
@@ -292,6 +303,7 @@ private:
Utils::optional<MessageId> m_currentRequest; Utils::optional<MessageId> m_currentRequest;
QMetaObject::Connection m_postponedUpdateConnection; QMetaObject::Connection m_postponedUpdateConnection;
int m_pos = -1; int m_pos = -1;
int m_basePos = -1;
}; };
LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(Client *client) LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(Client *client)
@@ -317,14 +329,13 @@ IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistIn
{ {
QTC_ASSERT(m_client, return nullptr); QTC_ASSERT(m_client, return nullptr);
m_pos = interface->position(); m_pos = interface->position();
m_basePos = m_pos;
auto isIdentifierChar = [](const QChar &c) { return c.isLetterOrNumber() || c == '_'; };
while (m_basePos > 0 && isIdentifierChar(interface->characterAt(m_basePos - 1)))
--m_basePos;
if (interface->reason() == IdleEditor) { if (interface->reason() == IdleEditor) {
// Trigger an automatic completion request only when we are on a word with at least n "identifier" characters // Trigger an automatic completion request only when we are on a word with at least n "identifier" characters
const QRegularExpression regexp("^[_a-zA-Z0-9]+$"); if (m_pos - m_basePos < TextEditorSettings::completionSettings().m_characterThreshold)
auto hasMatch = [&regexp](const QString &txt) { return regexp.match(txt).hasMatch(); };
int delta = 0;
while (m_pos - delta > 0 && hasMatch(interface->textAt(m_pos - delta - 1, delta + 1)))
++delta;
if (delta < TextEditorSettings::completionSettings().m_characterThreshold)
return nullptr; return nullptr;
if (m_client->documentUpdatePostponed(interface->filePath())) { if (m_client->documentUpdatePostponed(interface->filePath())) {
m_postponedUpdateConnection m_postponedUpdateConnection
@@ -416,7 +427,8 @@ void LanguageClientCompletionAssistProcessor::handleCompletionResponse(
model->loadContent(Utils::transform(items, [](const CompletionItem &item){ model->loadContent(Utils::transform(items, [](const CompletionItem &item){
return static_cast<AssistProposalItemInterface *>(new LanguageClientCompletionItem(item)); return static_cast<AssistProposalItemInterface *>(new LanguageClientCompletionItem(item));
})); }));
LanguageClientCompletionProposal *proposal = new LanguageClientCompletionProposal(m_pos, model); LanguageClientCompletionProposal *proposal = new LanguageClientCompletionProposal(m_basePos,
model);
proposal->m_document = m_document; proposal->m_document = m_document;
proposal->m_pos = m_pos; proposal->m_pos = m_pos;
proposal->setFragile(true); proposal->setFragile(true);

View File

@@ -57,6 +57,7 @@ public:
UTILS_DELETE_MOVE_AND_COPY(AssistProposalItemInterface) UTILS_DELETE_MOVE_AND_COPY(AssistProposalItemInterface)
virtual QString text() const = 0; virtual QString text() const = 0;
virtual QString filterText() const { return text(); }
virtual bool implicitlyApplies() const = 0; virtual bool implicitlyApplies() const = 0;
virtual bool prematurelyApplies(const QChar &typedCharacter) const = 0; virtual bool prematurelyApplies(const QChar &typedCharacter) const = 0;
virtual void apply(TextDocumentManipulatorInterface &manipulator, int basePosition) const = 0; virtual void apply(TextDocumentManipulatorInterface &manipulator, int basePosition) const = 0;

View File

@@ -305,7 +305,7 @@ void GenericProposalModel::filter(const QString &prefix)
const QString lowerPrefix = prefix.toLower(); const QString lowerPrefix = prefix.toLower();
const bool checkInfix = prefix.size() >= 3; const bool checkInfix = prefix.size() >= 3;
for (const auto &item : qAsConst(m_originalItems)) { for (const auto &item : qAsConst(m_originalItems)) {
const QString &text = item->text(); const QString &text = item->filterText();
// Direct match? // Direct match?
if (text.startsWith(prefix)) { if (text.startsWith(prefix)) {