LanguageClient: Offer to rename matching files along with symbols

Like we already do in CppEditor and ClangCodeModel.

Change-Id: I9cd292950e40c499d99cc561fbf0ad99af477803
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2022-09-21 12:21:57 +02:00
parent 378d813797
commit c46e5cef2d
2 changed files with 129 additions and 32 deletions

View File

@@ -10,17 +10,63 @@
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/find/searchresultwindow.h> #include <coreplugin/find/searchresultwindow.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <utils/mimeutils.h> #include <utils/mimeutils.h>
#include <QCheckBox>
#include <QFile> #include <QFile>
#include <QHBoxLayout>
#include <QLabel> #include <QLabel>
using namespace LanguageServerProtocol; using namespace LanguageServerProtocol;
namespace LanguageClient { namespace LanguageClient {
namespace {
class ReplaceWidget : public QWidget
{
Q_OBJECT
public:
ReplaceWidget()
{
m_infoLabel.setText(tr("Search Again to update results and re-enable Replace"));
m_infoLabel.setVisible(false);
m_renameFilesCheckBox.setVisible(false);
const auto layout = new QHBoxLayout(this);
layout->addWidget(&m_infoLabel);
layout->addWidget(&m_renameFilesCheckBox);
}
void showLabel(bool show)
{
m_infoLabel.setVisible(show);
if (show)
updateCheckBox({});
}
void updateCheckBox(const Utils::FilePaths &filesToRename)
{
if (filesToRename.isEmpty()) {
m_renameFilesCheckBox.hide();
return;
}
m_renameFilesCheckBox.setText(tr("Re&name %n files", nullptr, filesToRename.size()));
const auto filesForUser = Utils::transform<QStringList>(filesToRename,
[](const Utils::FilePath &fp) { return fp.toUserOutput(); });
m_renameFilesCheckBox.setToolTip(tr("Files:\n%1").arg(filesForUser.join('\n')));
m_renameFilesCheckBox.setVisible(true);
}
bool shouldRenameFiles() const { return m_renameFilesCheckBox.isChecked(); }
private:
QLabel m_infoLabel;
QCheckBox m_renameFilesCheckBox;
};
} // anonymous namespace
SymbolSupport::SymbolSupport(Client *client) : m_client(client) SymbolSupport::SymbolSupport(Client *client) : m_client(client)
{} {}
@@ -169,14 +215,29 @@ QList<Core::SearchResultItem> generateSearchResultItems(
bool limitToProjects = false) bool limitToProjects = false)
{ {
QList<Core::SearchResultItem> result; QList<Core::SearchResultItem> result;
const bool renaming = search && search->supportsReplace();
QString oldSymbolName;
QVariantList userData;
if (renaming) {
userData = search->userData().toList();
oldSymbolName = userData.first().toString();
}
Utils::FilePaths fileRenameCandidates;
for (auto it = rangesInDocument.begin(); it != rangesInDocument.end(); ++it) { for (auto it = rangesInDocument.begin(); it != rangesInDocument.end(); ++it) {
const Utils::FilePath &filePath = it.key(); const Utils::FilePath &filePath = it.key();
Core::SearchResultItem item; Core::SearchResultItem item;
item.setFilePath(filePath); item.setFilePath(filePath);
item.setUseTextEditorFont(true); item.setUseTextEditorFont(true);
if (search && search->supportsReplace() && limitToProjects) if (renaming && limitToProjects) {
item.setSelectForReplacement(ProjectExplorer::SessionManager::projectForFile(filePath)); const bool fileBelongsToProject
= ProjectExplorer::SessionManager::projectForFile(filePath);
item.setSelectForReplacement(fileBelongsToProject);
if (fileBelongsToProject && filePath.baseName().compare(oldSymbolName,
Qt::CaseInsensitive) == 0) {
fileRenameCandidates << filePath;
}
}
QStringList lines = SymbolSupport::getFileContents(filePath); QStringList lines = SymbolSupport::getFileContents(filePath);
for (const ItemData &data : it.value()) { for (const ItemData &data : it.value()) {
@@ -187,6 +248,12 @@ QList<Core::SearchResultItem> generateSearchResultItems(
result << item; result << item;
} }
} }
if (renaming) {
userData.append(Utils::transform(fileRenameCandidates, &Utils::FilePath::toString));
search->setUserData(userData);
const auto extraWidget = qobject_cast<ReplaceWidget *>(search->additionalReplaceWidget());
extraWidget->updateCheckBox(fileRenameCandidates);
}
return result; return result;
} }
@@ -289,7 +356,7 @@ bool SymbolSupport::supportsRename(TextEditor::TextDocument *document)
} }
void SymbolSupport::renameSymbol(TextEditor::TextDocument *document, const QTextCursor &cursor, void SymbolSupport::renameSymbol(TextEditor::TextDocument *document, const QTextCursor &cursor,
const QString &newSymbolName) const QString &newSymbolName, bool preferLowerCaseFileNames)
{ {
const TextDocumentPositionParams params = generateDocPosParams(document, cursor); const TextDocumentPositionParams params = generateDocPosParams(document, cursor);
QTextCursor tc = cursor; QTextCursor tc = cursor;
@@ -302,34 +369,40 @@ void SymbolSupport::renameSymbol(TextEditor::TextDocument *document, const QText
bool prepareSupported; bool prepareSupported;
if (!LanguageClient::supportsRename(m_client, document, prepareSupported)) { if (!LanguageClient::supportsRename(m_client, document, prepareSupported)) {
const QString error = tr("Renaming is not supported with %1").arg(m_client->name()); const QString error = tr("Renaming is not supported with %1").arg(m_client->name());
createSearch(params, placeholder)->finishSearch(true, error); createSearch(params, placeholder, {}, {})->finishSearch(true, error);
} else if (prepareSupported) { } else if (prepareSupported) {
requestPrepareRename(generateDocPosParams(document, cursor), placeholder); requestPrepareRename(generateDocPosParams(document, cursor), placeholder, oldSymbolName,
preferLowerCaseFileNames);
} else { } else {
startRenameSymbol(generateDocPosParams(document, cursor), placeholder); startRenameSymbol(generateDocPosParams(document, cursor), placeholder, oldSymbolName,
preferLowerCaseFileNames);
} }
} }
void SymbolSupport::requestPrepareRename(const TextDocumentPositionParams &params, void SymbolSupport::requestPrepareRename(
const QString &placeholder) const TextDocumentPositionParams &params,
const QString &placeholder,
const QString &oldSymbolName,
bool preferLowerCaseFileNames)
{ {
PrepareRenameRequest request(params); PrepareRenameRequest request(params);
request.setResponseCallback([this, params, placeholder]( request.setResponseCallback([this, params, placeholder, oldSymbolName, preferLowerCaseFileNames](
const PrepareRenameRequest::Response &response) { const PrepareRenameRequest::Response &response) {
const std::optional<PrepareRenameRequest::Response::Error> &error = response.error(); const std::optional<PrepareRenameRequest::Response::Error> &error = response.error();
if (error.has_value()) { if (error.has_value()) {
m_client->log(*error); m_client->log(*error);
createSearch(params, placeholder)->finishSearch(true, error->toString()); createSearch(params, placeholder, {}, {})->finishSearch(true, error->toString());
} }
const std::optional<PrepareRenameResult> &result = response.result(); const std::optional<PrepareRenameResult> &result = response.result();
if (result.has_value()) { if (result.has_value()) {
if (std::holds_alternative<PlaceHolderResult>(*result)) { if (std::holds_alternative<PlaceHolderResult>(*result)) {
auto placeHolderResult = std::get<PlaceHolderResult>(*result); auto placeHolderResult = std::get<PlaceHolderResult>(*result);
startRenameSymbol(params, placeHolderResult.placeHolder()); startRenameSymbol(params, placeHolderResult.placeHolder(), oldSymbolName,
preferLowerCaseFileNames);
} else if (std::holds_alternative<Range>(*result)) { } else if (std::holds_alternative<Range>(*result)) {
auto range = std::get<Range>(*result); auto range = std::get<Range>(*result);
startRenameSymbol(params, placeholder); startRenameSymbol(params, placeholder, oldSymbolName, preferLowerCaseFileNames);
} }
} }
}); });
@@ -375,8 +448,11 @@ QList<Core::SearchResultItem> generateReplaceItems(const WorkspaceEdit &edits,
return generateSearchResultItems(rangesInDocument, search, limitToProjects); return generateSearchResultItems(rangesInDocument, search, limitToProjects);
} }
Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams &positionParams, Core::SearchResult *SymbolSupport::createSearch(
const QString &placeholder) const TextDocumentPositionParams &positionParams,
const QString &placeholder,
const QString &oldSymbolName,
bool preferLowerCaseFileNames)
{ {
Core::SearchResult *search = Core::SearchResultWindow::instance()->startNewSearch( Core::SearchResult *search = Core::SearchResultWindow::instance()->startNewSearch(
tr("Find References with %1 for:").arg(m_client->name()), tr("Find References with %1 for:").arg(m_client->name()),
@@ -384,14 +460,16 @@ Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams
placeholder, placeholder,
Core::SearchResultWindow::SearchAndReplace); Core::SearchResultWindow::SearchAndReplace);
search->setSearchAgainSupported(true); search->setSearchAgainSupported(true);
auto label = new QLabel(tr("Search Again to update results and re-enable Replace")); search->setUserData(QVariantList{oldSymbolName, preferLowerCaseFileNames});
label->setVisible(false); const auto extraWidget = new ReplaceWidget;
search->setAdditionalReplaceWidget(label); search->setAdditionalReplaceWidget(extraWidget);
QObject::connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) { QObject::connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) {
Core::EditorManager::openEditorAtSearchResult(item); Core::EditorManager::openEditorAtSearchResult(item);
}); });
QObject::connect(search, &Core::SearchResult::replaceTextChanged, [search]() { QObject::connect(search, &Core::SearchResult::replaceTextChanged, [search, extraWidget]() {
search->additionalReplaceWidget()->setVisible(true); extraWidget->showLabel(true);
search->setUserData(search->userData().toList().first(2));
search->setSearchAgainEnabled(true); search->setSearchAgainEnabled(true);
search->setReplaceEnabled(false); search->setReplaceEnabled(false);
}); });
@@ -403,18 +481,20 @@ Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams
}); });
QObject::connect(search, QObject::connect(search,
&Core::SearchResult::replaceButtonClicked, &Core::SearchResult::replaceButtonClicked,
[this, positionParams](const QString & /*replaceText*/, [this, positionParams, search](const QString & /*replaceText*/,
const QList<Core::SearchResultItem> &checkedItems) { const QList<Core::SearchResultItem> &checkedItems) {
applyRename(checkedItems); applyRename(checkedItems, search);
}); });
return search; return search;
} }
void SymbolSupport::startRenameSymbol(const TextDocumentPositionParams &positionParams, void SymbolSupport::startRenameSymbol(const TextDocumentPositionParams &positionParams,
const QString &placeholder) const QString &placeholder, const QString &oldSymbolName,
bool preferLowerCaseFileNames)
{ {
requestRename(positionParams, placeholder, createSearch(positionParams, placeholder)); requestRename(positionParams, placeholder, createSearch(
positionParams, placeholder, oldSymbolName, preferLowerCaseFileNames));
} }
void SymbolSupport::handleRenameResponse(Core::SearchResult *search, void SymbolSupport::handleRenameResponse(Core::SearchResult *search,
@@ -431,7 +511,7 @@ void SymbolSupport::handleRenameResponse(Core::SearchResult *search,
if (edits.has_value()) { if (edits.has_value()) {
search->addResults(generateReplaceItems(*edits, search, m_limitRenamingToProjects), search->addResults(generateReplaceItems(*edits, search, m_limitRenamingToProjects),
Core::SearchResult::AddOrdered); Core::SearchResult::AddOrdered);
search->additionalReplaceWidget()->setVisible(false); qobject_cast<ReplaceWidget *>(search->additionalReplaceWidget())->showLabel(false);
search->setReplaceEnabled(true); search->setReplaceEnabled(true);
search->setSearchAgainEnabled(false); search->setSearchAgainEnabled(false);
search->finishSearch(false); search->finishSearch(false);
@@ -440,7 +520,8 @@ void SymbolSupport::handleRenameResponse(Core::SearchResult *search,
} }
} }
void SymbolSupport::applyRename(const QList<Core::SearchResultItem> &checkedItems) void SymbolSupport::applyRename(const QList<Core::SearchResultItem> &checkedItems,
Core::SearchResult *search)
{ {
QMap<DocumentUri, QList<TextEdit>> editsForDocuments; QMap<DocumentUri, QList<TextEdit>> editsForDocuments;
for (const Core::SearchResultItem &item : checkedItems) { for (const Core::SearchResultItem &item : checkedItems) {
@@ -452,6 +533,18 @@ void SymbolSupport::applyRename(const QList<Core::SearchResultItem> &checkedItem
for (auto it = editsForDocuments.begin(), end = editsForDocuments.end(); it != end; ++it) for (auto it = editsForDocuments.begin(), end = editsForDocuments.end(); it != end; ++it)
applyTextEdits(m_client, it.key(), it.value()); applyTextEdits(m_client, it.key(), it.value());
const auto extraWidget = qobject_cast<ReplaceWidget *>(search->additionalReplaceWidget());
QTC_ASSERT(extraWidget, return);
if (!extraWidget->shouldRenameFiles())
return;
const QVariantList userData = search->userData().toList();
QTC_ASSERT(userData.size() == 3, return);
const Utils::FilePaths filesToRename = Utils::transform(userData.at(2).toStringList(),
[](const QString &f) { return Utils::FilePath::fromString(f); });
ProjectExplorer::ProjectExplorerPlugin::renameFilesForSymbol(
userData.at(0).toString(), search->textToReplace(),
filesToRename, userData.at(1).toBool());
} }
Core::Search::TextRange SymbolSupport::convertRange(const Range &range) Core::Search::TextRange SymbolSupport::convertRange(const Range &range)
@@ -468,3 +561,5 @@ void SymbolSupport::setDefaultRenamingSymbolMapper(const SymbolMapper &mapper)
} }
} // namespace LanguageClient } // namespace LanguageClient
#include <languageclientsymbolsupport.moc>

View File

@@ -43,7 +43,7 @@ public:
bool supportsRename(TextEditor::TextDocument *document); bool supportsRename(TextEditor::TextDocument *document);
void renameSymbol(TextEditor::TextDocument *document, const QTextCursor &cursor, void renameSymbol(TextEditor::TextDocument *document, const QTextCursor &cursor,
const QString &newSymbolName = {}); const QString &newSymbolName = {}, bool preferLowerCaseFileNames = true);
static Core::Search::TextRange convertRange(const LanguageServerProtocol::Range &range); static Core::Search::TextRange convertRange(const LanguageServerProtocol::Range &range);
static QStringList getFileContents(const Utils::FilePath &filePath); static QStringList getFileContents(const Utils::FilePath &filePath);
@@ -60,17 +60,19 @@ private:
const ResultHandler &handler); const ResultHandler &handler);
void requestPrepareRename(const LanguageServerProtocol::TextDocumentPositionParams &params, void requestPrepareRename(const LanguageServerProtocol::TextDocumentPositionParams &params,
const QString &placeholder); const QString &placeholder, const QString &oldSymbolName,
bool preferLowerCaseFileNames);
void requestRename(const LanguageServerProtocol::TextDocumentPositionParams &positionParams, void requestRename(const LanguageServerProtocol::TextDocumentPositionParams &positionParams,
const QString &newName, Core::SearchResult *search); const QString &newName, Core::SearchResult *search);
Core::SearchResult *createSearch( Core::SearchResult *createSearch(const LanguageServerProtocol::TextDocumentPositionParams &positionParams,
const LanguageServerProtocol::TextDocumentPositionParams &positionParams, const QString &placeholder, const QString &oldSymbolName,
const QString &placeholder); bool preferLowerCaseFileNames);
void startRenameSymbol(const LanguageServerProtocol::TextDocumentPositionParams &params, void startRenameSymbol(const LanguageServerProtocol::TextDocumentPositionParams &params,
const QString &placeholder); const QString &placeholder, const QString &oldSymbolName,
bool preferLowerCaseFileNames);
void handleRenameResponse(Core::SearchResult *search, void handleRenameResponse(Core::SearchResult *search,
const LanguageServerProtocol::RenameRequest::Response &response); const LanguageServerProtocol::RenameRequest::Response &response);
void applyRename(const QList<Core::SearchResultItem> &checkedItems); void applyRename(const QList<Core::SearchResultItem> &checkedItems, Core::SearchResult *search);
Client *m_client = nullptr; Client *m_client = nullptr;
SymbolMapper m_defaultSymbolMapper; SymbolMapper m_defaultSymbolMapper;