ProjectExplorer: Let build systems do renamings in bulk

Like for the add and remove operations.

Change-Id: I734396b1b0f5a4ffb9cf193a0e32c8f7f60259ae
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2024-09-11 17:04:01 +02:00
parent ff44f589f5
commit 05430afdcf
24 changed files with 410 additions and 244 deletions

View File

@@ -2558,4 +2558,14 @@ FilePath TemporaryFilePath::filePath() const
return d->filePath; return d->filePath;
} }
FilePaths firstPaths(const FilePairs &pairs)
{
return transform(pairs, &FilePair::first);
}
FilePaths secondPaths(const FilePairs &pairs)
{
return transform(pairs, &FilePair::second);
}
} // Utils } // Utils

View File

@@ -17,6 +17,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <utility>
#include <variant> #include <variant>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -53,7 +54,12 @@ public:
const QDirIterator::IteratorFlags iteratorFlags = QDirIterator::NoIteratorFlags; const QDirIterator::IteratorFlags iteratorFlags = QDirIterator::NoIteratorFlags;
}; };
using FilePaths = QList<class FilePath>; class FilePath;
using FilePaths = QList<FilePath>;
using FilePair = std::pair<FilePath, FilePath>;
using FilePairs = QList<FilePair>;
QTCREATOR_UTILS_EXPORT FilePaths firstPaths(const FilePairs &pairs);
QTCREATOR_UTILS_EXPORT FilePaths secondPaths(const FilePairs &pairs);
class QTCREATOR_UTILS_EXPORT FilePathWatcher : public QObject class QTCREATOR_UTILS_EXPORT FilePathWatcher : public QObject
{ {

View File

@@ -962,74 +962,95 @@ bool CMakeBuildSystem::canRenameFile(Node *context,
return false; return false;
} }
bool CMakeBuildSystem::renameFile(Node *context, bool CMakeBuildSystem::renameFiles(Node *context, const FilePairs &filesToRename, FilePaths *notRenamed)
const FilePath &oldFilePath,
const FilePath &newFilePath)
{ {
if (auto n = dynamic_cast<CMakeTargetNode *>(context)) { const auto n = dynamic_cast<CMakeTargetNode *>(context);
const FilePath projDir = n->filePath().canonicalPath(); if (!n) {
const FilePath newRelPath = newFilePath.canonicalPath().relativePathFrom(projDir).cleanPath(); if (notRenamed)
const QString newRelPathName = newRelPath.toString(); *notRenamed = firstPaths(filesToRename);
return false;
const QString targetName = n->buildKey();
const QString key
= QStringList{projDir.path(), targetName, oldFilePath.path(), newFilePath.path()}.join(
";");
std::optional<CMakeBuildSystem::ProjectFileArgumentPosition> fileToRename
= m_filesToBeRenamed.take(key);
if (!fileToRename->cmakeFile.exists()) {
qCCritical(cmakeBuildSystemLog).noquote()
<< "File" << fileToRename->cmakeFile.path() << "does not exist.";
return false;
}
bool haveGlobbing = false;
do {
if (!fileToRename->fromGlobbing) {
BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(
Core::EditorManager::openEditorAt(
{fileToRename->cmakeFile,
static_cast<int>(fileToRename->argumentPosition.Line),
static_cast<int>(fileToRename->argumentPosition.Column - 1)},
Constants::CMAKE_EDITOR_ID,
Core::EditorManager::DoNotMakeVisible
| Core::EditorManager::DoNotChangeCurrentEditor));
if (!editor) {
qCCritical(cmakeBuildSystemLog).noquote()
<< "BaseTextEditor cannot be obtained for" << fileToRename->cmakeFile.path()
<< fileToRename->argumentPosition.Line
<< int(fileToRename->argumentPosition.Column);
return false;
}
// If quotes were used for the source file, skip the starting quote
if (fileToRename->argumentPosition.Delim == cmListFileArgument::Quoted)
editor->setCursorPosition(editor->position() + 1);
editor->replace(fileToRename->relativeFileName.length(), newRelPathName);
editor->editorWidget()->autoIndent();
if (!Core::DocumentManager::saveDocument(editor->document())) {
qCCritical(cmakeBuildSystemLog).noquote()
<< "Changes to" << fileToRename->cmakeFile.path() << "could not be saved.";
return false;
}
} else {
haveGlobbing = true;
}
// Try the next occurrence. This can happen if set_source_file_properties is used
fileToRename = projectFileArgumentPosition(targetName, fileToRename->relativeFileName);
} while (fileToRename && !fileToRename->fromGlobbing);
if (haveGlobbing && settings(project()).autorunCMake())
runCMake();
return true;
} }
return false; bool shouldRunCMake = false;
bool success = true;
for (const auto &[oldFilePath, newFilePath] : filesToRename) {
if (!renameFile(n, oldFilePath, newFilePath, shouldRunCMake)) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
}
}
if (shouldRunCMake && settings(project()).autorunCMake())
runCMake();
return success;
}
bool CMakeBuildSystem::renameFile(
CMakeTargetNode *context,
const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath,
bool &shouldRunCMake)
{
const FilePath projDir = context->filePath().canonicalPath();
const FilePath newRelPath = newFilePath.canonicalPath().relativePathFrom(projDir).cleanPath();
const QString newRelPathName = newRelPath.toString();
const QString targetName = context->buildKey();
const QString key
= QStringList{projDir.path(), targetName, oldFilePath.path(), newFilePath.path()}.join(
";");
std::optional<CMakeBuildSystem::ProjectFileArgumentPosition> fileToRename
= m_filesToBeRenamed.take(key);
if (!fileToRename->cmakeFile.exists()) {
qCCritical(cmakeBuildSystemLog).noquote()
<< "File" << fileToRename->cmakeFile.path() << "does not exist.";
return false;
}
bool haveGlobbing = false;
do {
if (!fileToRename->fromGlobbing) {
BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(
Core::EditorManager::openEditorAt(
{fileToRename->cmakeFile,
static_cast<int>(fileToRename->argumentPosition.Line),
static_cast<int>(fileToRename->argumentPosition.Column - 1)},
Constants::CMAKE_EDITOR_ID,
Core::EditorManager::DoNotMakeVisible
| Core::EditorManager::DoNotChangeCurrentEditor));
if (!editor) {
qCCritical(cmakeBuildSystemLog).noquote()
<< "BaseTextEditor cannot be obtained for" << fileToRename->cmakeFile.path()
<< fileToRename->argumentPosition.Line
<< int(fileToRename->argumentPosition.Column);
return false;
}
// If quotes were used for the source file, skip the starting quote
if (fileToRename->argumentPosition.Delim == cmListFileArgument::Quoted)
editor->setCursorPosition(editor->position() + 1);
editor->replace(fileToRename->relativeFileName.length(), newRelPathName);
editor->editorWidget()->autoIndent();
if (!Core::DocumentManager::saveDocument(editor->document())) {
qCCritical(cmakeBuildSystemLog).noquote()
<< "Changes to" << fileToRename->cmakeFile.path() << "could not be saved.";
return false;
}
} else {
haveGlobbing = true;
}
// Try the next occurrence. This can happen if set_source_file_properties is used
fileToRename = projectFileArgumentPosition(targetName, fileToRename->relativeFileName);
} while (fileToRename && !fileToRename->fromGlobbing);
if (haveGlobbing)
shouldRunCMake = true;
return true;
} }
void CMakeBuildSystem::buildNamedTarget(const QString &target) void CMakeBuildSystem::buildNamedTarget(const QString &target)

