forked from qt-creator/qt-creator
ClangCodeModel: Rename via LSP facilities
Contrary to our original expectation, clangd's textDocument/rename does not necessarily yield the same locations as /references. Instead, it can find fewer or more occurrences, depending on server-side logic about what constitutes a reference vs what should be renamed. Therefore, we need to use /rename for proper behavior. Fixes: QTCREATORBUG-27978 Fixes: QTCREATORBUG-28109 Change-Id: I27d092e807a4aa59dc0674111429c77ca13010e9 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -260,7 +260,7 @@ public:
|
|||||||
: q(q), settings(CppEditor::ClangdProjectSettings(project).settings()) {}
|
: q(q), settings(CppEditor::ClangdProjectSettings(project).settings()) {}
|
||||||
|
|
||||||
void findUsages(TextDocument *document, const QTextCursor &cursor,
|
void findUsages(TextDocument *document, const QTextCursor &cursor,
|
||||||
const QString &searchTerm, const std::optional<QString> &replacement,
|
const QString &searchTerm,
|
||||||
bool categorize);
|
bool categorize);
|
||||||
|
|
||||||
void handleDeclDefSwitchReplies();
|
void handleDeclDefSwitchReplies();
|
||||||
@@ -339,6 +339,9 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
|
|||||||
setLogTarget(LogTarget::Console);
|
setLogTarget(LogTarget::Console);
|
||||||
setCompletionAssistProvider(new ClangdCompletionAssistProvider(this));
|
setCompletionAssistProvider(new ClangdCompletionAssistProvider(this));
|
||||||
setQuickFixAssistProvider(new ClangdQuickFixProvider(this));
|
setQuickFixAssistProvider(new ClangdQuickFixProvider(this));
|
||||||
|
symbolSupport().setDefaultRenamingSymbolMapper(
|
||||||
|
[](const QString &oldSymbol) { return oldSymbol + "_new"; });
|
||||||
|
symbolSupport().setLimitRenamingToProjects(true);
|
||||||
if (!project) {
|
if (!project) {
|
||||||
QJsonObject initOptions;
|
QJsonObject initOptions;
|
||||||
const Utils::FilePath includeDir
|
const Utils::FilePath includeDir
|
||||||
@@ -459,25 +462,31 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor,
|
|||||||
if (searchTerm.isEmpty())
|
if (searchTerm.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (replacement) {
|
||||||
|
symbolSupport().renameSymbol(document, adjustedCursor, *replacement,
|
||||||
|
CppEditor::preferLowerCaseFileNames());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences();
|
const bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences();
|
||||||
|
|
||||||
// If it's a "normal" symbol, go right ahead.
|
// If it's a "normal" symbol, go right ahead.
|
||||||
if (searchTerm != "operator" && Utils::allOf(searchTerm, [](const QChar &c) {
|
if (searchTerm != "operator" && Utils::allOf(searchTerm, [](const QChar &c) {
|
||||||
return c.isLetterOrNumber() || c == '_';
|
return c.isLetterOrNumber() || c == '_';
|
||||||
})) {
|
})) {
|
||||||
d->findUsages(document, adjustedCursor, searchTerm, replacement, categorize);
|
d->findUsages(document, adjustedCursor, searchTerm, categorize);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise get the proper spelling of the search term from clang, so we can put it into the
|
// Otherwise get the proper spelling of the search term from clang, so we can put it into the
|
||||||
// search widget.
|
// search widget.
|
||||||
const auto symbolInfoHandler = [this, doc = QPointer(document), adjustedCursor, replacement, categorize]
|
const auto symbolInfoHandler = [this, doc = QPointer(document), adjustedCursor, categorize]
|
||||||
(const QString &name, const QString &, const MessageId &) {
|
(const QString &name, const QString &, const MessageId &) {
|
||||||
if (!doc)
|
if (!doc)
|
||||||
return;
|
return;
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
return;
|
return;
|
||||||
d->findUsages(doc.data(), adjustedCursor, name, replacement, categorize);
|
d->findUsages(doc.data(), adjustedCursor, name, categorize);
|
||||||
};
|
};
|
||||||
requestSymbolInfo(document->filePath(), Range(adjustedCursor).start(), symbolInfoHandler);
|
requestSymbolInfo(document->filePath(), Range(adjustedCursor).start(), symbolInfoHandler);
|
||||||
}
|
}
|
||||||
@@ -633,11 +642,9 @@ QVersionNumber ClangdClient::versionNumber() const
|
|||||||
CppEditor::ClangdSettings::Data ClangdClient::settingsData() const { return d->settings; }
|
CppEditor::ClangdSettings::Data ClangdClient::settingsData() const { return d->settings; }
|
||||||
|
|
||||||
void ClangdClient::Private::findUsages(TextDocument *document,
|
void ClangdClient::Private::findUsages(TextDocument *document,
|
||||||
const QTextCursor &cursor, const QString &searchTerm,
|
const QTextCursor &cursor, const QString &searchTerm, bool categorize)
|
||||||
const std::optional<QString> &replacement, bool categorize)
|
|
||||||
{
|
{
|
||||||
const auto findRefs = new ClangdFindReferences(q, document, cursor, searchTerm, replacement,
|
const auto findRefs = new ClangdFindReferences(q, document, cursor, searchTerm, categorize);
|
||||||
categorize);
|
|
||||||
if (isTesting) {
|
if (isTesting) {
|
||||||
connect(findRefs, &ClangdFindReferences::foundReferences,
|
connect(findRefs, &ClangdFindReferences::foundReferences,
|
||||||
q, &ClangdClient::foundReferences);
|
q, &ClangdClient::foundReferences);
|
||||||
|
|||||||
@@ -44,25 +44,12 @@ public:
|
|||||||
ClangdAstNode ast;
|
ClangdAstNode ast;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ReplacementData {
|
|
||||||
public:
|
|
||||||
QString oldSymbolName;
|
|
||||||
QString newSymbolName;
|
|
||||||
QSet<Utils::FilePath> fileRenameCandidates;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ClangdFindReferences::Private
|
class ClangdFindReferences::Private
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Private(ClangdFindReferences *q) : q(q) {}
|
Private(ClangdFindReferences *q) : q(q) {}
|
||||||
|
|
||||||
ClangdClient *client() const { return qobject_cast<ClangdClient *>(q->parent()); }
|
ClangdClient *client() const { return qobject_cast<ClangdClient *>(q->parent()); }
|
||||||
static void handleRenameRequest(
|
|
||||||
const SearchResult *search,
|
|
||||||
const ReplacementData &replacementData,
|
|
||||||
const QString &newSymbolName,
|
|
||||||
const QList<SearchResultItem> &checkedItems,
|
|
||||||
bool preserveCase);
|
|
||||||
void handleFindUsagesResult(const QList<Location> &locations);
|
void handleFindUsagesResult(const QList<Location> &locations);
|
||||||
void finishSearch();
|
void finishSearch();
|
||||||
void reportAllSearchResultsAndFinish();
|
void reportAllSearchResultsAndFinish();
|
||||||
@@ -74,50 +61,24 @@ public:
|
|||||||
QMap<DocumentUri, ReferencesFileData> fileData;
|
QMap<DocumentUri, ReferencesFileData> fileData;
|
||||||
QList<MessageId> pendingAstRequests;
|
QList<MessageId> pendingAstRequests;
|
||||||
QPointer<SearchResult> search;
|
QPointer<SearchResult> search;
|
||||||
std::optional<ReplacementData> replacementData;
|
|
||||||
bool canceled = false;
|
bool canceled = false;
|
||||||
bool categorize = false;
|
bool categorize = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *document,
|
ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *document,
|
||||||
const QTextCursor &cursor, const QString &searchTerm,
|
const QTextCursor &cursor, const QString &searchTerm, bool categorize)
|
||||||
const std::optional<QString> &replacement, bool categorize)
|
|
||||||
: QObject(client), d(new ClangdFindReferences::Private(this))
|
: QObject(client), d(new ClangdFindReferences::Private(this))
|
||||||
{
|
{
|
||||||
d->categorize = categorize;
|
d->categorize = categorize;
|
||||||
if (replacement) {
|
|
||||||
ReplacementData replacementData;
|
|
||||||
replacementData.oldSymbolName = searchTerm;
|
|
||||||
replacementData.newSymbolName = *replacement;
|
|
||||||
if (replacementData.newSymbolName.isEmpty())
|
|
||||||
replacementData.newSymbolName = replacementData.oldSymbolName;
|
|
||||||
d->replacementData = replacementData;
|
|
||||||
}
|
|
||||||
|
|
||||||
d->search = SearchResultWindow::instance()->startNewSearch(
|
d->search = SearchResultWindow::instance()->startNewSearch(
|
||||||
tr("C++ Usages:"),
|
tr("C++ Usages:"),
|
||||||
{},
|
{},
|
||||||
searchTerm,
|
searchTerm,
|
||||||
replacement ? SearchResultWindow::SearchAndReplace : SearchResultWindow::SearchOnly,
|
SearchResultWindow::SearchOnly,
|
||||||
SearchResultWindow::PreserveCaseDisabled,
|
SearchResultWindow::PreserveCaseDisabled,
|
||||||
"CppEditor");
|
"CppEditor");
|
||||||
if (categorize)
|
if (categorize)
|
||||||
d->search->setFilter(new CppSearchResultFilter);
|
d->search->setFilter(new CppSearchResultFilter);
|
||||||
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);
|
|
||||||
}
|
|
||||||
connect(d->search, &SearchResult::activated, [](const SearchResultItem& item) {
|
connect(d->search, &SearchResult::activated, [](const SearchResultItem& item) {
|
||||||
EditorManager::openEditorAtSearchResult(item);
|
EditorManager::openEditorAtSearchResult(item);
|
||||||
});
|
});
|
||||||
@@ -151,31 +112,6 @@ ClangdFindReferences::~ClangdFindReferences()
|
|||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClangdFindReferences::Private::handleFindUsagesResult(const QList<Location> &locations)
|
void ClangdFindReferences::Private::handleFindUsagesResult(const QList<Location> &locations)
|
||||||
{
|
{
|
||||||
if (!search || canceled) {
|
if (!search || canceled) {
|
||||||
@@ -218,7 +154,7 @@ void ClangdFindReferences::Private::handleFindUsagesResult(const QList<Location>
|
|||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(clangdLog) << "document count is" << fileData.size();
|
qCDebug(clangdLog) << "document count is" << fileData.size();
|
||||||
if (replacementData || !categorize) {
|
if (!categorize) {
|
||||||
qCDebug(clangdLog) << "skipping AST retrieval";
|
qCDebug(clangdLog) << "skipping AST retrieval";
|
||||||
reportAllSearchResultsAndFinish();
|
reportAllSearchResultsAndFinish();
|
||||||
return;
|
return;
|
||||||
@@ -262,18 +198,6 @@ void ClangdFindReferences::Private::finishSearch()
|
|||||||
if (!client()->testingEnabled() && search) {
|
if (!client()->testingEnabled() && search) {
|
||||||
search->finishSearch(canceled);
|
search->finishSearch(canceled);
|
||||||
search->disconnect(q);
|
search->disconnect(q);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
emit q->done();
|
emit q->done();
|
||||||
q->deleteLater();
|
q->deleteLater();
|
||||||
@@ -307,15 +231,6 @@ void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file
|
|||||||
item.setUseTextEditorFont(true);
|
item.setUseTextEditorFont(true);
|
||||||
item.setLineText(rangeWithText.second);
|
item.setLineText(rangeWithText.second);
|
||||||
item.setContainingFunctionName(getContainingFunctionName(astPath, range));
|
item.setContainingFunctionName(getContainingFunctionName(astPath, range));
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items << item;
|
items << item;
|
||||||
}
|
}
|
||||||
if (client()->testingEnabled())
|
if (client()->testingEnabled())
|
||||||
@@ -569,5 +484,3 @@ void ClangdFindLocalReferences::Private::finish()
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ClangCodeModel::Internal
|
} // namespace ClangCodeModel::Internal
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ClangCodeModel::Internal::ReplacementData)
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ClangdFindReferences : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit ClangdFindReferences(ClangdClient *client, TextEditor::TextDocument *document,
|
explicit ClangdFindReferences(ClangdClient *client, TextEditor::TextDocument *document,
|
||||||
const QTextCursor &cursor, const QString &searchTerm,
|
const QTextCursor &cursor, const QString &searchTerm,
|
||||||
const std::optional<QString> &replacement, bool categorize);
|
bool categorize);
|
||||||
~ClangdFindReferences();
|
~ClangdFindReferences();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
Reference in New Issue
Block a user