forked from qt-creator/qt-creator
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:
@@ -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 ¶ms,
|
void SymbolSupport::requestPrepareRename(
|
||||||
const QString &placeholder)
|
const TextDocumentPositionParams ¶ms,
|
||||||
|
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>
|
||||||
|
@@ -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 ¶ms,
|
void requestPrepareRename(const LanguageServerProtocol::TextDocumentPositionParams ¶ms,
|
||||||
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 ¶ms,
|
void startRenameSymbol(const LanguageServerProtocol::TextDocumentPositionParams ¶ms,
|
||||||
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;
|
||||||
|
Reference in New Issue
Block a user