View File

@@ -61,9 +61,9 @@ public:
bool canRenameFile(ProjectExplorer::Node *context, bool canRenameFile(ProjectExplorer::Node *context,
const Utils::FilePath &oldFilePath, const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath) final; const Utils::FilePath &newFilePath) final;
bool renameFile(ProjectExplorer::Node *context, bool renameFiles(ProjectExplorer::Node *context,
const Utils::FilePath &oldFilePath, const Utils::FilePairs &filesToRename,
const Utils::FilePath &newFilePath) final; Utils::FilePaths *notRenamed) final;
void buildNamedTarget(const QString &target) final; void buildNamedTarget(const QString &target) final;
Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final; Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final;
@@ -157,6 +157,9 @@ private:
Utils::FilePaths *); Utils::FilePaths *);
bool addTsFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths, bool addTsFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths,
Utils::FilePaths *); Utils::FilePaths *);
bool renameFile(CMakeTargetNode *context,
const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath, bool &shouldRunCMake);
// Actually ask for parsing: // Actually ask for parsing:
enum ReparseParameters { enum ReparseParameters {

View File

@@ -114,7 +114,10 @@ public:
} }
RemovedFilesFromProject removeFiles(Node *, const FilePaths &filePaths, FilePaths *) final; RemovedFilesFromProject removeFiles(Node *, const FilePaths &filePaths, FilePaths *) final;
bool renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) final; bool renameFiles(
Node *,
const Utils::FilePairs &filesToRename,
Utils::FilePaths *notRenamed) final;
bool addFiles(Node *, const FilePaths &filePaths, FilePaths *) final; bool addFiles(Node *, const FilePaths &filePaths, FilePaths *) final;
QString name() const final { return QLatin1String("generic"); } QString name() const final { return QLatin1String("generic"); }
@@ -376,21 +379,42 @@ bool GenericBuildSystem::setFiles(const QStringList &filePaths)
return saveRawFileList(newList); return saveRawFileList(newList);
} }
bool GenericBuildSystem::renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) bool GenericBuildSystem::renameFiles(Node *, const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
QStringList newList = m_rawFileList; QStringList newList = m_rawFileList;
QHash<QString, QString>::iterator i = m_rawListEntries.find(oldFilePath.toString()); bool success = true;
if (i != m_rawListEntries.end()) { for (const auto &[oldFilePath, newFilePath] : filesToRename) {
int index = newList.indexOf(i.value()); const auto fail = [&] {
if (index != -1) { success = false;
QDir baseDir(projectDirectory().toString()); if (notRenamed)
newList.removeAt(index); *notRenamed << oldFilePath;
insertSorted(&newList, baseDir.relativeFilePath(newFilePath.toString())); };
const auto i = m_rawListEntries.find(oldFilePath.toString());
if (i == m_rawListEntries.end()) {
fail();
continue;
} }
const int index = newList.indexOf(i.value());
if (index == -1) {
fail();
continue;
}
QDir baseDir(projectDirectory().toString());
newList.removeAt(index);
insertSorted(&newList, baseDir.relativeFilePath(newFilePath.toString()));
} }
return saveRawFileList(newList); if (!saveRawFileList(newList)) {
success = false;
if (notRenamed)
*notRenamed = firstPaths(filesToRename);
}
return success;
} }
static QStringList readFlags(const QString &filePath) static QStringList readFlags(const QString &filePath)

