2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2022-07-07 17:04:23 +02:00
|
|
|
|
|
|
|
|
#include "clangdfindreferences.h"
|
|
|
|
|
|
|
|
|
|
#include "clangdast.h"
|
|
|
|
|
#include "clangdclient.h"
|
|
|
|
|
|
2022-09-13 10:20:05 +02:00
|
|
|
#include <coreplugin/documentmanager.h>
|
2022-07-07 17:04:23 +02:00
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
|
|
|
#include <coreplugin/find/searchresultwindow.h>
|
|
|
|
|
#include <cplusplus/FindUsages.h>
|
|
|
|
|
#include <cppeditor/cppcodemodelsettings.h>
|
|
|
|
|
#include <cppeditor/cppfindreferences.h>
|
|
|
|
|
#include <cppeditor/cpptoolsreuse.h>
|
|
|
|
|
#include <languageclient/languageclientsymbolsupport.h>
|
|
|
|
|
#include <languageserverprotocol/lsptypes.h>
|
2022-09-21 13:38:42 +02:00
|
|
|
#include <projectexplorer/projectexplorer.h>
|
2022-07-07 17:04:23 +02:00
|
|
|
#include <projectexplorer/projectnodes.h>
|
|
|
|
|
#include <projectexplorer/projecttree.h>
|
|
|
|
|
#include <projectexplorer/session.h>
|
|
|
|
|
#include <texteditor/basefilefind.h>
|
|
|
|
|
#include <utils/filepath.h>
|
|
|
|
|
|
|
|
|
|
#include <QCheckBox>
|
|
|
|
|
#include <QMap>
|
|
|
|
|
#include <QSet>
|
|
|
|
|
|
|
|
|
|
using namespace Core;
|
|
|
|
|
using namespace CppEditor;
|
|
|
|
|
using namespace CPlusPlus;
|
|
|
|
|
using namespace LanguageClient;
|
|
|
|
|
using namespace LanguageServerProtocol;
|
|
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
using namespace TextEditor;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace ClangCodeModel::Internal {
|
|
|
|
|
|
|
|
|
|
class ReferencesFileData {
|
|
|
|
|
public:
|
|
|
|
|
QList<QPair<Range, QString>> rangesAndLineText;
|
|
|
|
|
QString fileContent;
|
|
|
|
|
ClangdAstNode ast;
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-11 17:32:38 +02:00
|
|
|
class ReplacementData {
|
|
|
|
|
public:
|
|
|
|
|
QString oldSymbolName;
|
|
|
|
|
QString newSymbolName;
|
|
|
|
|
QSet<Utils::FilePath> fileRenameCandidates;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-07 17:04:23 +02:00
|
|
|
class ClangdFindReferences::Private
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
Private(ClangdFindReferences *q) : q(q) {}
|
|
|
|
|
|
|
|
|
|
ClangdClient *client() const { return qobject_cast<ClangdClient *>(q->parent()); }
|
2022-10-11 17:32:38 +02:00
|
|
|
static void handleRenameRequest(
|
|
|
|
|
const SearchResult *search,
|
|
|
|
|
const ReplacementData &replacementData,
|
|
|
|
|
const QString &newSymbolName,
|
|
|
|
|
const QList<SearchResultItem> &checkedItems,
|
|
|
|
|
bool preserveCase);
|
2022-07-07 17:04:23 +02:00
|
|
|
void handleFindUsagesResult(const QList<Location> &locations);
|
|
|
|
|
void finishSearch();
|
|
|
|
|
void reportAllSearchResultsAndFinish();
|
|
|
|
|
void addSearchResultsForFile(const FilePath &file, const ReferencesFileData &fileData);
|
2022-08-26 10:30:00 +02:00
|
|
|
std::optional<QString> getContainingFunctionName(const ClangdAstPath &astPath,
|
2022-07-07 17:04:23 +02:00
|
|
|
const Range& range);
|
|
|
|
|
|
|
|
|
|
ClangdFindReferences * const q;
|
|
|
|
|
QMap<DocumentUri, ReferencesFileData> fileData;
|
|
|
|
|
QList<MessageId> pendingAstRequests;
|
|
|
|
|
QPointer<SearchResult> search;
|
2022-10-11 17:32:38 +02:00
|
|
|
std::optional<ReplacementData> replacementData;
|
2022-11-03 14:53:48 +01:00
|
|
|
QString searchTerm;
|
2022-07-07 17:04:23 +02:00
|
|
|
bool canceled = false;
|
|
|
|
|
bool categorize = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *document,
|
2022-10-11 17:32:38 +02:00
|
|
|
const QTextCursor &cursor, const QString &searchTerm,
|
|
|
|
|
const std::optional<QString> &replacement, bool categorize)
|
2022-07-07 17:04:23 +02:00
|
|
|
: QObject(client), d(new ClangdFindReferences::Private(this))
|
|
|
|
|
{
|
|
|
|
|
d->categorize = categorize;
|
2022-11-03 14:53:48 +01:00
|
|
|
d->searchTerm = searchTerm;
|
2022-10-11 17:32:38 +02:00
|
|
|
if (replacement) {
|
|
|
|
|
ReplacementData replacementData;
|
|
|
|
|
replacementData.oldSymbolName = searchTerm;
|
|
|
|
|
replacementData.newSymbolName = *replacement;
|
|
|
|
|
if (replacementData.newSymbolName.isEmpty())
|
|
|
|
|
replacementData.newSymbolName = replacementData.oldSymbolName;
|
|
|
|
|
d->replacementData = replacementData;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-07 17:04:23 +02:00
|
|
|
d->search = SearchResultWindow::instance()->startNewSearch(
|
|
|
|
|
tr("C++ Usages:"),
|
|
|
|
|
{},
|
|
|
|
|
searchTerm,
|
2022-10-11 17:32:38 +02:00
|
|
|
replacement ? SearchResultWindow::SearchAndReplace : SearchResultWindow::SearchOnly,
|
2022-07-07 17:04:23 +02:00
|
|
|
SearchResultWindow::PreserveCaseDisabled,
|
|
|
|
|
"CppEditor");
|
|
|
|
|
if (categorize)
|
|
|
|
|
d->search->setFilter(new CppSearchResultFilter);
|
2022-10-11 17:32:38 +02:00
|
|
|
if (d->replacementData) {
|
|
|
|
|
d->search->setTextToReplace(d->replacementData->newSymbolName);
|
|
|
|
|
const auto renameFilesCheckBox = new QCheckBox;
|
|
|
|
|
renameFilesCheckBox->setVisible(false);
|
|
|
|
|
d->search->setAdditionalReplaceWidget(renameFilesCheckBox);
|
|
|
|
|
const auto renameHandler =
|
|
|
|
|
[search = d->search](const QString &newSymbolName,
|
|
|
|
|
const QList<SearchResultItem> &checkedItems,
|
|
|
|
|
bool preserveCase) {
|
|
|
|
|
const auto replacementData = search->userData().value<ReplacementData>();
|
|
|
|
|
Private::handleRenameRequest(search, replacementData, newSymbolName, checkedItems,
|
|
|
|
|
preserveCase);
|
|
|
|
|
};
|
|
|
|
|
connect(d->search, &SearchResult::replaceButtonClicked, renameHandler);
|
|
|
|
|
}
|
2022-07-07 17:04:23 +02:00
|
|
|
connect(d->search, &SearchResult::activated, [](const SearchResultItem& item) {
|
|
|
|
|
EditorManager::openEditorAtSearchResult(item);
|
|
|
|
|
});
|
|
|
|
|
SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
|
|
|
|
|
|
2022-08-26 10:30:00 +02:00
|
|
|
const std::optional<MessageId> requestId = client->symbolSupport().findUsages(
|
2022-07-07 17:04:23 +02:00
|
|
|
document, cursor, [self = QPointer(this)](const QList<Location> &locations) {
|
|
|
|
|
if (self)
|
|
|
|
|
self->d->handleFindUsagesResult(locations);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!requestId) {
|
|
|
|
|
d->finishSearch();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
QObject::connect(d->search, &SearchResult::canceled, this, [this, requestId] {
|
|
|
|
|
d->client()->cancelRequest(*requestId);
|
|
|
|
|
d->canceled = true;
|
|
|
|
|
d->search->disconnect(this);
|
|
|
|
|
d->finishSearch();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(client, &ClangdClient::initialized, this, [this] {
|
|
|
|
|
// On a client crash, report all search results found so far.
|
|
|
|
|
d->reportAllSearchResultsAndFinish();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClangdFindReferences::~ClangdFindReferences()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-11 17:32:38 +02:00
|
|
|
void ClangdFindReferences::Private::handleRenameRequest(
|
|
|
|
|
const SearchResult *search,
|
|
|
|
|
const ReplacementData &replacementData,
|
|
|
|
|
const QString &newSymbolName,
|
|
|
|
|
const QList<SearchResultItem> &checkedItems,
|
|
|
|
|
bool preserveCase)
|
|
|
|
|
{
|
|
|
|
|
const Utils::FilePaths filePaths = BaseFileFind::replaceAll(newSymbolName, checkedItems,
|
|
|
|
|
preserveCase);
|
|
|
|
|
if (!filePaths.isEmpty()) {
|
|
|
|
|
DocumentManager::notifyFilesChangedInternally(filePaths);
|
|
|
|
|
SearchResultWindow::instance()->hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto renameFilesCheckBox = qobject_cast<QCheckBox *>(search->additionalReplaceWidget());
|
|
|
|
|
QTC_ASSERT(renameFilesCheckBox, return);
|
|
|
|
|
if (!renameFilesCheckBox->isChecked())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ProjectExplorerPlugin::renameFilesForSymbol(
|
|
|
|
|
replacementData.oldSymbolName, newSymbolName,
|
|
|
|
|
Utils::toList(replacementData.fileRenameCandidates),
|
|
|
|
|
CppEditor::preferLowerCaseFileNames());
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-07 17:04:23 +02:00
|
|
|
void ClangdFindReferences::Private::handleFindUsagesResult(const QList<Location> &locations)
|
|
|
|
|
{
|
|
|
|
|
if (!search || canceled) {
|
|
|
|
|
finishSearch();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
search->disconnect(q);
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLog) << "found" << locations.size() << "locations";
|
|
|
|
|
if (locations.isEmpty()) {
|
|
|
|
|
finishSearch();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QObject::connect(search, &SearchResult::canceled, q, [this] {
|
|
|
|
|
canceled = true;
|
|
|
|
|
search->disconnect(q);
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const MessageId &id : std::as_const(pendingAstRequests))
|
2022-07-07 17:04:23 +02:00
|
|
|
client()->cancelRequest(id);
|
|
|
|
|
pendingAstRequests.clear();
|
|
|
|
|
finishSearch();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const Location &loc : locations)
|
2022-09-30 14:11:20 +02:00
|
|
|
fileData[loc.uri()].rangesAndLineText.push_back({loc.range(), {}});
|
2022-07-07 17:04:23 +02:00
|
|
|
for (auto it = fileData.begin(); it != fileData.end();) {
|
|
|
|
|
const Utils::FilePath filePath = it.key().toFilePath();
|
|
|
|
|
if (!filePath.exists()) { // https://github.com/clangd/clangd/issues/935
|
|
|
|
|
it = fileData.erase(it);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const QStringList lines = SymbolSupport::getFileContents(filePath);
|
|
|
|
|
it->fileContent = lines.join('\n');
|
|
|
|
|
for (auto &rangeWithText : it.value().rangesAndLineText) {
|
|
|
|
|
const int lineNo = rangeWithText.first.start().line();
|
|
|
|
|
if (lineNo >= 0 && lineNo < lines.size())
|
|
|
|
|
rangeWithText.second = lines.at(lineNo);
|
|
|
|
|
}
|
|
|
|
|
++it;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLog) << "document count is" << fileData.size();
|
2022-10-11 17:32:38 +02:00
|
|
|
if (replacementData || !categorize) {
|
2022-07-07 17:04:23 +02:00
|
|
|
qCDebug(clangdLog) << "skipping AST retrieval";
|
|
|
|
|
reportAllSearchResultsAndFinish();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto it = fileData.begin(); it != fileData.end(); ++it) {
|
|
|
|
|
const TextDocument * const doc = client()->documentForFilePath(it.key().toFilePath());
|
|
|
|
|
if (!doc)
|
|
|
|
|
client()->openExtraFile(it.key().toFilePath(), it->fileContent);
|
|
|
|
|
it->fileContent.clear();
|
|
|
|
|
const auto docVariant = doc ? ClangdClient::TextDocOrFile(doc)
|
|
|
|
|
: ClangdClient::TextDocOrFile(it.key().toFilePath());
|
|
|
|
|
const auto astHandler = [sentinel = QPointer(q), this, loc = it.key()](
|
|
|
|
|
const ClangdAstNode &ast, const MessageId &reqId) {
|
|
|
|
|
qCDebug(clangdLog) << "AST for" << loc.toFilePath();
|
|
|
|
|
if (!sentinel)
|
|
|
|
|
return;
|
|
|
|
|
if (!search || canceled)
|
|
|
|
|
return;
|
|
|
|
|
ReferencesFileData &data = fileData[loc];
|
|
|
|
|
data.ast = ast;
|
|
|
|
|
pendingAstRequests.removeOne(reqId);
|
|
|
|
|
qCDebug(clangdLog) << pendingAstRequests.size() << "AST requests still pending";
|
|
|
|
|
addSearchResultsForFile(loc.toFilePath(), data);
|
|
|
|
|
fileData.remove(loc);
|
|
|
|
|
if (pendingAstRequests.isEmpty()) {
|
|
|
|
|
qDebug(clangdLog) << "retrieved all ASTs";
|
|
|
|
|
finishSearch();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const MessageId reqId = client()->getAndHandleAst(
|
|
|
|
|
docVariant, astHandler, ClangdClient::AstCallbackMode::AlwaysAsync, {});
|
|
|
|
|
pendingAstRequests << reqId;
|
|
|
|
|
if (!doc)
|
|
|
|
|
client()->closeExtraFile(it.key().toFilePath());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFindReferences::Private::finishSearch()
|
|
|
|
|
{
|
|
|
|
|
if (!client()->testingEnabled() && search) {
|
|
|
|
|
search->finishSearch(canceled);
|
|
|
|
|
search->disconnect(q);
|
2022-10-11 17:32:38 +02:00
|
|
|
if (replacementData) {
|
|
|
|
|
const auto renameCheckBox = qobject_cast<QCheckBox *>(
|
|
|
|
|
search->additionalReplaceWidget());
|
|
|
|
|
QTC_CHECK(renameCheckBox);
|
|
|
|
|
const QSet<Utils::FilePath> files = replacementData->fileRenameCandidates;
|
|
|
|
|
renameCheckBox->setText(tr("Re&name %n files", nullptr, files.size()));
|
|
|
|
|
const QStringList filesForUser = Utils::transform<QStringList>(files,
|
|
|
|
|
[](const Utils::FilePath &fp) { return fp.toUserOutput(); });
|
|
|
|
|
renameCheckBox->setToolTip(tr("Files:\n%1").arg(filesForUser.join('\n')));
|
|
|
|
|
renameCheckBox->setVisible(true);
|
|
|
|
|
search->setUserData(QVariant::fromValue(*replacementData));
|
|
|
|
|
}
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
emit q->done();
|
|
|
|
|
q->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFindReferences::Private::reportAllSearchResultsAndFinish()
|
|
|
|
|
{
|
|
|
|
|
for (auto it = fileData.begin(); it != fileData.end(); ++it)
|
|
|
|
|
addSearchResultsForFile(it.key().toFilePath(), it.value());
|
|
|
|
|
finishSearch();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-03 14:53:48 +01:00
|
|
|
static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &searchTerm);
|
2022-07-07 17:04:23 +02:00
|
|
|
|
|
|
|
|
void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file,
|
|
|
|
|
const ReferencesFileData &fileData)
|
|
|
|
|
{
|
|
|
|
|
QList<SearchResultItem> items;
|
|
|
|
|
qCDebug(clangdLog) << file << "has valid AST:" << fileData.ast.isValid();
|
|
|
|
|
for (const auto &rangeWithText : fileData.rangesAndLineText) {
|
|
|
|
|
const Range &range = rangeWithText.first;
|
|
|
|
|
const ClangdAstPath astPath = getAstPath(fileData.ast, range);
|
2022-11-03 14:53:48 +01:00
|
|
|
const Usage::Tags usageType = fileData.ast.isValid() ? getUsageType(astPath, searchTerm)
|
2022-11-02 12:16:38 +01:00
|
|
|
: Usage::Tags();
|
2022-07-07 17:04:23 +02:00
|
|
|
|
|
|
|
|
SearchResultItem item;
|
2022-11-02 12:16:38 +01:00
|
|
|
item.setUserData(usageType.toInt());
|
2022-07-07 17:04:23 +02:00
|
|
|
item.setStyle(CppEditor::colorStyleForUsageType(usageType));
|
|
|
|
|
item.setFilePath(file);
|
|
|
|
|
item.setMainRange(SymbolSupport::convertRange(range));
|
|
|
|
|
item.setUseTextEditorFont(true);
|
|
|
|
|
item.setLineText(rangeWithText.second);
|
|
|
|
|
item.setContainingFunctionName(getContainingFunctionName(astPath, range));
|
2022-10-11 17:32:38 +02:00
|
|
|
|
|
|
|
|
if (search->supportsReplace()) {
|
|
|
|
|
const bool fileInSession = SessionManager::projectForFile(file);
|
|
|
|
|
item.setSelectForReplacement(fileInSession);
|
|
|
|
|
if (fileInSession && file.baseName().compare(replacementData->oldSymbolName,
|
|
|
|
|
Qt::CaseInsensitive) == 0) {
|
|
|
|
|
replacementData->fileRenameCandidates << file;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-07 17:04:23 +02:00
|
|
|
items << item;
|
|
|
|
|
}
|
|
|
|
|
if (client()->testingEnabled())
|
|
|
|
|
emit q->foundReferences(items);
|
|
|
|
|
else
|
|
|
|
|
search->addResults(items, SearchResult::AddOrdered);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-26 10:30:00 +02:00
|
|
|
std::optional<QString> ClangdFindReferences::Private::getContainingFunctionName(
|
2022-07-07 17:04:23 +02:00
|
|
|
const ClangdAstPath &astPath, const Range& range)
|
|
|
|
|
{
|
|
|
|
|
const ClangdAstNode* containingFuncNode{nullptr};
|
|
|
|
|
const ClangdAstNode* lastCompoundStmtNode{nullptr};
|
|
|
|
|
|
|
|
|
|
for (auto it = astPath.crbegin(); it != astPath.crend(); ++it) {
|
|
|
|
|
if (it->arcanaContains("CompoundStmt"))
|
|
|
|
|
lastCompoundStmtNode = &*it;
|
|
|
|
|
|
|
|
|
|
if (it->isFunction()) {
|
|
|
|
|
if (lastCompoundStmtNode && lastCompoundStmtNode->hasRange()
|
|
|
|
|
&& lastCompoundStmtNode->range().contains(range)) {
|
|
|
|
|
containingFuncNode = &*it;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!containingFuncNode || !containingFuncNode->isValid())
|
2022-08-26 10:30:00 +02:00
|
|
|
return std::nullopt;
|
2022-07-07 17:04:23 +02:00
|
|
|
|
|
|
|
|
return containingFuncNode->detail();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-03 14:53:48 +01:00
|
|
|
static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &searchTerm)
|
2022-07-07 17:04:23 +02:00
|
|
|
{
|
|
|
|
|
bool potentialWrite = false;
|
|
|
|
|
bool isFunction = false;
|
|
|
|
|
const bool symbolIsDataType = path.last().role() == "type" && path.last().kind() == "Record";
|
2022-11-03 14:53:48 +01:00
|
|
|
QString invokedConstructor;
|
|
|
|
|
if (path.last().role() == "expression" && path.last().kind() == "CXXConstruct")
|
|
|
|
|
invokedConstructor = path.last().detail().value_or(QString());
|
2022-07-07 17:04:23 +02:00
|
|
|
const auto isPotentialWrite = [&] { return potentialWrite && !isFunction; };
|
2022-11-07 17:39:46 +01:00
|
|
|
const auto isSomeSortOfTemplate = [&](auto declPathIt) {
|
|
|
|
|
if (declPathIt->kind() == "Function") {
|
|
|
|
|
const auto children = declPathIt->children().value_or(QList<ClangdAstNode>());
|
|
|
|
|
for (const ClangdAstNode &child : children) {
|
|
|
|
|
if (child.role() == "template argument")
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (; declPathIt != path.rend(); ++declPathIt) {
|
|
|
|
|
if (declPathIt->kind() == "FunctionTemplate" || declPathIt->kind() == "ClassTemplate"
|
|
|
|
|
|| declPathIt->kind() == "ClassTemplatePartialSpecialization") {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
2022-07-07 17:04:23 +02:00
|
|
|
for (auto pathIt = path.rbegin(); pathIt != path.rend(); ++pathIt) {
|
|
|
|
|
if (pathIt->arcanaContains("non_odr_use_unevaluated"))
|
2022-11-02 12:16:38 +01:00
|
|
|
return {};
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->kind() == "CXXDelete")
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::Write;
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->kind() == "CXXNew")
|
2022-11-02 12:16:38 +01:00
|
|
|
return {};
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->kind() == "Switch" || pathIt->kind() == "If")
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->kind() == "Call")
|
2022-11-02 12:16:38 +01:00
|
|
|
return isFunction ? Usage::Tags()
|
|
|
|
|
: potentialWrite ? Usage::Tag::WritableRef : Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->kind() == "CXXMemberCall") {
|
|
|
|
|
const auto children = pathIt->children();
|
|
|
|
|
if (children && children->size() == 1
|
|
|
|
|
&& children->first() == path.last()
|
|
|
|
|
&& children->first().arcanaContains("bound member function")) {
|
2022-11-02 12:16:38 +01:00
|
|
|
return {};
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
2022-11-02 12:16:38 +01:00
|
|
|
return isPotentialWrite() ? Usage::Tag::WritableRef : Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
if ((pathIt->kind() == "DeclRef" || pathIt->kind() == "Member")
|
|
|
|
|
&& pathIt->arcanaContains("lvalue")) {
|
|
|
|
|
if (pathIt->arcanaContains(" Function "))
|
|
|
|
|
isFunction = true;
|
|
|
|
|
else
|
|
|
|
|
potentialWrite = true;
|
|
|
|
|
}
|
|
|
|
|
if (pathIt->role() == "declaration") {
|
|
|
|
|
if (symbolIsDataType)
|
2022-11-02 12:16:38 +01:00
|
|
|
return {};
|
2022-11-03 14:53:48 +01:00
|
|
|
if (!invokedConstructor.isEmpty() && invokedConstructor == searchTerm)
|
|
|
|
|
return {};
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->arcanaContains("cinit")) {
|
|
|
|
|
if (pathIt == path.rbegin())
|
2022-11-02 12:16:38 +01:00
|
|
|
return {Usage::Tag::Declaration, Usage::Tag::Write};
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->childContainsRange(0, path.last().range()))
|
2022-11-02 12:16:38 +01:00
|
|
|
return {Usage::Tag::Declaration, Usage::Tag::Write};
|
2022-07-07 17:04:23 +02:00
|
|
|
if (isFunction)
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
if (!pathIt->hasConstType())
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::WritableRef;
|
|
|
|
|
return Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
2022-11-02 14:27:17 +01:00
|
|
|
Usage::Tags tags = Usage::Tag::Declaration;
|
|
|
|
|
const auto children = pathIt->children().value_or(QList<ClangdAstNode>());
|
|
|
|
|
for (const ClangdAstNode &child : children) {
|
|
|
|
|
if (child.role() == "attribute") {
|
|
|
|
|
if (child.kind() == "Override" || child.kind() == "Final")
|
|
|
|
|
tags |= Usage::Tag::Override;
|
|
|
|
|
else if (child.kind() == "Annotate" && child.arcanaContains("qt_"))
|
|
|
|
|
tags |= Usage::Tag::MocInvokable;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-11-07 17:39:46 +01:00
|
|
|
if (isSomeSortOfTemplate(pathIt))
|
|
|
|
|
tags |= Usage::Tag::Template;
|
2022-11-02 14:27:17 +01:00
|
|
|
return tags;
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
if (pathIt->kind() == "MemberInitializer")
|
2022-11-02 12:16:38 +01:00
|
|
|
return pathIt == path.rbegin() ? Usage::Tag::Write : Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->kind() == "UnaryOperator"
|
|
|
|
|
&& (pathIt->detailIs("++") || pathIt->detailIs("--"))) {
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::Write;
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LLVM uses BinaryOperator only for built-in types; for classes, CXXOperatorCall
|
|
|
|
|
// is used. The latter has an additional node at index 0, so the left-hand side
|
|
|
|
|
// of an assignment is at index 1.
|
|
|
|
|
const bool isBinaryOp = pathIt->kind() == "BinaryOperator";
|
|
|
|
|
const bool isOpCall = pathIt->kind() == "CXXOperatorCall";
|
|
|
|
|
if (isBinaryOp || isOpCall) {
|
|
|
|
|
if (isOpCall && symbolIsDataType) // Constructor invocation.
|
2022-11-02 12:16:38 +01:00
|
|
|
return {};
|
2022-07-07 17:04:23 +02:00
|
|
|
|
|
|
|
|
const QString op = pathIt->operatorString();
|
|
|
|
|
if (op.endsWith("=") && op != "==") { // Assignment.
|
|
|
|
|
const int lhsIndex = isBinaryOp ? 0 : 1;
|
|
|
|
|
if (pathIt->childContainsRange(lhsIndex, path.last().range()))
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::Write;
|
|
|
|
|
return isPotentialWrite() ? Usage::Tag::WritableRef : Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pathIt->kind() == "ImplicitCast") {
|
|
|
|
|
if (pathIt->detailIs("FunctionToPointerDecay"))
|
2022-11-02 12:16:38 +01:00
|
|
|
return {};
|
2022-07-07 17:04:23 +02:00
|
|
|
if (pathIt->hasConstType())
|
2022-11-02 12:16:38 +01:00
|
|
|
return Usage::Tag::Read;
|
2022-07-07 17:04:23 +02:00
|
|
|
potentialWrite = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-02 12:16:38 +01:00
|
|
|
return {};
|
2022-07-07 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-03 12:58:48 +02:00
|
|
|
class ClangdFindLocalReferences::Private
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
Private(ClangdFindLocalReferences *q, TextDocument *document, const QTextCursor &cursor,
|
|
|
|
|
const RenameCallback &callback)
|
|
|
|
|
: q(q), document(document), cursor(cursor), callback(callback),
|
|
|
|
|
uri(DocumentUri::fromFilePath(document->filePath())),
|
|
|
|
|
revision(document->document()->revision())
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
ClangdClient *client() const { return qobject_cast<ClangdClient *>(q->parent()); }
|
|
|
|
|
void findDefinition();
|
|
|
|
|
void getDefinitionAst(const Link &link);
|
|
|
|
|
void checkDefinitionAst(const ClangdAstNode &ast);
|
|
|
|
|
void handleReferences(const QList<Location> &references);
|
|
|
|
|
void finish();
|
|
|
|
|
|
|
|
|
|
ClangdFindLocalReferences * const q;
|
|
|
|
|
const QPointer<TextDocument> document;
|
|
|
|
|
const QTextCursor cursor;
|
|
|
|
|
RenameCallback callback;
|
|
|
|
|
const DocumentUri uri;
|
|
|
|
|
const int revision;
|
|
|
|
|
Link defLink;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ClangdFindLocalReferences::ClangdFindLocalReferences(
|
|
|
|
|
ClangdClient *client, TextDocument *document, const QTextCursor &cursor,
|
|
|
|
|
const RenameCallback &callback)
|
|
|
|
|
: QObject(client), d(new Private(this, document, cursor, callback))
|
|
|
|
|
{
|
|
|
|
|
d->findDefinition();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClangdFindLocalReferences::~ClangdFindLocalReferences()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFindLocalReferences::Private::findDefinition()
|
|
|
|
|
{
|
|
|
|
|
const auto linkHandler = [sentinel = QPointer(q), this](const Link &l) {
|
|
|
|
|
if (sentinel)
|
|
|
|
|
getDefinitionAst(l);
|
|
|
|
|
};
|
|
|
|
|
client()->symbolSupport().findLinkAt(document, cursor, linkHandler, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFindLocalReferences::Private::getDefinitionAst(const Link &link)
|
|
|
|
|
{
|
|
|
|
|
qCDebug(clangdLog) << "received go to definition response" << link.targetFilePath
|
|
|
|
|
<< link.targetLine << (link.targetColumn + 1);
|
|
|
|
|
|
2022-09-20 15:39:34 +02:00
|
|
|
if (!link.hasValidTarget() || !document
|
|
|
|
|
|| link.targetFilePath.canonicalPath() != document->filePath().canonicalPath()) {
|
2022-08-03 12:58:48 +02:00
|
|
|
finish();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defLink = link;
|
|
|
|
|
qCDebug(clangdLog) << "sending ast request for link";
|
|
|
|
|
const auto astHandler = [sentinel = QPointer(q), this]
|
|
|
|
|
(const ClangdAstNode &ast, const MessageId &) {
|
|
|
|
|
if (sentinel)
|
|
|
|
|
checkDefinitionAst(ast);
|
|
|
|
|
};
|
|
|
|
|
client()->getAndHandleAst(document, astHandler, ClangdClient::AstCallbackMode::SyncIfPossible,
|
|
|
|
|
{});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFindLocalReferences::Private::checkDefinitionAst(const ClangdAstNode &ast)
|
|
|
|
|
{
|
|
|
|
|
qCDebug(clangdLog) << "received ast response";
|
|
|
|
|
if (!ast.isValid() || !document) {
|
|
|
|
|
finish();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Position linkPos(defLink.targetLine - 1, defLink.targetColumn);
|
|
|
|
|
const ClangdAstPath astPath = getAstPath(ast, linkPos);
|
|
|
|
|
bool isVar = false;
|
|
|
|
|
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
|
|
|
|
|
if (it->role() == "declaration"
|
|
|
|
|
&& (it->kind() == "Function" || it->kind() == "CXXMethod"
|
|
|
|
|
|| it->kind() == "CXXConstructor" || it->kind() == "CXXDestructor"
|
|
|
|
|
|| it->kind() == "Lambda")) {
|
|
|
|
|
if (!isVar)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLog) << "finding references for local var";
|
|
|
|
|
const auto refsHandler = [sentinel = QPointer(q), this](const QList<Location> &refs) {
|
|
|
|
|
if (sentinel)
|
|
|
|
|
handleReferences(refs);
|
|
|
|
|
};
|
|
|
|
|
client()->symbolSupport().findUsages(document, cursor, refsHandler);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!isVar && it->role() == "declaration"
|
|
|
|
|
&& (it->kind() == "Var" || it->kind() == "ParmVar")) {
|
|
|
|
|
isVar = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finish();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFindLocalReferences::Private::handleReferences(const QList<Location> &references)
|
|
|
|
|
{
|
|
|
|
|
qCDebug(clangdLog) << "found" << references.size() << "local references";
|
|
|
|
|
const Utils::Links links = Utils::transform(references, &Location::toLink);
|
|
|
|
|
|
|
|
|
|
// The callback only uses the symbol length, so we just create a dummy.
|
|
|
|
|
// Note that the calculation will be wrong for identifiers with
|
|
|
|
|
// embedded newlines, but we've never supported that.
|
|
|
|
|
QString symbol;
|
|
|
|
|
if (!references.isEmpty()) {
|
|
|
|
|
const Range r = references.first().range();
|
|
|
|
|
symbol = QString(r.end().character() - r.start().character(), 'x');
|
|
|
|
|
}
|
|
|
|
|
callback(symbol, links, revision);
|
|
|
|
|
callback = {};
|
|
|
|
|
finish();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangdFindLocalReferences::Private::finish()
|
|
|
|
|
{
|
|
|
|
|
if (callback)
|
|
|
|
|
callback({}, {}, revision);
|
|
|
|
|
emit q->done();
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-07 17:04:23 +02:00
|
|
|
} // namespace ClangCodeModel::Internal
|
2022-10-11 17:32:38 +02:00
|
|
|
|
|
|
|
|
Q_DECLARE_METATYPE(ClangCodeModel::Internal::ReplacementData)
|