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

View File

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

View File

@@ -252,11 +252,14 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason,
break;
}
case IAssistProvider::Asynchronous: {
processor->setAsyncCompletionAvailableHandler([this, reason, processor](IAssistProposal *newProposal) {
processor->setAsyncCompletionAvailableHandler([this, reason, processor](
IAssistProposal *newProposal) {
if (!processor->running()) {
// do not delete this processor directly since this function is called from within the processor
QMetaObject::invokeMethod(QCoreApplication::instance(), [processor]() {
delete processor;
}, Qt::QueuedConnection);
}
if (processor != m_asyncProcessor)
return;
invalidateCurrentRequestData();
@@ -266,6 +269,9 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason,
requestProposal(reason, m_assistKind, m_requestProvider);
} else {
displayProposal(newProposal, reason);
if (processor && processor->running())
m_asyncProcessor = processor;
else
emit q->finished();
}
});
@@ -565,6 +571,9 @@ bool CodeAssistantPrivate::eventFilter(QObject *o, QEvent *e)
destroyContext();
else if (!keyText.isEmpty() && !m_receivedContentWhileWaiting)
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(
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);
// Internal, used by CodeAssist
using AsyncCompletionsAvailableHandler = std::function<void (IAssistProposal *proposal)>;
void setAsyncCompletionAvailableHandler(const AsyncCompletionsAvailableHandler &finalizer);
using AsyncCompletionsAvailableHandler
= std::function<void (IAssistProposal *proposal)>;
void setAsyncCompletionAvailableHandler(const AsyncCompletionsAvailableHandler &handler);
virtual bool running() { return false; }
virtual bool needsRestart() const { return false; }