View File

@@ -234,9 +234,17 @@ bool NimbleBuildSystem::deleteFiles(Node *, const FilePaths &)
return true; return true;
} }
bool NimbleBuildSystem::renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) bool NimbleBuildSystem::renameFiles(Node *, const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
return m_projectScanner.renameFile(oldFilePath.toString(), newFilePath.toString()); bool success = true;
for (const auto &[oldFilePath, newFilePath] : filesToRename) {
if (!m_projectScanner.renameFile(oldFilePath.toString(), newFilePath.toString())) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
}
}
return success;
} }
} // Nim } // Nim

View File

@@ -58,8 +58,10 @@ private:
const Utils::FilePaths &filePaths, const Utils::FilePaths &filePaths,
Utils::FilePaths *) override; Utils::FilePaths *) override;
bool deleteFiles(ProjectExplorer::Node *, const Utils::FilePaths &) override; bool deleteFiles(ProjectExplorer::Node *, const Utils::FilePaths &) override;
bool renameFile(ProjectExplorer::Node *, bool renameFiles(
const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override; ProjectExplorer::Node *,
const Utils::FilePairs &filesToRename,
Utils::FilePaths *notRenamed) override;
QString name() const final { return QLatin1String("mimble"); } QString name() const final { return QLatin1String("mimble"); }
void triggerParsing() final; void triggerParsing() final;

View File

@@ -152,7 +152,10 @@ public:
const FilePaths &filePaths, const FilePaths &filePaths,
FilePaths *) final; FilePaths *) final;
bool deleteFiles(Node *, const FilePaths &) final; bool deleteFiles(Node *, const FilePaths &) final;
bool renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) final; bool renameFiles(
Node *,
const Utils::FilePairs &filesToRename,
Utils::FilePaths *notRenamed) final;
QString name() const final { return QLatin1String("nim"); } QString name() const final { return QLatin1String("nim"); }
void triggerParsing() final; void triggerParsing() final;
@@ -237,9 +240,17 @@ bool NimBuildSystem::deleteFiles(Node *, const FilePaths &)
return true; return true;
} }
bool NimBuildSystem::renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) bool NimBuildSystem::renameFiles(Node *, const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
return m_projectScanner.renameFile(oldFilePath.toString(), newFilePath.toString()); bool success = true;
for (const auto &[oldFilePath, newFilePath] : filesToRename) {
if (!m_projectScanner.renameFile(oldFilePath.toString(), newFilePath.toString())) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
}
}
return success;
} }
BuildSystem *createNimBuildSystem(Target *target) BuildSystem *createNimBuildSystem(Target *target)

View File

@@ -224,10 +224,10 @@ bool BuildSystem::canRenameFile(Node *, const FilePath &oldFilePath, const FileP
return true; return true;
} }
bool BuildSystem::renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) bool BuildSystem::renameFiles(Node *, const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
Q_UNUSED(oldFilePath) if (notRenamed)
Q_UNUSED(newFilePath) *notRenamed = firstPaths(filesToRename);
return false; return false;
} }

View File

@@ -77,9 +77,8 @@ public:
virtual bool canRenameFile(Node *context, virtual bool canRenameFile(Node *context,
const Utils::FilePath &oldFilePath, const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath); const Utils::FilePath &newFilePath);
virtual bool renameFile(Node *context, virtual bool renameFiles(
const Utils::FilePath &oldFilePath, Node *context, const Utils::FilePairs &filesToRename, Utils::FilePaths *notRenamed);
const Utils::FilePath &newFilePath);
virtual bool addDependencies(Node *context, const QStringList &dependencies); virtual bool addDependencies(Node *context, const QStringList &dependencies);
virtual bool supportsAction(Node *context, ProjectAction action, const Node *node) const; virtual bool supportsAction(Node *context, ProjectAction action, const Node *node) const;
virtual void buildNamedTarget(const QString &target) { Q_UNUSED(target) } virtual void buildNamedTarget(const QString &target) { Q_UNUSED(target) }

View File

