diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index bc1399ef8ea..8252825d9e9 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -2558,4 +2558,14 @@ FilePath TemporaryFilePath::filePath() const return d->filePath; } +FilePaths firstPaths(const FilePairs &pairs) +{ + return transform(pairs, &FilePair::first); +} + +FilePaths secondPaths(const FilePairs &pairs) +{ + return transform(pairs, &FilePair::second); +} + } // Utils diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index a14c525e950..8c24e956fff 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -17,6 +17,7 @@ #include #include #include +#include #include QT_BEGIN_NAMESPACE @@ -53,7 +54,12 @@ public: const QDirIterator::IteratorFlags iteratorFlags = QDirIterator::NoIteratorFlags; }; -using FilePaths = QList; +class FilePath; +using FilePaths = QList; +using FilePair = std::pair; +using FilePairs = QList; +QTCREATOR_UTILS_EXPORT FilePaths firstPaths(const FilePairs &pairs); +QTCREATOR_UTILS_EXPORT FilePaths secondPaths(const FilePairs &pairs); class QTCREATOR_UTILS_EXPORT FilePathWatcher : public QObject { diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 791a3e755e6..1d3b1eb83b5 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -962,74 +962,95 @@ bool CMakeBuildSystem::canRenameFile(Node *context, return false; } -bool CMakeBuildSystem::renameFile(Node *context, - const FilePath &oldFilePath, - const FilePath &newFilePath) +bool CMakeBuildSystem::renameFiles(Node *context, const FilePairs &filesToRename, FilePaths *notRenamed) { - if (auto n = dynamic_cast(context)) { - const FilePath projDir = n->filePath().canonicalPath(); - const FilePath newRelPath = newFilePath.canonicalPath().relativePathFrom(projDir).cleanPath(); - const QString newRelPathName = newRelPath.toString(); - - const QString targetName = n->buildKey(); - const QString key - = QStringList{projDir.path(), targetName, oldFilePath.path(), newFilePath.path()}.join( - ";"); - - std::optional 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( - Core::EditorManager::openEditorAt( - {fileToRename->cmakeFile, - static_cast(fileToRename->argumentPosition.Line), - static_cast(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; + const auto n = dynamic_cast(context); + if (!n) { + if (notRenamed) + *notRenamed = firstPaths(filesToRename); + return false; } - 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 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( + Core::EditorManager::openEditorAt( + {fileToRename->cmakeFile, + static_cast(fileToRename->argumentPosition.Line), + static_cast(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) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h index b4ac08c3b7b..2b55097852d 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -61,9 +61,9 @@ public: bool canRenameFile(ProjectExplorer::Node *context, const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; - bool renameFile(ProjectExplorer::Node *context, - const Utils::FilePath &oldFilePath, - const Utils::FilePath &newFilePath) final; + bool renameFiles(ProjectExplorer::Node *context, + const Utils::FilePairs &filesToRename, + Utils::FilePaths *notRenamed) final; void buildNamedTarget(const QString &target) final; Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final; @@ -157,6 +157,9 @@ private: Utils::FilePaths *); bool addTsFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths, Utils::FilePaths *); + bool renameFile(CMakeTargetNode *context, + const Utils::FilePath &oldFilePath, + const Utils::FilePath &newFilePath, bool &shouldRunCMake); // Actually ask for parsing: enum ReparseParameters { diff --git a/src/plugins/genericprojectmanager/genericproject.cpp b/src/plugins/genericprojectmanager/genericproject.cpp index 81543685cdf..d299f1f22b3 100644 --- a/src/plugins/genericprojectmanager/genericproject.cpp +++ b/src/plugins/genericprojectmanager/genericproject.cpp @@ -114,7 +114,10 @@ public: } 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; QString name() const final { return QLatin1String("generic"); } @@ -376,21 +379,42 @@ bool GenericBuildSystem::setFiles(const QStringList &filePaths) 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; - QHash::iterator i = m_rawListEntries.find(oldFilePath.toString()); - if (i != m_rawListEntries.end()) { - int index = newList.indexOf(i.value()); - if (index != -1) { - QDir baseDir(projectDirectory().toString()); - newList.removeAt(index); - insertSorted(&newList, baseDir.relativeFilePath(newFilePath.toString())); + bool success = true; + for (const auto &[oldFilePath, newFilePath] : filesToRename) { + const auto fail = [&] { + success = false; + if (notRenamed) + *notRenamed << oldFilePath; + }; + + 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) diff --git a/src/plugins/nim/project/nimblebuildsystem.cpp b/src/plugins/nim/project/nimblebuildsystem.cpp index 91f4eb2f202..b08a3f6dc04 100644 --- a/src/plugins/nim/project/nimblebuildsystem.cpp +++ b/src/plugins/nim/project/nimblebuildsystem.cpp @@ -234,9 +234,17 @@ bool NimbleBuildSystem::deleteFiles(Node *, const FilePaths &) 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 diff --git a/src/plugins/nim/project/nimblebuildsystem.h b/src/plugins/nim/project/nimblebuildsystem.h index 63fef296bef..78e130ea746 100644 --- a/src/plugins/nim/project/nimblebuildsystem.h +++ b/src/plugins/nim/project/nimblebuildsystem.h @@ -58,8 +58,10 @@ private: const Utils::FilePaths &filePaths, Utils::FilePaths *) override; bool deleteFiles(ProjectExplorer::Node *, const Utils::FilePaths &) override; - bool renameFile(ProjectExplorer::Node *, - const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override; + bool renameFiles( + ProjectExplorer::Node *, + const Utils::FilePairs &filesToRename, + Utils::FilePaths *notRenamed) override; QString name() const final { return QLatin1String("mimble"); } void triggerParsing() final; diff --git a/src/plugins/nim/project/nimbuildsystem.cpp b/src/plugins/nim/project/nimbuildsystem.cpp index 39acc8635d0..0458d847bd1 100644 --- a/src/plugins/nim/project/nimbuildsystem.cpp +++ b/src/plugins/nim/project/nimbuildsystem.cpp @@ -152,7 +152,10 @@ public: const FilePaths &filePaths, 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"); } void triggerParsing() final; @@ -237,9 +240,17 @@ bool NimBuildSystem::deleteFiles(Node *, const FilePaths &) 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) diff --git a/src/plugins/projectexplorer/buildsystem.cpp b/src/plugins/projectexplorer/buildsystem.cpp index 405de77e178..6b98866135a 100644 --- a/src/plugins/projectexplorer/buildsystem.cpp +++ b/src/plugins/projectexplorer/buildsystem.cpp @@ -224,10 +224,10 @@ bool BuildSystem::canRenameFile(Node *, const FilePath &oldFilePath, const FileP return true; } -bool BuildSystem::renameFile(Node *, const FilePath &oldFilePath, const FilePath &newFilePath) +bool BuildSystem::renameFiles(Node *, const FilePairs &filesToRename, FilePaths *notRenamed) { - Q_UNUSED(oldFilePath) - Q_UNUSED(newFilePath) + if (notRenamed) + *notRenamed = firstPaths(filesToRename); return false; } diff --git a/src/plugins/projectexplorer/buildsystem.h b/src/plugins/projectexplorer/buildsystem.h index 19f32d83f87..f18cbbbea71 100644 --- a/src/plugins/projectexplorer/buildsystem.h +++ b/src/plugins/projectexplorer/buildsystem.h @@ -77,9 +77,8 @@ public: virtual bool canRenameFile(Node *context, const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath); - virtual bool renameFile(Node *context, - const Utils::FilePath &oldFilePath, - const Utils::FilePath &newFilePath); + virtual bool renameFiles( + Node *context, const Utils::FilePairs &filesToRename, Utils::FilePaths *notRenamed); virtual bool addDependencies(Node *context, const QStringList &dependencies); virtual bool supportsAction(Node *context, ProjectAction action, const Node *node) const; virtual void buildNamedTarget(const QString &target) { Q_UNUSED(target) } diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index 581e14f3006..0e85065c909 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -2478,16 +2478,88 @@ void ProjectExplorerPlugin::showOutputPaneForRunControl(RunControl *runControl) appOutputPane().showOutputPaneForRunControl(runControl); } -QList> ProjectExplorerPlugin::renameFiles( - const QList> &nodesAndNewFilePaths) +static HandleIncludeGuards canTryToRenameIncludeGuards(const Node *node) { - QList> renamedFiles; - for (const auto &[node, newFilePath] : nodesAndNewFilePaths) { - if (const auto res = renameFile(node, newFilePath.toString())) - renamedFiles << *res; + return node->asFileNode() && node->asFileNode()->fileType() == FileType::Header + ? HandleIncludeGuards::Yes : HandleIncludeGuards::No; +} + +FilePairs ProjectExplorerPlugin::renameFiles( + const QList> &nodesAndNewFilePaths) +{ + const QList> nodesAndNewFilePathsFiltered + = Utils::filtered(nodesAndNewFilePaths, [](const std::pair &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>> 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 &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, ¬Renamed)) + renamedOnly << notRenamed; + } + + if (!failedRenamings.isEmpty() || !renamedOnly.isEmpty()) { + const auto pathsAsHtmlList = [](const FilePaths &files) { + QString s("
    "); + for (const FilePath &f : files) + s.append("
  • ").append(f.toUserOutput()).append("
  • "); + return s.append("
