TextEditor: Allow incremental proposals

... and make use of them with clangd.
This way, users can get immediate feedback when a new proposal entry
has been found, rather than having to wait until all of them have been
collected.

Change-Id: I2adfe0153aa7a058f28eb3bd65c71dd30ea018e0
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2021-05-31 11:21:30 +02:00
parent 58f984d2b9
commit dc4a8b3866
5 changed files with 108 additions and 78 deletions

View File

@@ -425,6 +425,7 @@ public:
void cancel() override; void cancel() override;
bool running() override { return m_data; } bool running() override { return m_data; }
void update();
void finalize(); void finalize();
private: private:
@@ -433,9 +434,17 @@ private:
return nullptr; return nullptr;
} }
TextEditor::IAssistProposal *immediateProposal(const TextEditor::AssistInterface *) override; TextEditor::IAssistProposal *immediateProposal(const TextEditor::AssistInterface *) override
{
return createProposal(false);
}
void resetData();
TextEditor::IAssistProposal *immediateProposalImpl() const; TextEditor::IAssistProposal *immediateProposalImpl() const;
TextEditor::IAssistProposal *createProposal(bool final) const;
CppTools::VirtualFunctionProposalItem *createEntry(const QString &name,
const Utils::Link &link) const;
ClangdClient::Private *m_data = nullptr; ClangdClient::Private *m_data = nullptr;
}; };
@@ -511,6 +520,7 @@ public:
SymbolDataList symbolsToDisplay; SymbolDataList symbolsToDisplay;
std::set<Utils::FilePath> openedFiles; std::set<Utils::FilePath> openedFiles;
VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr; VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr;
bool finished = false;
}; };
class SwitchDeclDefData { class SwitchDeclDefData {
@@ -1156,6 +1166,7 @@ void ClangdClient::Private::handleGotoImplementationResult(
} }
} }
followSymbolData->pendingSymbolInfoRequests.removeOne(reqId); followSymbolData->pendingSymbolInfoRequests.removeOne(reqId);
followSymbolData->virtualFuncAssistProcessor->update();
if (followSymbolData->pendingSymbolInfoRequests.isEmpty() if (followSymbolData->pendingSymbolInfoRequests.isEmpty()
&& followSymbolData->pendingGotoDefRequests.isEmpty() && followSymbolData->pendingGotoDefRequests.isEmpty()
&& followSymbolData->defLinkNode.isValid()) { && followSymbolData->defLinkNode.isValid()) {
@@ -1275,6 +1286,33 @@ void ClangdClient::Private::handleDeclDefSwitchReplies()
} }
void ClangdClient::VirtualFunctionAssistProcessor::cancel() void ClangdClient::VirtualFunctionAssistProcessor::cancel()
{
resetData();
}
void ClangdClient::VirtualFunctionAssistProcessor::update()
{
if (!m_data->followSymbolData->isEditorWidgetStillAlive())
return;
setAsyncProposalAvailable(createProposal(false));
}
void ClangdClient::VirtualFunctionAssistProcessor::finalize()
{
if (!m_data->followSymbolData->isEditorWidgetStillAlive())
return;
const auto proposal = createProposal(true);
if (m_data->followSymbolData->editorWidget->inTestMode) {
m_data->followSymbolData->symbolsToDisplay.clear();
const auto immediateProposal = createProposal(false);
m_data->followSymbolData->editorWidget->setProposals(immediateProposal, proposal);
} else {
setAsyncProposalAvailable(proposal);
}
resetData();
}
void ClangdClient::VirtualFunctionAssistProcessor::resetData()
{ {
if (!m_data) if (!m_data)
return; return;
@@ -1283,77 +1321,58 @@ void ClangdClient::VirtualFunctionAssistProcessor::cancel()
m_data = nullptr; m_data = nullptr;
} }
void ClangdClient::VirtualFunctionAssistProcessor::finalize() TextEditor::IAssistProposal *ClangdClient::VirtualFunctionAssistProcessor::createProposal(bool final) const
{
QList<TextEditor::AssistProposalItemInterface *> items;
for (const SymbolData &symbol : qAsConst(m_data->followSymbolData->symbolsToDisplay)) {
Utils::Link link = symbol.second;
const bool isOriginalLink = m_data->followSymbolData->defLink == link;
if (isOriginalLink && m_data->followSymbolData->defLinkNode.range()
.contains(Position(m_data->followSymbolData->cursor))) {
continue;
}
if (!isOriginalLink) {
const Utils::Link defLink = m_data->followSymbolData->declDefMap.value(symbol.second);
if (defLink.hasValidTarget())
link = defLink;
}
const auto item = new CppTools::VirtualFunctionProposalItem(
link, m_data->followSymbolData->openInSplit);
QString text = symbol.first;
if (isOriginalLink) {
item->setOrder(1000); // Ensure base declaration is on top.
if (m_data->followSymbolData->defLinkNode.isPureVirtualDeclaration()
|| m_data->followSymbolData->defLinkNode.isPureVirtualDefinition()) {
text += " = 0";
}
}
item->setText(text);
items << item;
}
const auto finalProposal = new CppTools::VirtualFunctionProposal(
m_data->followSymbolData->cursor.position(),
items, m_data->followSymbolData->openInSplit);
if (m_data->followSymbolData->isEditorWidgetStillAlive()
&& m_data->followSymbolData->editorWidget->inTestMode) {
m_data->followSymbolData->editorWidget->setProposals(immediateProposalImpl(),
finalProposal);
} else {
setAsyncProposalAvailable(finalProposal);
}
m_data->followSymbolData->virtualFuncAssistProcessor = nullptr;
m_data->followSymbolData.reset();
m_data = nullptr;
}
TextEditor::IAssistProposal *ClangdClient::VirtualFunctionAssistProcessor::immediateProposal(
const TextEditor::AssistInterface *)
{
if (m_data->followSymbolData->isEditorWidgetStillAlive()
&& m_data->followSymbolData->editorWidget->inTestMode) {
return nullptr;
}
return immediateProposalImpl();
}
TextEditor::IAssistProposal *
ClangdClient::VirtualFunctionAssistProcessor::immediateProposalImpl() const
{ {
QTC_ASSERT(m_data && m_data->followSymbolData, return nullptr); QTC_ASSERT(m_data && m_data->followSymbolData, return nullptr);
QList<TextEditor::AssistProposalItemInterface *> items; QList<TextEditor::AssistProposalItemInterface *> items;
if (!m_data->followSymbolData->cursorNode.isPureVirtualDeclaration()) { bool needsBaseDeclEntry = !m_data->followSymbolData->defLinkNode.range()
const auto defLinkItem = new CppTools::VirtualFunctionProposalItem( .contains(Position(m_data->followSymbolData->cursor));
m_data->followSymbolData->defLink, m_data->followSymbolData->openInSplit); for (const SymbolData &symbol : qAsConst(m_data->followSymbolData->symbolsToDisplay)) {
defLinkItem->setText(ClangdClient::tr("<base declaration>")); Utils::Link link = symbol.second;
items << defLinkItem; if (m_data->followSymbolData->defLink == link) {
if (!needsBaseDeclEntry)
continue;
needsBaseDeclEntry = false;
} else {
const Utils::Link defLink = m_data->followSymbolData->declDefMap.value(symbol.second);
if (defLink.hasValidTarget())
link = defLink;
}
items << createEntry(symbol.first, link);
} }
const auto infoItem = new CppTools::VirtualFunctionProposalItem({}, false); if (needsBaseDeclEntry)
infoItem->setText(ClangdClient::tr("collecting overrides ...")); items << createEntry({}, m_data->followSymbolData->defLink);
items << infoItem; if (!final) {
return new CppTools::VirtualFunctionProposal(m_data->followSymbolData->cursor.position(), const auto infoItem = new CppTools::VirtualFunctionProposalItem({}, false);
items, m_data->followSymbolData->openInSplit); infoItem->setText(ClangdClient::tr("collecting overrides ..."));
infoItem->setOrder(-1);
items << infoItem;
}
return new CppTools::VirtualFunctionProposal(
m_data->followSymbolData->cursor.position(),
items, m_data->followSymbolData->openInSplit);
}
CppTools::VirtualFunctionProposalItem *
ClangdClient::VirtualFunctionAssistProcessor::createEntry(const QString &name,
const Utils::Link &link) const
{
const auto item = new CppTools::VirtualFunctionProposalItem(
link, m_data->followSymbolData->openInSplit);
QString text = name;
if (link == m_data->followSymbolData->defLink) {
item->setOrder(1000); // Ensure base declaration is on top.
if (text.isEmpty()) {
text = ClangdClient::tr("<base declaration>");
} else if (m_data->followSymbolData->defLinkNode.isPureVirtualDeclaration()
|| m_data->followSymbolData->defLinkNode.isPureVirtualDefinition()) {
text += " = 0";
}
}
item->setText(text);
return item;
} }
TextEditor::IAssistProcessor *ClangdClient::VirtualFunctionAssistProvider::createProcessor() const TextEditor::IAssistProcessor *ClangdClient::VirtualFunctionAssistProvider::createProcessor() const