@@ -2478,16 +2478,88 @@ void ProjectExplorerPlugin::showOutputPaneForRunControl(RunControl *runControl)
appOutputPane().showOutputPaneForRunControl(runControl); appOutputPane().showOutputPaneForRunControl(runControl);
} }
QList<std::pair<FilePath, FilePath>> ProjectExplorerPlugin::renameFiles( static HandleIncludeGuards canTryToRenameIncludeGuards(const Node *node)
const QList<std::pair<Node *, Utils::FilePath>> &nodesAndNewFilePaths)
{ {
QList<std::pair<FilePath, FilePath>> renamedFiles; return node->asFileNode() && node->asFileNode()->fileType() == FileType::Header
for (const auto &[node, newFilePath] : nodesAndNewFilePaths) { ? HandleIncludeGuards::Yes : HandleIncludeGuards::No;
if (const auto res = renameFile(node, newFilePath.toString())) }
renamedFiles << *res;
FilePairs ProjectExplorerPlugin::renameFiles(
const QList<std::pair<Node *, FilePath>> &nodesAndNewFilePaths)
{
const QList<std::pair<Node *, FilePath>> nodesAndNewFilePathsFiltered
= Utils::filtered(nodesAndNewFilePaths, [](const std::pair<Node *, FilePath> &elem) {
return !elem.first->filePath().equalsCaseSensitive(elem.second);
});
FilePaths renamedOnly;
FilePaths failedRenamings;
const auto renameFile = [&failedRenamings](const Node *node, const FilePath &newFilePath) {
if (!Core::FileUtils::renameFile(
node->filePath(), newFilePath, canTryToRenameIncludeGuards(node))) {
failedRenamings << node->filePath();
return false;
}
return true;
};
QHash<FolderNode *, QList<std::pair<Node *, FilePath>>> renamingsPerParentNode;
for (const auto &elem : nodesAndNewFilePathsFiltered) {
if (FolderNode * const folderNode = elem.first->parentFolderNode())
renamingsPerParentNode[folderNode] << elem;
else if (renameFile(elem.first, elem.second))
renamedOnly << elem.first->filePath();
} }
emit instance()->filesRenamed(renamedFiles);
return renamedFiles; for (auto it = renamingsPerParentNode.cbegin(); it != renamingsPerParentNode.cend(); ++it) {
FilePairs toUpdateInProject;
for (const std::pair<Node *, FilePath> &elem : it.value()) {
const bool canUpdateProject
= it.key()->canRenameFile(elem.first->filePath(), elem.second);
if (renameFile(elem.first, elem.second)) {
if (canUpdateProject )
toUpdateInProject << std::make_pair(elem.first->filePath(), elem.second);
else
renamedOnly << elem.first->filePath();
}
}
if (toUpdateInProject.isEmpty())
continue;
FilePaths notRenamed;
if (!it.key()->renameFiles(toUpdateInProject, &notRenamed))
renamedOnly << notRenamed;
}
if (!failedRenamings.isEmpty() || !renamedOnly.isEmpty()) {
const auto pathsAsHtmlList = [](const FilePaths &files) {
QString s("<ul>");
for (const FilePath &f : files)
s.append("<li>").append(f.toUserOutput()).append("</li>");
return s.append("</ul>");
};
QString failedRenamingsString;
if (!failedRenamings.isEmpty()) {
failedRenamingsString = Tr::tr("The following files could not be renamed: %1")
.arg(pathsAsHtmlList(failedRenamings));
}
QString renamedOnlyString;
if (!renamedOnly.isEmpty()) {
renamedOnlyString = Tr::tr(
"<br>The following files were renamed, but their project files could not "
"be updated accordingly: %1")
.arg(pathsAsHtmlList(renamedOnly));
}
QTimer::singleShot(0, m_instance, [message = failedRenamingsString + renamedOnlyString] {
QMessageBox::warning(
ICore::dialogParent(), Tr::tr("Renaming Did Not Fully Succeed"), message);
});
}
FilePairs allRenamedFiles;
for (const std::pair<Node *, FilePath> &candidate : nodesAndNewFilePathsFiltered) {
if (!failedRenamings.contains(candidate.first->filePath()))
allRenamedFiles.emplaceBack(candidate.first->filePath(), candidate.second);
}
emit instance()->filesRenamed(allRenamedFiles);
return allRenamedFiles;
} }
#ifdef WITH_TESTS #ifdef WITH_TESTS
@@ -2761,7 +2833,7 @@ void ProjectExplorerPluginPrivate::extendFolderNavigationWidgetFactory()
const QVector<FolderNode *> folderNodes = renamableFolderNodes(before, after); const QVector<FolderNode *> folderNodes = renamableFolderNodes(before, after);
QVector<FolderNode *> failedNodes; QVector<FolderNode *> failedNodes;
for (FolderNode *folder : folderNodes) { for (FolderNode *folder : folderNodes) {
if (!folder->renameFile(before, after)) if (!folder->renameFiles({std::make_pair(before, after)}, nullptr))
failedNodes.append(folder); failedNodes.append(folder);
} }
if (!failedNodes.isEmpty()) { if (!failedNodes.isEmpty()) {
@@ -3859,12 +3931,6 @@ void ProjectExplorerPluginPrivate::removeFile()
Core::FileUtils::removeFiles(pathList, deleteFile); Core::FileUtils::removeFiles(pathList, deleteFile);
} }
static HandleIncludeGuards canTryToRenameIncludeGuards(const Node *node)
{
return node->asFileNode() && node->asFileNode()->fileType() == FileType::Header
? HandleIncludeGuards::Yes : HandleIncludeGuards::No;
}
void ProjectExplorerPluginPrivate::duplicateFile() void ProjectExplorerPluginPrivate::duplicateFile()
{ {
Node *currentNode = ProjectTree::currentNode(); Node *currentNode = ProjectTree::currentNode();
@@ -3959,67 +4025,6 @@ void ProjectExplorerPluginPrivate::handleRenameFile()
} }
} }
std::optional<std::pair<FilePath, FilePath>>
ProjectExplorerPlugin::renameFile(Node *node, const QString &newFileName)
{
const FilePath oldFilePath = node->filePath().absoluteFilePath();
FolderNode *folderNode = node->parentFolderNode();
QTC_ASSERT(folderNode, return {});
const QString projectFileName = folderNode->managingProject()->filePath().fileName();
const FilePath newFilePath = FilePath::fromString(newFileName);
if (oldFilePath.equalsCaseSensitive(newFilePath))
return {};
const HandleIncludeGuards handleGuards = canTryToRenameIncludeGuards(node);
if (!folderNode->canRenameFile(oldFilePath, newFilePath)) {
QTimer::singleShot(0, m_instance,
[oldFilePath, newFilePath, projectFileName, handleGuards] {
int res = QMessageBox::question(ICore::dialogParent(),
Tr::tr("Project Editing Failed"),
Tr::tr("The project file %1 cannot be automatically changed.\n\n"
"Rename %2 to %3 anyway?")
.arg(projectFileName)
.arg(oldFilePath.toUserOutput())
.arg(newFilePath.toUserOutput()));
if (res == QMessageBox::Yes) {
QTC_CHECK(Core::FileUtils::renameFile(oldFilePath, newFilePath, handleGuards));
}
});
return {};
}
if (Core::FileUtils::renameFile(oldFilePath, newFilePath, handleGuards)) {
// Tell the project plugin about rename
// TODO: We might want to separate this into an extra step to make bulk renamings safer;
// see CppModelManager::renameIncludes().
if (!folderNode->renameFile(oldFilePath, newFilePath)) {
const QString renameFileError = Tr::tr("The file %1 was renamed to %2, but the project "
"file %3 could not be automatically changed.")
.arg(oldFilePath.toUserOutput())
.arg(newFilePath.toUserOutput())
.arg(projectFileName);
QTimer::singleShot(0, m_instance, [renameFileError] {
QMessageBox::warning(ICore::dialogParent(),
Tr::tr("Project Editing Failed"),
renameFileError);
});
}
return std::make_pair(oldFilePath, newFilePath);
}
const QString renameFileError = Tr::tr("The file %1 could not be renamed %2.")
.arg(oldFilePath.toUserOutput())
.arg(newFilePath.toUserOutput());
QTimer::singleShot(0, m_instance, [renameFileError] {
QMessageBox::warning(ICore::dialogParent(), Tr::tr("Cannot Rename File"), renameFileError);
});
return {};
}
void ProjectExplorerPluginPrivate::handleSetStartupProject() void ProjectExplorerPluginPrivate::handleSetStartupProject()
{ {
setStartupProject(ProjectTree::currentProject()); setStartupProject(ProjectTree::currentProject());
@@ -4133,7 +4138,7 @@ void ProjectExplorerPlugin::renameFilesForSymbol(const QString &oldSymbolName,
const QString &newSymbolName, const FilePaths &files, bool preferLowerCaseFileNames) const QString &newSymbolName, const FilePaths &files, bool preferLowerCaseFileNames)
{ {
static const auto isAllLowerCase = [](const QString &text) { return text.toLower() == text; }; static const auto isAllLowerCase = [](const QString &text) { return text.toLower() == text; };
QList<std::pair<FilePath, FilePath>> renamedFiles; QList<std::pair<Node *, FilePath>> filesToRename;
for (const FilePath &file : files) { for (const FilePath &file : files) {
Node * const node = ProjectTree::nodeForFile(file); Node * const node = ProjectTree::nodeForFile(file);
if (!node) if (!node)
@@ -4164,10 +4169,9 @@ void ProjectExplorerPlugin::renameFilesForSymbol(const QString &oldSymbolName,
const QString newFilePath = file.absolutePath().toString() + '/' + newBaseName + '.' const QString newFilePath = file.absolutePath().toString() + '/' + newBaseName + '.'
+ file.completeSuffix(); + file.completeSuffix();
if (const auto res = renameFile(node, newFilePath)) filesToRename.emplaceBack(node, FilePath::fromString(newFilePath));
renamedFiles << *res;
} }
emit instance()->filesRenamed(renamedFiles); renameFiles(filesToRename);
} }
void ProjectManager::registerProjectCreator(const QString &mimeType, void ProjectManager::registerProjectCreator(const QString &mimeType,

View File

@@ -123,8 +123,8 @@ public:
static void startRunControl(RunControl *runControl); static void startRunControl(RunControl *runControl);
static void showOutputPaneForRunControl(RunControl *runControl); static void showOutputPaneForRunControl(RunControl *runControl);
static QList<std::pair<Utils::FilePath, Utils::FilePath>> static Utils::FilePairs renameFiles(
renameFiles(const QList<std::pair<Node *, Utils::FilePath>> &nodesAndNewFilePaths); const QList<std::pair<Node *, Utils::FilePath>> &nodesAndNewFilePaths);
#ifdef WITH_TESTS #ifdef WITH_TESTS
static bool renameFile(const Utils::FilePath &source, const Utils::FilePath &target, static bool renameFile(const Utils::FilePath &source, const Utils::FilePath &target,
@@ -182,13 +182,11 @@ signals:
void runControlStarted(ProjectExplorer::RunControl *runControl); void runControlStarted(ProjectExplorer::RunControl *runControl);
void runControlStoped(ProjectExplorer::RunControl *runControl); void runControlStoped(ProjectExplorer::RunControl *runControl);
void filesRenamed(const QList<std::pair<Utils::FilePath, Utils::FilePath>> &oldAndNewPaths); void filesRenamed(const Utils::FilePairs &oldAndNewPaths);
private: private:
static bool coreAboutToClose(); static bool coreAboutToClose();
void handleCommandLineArguments(const QStringList &arguments); void handleCommandLineArguments(const QStringList &arguments);
static std::optional<std::pair<Utils::FilePath, Utils::FilePath>>
renameFile(Node *node, const QString &newFilePath);
}; };
} // namespace ProjectExplorer } // namespace ProjectExplorer

View File

@@ -837,11 +837,11 @@ bool FolderNode::canRenameFile(const FilePath &oldFilePath, const FilePath &newF
return false; return false;
} }
bool FolderNode::renameFile(const FilePath &oldFilePath, const FilePath &newFilePath) bool FolderNode::renameFiles(const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
ProjectNode *pn = managingProject(); ProjectNode *pn = managingProject();
if (pn) if (pn)
return pn->renameFile(oldFilePath, newFilePath); return pn->renameFiles(filesToRename, notRenamed);
return false; return false;
} }
@@ -970,17 +970,20 @@ bool ProjectNode::deleteFiles(const FilePaths &filePaths)
return false; return false;
} }
bool ProjectNode::canRenameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) bool ProjectNode::canRenameFile(
const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath)
{ {
if (BuildSystem *bs = buildSystem()) if (BuildSystem * const bs = buildSystem())
return bs->canRenameFile(this, oldFilePath, newFilePath); return bs->canRenameFile(this, oldFilePath, newFilePath);
return true; return false;
} }
bool ProjectNode::renameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) bool ProjectNode::renameFiles(const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
if (BuildSystem *bs = buildSystem()) if (BuildSystem *bs = buildSystem())
return bs->renameFile(this, oldFilePath, newFilePath); return bs->renameFiles(this, filesToRename, notRenamed);
if (notRenamed)
*notRenamed = firstPaths(filesToRename);
return false; return false;
} }