"); + }; + 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( + "
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 &candidate : nodesAndNewFilePathsFiltered) { + if (!failedRenamings.contains(candidate.first->filePath())) + allRenamedFiles.emplaceBack(candidate.first->filePath(), candidate.second); + } + emit instance()->filesRenamed(allRenamedFiles); + return allRenamedFiles; } #ifdef WITH_TESTS @@ -2761,7 +2833,7 @@ void ProjectExplorerPluginPrivate::extendFolderNavigationWidgetFactory() const QVector folderNodes = renamableFolderNodes(before, after); QVector failedNodes; for (FolderNode *folder : folderNodes) { - if (!folder->renameFile(before, after)) + if (!folder->renameFiles({std::make_pair(before, after)}, nullptr)) failedNodes.append(folder); } if (!failedNodes.isEmpty()) { @@ -3859,12 +3931,6 @@ void ProjectExplorerPluginPrivate::removeFile() 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() { Node *currentNode = ProjectTree::currentNode(); @@ -3959,67 +4025,6 @@ void ProjectExplorerPluginPrivate::handleRenameFile() } } -std::optional> -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() { setStartupProject(ProjectTree::currentProject()); @@ -4133,7 +4138,7 @@ void ProjectExplorerPlugin::renameFilesForSymbol(const QString &oldSymbolName, const QString &newSymbolName, const FilePaths &files, bool preferLowerCaseFileNames) { static const auto isAllLowerCase = [](const QString &text) { return text.toLower() == text; }; - QList> renamedFiles; + QList> filesToRename; for (const FilePath &file : files) { Node * const node = ProjectTree::nodeForFile(file); if (!node) @@ -4164,10 +4169,9 @@ void ProjectExplorerPlugin::renameFilesForSymbol(const QString &oldSymbolName, const QString newFilePath = file.absolutePath().toString() + '/' + newBaseName + '.' + file.completeSuffix(); - if (const auto res = renameFile(node, newFilePath)) - renamedFiles << *res; + filesToRename.emplaceBack(node, FilePath::fromString(newFilePath)); } - emit instance()->filesRenamed(renamedFiles); + renameFiles(filesToRename); } void ProjectManager::registerProjectCreator(const QString &mimeType, diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h index e1d2116df15..3333a0a6b69 100644 --- a/src/plugins/projectexplorer/projectexplorer.h +++ b/src/plugins/projectexplorer/projectexplorer.h @@ -123,8 +123,8 @@ public: static void startRunControl(RunControl *runControl); static void showOutputPaneForRunControl(RunControl *runControl); - static QList> - renameFiles(const QList> &nodesAndNewFilePaths); + static Utils::FilePairs renameFiles( + const QList> &nodesAndNewFilePaths); #ifdef WITH_TESTS static bool renameFile(const Utils::FilePath &source, const Utils::FilePath &target, @@ -182,13 +182,11 @@ signals: void runControlStarted(ProjectExplorer::RunControl *runControl); void runControlStoped(ProjectExplorer::RunControl *runControl); - void filesRenamed(const QList> &oldAndNewPaths); + void filesRenamed(const Utils::FilePairs &oldAndNewPaths); private: static bool coreAboutToClose(); void handleCommandLineArguments(const QStringList &arguments); - static std::optional> - renameFile(Node *node, const QString &newFilePath); }; } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/projectnodes.cpp b/src/plugins/projectexplorer/projectnodes.cpp index 4a002130ab3..6beb3408d5d 100644 --- a/src/plugins/projectexplorer/projectnodes.cpp +++ b/src/plugins/projectexplorer/projectnodes.cpp @@ -837,11 +837,11 @@ bool FolderNode::canRenameFile(const FilePath &oldFilePath, const FilePath &newF return false; } -bool FolderNode::renameFile(const FilePath &oldFilePath, const FilePath &newFilePath) +bool FolderNode::renameFiles(const FilePairs &filesToRename, FilePaths *notRenamed) { ProjectNode *pn = managingProject(); if (pn) - return pn->renameFile(oldFilePath, newFilePath); + return pn->renameFiles(filesToRename, notRenamed); return false; } @@ -970,17 +970,20 @@ bool ProjectNode::deleteFiles(const FilePaths &filePaths) 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 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()) - return bs->renameFile(this, oldFilePath, newFilePath); + return bs->renameFiles(this, filesToRename, notRenamed); + if (notRenamed) + *notRenamed = firstPaths(filesToRename); return false; } diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h index 2a0c02af9b9..b4bd2c3e7a9 100644 --- a/src/plugins/projectexplorer/projectnodes.h +++ b/src/plugins/projectexplorer/projectnodes.h @@ -13,6 +13,7 @@ #include #include +#include #include namespace Utils { class MimeType; } @@ -300,7 +301,7 @@ public: virtual bool deleteFiles(const Utils::FilePaths &filePaths); virtual bool canRenameFile(const Utils::FilePath &oldFilePath, 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); class AddNewInformation @@ -382,8 +383,9 @@ public: RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths, Utils::FilePaths *notRemoved = nullptr) final; bool deleteFiles(const Utils::FilePaths &filePaths) final; - bool canRenameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; - bool renameFile(const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; + bool canRenameFile( + 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 supportsAction(ProjectAction action, const Node *node) const final; diff --git a/src/plugins/python/pythonbuildsystem.cpp b/src/plugins/python/pythonbuildsystem.cpp index b2263811a25..619e59ad527 100644 --- a/src/plugins/python/pythonbuildsystem.cpp +++ b/src/plugins/python/pythonbuildsystem.cpp @@ -305,17 +305,33 @@ bool PythonBuildSystem::deleteFiles(Node *, const FilePaths &) 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) { - if (entry.filePath == oldFilePath) { - entry.filePath = newFilePath; - entry.rawEntry = newFilePath.relativeChildPath(projectDirectory()).toString(); - break; + bool success = true; + for (const auto &[oldFilePath, newFilePath] : filesToRename) { + bool found = false; + for (FileEntry &entry : m_files) { + 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() diff --git a/src/plugins/python/pythonbuildsystem.h b/src/plugins/python/pythonbuildsystem.h index 59de3401147..09616015662 100644 --- a/src/plugins/python/pythonbuildsystem.h +++ b/src/plugins/python/pythonbuildsystem.h @@ -25,9 +25,10 @@ public: const Utils::FilePaths &filePaths, Utils::FilePaths *) override; bool deleteFiles(ProjectExplorer::Node *, const Utils::FilePaths &) override; - bool renameFile(ProjectExplorer::Node *, - const Utils::FilePath &oldFilePath, - const Utils::FilePath &newFilePath) override; + bool renameFiles( + ProjectExplorer::Node *, + const Utils::FilePairs &filesToRename, + Utils::FilePaths *notRenamed) override; QString name() const override { return QLatin1String("python"); } void parse(); diff --git a/src/plugins/qbsprojectmanager/qbsproject.cpp b/src/plugins/qbsprojectmanager/qbsproject.cpp index e487107ad12..19ba85ce1d7 100644 --- a/src/plugins/qbsprojectmanager/qbsproject.cpp +++ b/src/plugins/qbsprojectmanager/qbsproject.cpp @@ -267,27 +267,43 @@ RemovedFilesFromProject QbsBuildSystem::removeFiles(Node *context, const FilePat return BuildSystem::removeFiles(context, filePaths, notRemoved); } -bool QbsBuildSystem::renameFile(Node *context, - const FilePath &oldFilePath, - const FilePath &newFilePath) +bool QbsBuildSystem::renameFiles(Node *context, const FilePairs &filesToRename, FilePaths *notRenamed) { if (auto *n = dynamic_cast(context)) { const QbsProductNode * const prdNode = parentQbsProductNode(n); QTC_ASSERT(prdNode, return false); - return renameFileInProduct(oldFilePath.toString(), - newFilePath.toString(), - prdNode->productData(), - n->groupData()); + bool success = true; + for (const auto &[oldFilePath, newFilePath] : filesToRename) { + if (!renameFileInProduct( + oldFilePath.toString(), + newFilePath.toString(), + prdNode->productData(), + n->groupData())) { + success = false; + if (notRenamed) + *notRenamed << oldFilePath; + } + } + return success; } if (auto *n = dynamic_cast(context)) { - return renameFileInProduct(oldFilePath.toString(), - newFilePath.toString(), - n->productData(), - n->mainGroup()); + bool success = true; + for (const auto &[oldFilePath, newFilePath] : filesToRename) { + if (!renameFileInProduct( + 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 @@ -409,6 +425,7 @@ bool QbsBuildSystem::renameFileInProduct( if (newPath.isEmpty()) return false; FilePaths dummy; + // FIXME: The qbs API need a (bulk) renaming feature if (removeFilesFromProduct({FilePath::fromString(oldPath)}, product, group, &dummy) != RemovedFilesFromProject::Ok) { return false; diff --git a/src/plugins/qbsprojectmanager/qbsproject.h b/src/plugins/qbsprojectmanager/qbsproject.h index 62695d013c9..d7c5eaebd38 100644 --- a/src/plugins/qbsprojectmanager/qbsproject.h +++ b/src/plugins/qbsprojectmanager/qbsproject.h @@ -67,8 +67,10 @@ public: ProjectExplorer::RemovedFilesFromProject removeFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths, Utils::FilePaths *notRemoved = nullptr) final; - bool renameFile(ProjectExplorer::Node *context, - const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) final; + bool renameFiles( + ProjectExplorer::Node *context, + const Utils::FilePairs &filesToRename, + Utils::FilePaths *notRenamed) final; Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final; QVariant additionalData(Utils::Id id) const final; QString name() const final { return QLatin1String("qbs"); } diff --git a/src/plugins/qmakeprojectmanager/qmakenodes.cpp b/src/plugins/qmakeprojectmanager/qmakenodes.cpp index 03024253c2a..3e696699b4c 100644 --- a/src/plugins/qmakeprojectmanager/qmakenodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakenodes.cpp @@ -254,16 +254,27 @@ bool QmakeBuildSystem::canRenameFile(Node *context, return BuildSystem::canRenameFile(context, oldFilePath, newFilePath); } -bool QmakeBuildSystem::renameFile(Node *context, - const FilePath &oldFilePath, - const FilePath &newFilePath) +bool QmakeBuildSystem::renameFiles(Node *context, const FilePairs &filesToRename, FilePaths *notRenamed) { if (auto n = dynamic_cast(context)) { 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) diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.h b/src/plugins/qmakeprojectmanager/qmakeproject.h index ce0a1bcacfc..a6c15ea780e 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.h +++ b/src/plugins/qmakeprojectmanager/qmakeproject.h @@ -79,9 +79,9 @@ public: bool canRenameFile(ProjectExplorer::Node *context, const Utils::FilePath &oldFilePath, const Utils::FilePath &newFilePath) override; - bool renameFile(ProjectExplorer::Node *context, - const Utils::FilePath &oldFilePath, - const Utils::FilePath &newFilePath) override; + bool renameFiles(ProjectExplorer::Node *context, + const Utils::FilePairs &filesToRename, + Utils::FilePaths *notRenamed) override; bool addDependencies(ProjectExplorer::Node *context, const QStringList &dependencies) override; QString name() const final { return QLatin1String("qmake"); } diff --git a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp index 0d188bebd60..6a10ff08dab 100644 --- a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp +++ b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp @@ -627,19 +627,35 @@ bool QmlBuildSystem::deleteFiles(Node *context, const Utils::FilePaths &filePath return BuildSystem::deleteFiles(context, filePaths); } -bool QmlBuildSystem::renameFile(Node *context, - const Utils::FilePath &oldFilePath, - const Utils::FilePath &newFilePath) +bool QmlBuildSystem::renameFiles(Node *context, + const Utils::FilePairs &filesToRename, + Utils::FilePaths *notRenamed) { - if (dynamic_cast(context)) { - if (oldFilePath.endsWith(mainFile())) - return setMainFileInProjectFile(newFilePath); - if (oldFilePath.endsWith(m_projectItem->mainUiFile())) - return setMainUiFileInProjectFile(newFilePath); - return true; + if (!dynamic_cast(context)) + return BuildSystem::renameFiles(context, filesToRename, notRenamed); + + bool success = true; + for (const auto &[oldFilePath, newFilePath] : filesToRename) { + 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 diff --git a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h index 87a2448e170..3bb0fa8cd79 100644 --- a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h +++ b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.h @@ -35,9 +35,9 @@ public: const Utils::FilePaths &filePaths, Utils::FilePaths *notAdded = nullptr) override; bool deleteFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths) override; - bool renameFile(ProjectExplorer::Node *context, - const Utils::FilePath &oldFilePath, - const Utils::FilePath &newFilePath) override; + bool renameFiles(ProjectExplorer::Node *context, + const Utils::FilePairs &filesToRename, + Utils::FilePaths *notRenamed) override; bool updateProjectFile(); diff --git a/src/plugins/resourceeditor/resourcenode.cpp b/src/plugins/resourceeditor/resourcenode.cpp index e7198a41f74..2d32c320493 100644 --- a/src/plugins/resourceeditor/resourcenode.cpp +++ b/src/plugins/resourceeditor/resourcenode.cpp @@ -157,7 +157,7 @@ public: RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths, Utils::FilePaths *notRemoved) final; 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; } ResourceTopLevelNode *resourceNode() const { return m_topLevelNode; } @@ -211,9 +211,9 @@ bool SimpleResourceFolderNode::canRenameFile(const FilePath &oldFilePath, 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 @@ -531,7 +531,7 @@ bool ResourceFolderNode::canRenameFile(const FilePath &oldFilePath, const FilePa return fileEntryExists; } -bool ResourceFolderNode::renameFile(const FilePath &oldFilePath, const FilePath &newFilePath) +bool ResourceFolderNode::renameFiles(const FilePairs &filesToRename, FilePaths *notRenamed) { ResourceFile file(m_topLevelNode->filePath()); if (file.load() != IDocument::OpenResult::Success) @@ -540,16 +540,27 @@ bool ResourceFolderNode::renameFile(const FilePath &oldFilePath, const FilePath if (index == -1) return false; - for (int j = 0; j < file.fileCount(index); ++j) { - if (file.file(index, j) == oldFilePath.toString()) { - file.replaceFile(index, j, newFilePath.toString()); - FileChangeBlocker changeGuard(m_topLevelNode->filePath()); - file.save(); - return true; + bool success = true; + for (const auto &[oldFilePath, newFilePath] : filesToRename) { + bool found = false; + for (int j = 0; j < file.fileCount(index); ++j) { + if (file.file(index, j) == oldFilePath.toString()) { + 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) diff --git a/src/plugins/resourceeditor/resourcenode.h b/src/plugins/resourceeditor/resourcenode.h index d857a643761..f90918c03a2 100644 --- a/src/plugins/resourceeditor/resourcenode.h +++ b/src/plugins/resourceeditor/resourcenode.h @@ -55,7 +55,8 @@ public: ProjectExplorer::RemovedFilesFromProject removeFiles(const Utils::FilePaths &filePaths, Utils::FilePaths *notRemoved) 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);