View File

@@ -61,8 +61,9 @@ public:
bool gotResults = false; bool gotResults = false;
processor->setAsyncCompletionAvailableHandler( processor->setAsyncCompletionAvailableHandler(
[this, &gotResults] (TextEditor::IAssistProposal *proposal) { [this, processor, &gotResults] (TextEditor::IAssistProposal *proposal) {
QTC_ASSERT(proposal, return); QTC_ASSERT(proposal, return);
QTC_CHECK(!processor->running());
proposalModel = proposal->model(); proposalModel = proposal->model();
delete proposal; delete proposal;
gotResults = true; gotResults = true;

View File

@@ -252,11 +252,14 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason,
break; break;
} }
case IAssistProvider::Asynchronous: { case IAssistProvider::Asynchronous: {
processor->setAsyncCompletionAvailableHandler([this, reason, processor](IAssistProposal *newProposal) { processor->setAsyncCompletionAvailableHandler([this, reason, processor](
// do not delete this processor directly since this function is called from within the processor IAssistProposal *newProposal) {
QMetaObject::invokeMethod(QCoreApplication::instance(), [processor]() { if (!processor->running()) {
delete processor; // do not delete this processor directly since this function is called from within the processor
}, Qt::QueuedConnection); QMetaObject::invokeMethod(QCoreApplication::instance(), [processor]() {
delete processor;
}, Qt::QueuedConnection);
}
if (processor != m_asyncProcessor) if (processor != m_asyncProcessor)
return; return;
invalidateCurrentRequestData(); invalidateCurrentRequestData();
@@ -266,7 +269,10 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason,
requestProposal(reason, m_assistKind, m_requestProvider); requestProposal(reason, m_assistKind, m_requestProvider);
} else { } else {
displayProposal(newProposal, reason); displayProposal(newProposal, reason);
emit q->finished(); if (processor && processor->running())
m_asyncProcessor = processor;
else
emit q->finished();
} }
}); });
@@ -565,6 +571,9 @@ bool CodeAssistantPrivate::eventFilter(QObject *o, QEvent *e)
destroyContext(); destroyContext();
else if (!keyText.isEmpty() && !m_receivedContentWhileWaiting) else if (!keyText.isEmpty() && !m_receivedContentWhileWaiting)
m_receivedContentWhileWaiting = true; m_receivedContentWhileWaiting = true;
} else if (type == QEvent::KeyRelease
&& static_cast<QKeyEvent *>(e)->key() == Qt::Key_Escape) {
destroyContext();
} }
} }