View File

@@ -13,6 +13,7 @@
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <utility>
#include <variant> #include <variant>
namespace Utils { class MimeType; } namespace Utils { class MimeType; }
@@ -300,7 +301,7 @@ public:
virtual bool deleteFiles(const Utils::FilePaths &filePaths); virtual bool deleteFiles(const Utils::FilePaths &filePaths);
virtual bool canRenameFile(const Utils::FilePath &oldFilePath, virtual bool canRenameFile(const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath); const Utils::FilePath &newFilePath);
virtual bool renameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath); virtual bool renameFiles(const Utils::FilePairs &filesToRename, Utils::FilePaths *notRenamed);
virtual bool addDependencies(const QStringList &dependencies); virtual bool addDependencies(const QStringList &dependencies);
class AddNewInformation class AddNewInformation
@@ -382,8 +383,9 @@ public:
RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths, RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths,
Utils::FilePaths *notRemoved = nullptr) final; Utils::FilePaths *notRemoved = nullptr) final;
bool deleteFiles(const Utils::FilePaths &filePaths) final; bool deleteFiles(const Utils::FilePaths &filePaths) final;
bool canRenameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; bool canRenameFile(
bool renameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override;
bool renameFiles(const Utils::FilePairs &filesToRename, Utils::FilePaths *notRenamed) final;
bool addDependencies(const QStringList &dependencies) final; bool addDependencies(const QStringList &dependencies) final;
bool supportsAction(ProjectAction action, const Node *node) const final; bool supportsAction(ProjectAction action, const Node *node) const final;

View File

@@ -305,17 +305,33 @@ bool PythonBuildSystem::deleteFiles(Node *, const FilePaths &)
return true; return true;
} }
bool PythonBuildSystem::renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) bool PythonBuildSystem::renameFiles(Node *, const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
for (FileEntry &entry : m_files) { bool success = true;
if (entry.filePath == oldFilePath) { for (const auto &[oldFilePath, newFilePath] : filesToRename) {
entry.filePath = newFilePath; bool found = false;
entry.rawEntry = newFilePath.relativeChildPath(projectDirectory()).toString(); for (FileEntry &entry : m_files) {
break; if (entry.filePath == oldFilePath) {
found = true;
entry.filePath = newFilePath;
entry.rawEntry = newFilePath.relativeChildPath(projectDirectory()).toString();
break;
}
}
if (!found) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
} }
} }
return save(); if (!save()) {
if (notRenamed)
*notRenamed = firstPaths(filesToRename);
return false;
}
return success;
} }
void PythonBuildSystem::parse() void PythonBuildSystem::parse()

View File

@@ -25,9 +25,10 @@ public:
const Utils::FilePaths &filePaths, const Utils::FilePaths &filePaths,
Utils::FilePaths *) override; Utils::FilePaths *) override;
bool deleteFiles(ProjectExplorer::Node *, const Utils::FilePaths &) override; bool deleteFiles(ProjectExplorer::Node *, const Utils::FilePaths &) override;
bool renameFile(ProjectExplorer::Node *, bool renameFiles(
const Utils::FilePath &oldFilePath, ProjectExplorer::Node *,
const Utils::FilePath &newFilePath) override; const Utils::FilePairs &filesToRename,
Utils::FilePaths *notRenamed) override;
QString name() const override { return QLatin1String("python"); } QString name() const override { return QLatin1String("python"); }
void parse(); void parse();

View File

@@ -267,27 +267,43 @@ RemovedFilesFromProject QbsBuildSystem::removeFiles(Node *context, const FilePat
return BuildSystem::removeFiles(context, filePaths, notRemoved); return BuildSystem::removeFiles(context, filePaths, notRemoved);
} }
bool QbsBuildSystem::renameFile(Node *context, bool QbsBuildSystem::renameFiles(Node *context, const FilePairs &filesToRename, FilePaths *notRenamed)
const FilePath &oldFilePath,
const FilePath &newFilePath)
{ {
if (auto *n = dynamic_cast<QbsGroupNode *>(context)) { if (auto *n = dynamic_cast<QbsGroupNode *>(context)) {
const QbsProductNode * const prdNode = parentQbsProductNode(n); const QbsProductNode * const prdNode = parentQbsProductNode(n);
QTC_ASSERT(prdNode, return false); QTC_ASSERT(prdNode, return false);
return renameFileInProduct(oldFilePath.toString(), bool success = true;
newFilePath.toString(), for (const auto &[oldFilePath, newFilePath] : filesToRename) {
prdNode->productData(), if (!renameFileInProduct(
n->groupData()); oldFilePath.toString(),
newFilePath.toString(),
prdNode->productData(),
n->groupData())) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
}
}
return success;
} }
if (auto *n = dynamic_cast<QbsProductNode *>(context)) { if (auto *n = dynamic_cast<QbsProductNode *>(context)) {
return renameFileInProduct(oldFilePath.toString(), bool success = true;
newFilePath.toString(), for (const auto &[oldFilePath, newFilePath] : filesToRename) {
n->productData(), if (!renameFileInProduct(
n->mainGroup()); oldFilePath.toString(),
newFilePath.toString(),
n->productData(),
n->mainGroup())) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
}
}
return success;
} }
return BuildSystem::renameFile(context, oldFilePath, newFilePath); return BuildSystem::renameFiles(context, filesToRename, notRenamed);
} }
QVariant QbsBuildSystem::additionalData(Id id) const QVariant QbsBuildSystem::additionalData(Id id) const
@@ -409,6 +425,7 @@ bool QbsBuildSystem::renameFileInProduct(
if (newPath.isEmpty()) if (newPath.isEmpty())
return false; return false;
FilePaths dummy; FilePaths dummy;
// FIXME: The qbs API need a (bulk) renaming feature
if (removeFilesFromProduct({FilePath::fromString(oldPath)}, product, group, &dummy) if (removeFilesFromProduct({FilePath::fromString(oldPath)}, product, group, &dummy)
!= RemovedFilesFromProject::Ok) { != RemovedFilesFromProject::Ok) {
return false; return false;

View File

@@ -67,8 +67,10 @@ public:
ProjectExplorer::RemovedFilesFromProject removeFiles(ProjectExplorer::Node *context, ProjectExplorer::RemovedFilesFromProject removeFiles(ProjectExplorer::Node *context,
const Utils::FilePaths &filePaths, const Utils::FilePaths &filePaths,
Utils::FilePaths *notRemoved = nullptr) final; Utils::FilePaths *notRemoved = nullptr) final;
bool renameFile(ProjectExplorer::Node *context, bool renameFiles(
const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; ProjectExplorer::Node *context,
const Utils::FilePairs &filesToRename,
Utils::FilePaths *notRenamed) final;
Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final; Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final;
QVariant additionalData(Utils::Id id) const final; QVariant additionalData(Utils::Id id) const final;
QString name() const final { return QLatin1String("qbs"); } QString name() const final { return QLatin1String("qbs"); }

View File

@@ -254,16 +254,27 @@ bool QmakeBuildSystem::canRenameFile(Node *context,
return BuildSystem::canRenameFile(context, oldFilePath, newFilePath); return BuildSystem::canRenameFile(context, oldFilePath, newFilePath);
} }
bool QmakeBuildSystem::renameFile(Node *context, bool QmakeBuildSystem::renameFiles(Node *context, const FilePairs &filesToRename, FilePaths *notRenamed)
const FilePath &oldFilePath,
const FilePath &newFilePath)
{ {
if (auto n = dynamic_cast<QmakePriFileNode *>(context)) { if (auto n = dynamic_cast<QmakePriFileNode *>(context)) {
QmakePriFile *pri = n->priFile(); QmakePriFile *pri = n->priFile();
return pri ? pri->renameFile(oldFilePath, newFilePath) : false; if (!pri) {
if (notRenamed)
*notRenamed = firstPaths(filesToRename);
return false;
}
bool success = true;
for (const auto &[oldFilePath, newFilePath] : filesToRename) {
if (!pri->renameFile(oldFilePath, newFilePath)) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
}
}
return success;
} }
return BuildSystem::renameFile(context, oldFilePath, newFilePath); return BuildSystem::renameFiles(context, filesToRename, notRenamed);
} }
bool QmakeBuildSystem::addDependencies(Node *context, const QStringList &dependencies) bool QmakeBuildSystem::addDependencies(Node *context, const QStringList &dependencies)

View File

@@ -79,9 +79,9 @@ public:
bool canRenameFile(ProjectExplorer::Node *context, bool canRenameFile(ProjectExplorer::Node *context,
const Utils::FilePath &oldFilePath, const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath) override; const Utils::FilePath &newFilePath) override;
bool renameFile(ProjectExplorer::Node *context, bool renameFiles(ProjectExplorer::Node *context,
const Utils::FilePath &oldFilePath, const Utils::FilePairs &filesToRename,
const Utils::FilePath &newFilePath) override; Utils::FilePaths *notRenamed) override;
bool addDependencies(ProjectExplorer::Node *context, bool addDependencies(ProjectExplorer::Node *context,
const QStringList &dependencies) override; const QStringList &dependencies) override;
QString name() const final { return QLatin1String("qmake"); } QString name() const final { return QLatin1String("qmake"); }

View File

@@ -627,19 +627,35 @@ bool QmlBuildSystem::deleteFiles(Node *context, const Utils::FilePaths &filePath
return BuildSystem::deleteFiles(context, filePaths); return BuildSystem::deleteFiles(context, filePaths);
} }
bool QmlBuildSystem::renameFile(Node *context, bool QmlBuildSystem::renameFiles(Node *context,
const Utils::FilePath &oldFilePath, const Utils::FilePairs &filesToRename,
const Utils::FilePath &newFilePath) Utils::FilePaths *notRenamed)
{ {
if (dynamic_cast<Internal::QmlProjectNode *>(context)) { if (!dynamic_cast<Internal::QmlProjectNode *>(context))
if (oldFilePath.endsWith(mainFile())) return BuildSystem::renameFiles(context, filesToRename, notRenamed);
return setMainFileInProjectFile(newFilePath);
if (oldFilePath.endsWith(m_projectItem->mainUiFile())) bool success = true;
return setMainUiFileInProjectFile(newFilePath); for (const auto &[oldFilePath, newFilePath] : filesToRename) {
return true; const auto fail = [&] {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
};
if (oldFilePath.endsWith(mainFile())) {
if (!setMainFileInProjectFile(newFilePath))
fail();
continue;
}
if (oldFilePath.endsWith(m_projectItem->mainUiFile())) {
if (!setMainUiFileInProjectFile(newFilePath))
fail();
continue;
}
// Why is this not an error?
} }
return BuildSystem::renameFile(context, oldFilePath, newFilePath); return success;
} }
QString QmlBuildSystem::mainFile() const QString QmlBuildSystem::mainFile() const

View File

@@ -35,9 +35,9 @@ public:
const Utils::FilePaths &filePaths, const Utils::FilePaths &filePaths,
Utils::FilePaths *notAdded = nullptr) override; Utils::FilePaths *notAdded = nullptr) override;
bool deleteFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths) override; bool deleteFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths) override;
bool renameFile(ProjectExplorer::Node *context, bool renameFiles(ProjectExplorer::Node *context,
const Utils::FilePath &oldFilePath, const Utils::FilePairs &filesToRename,
const Utils::FilePath &newFilePath) override; Utils::FilePaths *notRenamed) override;
bool updateProjectFile(); bool updateProjectFile();