View File

@@ -47,9 +47,9 @@ void IAssistProcessor::setAsyncProposalAvailable(IAssistProposal *proposal)
} }
void IAssistProcessor::setAsyncCompletionAvailableHandler( void IAssistProcessor::setAsyncCompletionAvailableHandler(
const IAssistProcessor::AsyncCompletionsAvailableHandler &finalizer) const IAssistProcessor::AsyncCompletionsAvailableHandler &handler)
{ {
m_asyncCompletionsAvailableHandler = finalizer; m_asyncCompletionsAvailableHandler = handler;
} }
/*! /*!

View File

@@ -46,8 +46,9 @@ public:
void setAsyncProposalAvailable(IAssistProposal *proposal); void setAsyncProposalAvailable(IAssistProposal *proposal);
// Internal, used by CodeAssist // Internal, used by CodeAssist
using AsyncCompletionsAvailableHandler = std::function<void (IAssistProposal *proposal)>; using AsyncCompletionsAvailableHandler
void setAsyncCompletionAvailableHandler(const AsyncCompletionsAvailableHandler &finalizer); = std::function<void (IAssistProposal *proposal)>;
void setAsyncCompletionAvailableHandler(const AsyncCompletionsAvailableHandler &handler);
virtual bool running() { return false; } virtual bool running() { return false; }
virtual bool needsRestart() const { return false; } virtual bool needsRestart() const { return false; }