View File

@@ -157,7 +157,7 @@ public:
RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths, RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths,
Utils::FilePaths *notRemoved) final; Utils::FilePaths *notRemoved) final;
bool canRenameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override; bool canRenameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override;
bool renameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; bool renameFiles(const FilePairs &filesToRename, FilePaths *notRenamed) final;
QString prefix() const { return m_prefix; } QString prefix() const { return m_prefix; }
ResourceTopLevelNode *resourceNode() const { return m_topLevelNode; } ResourceTopLevelNode *resourceNode() const { return m_topLevelNode; }
@@ -211,9 +211,9 @@ bool SimpleResourceFolderNode::canRenameFile(const FilePath &oldFilePath,
return prefixNode()->canRenameFile(oldFilePath, newFilePath); return prefixNode()->canRenameFile(oldFilePath, newFilePath);
} }
bool SimpleResourceFolderNode::renameFile(const FilePath &oldFilePath, const FilePath &newFilePath) bool SimpleResourceFolderNode::renameFiles(const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
return prefixNode()->renameFile(oldFilePath, newFilePath); return prefixNode()->renameFiles(filesToRename, notRenamed);
} }
} // Internal } // Internal
@@ -531,7 +531,7 @@ bool ResourceFolderNode::canRenameFile(const FilePath &oldFilePath, const FilePa
return fileEntryExists; return fileEntryExists;
} }
bool ResourceFolderNode::renameFile(const FilePath &oldFilePath, const FilePath &newFilePath) bool ResourceFolderNode::renameFiles(const FilePairs &filesToRename, FilePaths *notRenamed)
{ {
ResourceFile file(m_topLevelNode->filePath()); ResourceFile file(m_topLevelNode->filePath());
if (file.load() != IDocument::OpenResult::Success) if (file.load() != IDocument::OpenResult::Success)
@@ -540,16 +540,27 @@ bool ResourceFolderNode::renameFile(const FilePath &oldFilePath, const FilePath
if (index == -1) if (index == -1)
return false; return false;
for (int j = 0; j < file.fileCount(index); ++j) { bool success = true;
if (file.file(index, j) == oldFilePath.toString()) { for (const auto &[oldFilePath, newFilePath] : filesToRename) {
file.replaceFile(index, j, newFilePath.toString()); bool found = false;
FileChangeBlocker changeGuard(m_topLevelNode->filePath()); for (int j = 0; j < file.fileCount(index); ++j) {
file.save(); if (file.file(index, j) == oldFilePath.toString()) {
return true; file.replaceFile(index, j, newFilePath.toString());
found = true;
break;
}
}
if (!found) {
success = false;
if (notRenamed)
*notRenamed << oldFilePath;
} }
} }
return false; FileChangeBlocker changeGuard(m_topLevelNode->filePath());
file.save();
return success;
} }
bool ResourceFolderNode::renamePrefix(const QString &prefix, const QString &lang) bool ResourceFolderNode::renamePrefix(const QString &prefix, const QString &lang)

View File

@@ -55,7 +55,8 @@ public:
ProjectExplorer::RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths, ProjectExplorer::RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths,
Utils::FilePaths *notRemoved) override; Utils::FilePaths *notRemoved) override;
bool canRenameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override; bool canRenameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override;
bool renameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override; bool renameFiles(
const Utils::FilePairs &filesToRename, Utils::FilePaths *notRenamed) override;
bool renamePrefix(const QString &prefix, const QString &lang); bool renamePrefix(const QString &prefix, const QString &lang);