diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2023-01-16 17:03:26 +0100 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2023-01-27 10:02:52 +0000 |
commit | 0d909c353c54ecf2fee2d13b7d2fe19eee01591d (patch) | |
tree | 22fa92a342dab8f9039c49e6cb99ee618462650b | |
parent | b4b260071587d681c78768a7a84807226857b081 (diff) |
Designer: Update C++ code model on an object name change in designer
We try to locate the old symbol name in the generated ui header and
rename the symbol in the background.
Task-number: QTCREATORBUG-1179
Change-Id: Iaf68e3922cd728cbc87d0dc97125e34b8bdaa6be
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: David Schulz <david.schulz@qt.io>
36 files changed, 470 insertions, 63 deletions
diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 6735b96aeb..6d2d2fb95d 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -292,7 +292,7 @@ public: void findUsages(TextDocument *document, const QTextCursor &cursor, const QString &searchTerm, const std::optional<QString> &replacement, - bool categorize); + const std::function<void()> &callback, bool categorize); void handleDeclDefSwitchReplies(); @@ -509,7 +509,8 @@ void ClangdClient::closeExtraFile(const Utils::FilePath &filePath) } void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor, - const std::optional<QString> &replacement) + const std::optional<QString> &replacement, + const std::function<void()> &renameCallback) { // Quick check: Are we even on anything searchable? const QTextCursor adjustedCursor = d->adjustedCursor(cursor, document); @@ -519,7 +520,7 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor, if (replacement && versionNumber() >= QVersionNumber(16) && Utils::qtcEnvironmentVariable("QTC_CLANGD_RENAMING") != "0") { - symbolSupport().renameSymbol(document, adjustedCursor, *replacement, + symbolSupport().renameSymbol(document, adjustedCursor, *replacement, renameCallback, CppEditor::preferLowerCaseFileNames()); return; } @@ -530,19 +531,20 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor, if (searchTerm != "operator" && Utils::allOf(searchTerm, [](const QChar &c) { return c.isLetterOrNumber() || c == '_'; })) { - d->findUsages(document, adjustedCursor, searchTerm, replacement, categorize); + d->findUsages(document, adjustedCursor, searchTerm, replacement, renameCallback, categorize); return; } // Otherwise get the proper spelling of the search term from clang, so we can put it into the // search widget. - const auto symbolInfoHandler = [this, doc = QPointer(document), adjustedCursor, replacement, categorize] + const auto symbolInfoHandler = [this, doc = QPointer(document), adjustedCursor, replacement, + renameCallback, categorize] (const QString &name, const QString &, const MessageId &) { if (!doc) return; if (name.isEmpty()) return; - d->findUsages(doc.data(), adjustedCursor, name, replacement, categorize); + d->findUsages(doc.data(), adjustedCursor, name, replacement, renameCallback, categorize); }; requestSymbolInfo(document->filePath(), Range(adjustedCursor).start(), symbolInfoHandler); } @@ -704,10 +706,11 @@ CppEditor::ClangdSettings::Data ClangdClient::settingsData() const { return d->s void ClangdClient::Private::findUsages(TextDocument *document, const QTextCursor &cursor, const QString &searchTerm, - const std::optional<QString> &replacement, bool categorize) + const std::optional<QString> &replacement, const std::function<void()> &renameCallback, + bool categorize) { const auto findRefs = new ClangdFindReferences(q, document, cursor, searchTerm, replacement, - categorize); + renameCallback, categorize); if (isTesting) { connect(findRefs, &ClangdFindReferences::foundReferences, q, &ClangdClient::foundReferences); diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index ff64a9c3f5..772920b277 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -54,7 +54,8 @@ public: void closeExtraFile(const Utils::FilePath &filePath); void findUsages(TextEditor::TextDocument *document, const QTextCursor &cursor, - const std::optional<QString> &replacement); + const std::optional<QString> &replacement, + const std::function<void()> &renameCallback); void checkUnused(const Utils::Link &link, Core::SearchResult *search, const Utils::LinkHandler &callback); void followSymbol(TextEditor::TextDocument *document, diff --git a/src/plugins/clangcodemodel/clangdfindreferences.cpp b/src/plugins/clangcodemodel/clangdfindreferences.cpp index 2458183164..f2102325f0 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.cpp +++ b/src/plugins/clangcodemodel/clangdfindreferences.cpp @@ -109,7 +109,8 @@ public: ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *document, const QTextCursor &cursor, const QString &searchTerm, - const std::optional<QString> &replacement, bool categorize) + const std::optional<QString> &replacement, const std::function<void()> &callback, + bool categorize) : QObject(client), d(new ClangdFindReferences::Private(this)) { d->categorize = categorize; @@ -130,6 +131,7 @@ ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *d replacement ? SearchResultWindow::SearchAndReplace : SearchResultWindow::SearchOnly, SearchResultWindow::PreserveCaseDisabled, "CppEditor"); + d->search->makeNonInteractive(callback); if (categorize) d->search->setFilter(new CppSearchResultFilter); if (d->replacementData) { @@ -150,7 +152,8 @@ ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *d connect(d->search, &SearchResult::activated, [](const SearchResultItem& item) { EditorManager::openEditorAtSearchResult(item); }); - SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); + if (d->search->isInteractive()) + SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); const std::optional<MessageId> requestId = client->symbolSupport().findUsages( document, cursor, [self = QPointer(this)](const QList<Location> &locations) { diff --git a/src/plugins/clangcodemodel/clangdfindreferences.h b/src/plugins/clangcodemodel/clangdfindreferences.h index ce6d472630..d904db31f4 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.h +++ b/src/plugins/clangcodemodel/clangdfindreferences.h @@ -27,7 +27,9 @@ class ClangdFindReferences : public QObject public: ClangdFindReferences(ClangdClient *client, TextEditor::TextDocument *document, const QTextCursor &cursor, const QString &searchTerm, - const std::optional<QString> &replacement, bool categorize); + const std::optional<QString> &replacement, + const std::function<void()> &callback, + bool categorize); ClangdFindReferences(ClangdClient *client, const Utils::Link &link, Core::SearchResult *search, const Utils::LinkHandler &callback); ~ClangdFindReferences(); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index e9742195f8..64a8dac7bd 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -313,16 +313,17 @@ void ClangModelManagerSupport::startLocalRenaming(const CppEditor::CursorInEdito } void ClangModelManagerSupport::globalRename(const CppEditor::CursorInEditor &cursor, - const QString &replacement) + const QString &replacement, + const std::function<void()> &callback) { if (ClangdClient * const client = clientForFile(cursor.filePath()); client && client->isFullyIndexed()) { QTC_ASSERT(client->documentOpen(cursor.textDocument()), client->openDocument(cursor.textDocument())); - client->findUsages(cursor.textDocument(), cursor.cursor(), replacement); + client->findUsages(cursor.textDocument(), cursor.cursor(), replacement, callback); return; } - CppModelManager::globalRename(cursor, replacement, CppModelManager::Backend::Builtin); + CppModelManager::globalRename(cursor, replacement, callback, CppModelManager::Backend::Builtin); } void ClangModelManagerSupport::findUsages(const CppEditor::CursorInEditor &cursor) const @@ -331,7 +332,7 @@ void ClangModelManagerSupport::findUsages(const CppEditor::CursorInEditor &curso client && client->isFullyIndexed()) { QTC_ASSERT(client->documentOpen(cursor.textDocument()), client->openDocument(cursor.textDocument())); - client->findUsages(cursor.textDocument(), cursor.cursor(), {}); + client->findUsages(cursor.textDocument(), cursor.cursor(), {}, {}); return; } diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h index 983d8e89ea..a901b4407e 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h @@ -60,7 +60,8 @@ private: void startLocalRenaming(const CppEditor::CursorInEditor &data, const CppEditor::ProjectPart *projectPart, CppEditor::RenameCallback &&renameSymbolsCallback) override; - void globalRename(const CppEditor::CursorInEditor &cursor, const QString &replacement) override; + void globalRename(const CppEditor::CursorInEditor &cursor, const QString &replacement, + const std::function<void()> &callback) override; void findUsages(const CppEditor::CursorInEditor &cursor) const override; void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) override; void checkUnused(const Utils::Link &link, Core::SearchResult *search, diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index affeb8db5f..c2517c1357 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -294,7 +294,7 @@ void ClangdTestFindReferences::test() QVERIFY(doc); QTextCursor cursor(doc->document()); cursor.setPosition(pos); - client()->findUsages(doc, cursor, {}); + client()->findUsages(doc, cursor, {}, {}); QVERIFY(waitForSignalOrTimeout(client(), &ClangdClient::findUsagesDone, timeOutInMs())); QCOMPARE(m_actualResults.size(), expectedResults.size()); diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 37cc9ff29f..daef1d30da 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -1401,4 +1401,11 @@ void CMakeBuildSystem::runGenerator(Id id) proc->start(); } +ExtraCompiler *CMakeBuildSystem::extraCompilerForSource(const Utils::FilePath &source) +{ + return Utils::findOrDefault(m_extraCompilers, [source](ExtraCompiler *ec) { + return ec->source() == source; + }); +} + } // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h index e9b1acedc7..ec257ea09c 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -122,6 +122,7 @@ signals: private: QList<QPair<Utils::Id, QString>> generators() const override; void runGenerator(Utils::Id id) override; + ProjectExplorer::ExtraCompiler *extraCompilerForSource(const Utils::FilePath &source) override; enum ForceEnabledChanged { False, True }; void clearError(ForceEnabledChanged fec = ForceEnabledChanged::False); diff --git a/src/plugins/coreplugin/find/searchresultwidget.cpp b/src/plugins/coreplugin/find/searchresultwidget.cpp index 0d590743b5..bb16aaf23d 100644 --- a/src/plugins/coreplugin/find/searchresultwidget.cpp +++ b/src/plugins/coreplugin/find/searchresultwidget.cpp @@ -468,12 +468,16 @@ void SearchResultWidget::handleReplaceButton() { // check if button is actually enabled, because this is also triggered // by pressing return in replace line edit - if (m_replaceButton->isEnabled()) { - m_infoBar.clear(); - setShowReplaceUI(false); - emit replaceButtonClicked(m_replaceTextEdit->text(), checkedItems(), - m_preserveCaseSupported && m_preserveCaseCheck->isChecked()); - } + if (m_replaceButton->isEnabled()) + doReplace(); +} + +void SearchResultWidget::doReplace() +{ + m_infoBar.clear(); + setShowReplaceUI(false); + emit replaceButtonClicked(m_replaceTextEdit->text(), checkedItems(), + m_preserveCaseSupported && m_preserveCaseCheck->isChecked()); } void SearchResultWidget::cancel() diff --git a/src/plugins/coreplugin/find/searchresultwidget.h b/src/plugins/coreplugin/find/searchresultwidget.h index f761dde0e1..f10bd5dd67 100644 --- a/src/plugins/coreplugin/find/searchresultwidget.h +++ b/src/plugins/coreplugin/find/searchresultwidget.h @@ -39,6 +39,7 @@ public: void setSupportsReplace(bool replaceSupported, const QString &group); bool supportsReplace() const; + void triggerReplace() { doReplace(); } void setTextToReplace(const QString &textToReplace); QString textToReplace() const; @@ -91,6 +92,7 @@ signals: private: void handleJumpToSearchResult(const SearchResultItem &item); void handleReplaceButton(); + void doReplace(); void cancel(); void searchAgain(); diff --git a/src/plugins/coreplugin/find/searchresultwindow.cpp b/src/plugins/coreplugin/find/searchresultwindow.cpp index 29c1953a08..3b2ea6e8ae 100644 --- a/src/plugins/coreplugin/find/searchresultwindow.cpp +++ b/src/plugins/coreplugin/find/searchresultwindow.cpp @@ -850,6 +850,12 @@ void SearchResult::setFilter(SearchResultFilter *filter) void SearchResult::finishSearch(bool canceled, const QString &reason) { m_widget->finishSearch(canceled, reason); + if (m_finishedHandler) { + if (!canceled) + m_widget->triggerReplace(); + m_finishedHandler(); + m_finishedHandler = {}; + } } /*! @@ -893,6 +899,13 @@ void SearchResult::popup() m_widget->sendRequestPopup(); } +void Core::SearchResult::makeNonInteractive(const std::function<void ()> &callback) +{ + QTC_ASSERT(callback, return); + m_widget->setEnabled(false); + m_finishedHandler = callback; +} + } // namespace Core #include "searchresultwindow.moc" diff --git a/src/plugins/coreplugin/find/searchresultwindow.h b/src/plugins/coreplugin/find/searchresultwindow.h index e8758e657c..da1bcf67e4 100644 --- a/src/plugins/coreplugin/find/searchresultwindow.h +++ b/src/plugins/coreplugin/find/searchresultwindow.h @@ -12,6 +12,8 @@ #include <QStringList> #include <QIcon> +#include <functional> + QT_BEGIN_NAMESPACE class QFont; QT_END_NAMESPACE @@ -53,6 +55,8 @@ public: void setSearchAgainSupported(bool supported); QWidget *additionalReplaceWidget() const; void setAdditionalReplaceWidget(QWidget *widget); + void makeNonInteractive(const std::function<void()> &callback); + bool isInteractive() const { return !m_finishedHandler; } public slots: void addResult(const SearchResultItem &item); @@ -83,6 +87,7 @@ private: private: Internal::SearchResultWidget *m_widget; QVariant m_userData; + std::function<void()> m_finishedHandler; }; class CORE_EXPORT SearchResultWindow : public IOutputPane diff --git a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp index 3c6900e58f..c59fd475bd 100644 --- a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp +++ b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp @@ -142,7 +142,8 @@ void BuiltinModelManagerSupport::startLocalRenaming(const CursorInEditor &data, } void BuiltinModelManagerSupport::globalRename(const CursorInEditor &data, - const QString &replacement) + const QString &replacement, + const std::function<void()> &callback) { CppModelManager *modelManager = CppModelManager::instance(); if (!modelManager) @@ -161,7 +162,7 @@ void BuiltinModelManagerSupport::globalRename(const CursorInEditor &data, Internal::CanonicalSymbol cs(info.doc, info.snapshot); CPlusPlus::Symbol *canonicalSymbol = cs(cursor); if (canonicalSymbol) - modelManager->renameUsages(canonicalSymbol, cs.context(), replacement); + modelManager->renameUsages(canonicalSymbol, cs.context(), replacement, callback); } } diff --git a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.h b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.h index 3518c33401..04de866d5c 100644 --- a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.h +++ b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.h @@ -38,7 +38,8 @@ private: void startLocalRenaming(const CursorInEditor &data, const ProjectPart *projectPart, RenameCallback &&renameSymbolsCallback) override; - void globalRename(const CursorInEditor &data, const QString &replacement) override; + void globalRename(const CursorInEditor &data, const QString &replacement, + const std::function<void()> &callback) override; void findUsages(const CursorInEditor &data) const override; void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) override; void checkUnused(const Utils::Link &link, Core::SearchResult *search, diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp index acf4e88fef..fe693bbbfc 100644 --- a/src/plugins/cppeditor/cppeditorwidget.cpp +++ b/src/plugins/cppeditor/cppeditorwidget.cpp @@ -619,6 +619,16 @@ void CppEditorWidget::renameUsages(const QString &replacement, QTextCursor curso d->m_modelManager->globalRename(cursorInEditor, replacement); } +void CppEditorWidget::renameUsages(const Utils::FilePath &filePath, const QString &replacement, + QTextCursor cursor, const std::function<void ()> &callback) +{ + if (cursor.isNull()) + cursor = textCursor(); + CursorInEditor cursorInEditor{cursor, filePath, this, textDocument()}; + QPointer<CppEditorWidget> cppEditorWidget = this; + d->m_modelManager->globalRename(cursorInEditor, replacement, callback); +} + bool CppEditorWidget::selectBlockUp() { if (!behaviorSettings().m_smartSelectionChanging) @@ -1160,7 +1170,7 @@ void CppEditorWidget::updateSemanticInfo() void CppEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo, bool updateUseSelectionSynchronously) { - if (semanticInfo.revision != documentRevision()) + if (semanticInfo.revision < documentRevision()) return; d->m_lastSemanticInfo = semanticInfo; diff --git a/src/plugins/cppeditor/cppeditorwidget.h b/src/plugins/cppeditor/cppeditorwidget.h index 3270687201..a42d786ead 100644 --- a/src/plugins/cppeditor/cppeditorwidget.h +++ b/src/plugins/cppeditor/cppeditorwidget.h @@ -10,6 +10,8 @@ #include <QScopedPointer> +#include <functional> + namespace TextEditor { class IAssistProposal; class IAssistProvider; @@ -61,6 +63,10 @@ public: void findUsages(QTextCursor cursor); void renameUsages(const QString &replacement = QString(), QTextCursor cursor = QTextCursor()); + void renameUsages(const Utils::FilePath &filePath, + const QString &replacement = QString(), + QTextCursor cursor = QTextCursor(), + const std::function<void()> &callback = {}); void renameSymbolUnderCursor() override; bool selectBlockUp() override; diff --git a/src/plugins/cppeditor/cppfindreferences.cpp b/src/plugins/cppeditor/cppfindreferences.cpp index f6116f62d1..940eb358e9 100644 --- a/src/plugins/cppeditor/cppfindreferences.cpp +++ b/src/plugins/cppeditor/cppfindreferences.cpp @@ -368,12 +368,13 @@ static void find_helper(QFutureInterface<CPlusPlus::Usage> &future, void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context) { - findUsages(symbol, context, QString(), false); + findUsages(symbol, context, QString(), {}, false); } void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, const QString &replacement, + const std::function<void()> &callback, bool replace) { CPlusPlus::Overview overview; @@ -385,6 +386,8 @@ void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol, SearchResultWindow::PreserveCaseDisabled, QLatin1String("CppEditor")); search->setTextToReplace(replacement); + if (callback) + search->makeNonInteractive(callback); if (codeModelSettings()->categorizeFindReferences()) search->setFilter(new CppSearchResultFilter); setupSearch(search); @@ -408,12 +411,13 @@ void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol, void CppFindReferences::renameUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, - const QString &replacement) + const QString &replacement, + const std::function<void()> &callback) { if (const CPlusPlus::Identifier *id = symbol->identifier()) { const QString textToReplace = replacement.isEmpty() ? QString::fromUtf8(id->chars(), id->size()) : replacement; - findUsages(symbol, context, textToReplace, true); + findUsages(symbol, context, textToReplace, callback, true); } } @@ -429,7 +433,8 @@ void CppFindReferences::findAll_helper(SearchResult *search, CPlusPlus::Symbol * Core::EditorManager::openEditorAtSearchResult(item); }); - SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); + if (search->isInteractive()) + SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); const WorkingCopy workingCopy = m_modelManager->workingCopy(); QFuture<CPlusPlus::Usage> result; result = Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper, diff --git a/src/plugins/cppeditor/cppfindreferences.h b/src/plugins/cppeditor/cppfindreferences.h index fc76888ad0..54f40bf4cf 100644 --- a/src/plugins/cppeditor/cppfindreferences.h +++ b/src/plugins/cppeditor/cppfindreferences.h @@ -14,6 +14,8 @@ #include <QPointer> #include <QFuture> +#include <functional> + QT_FORWARD_DECLARE_CLASS(QTimer) namespace Core { @@ -65,7 +67,8 @@ public: public: void findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context); void renameUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, - const QString &replacement = QString()); + const QString &replacement = QString(), + const std::function<void()> &callback = {}); void findMacroUses(const CPlusPlus::Macro ¯o); void renameMacroUses(const CPlusPlus::Macro ¯o, const QString &replacement = QString()); @@ -80,7 +83,8 @@ private: void searchAgain(Core::SearchResult *search); void findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, - const QString &replacement, bool replace); + const QString &replacement, const std::function<void ()> &callback, + bool replace); void findMacroUses(const CPlusPlus::Macro ¯o, const QString &replacement, bool replace); void findAll_helper(Core::SearchResult *search, CPlusPlus::Symbol *symbol, diff --git a/src/plugins/cppeditor/cppmodelmanager.cpp b/src/plugins/cppeditor/cppmodelmanager.cpp index f8dc3173c2..9c7500a6c4 100644 --- a/src/plugins/cppeditor/cppmodelmanager.cpp +++ b/src/plugins/cppeditor/cppmodelmanager.cpp @@ -6,6 +6,7 @@ #include "abstracteditorsupport.h" #include "baseeditordocumentprocessor.h" #include "compileroptionsbuilder.h" +#include "cppcanonicalsymbol.h" #include "cppcodemodelinspectordumper.h" #include "cppcodemodelsettings.h" #include "cppcurrentdocumentfilter.h" @@ -325,9 +326,9 @@ void CppModelManager::startLocalRenaming(const CursorInEditor &data, } void CppModelManager::globalRename(const CursorInEditor &data, const QString &replacement, - Backend backend) + const std::function<void()> &callback, Backend backend) { - instance()->modelManagerSupport(backend)->globalRename(data, replacement); + instance()->modelManagerSupport(backend)->globalRename(data, replacement, callback); } void CppModelManager::findUsages(const CursorInEditor &data, Backend backend) @@ -1170,10 +1171,21 @@ void CppModelManager::findUsages(Symbol *symbol, const LookupContext &context) void CppModelManager::renameUsages(Symbol *symbol, const LookupContext &context, - const QString &replacement) + const QString &replacement, + const std::function<void()> &callback) { if (symbol->identifier()) - d->m_findReferences->renameUsages(symbol, context, replacement); + d->m_findReferences->renameUsages(symbol, context, replacement, callback); +} + +void CppModelManager::renameUsages(const Document::Ptr &doc, const QTextCursor &cursor, + const Snapshot &snapshot, const QString &replacement, + const std::function<void ()> &callback) +{ + Internal::CanonicalSymbol cs(doc, snapshot); + CPlusPlus::Symbol *canonicalSymbol = cs(cursor); + if (canonicalSymbol) + renameUsages(canonicalSymbol, cs.context(), replacement, callback); } void CppModelManager::findMacroUsages(const CPlusPlus::Macro ¯o) diff --git a/src/plugins/cppeditor/cppmodelmanager.h b/src/plugins/cppeditor/cppmodelmanager.h index 3d3cc2eca1..2929d615e6 100644 --- a/src/plugins/cppeditor/cppmodelmanager.h +++ b/src/plugins/cppeditor/cppmodelmanager.h @@ -19,6 +19,7 @@ #include <QObject> #include <QStringList> +#include <functional> #include <memory> namespace Core { @@ -149,7 +150,13 @@ public: int position) const; void renameUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, - const QString &replacement = QString()); + const QString &replacement = QString(), + const std::function<void()> &callback = {}); + void renameUsages(const CPlusPlus::Document::Ptr &doc, + const QTextCursor &cursor, + const CPlusPlus::Snapshot &snapshot, + const QString &replacement, + const std::function<void()> &callback); void findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context); void findMacroUsages(const CPlusPlus::Macro ¯o); @@ -178,6 +185,7 @@ public: RenameCallback &&renameSymbolsCallback, Backend backend = Backend::Best); static void globalRename(const CursorInEditor &data, const QString &replacement, + const std::function<void()> &callback = {}, Backend backend = Backend::Best); static void findUsages(const CursorInEditor &data, Backend backend = Backend::Best); static void switchHeaderSource(bool inNextSplit, Backend backend = Backend::Best); diff --git a/src/plugins/cppeditor/cppmodelmanagersupport.h b/src/plugins/cppeditor/cppmodelmanagersupport.h index 387d86272e..392712d86a 100644 --- a/src/plugins/cppeditor/cppmodelmanagersupport.h +++ b/src/plugins/cppeditor/cppmodelmanagersupport.h @@ -11,6 +11,7 @@ #include <QSharedPointer> #include <QString> +#include <functional> #include <memory> namespace Core { class SearchResult; } @@ -49,7 +50,8 @@ public: virtual void startLocalRenaming(const CursorInEditor &data, const ProjectPart *projectPart, RenameCallback &&renameSymbolsCallback) = 0; - virtual void globalRename(const CursorInEditor &data, const QString &replacement) = 0; + virtual void globalRename(const CursorInEditor &data, const QString &replacement, + const std::function<void()> &callback) = 0; virtual void findUsages(const CursorInEditor &data) const = 0; virtual void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) = 0; diff --git a/src/plugins/designer/qtcreatorintegration.cpp b/src/plugins/designer/qtcreatorintegration.cpp index c2dd4ed45a..db96523c36 100644 --- a/src/plugins/designer/qtcreatorintegration.cpp +++ b/src/plugins/designer/qtcreatorintegration.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "designertr.h" -#include "editordata.h" #include "formeditorw.h" #include "formwindoweditor.h" #include "qtcreatorintegration.h" @@ -10,7 +9,9 @@ #include <designer/cpp/formclasswizardpage.h> #include <cppeditor/cppeditorconstants.h> +#include <cppeditor/cppeditorwidget.h> #include <cppeditor/cppmodelmanager.h> +#include <cppeditor/cppsemanticinfo.h> #include <cppeditor/cpptoolsreuse.h> #include <cppeditor/cppworkingcopy.h> #include <cppeditor/insertionpointlocator.h> @@ -19,28 +20,38 @@ #include <cplusplus/Overview.h> #include <coreplugin/icore.h> #include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/messagemanager.h> #include <texteditor/texteditor.h> #include <texteditor/textdocument.h> +#include <projectexplorer/buildsystem.h> +#include <projectexplorer/extracompiler.h> #include <projectexplorer/projectexplorer.h> #include <projectexplorer/projecttree.h> #include <projectexplorer/session.h> +#include <projectexplorer/target.h> +#include <utils/algorithm.h> #include <utils/mimeutils.h> #include <utils/qtcassert.h> #include <utils/stringutils.h> +#include <utils/temporaryfile.h> #include <QDesignerFormWindowInterface> #include <QDesignerFormEditorInterface> - -#include <QMessageBox> - -#include <QFileInfo> -#include <QDir> #include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QLoggingCategory> +#include <QMessageBox> +#include <QHash> #include <QUrl> +#include <memory> + enum { indentation = 4 }; +Q_LOGGING_CATEGORY(log, "qtc.designer", QtWarningMsg); + using namespace Designer::Internal; using namespace CPlusPlus; using namespace TextEditor; @@ -60,8 +71,23 @@ static QString msgClassNotFound(const QString &uiClassName, const QList<Document .arg(uiClassName, files); } +static void reportRenamingError(const QString &oldName, const QString &reason) +{ + Core::MessageManager::writeFlashing( + Designer::Tr::tr("Cannot rename UI symbol \"%1\" in C++ files: %2") + .arg(oldName, reason)); +} + +class QtCreatorIntegration::Private +{ +public: + // See QTCREATORBUG-19141 for why this is necessary. + QHash<QDesignerFormWindowInterface *, QPointer<ExtraCompiler>> extraCompilers; + std::optional<bool> showPropertyEditorRenameWarning = false; +}; + QtCreatorIntegration::QtCreatorIntegration(QDesignerFormEditorInterface *core, QObject *parent) - : QDesignerIntegration(core, parent) + : QDesignerIntegration(core, parent), d(new Private) { setResourceFileWatcherBehaviour(ReloadResourceFileSilently); Feature f = features(); @@ -77,6 +103,44 @@ QtCreatorIntegration::QtCreatorIntegration(QDesignerFormEditorInterface *core, Q slotSyncSettingsToDesigner(); connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, this, &QtCreatorIntegration::slotSyncSettingsToDesigner); + + // The problem is as follows: + // - If the user edits the object name in the property editor, the objectNameChanged() signal + // is emitted for every keystroke (QTCREATORBUG-19141). We should not try to rename + // in that case, because the signals will likely come in faster than the renaming + // procedure takes, putting the code model in some non-deterministic state. + // - Unfortunately, this condition is not trivial to detect, because the propertyChanged() + // signal is (somewhat surprisingly) emitted *after* objectNameChanged(). + // - We can also not simply use a queued connection for objectNameChanged(), because then + // the ExtraCompiler might have run before our handler, and we won't find the old + // object name in the source code anymore. + // The solution is as follows: + // - Upon receiving objectNameChanged(), we retrieve the corresponding ExtraCompiler, + // block it and store it away. Then we invoke the actual handler delayed. + // - Upon receiving propertyChanged(), we check whether it refers to an object name change. + // If it does, we unblock the ExtraCompiler and remove it from our map. + // - When the real handler runs, it first checks for the ExtraCompiler. If it is not found, + // we don't do anything. Otherwise the actual renaming procedure is run. + connect(this, &QtCreatorIntegration::objectNameChanged, + this, &QtCreatorIntegration::handleSymbolRenameStage1); + connect(this, &QtCreatorIntegration::propertyChanged, + this, [this](QDesignerFormWindowInterface *formWindow, const QString &name, + const QVariant &) { + if (name == "objectName") { + if (const auto extraCompiler = d->extraCompilers.find(formWindow); + extraCompiler != d->extraCompilers.end()) { + (*extraCompiler)->unblock(); + d->extraCompilers.erase(extraCompiler); + if (d->showPropertyEditorRenameWarning) + d->showPropertyEditorRenameWarning = true; + } + } + }); +} + +QtCreatorIntegration::~QtCreatorIntegration() +{ + delete d; } void QtCreatorIntegration::slotDesignerHelpRequested(const QString &manual, const QString &document) @@ -565,6 +629,162 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName, return false; } +void QtCreatorIntegration::handleSymbolRenameStage1( + QDesignerFormWindowInterface *formWindow, QObject *object, + const QString &newName, const QString &oldName) +{ + const FilePath uiFile = FilePath::fromString(formWindow->fileName()); + qCDebug(log) << Q_FUNC_INFO << uiFile << object << oldName << newName; + if (newName.isEmpty() || newName == oldName) + return; + + // Get ExtraCompiler. + const Project * const project = SessionManager::projectForFile(uiFile); + if (!project) { + return reportRenamingError(oldName, Designer::Tr::tr("File \"%1\" not found in project.") + .arg(uiFile.toUserOutput())); + } + const Target * const target = project->activeTarget(); + if (!target) + return reportRenamingError(oldName, Designer::Tr::tr("No active target.")); + BuildSystem * const buildSystem = target->buildSystem(); + if (!buildSystem) + return reportRenamingError(oldName, Designer::Tr::tr("No active build system.")); + ExtraCompiler * const ec = buildSystem->extraCompilerForSource(uiFile); + if (!ec) + return reportRenamingError(oldName, Designer::Tr::tr("Failed to find the ui header.")); + ec->block(); + d->extraCompilers.insert(formWindow, ec); + qCDebug(log) << "\tfound extra compiler, scheduling stage 2"; + QMetaObject::invokeMethod(this, [this, formWindow, newName, oldName] { + handleSymbolRenameStage2(formWindow, newName, oldName); + }, Qt::QueuedConnection); +} + +void QtCreatorIntegration::handleSymbolRenameStage2( + QDesignerFormWindowInterface *formWindow, const QString &newName, const QString &oldName) +{ + // Retrieve and check previously stored ExtraCompiler. + ExtraCompiler * const ec = d->extraCompilers.take(formWindow); + if (!ec) { + qCDebug(log) << "\tchange came from property editor, ignoring"; + if (d->showPropertyEditorRenameWarning && *d->showPropertyEditorRenameWarning) { + d->showPropertyEditorRenameWarning.reset(); + reportRenamingError(oldName, Designer::Tr::tr("Renaming via the property editor " + "cannot be synced with C++ code; see QTCREATORBUG-19141." + " This message will not be repeated.")); + } + return; + } + + class ResourceHandler { + public: + ResourceHandler(ExtraCompiler *ec) : m_ec(ec) {} + void setEditor(BaseTextEditor *editorToClose) { m_editorToClose = editorToClose; } + void setTempFile(std::unique_ptr<TemporaryFile> &&tempFile) { + m_tempFile = std::move(tempFile); + } + ~ResourceHandler() + { + if (m_ec) + m_ec->unblock(); + if (m_editorToClose) + Core::EditorManager::closeEditors({m_editorToClose}, false); + } + private: + const QPointer<ExtraCompiler> m_ec; + QPointer<BaseTextEditor> m_editorToClose; + std::unique_ptr<TemporaryFile> m_tempFile; + }; + const auto resourceHandler = std::make_shared<ResourceHandler>(ec); + + QTC_ASSERT(ec->targets().size() == 1, return); + const FilePath uiHeader = ec->targets().first(); + qCDebug(log) << '\t' << uiHeader; + const QByteArray virtualContent = ec->content(uiHeader); + if (virtualContent.isEmpty()) { + qCDebug(log) << "\textra compiler unexpectedly has no contents"; + return reportRenamingError(oldName, + Designer::Tr::tr("Failed to retrieve ui header contents.")); + } + + // Secretly open ui header file contents in editor. + // Use a temp file rather than the actual ui header path. + const auto openFlags = Core::EditorManager::DoNotMakeVisible + | Core::EditorManager::DoNotChangeCurrentEditor; + std::unique_ptr<TemporaryFile> tempFile + = std::make_unique<TemporaryFile>("XXXXXX" + uiHeader.fileName()); + QTC_ASSERT(tempFile->open(), return); + qCDebug(log) << '\t' << tempFile->fileName(); + const auto editor = qobject_cast<BaseTextEditor *>( + Core::EditorManager::openEditor(FilePath::fromString(tempFile->fileName()), {}, + openFlags)); + QTC_ASSERT(editor, return); + resourceHandler->setTempFile(std::move(tempFile)); + resourceHandler->setEditor(editor); + + const auto editorWidget = qobject_cast<CppEditor::CppEditorWidget *>(editor->editorWidget()); + QTC_ASSERT(editorWidget && editorWidget->textDocument(), return); + + // Parse temp file with built-in code model. Pretend it's the real ui header. + // In the case of clangd, this entails doing a "virtual rename" on the TextDocument, + // as the LanguageClient cannot be forced into taking a document and assuming a different + // file path. + const bool usesClangd = CppEditor::CppModelManager::usesClangd(editorWidget->textDocument()); + if (usesClangd) + editorWidget->textDocument()->setFilePath(uiHeader); + editorWidget->textDocument()->setPlainText(QString::fromUtf8(virtualContent)); + Snapshot snapshot = CppEditor::CppModelManager::instance()->snapshot(); + snapshot.remove(uiHeader); + snapshot.remove(editor->textDocument()->filePath()); + const Document::Ptr cppDoc = snapshot.preprocessedDocument(virtualContent, uiHeader); + cppDoc->check(); + QTC_ASSERT(cppDoc && cppDoc->isParsed(), return); + + // Locate old identifier in ui header. + const QByteArray oldNameBa = oldName.toUtf8(); + const Identifier oldIdentifier(oldNameBa.constData(), oldNameBa.size()); + QList<const Scope *> scopes{cppDoc->globalNamespace()}; + while (!scopes.isEmpty()) { + const Scope * const scope = scopes.takeFirst(); + qCDebug(log) << '\t' << scope->memberCount(); + for (int i = 0; i < scope->memberCount(); ++i) { + Symbol * const symbol = scope->memberAt(i); + if (const Scope * const s = symbol->asScope()) + scopes << s; + if (symbol->asNamespace()) + continue; + qCDebug(log) << '\t' << Overview().prettyName(symbol->name()); + if (!symbol->name()->match(&oldIdentifier)) + continue; + QTextCursor cursor(editorWidget->textCursor()); + cursor.setPosition(cppDoc->translationUnit()->getTokenPositionInDocument( + symbol->sourceLocation(), editorWidget->document())); + qCDebug(log) << '\t' << cursor.position() << cursor.blockNumber() + << cursor.positionInBlock(); + + // Trigger non-interactive renaming. The callback is destructed after invocation, + // closing the editor, removing the temp file and unblocking the extra compiler. + // For the built-in code model, we must access the model manager directly, + // as otherwise our file path trickery would be found out. + const auto callback = [resourceHandler] { }; + if (usesClangd) { + qCDebug(log) << "renaming with clangd"; + editorWidget->renameUsages(uiHeader, newName, cursor, callback); + } else { + qCDebug(log) << "renaming with built-in code model"; + snapshot.insert(cppDoc); + snapshot.updateDependencyTable(); + CppEditor::CppModelManager::instance()->renameUsages(cppDoc, cursor, snapshot, + newName, callback); + } + return; + } + } + reportRenamingError(oldName, + Designer::Tr::tr("Failed to locate corresponding symbol in ui header.")); +} + void QtCreatorIntegration::slotSyncSettingsToDesigner() { // Set promotion-relevant parameters on integration. diff --git a/src/plugins/designer/qtcreatorintegration.h b/src/plugins/designer/qtcreatorintegration.h index 4789d28bfc..c27d296ab0 100644 --- a/src/plugins/designer/qtcreatorintegration.h +++ b/src/plugins/designer/qtcreatorintegration.h @@ -17,6 +17,7 @@ class QtCreatorIntegration : public QDesignerIntegration public: explicit QtCreatorIntegration(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~QtCreatorIntegration(); QWidget *containerWindow(QWidget *widget) const override; @@ -36,6 +37,13 @@ private: const QString &signalSignature, const QStringList ¶meterNames, QString *errorMessage); + void handleSymbolRenameStage1(QDesignerFormWindowInterface *formWindow, QObject *object, + const QString &newName, const QString &oldName); + void handleSymbolRenameStage2(QDesignerFormWindowInterface *formWindow, + const QString &newName, const QString &oldName); + + class Private; + Private * const d; }; } // namespace Internal diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp index a70b75b547..995ca770f3 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.cpp +++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp @@ -295,7 +295,8 @@ void SymbolSupport::handleFindReferencesResponse(const FindReferencesRequest::Re Core::EditorManager::openEditorAtSearchResult(item); }); search->finishSearch(false); - search->popup(); + if (search->isInteractive()) + search->popup(); } } @@ -365,6 +366,7 @@ bool SymbolSupport::supportsRename(TextEditor::TextDocument *document) void SymbolSupport::renameSymbol(TextEditor::TextDocument *document, const QTextCursor &cursor, const QString &newSymbolName, + const std::function<void ()> &callback, bool preferLowerCaseFileNames) { const TextDocumentPositionParams params = generateDocPosParams(document, cursor, m_client); @@ -376,17 +378,19 @@ void SymbolSupport::renameSymbol(TextEditor::TextDocument *document, if (!LanguageClient::supportsRename(m_client, document, prepareSupported)) { const QString error = Tr::tr("Renaming is not supported with %1").arg(m_client->name()); createSearch(params, derivePlaceholder(oldSymbolName, newSymbolName), - {}, {})->finishSearch(true, error); + {}, callback, {})->finishSearch(true, error); } else if (prepareSupported) { requestPrepareRename(document, generateDocPosParams(document, cursor, m_client), newSymbolName, oldSymbolName, + callback, preferLowerCaseFileNames); } else { startRenameSymbol(generateDocPosParams(document, cursor, m_client), newSymbolName, oldSymbolName, + callback, preferLowerCaseFileNames); } } @@ -395,6 +399,7 @@ void SymbolSupport::requestPrepareRename(TextEditor::TextDocument *document, const TextDocumentPositionParams ¶ms, const QString &placeholder, const QString &oldSymbolName, + const std::function<void()> &callback, bool preferLowerCaseFileNames) { PrepareRenameRequest request(params); @@ -402,13 +407,15 @@ void SymbolSupport::requestPrepareRename(TextEditor::TextDocument *document, params, placeholder, oldSymbolName, + callback, preferLowerCaseFileNames, document = QPointer<TextEditor::TextDocument>(document)]( const PrepareRenameRequest::Response &response) { const std::optional<PrepareRenameRequest::Response::Error> &error = response.error(); if (error.has_value()) { m_client->log(*error); - createSearch(params, placeholder, {}, {})->finishSearch(true, error->toString()); + createSearch(params, placeholder, {}, callback, {}) + ->finishSearch(true, error->toString()); } const std::optional<PrepareRenameResult> &result = response.result(); @@ -419,6 +426,7 @@ void SymbolSupport::requestPrepareRename(TextEditor::TextDocument *document, placeholder.isEmpty() ? placeHolderResult.placeHolder() : placeholder, oldSymbolName, + callback, preferLowerCaseFileNames); } else if (std::holds_alternative<Range>(*result)) { auto range = std::get<Range>(*result); @@ -429,9 +437,11 @@ void SymbolSupport::requestPrepareRename(TextEditor::TextDocument *document, startRenameSymbol(params, derivePlaceholder(reportedSymbolName, placeholder), reportedSymbolName, + callback, preferLowerCaseFileNames); } else { - startRenameSymbol(params, placeholder, oldSymbolName, preferLowerCaseFileNames); + startRenameSymbol(params, placeholder, oldSymbolName, callback, + preferLowerCaseFileNames); } } } @@ -449,7 +459,8 @@ void SymbolSupport::requestRename(const TextDocumentPositionParams &positionPara handleRenameResponse(search, response); }); m_client->sendMessage(request); - search->popup(); + if (search->isInteractive()) + search->popup(); } QList<Core::SearchResultItem> generateReplaceItems(const WorkspaceEdit &edits, @@ -480,6 +491,7 @@ QList<Core::SearchResultItem> generateReplaceItems(const WorkspaceEdit &edits, Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams &positionParams, const QString &placeholder, const QString &oldSymbolName, + const std::function<void()> &callback, bool preferLowerCaseFileNames) { Core::SearchResult *search = Core::SearchResultWindow::instance()->startNewSearch( @@ -491,6 +503,8 @@ Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams const auto extraWidget = new ReplaceWidget; search->setAdditionalReplaceWidget(extraWidget); search->setTextToReplace(placeholder); + if (callback) + search->makeNonInteractive(callback); connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) { Core::EditorManager::openEditorAtSearchResult(item); @@ -521,10 +535,12 @@ Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams void SymbolSupport::startRenameSymbol(const TextDocumentPositionParams &positionParams, const QString &placeholder, const QString &oldSymbolName, + const std::function<void()> &callback, bool preferLowerCaseFileNames) { requestRename(positionParams, - createSearch(positionParams, placeholder, oldSymbolName, preferLowerCaseFileNames)); + createSearch(positionParams, placeholder, oldSymbolName, callback, + preferLowerCaseFileNames)); } void SymbolSupport::handleRenameResponse(Core::SearchResult *search, diff --git a/src/plugins/languageclient/languageclientsymbolsupport.h b/src/plugins/languageclient/languageclientsymbolsupport.h index 864c963b8b..a4b910a9db 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.h +++ b/src/plugins/languageclient/languageclientsymbolsupport.h @@ -42,7 +42,9 @@ public: bool supportsRename(TextEditor::TextDocument *document); void renameSymbol(TextEditor::TextDocument *document, const QTextCursor &cursor, - const QString &newSymbolName = {}, bool preferLowerCaseFileNames = true); + const QString &newSymbolName = {}, + const std::function<void()> &callback = {}, + bool preferLowerCaseFileNames = true); static Core::Search::TextRange convertRange(const LanguageServerProtocol::Range &range); static QStringList getFileContents(const Utils::FilePath &filePath); @@ -61,16 +63,17 @@ private: void requestPrepareRename(TextEditor::TextDocument *document, const LanguageServerProtocol::TextDocumentPositionParams ¶ms, const QString &placeholder, - const QString &oldSymbolName, + const QString &oldSymbolName, const std::function<void()> &callback, bool preferLowerCaseFileNames); void requestRename(const LanguageServerProtocol::TextDocumentPositionParams &positionParams, Core::SearchResult *search); Core::SearchResult *createSearch(const LanguageServerProtocol::TextDocumentPositionParams &positionParams, const QString &placeholder, const QString &oldSymbolName, + const std::function<void()> &callback, bool preferLowerCaseFileNames); void startRenameSymbol(const LanguageServerProtocol::TextDocumentPositionParams ¶ms, const QString &placeholder, const QString &oldSymbolName, - bool preferLowerCaseFileNames); + const std::function<void()> &callback, bool preferLowerCaseFileNames); void handleRenameResponse(Core::SearchResult *search, const LanguageServerProtocol::RenameRequest::Response &response); void applyRename(const QList<Core::SearchResultItem> &checkedItems, Core::SearchResult *search); diff --git a/src/plugins/projectexplorer/buildsystem.cpp b/src/plugins/projectexplorer/buildsystem.cpp index e0e6f93dcd..000949f3e5 100644 --- a/src/plugins/projectexplorer/buildsystem.cpp +++ b/src/plugins/projectexplorer/buildsystem.cpp @@ -236,6 +236,12 @@ bool BuildSystem::supportsAction(Node *, ProjectAction, const Node *) const return false; } +ExtraCompiler *BuildSystem::extraCompilerForSource(const Utils::FilePath &source) +{ + Q_UNUSED(source); + return nullptr; +} + MakeInstallCommand BuildSystem::makeInstallCommand(const FilePath &installRoot) const { QTC_ASSERT(target()->project()->hasMakeInstallEquivalent(), return {}); diff --git a/src/plugins/projectexplorer/buildsystem.h b/src/plugins/projectexplorer/buildsystem.h index b60cb2f6e4..f43eb37f7d 100644 --- a/src/plugins/projectexplorer/buildsystem.h +++ b/src/plugins/projectexplorer/buildsystem.h @@ -19,6 +19,7 @@ namespace ProjectExplorer { class BuildConfiguration; class BuildStepList; +class ExtraCompiler; class Node; struct TestCaseInfo @@ -82,6 +83,9 @@ public: virtual bool supportsAction(Node *context, ProjectAction action, const Node *node) const; virtual QString name() const = 0; + // Owned by the build system. Use only in main thread. Can go away at any time. + virtual ExtraCompiler *extraCompilerForSource(const Utils::FilePath &source); + virtual MakeInstallCommand makeInstallCommand(const Utils::FilePath &installRoot) const; virtual Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const; diff --git a/src/plugins/projectexplorer/extracompiler.cpp b/src/plugins/projectexplorer/extracompiler.cpp index 9fb7cd3ad7..ff7c061790 100644 --- a/src/plugins/projectexplorer/extracompiler.cpp +++ b/src/plugins/projectexplorer/extracompiler.cpp @@ -13,10 +13,12 @@ #include <utils/asynctask.h> #include <utils/expected.h> +#include <utils/guard.h> #include <utils/qtcprocess.h> #include <QDateTime> #include <QFutureInterface> +#include <QLoggingCategory> #include <QThreadPool> #include <QTimer> @@ -26,6 +28,7 @@ namespace ProjectExplorer { Q_GLOBAL_STATIC(QThreadPool, s_extraCompilerThreadPool); Q_GLOBAL_STATIC(QList<ExtraCompilerFactory *>, factories); +Q_LOGGING_CATEGORY(log, "qtc.projectexplorer.extracompiler", QtWarningMsg); class ExtraCompilerPrivate { @@ -37,6 +40,7 @@ public: Core::IEditor *lastEditor = nullptr; QMetaObject::Connection activeBuildConfigConnection; QMetaObject::Connection activeEnvironmentConnection; + Utils::Guard lock; bool dirty = false; QTimer timer; @@ -55,13 +59,7 @@ ExtraCompiler::ExtraCompiler(const Project *project, const FilePath &source, d->contents.insert(target, QByteArray()); d->timer.setSingleShot(true); - connect(&d->timer, &QTimer::timeout, this, [this] { - if (d->dirty && d->lastEditor) { - d->dirty = false; - compileContent(d->lastEditor->document()->contents()); - } - }); - + connect(&d->timer, &QTimer::timeout, this, &ExtraCompiler::compileIfDirty); connect(BuildManager::instance(), &BuildManager::buildStateChanged, this, &ExtraCompiler::onTargetsBuilt); @@ -163,6 +161,16 @@ void ExtraCompiler::compileImpl(const ContentProvider &provider) d->m_taskTree->start(); } +void ExtraCompiler::compileIfDirty() +{ + qCDebug(log) << Q_FUNC_INFO; + if (!d->lock.isLocked() && d->dirty && d->lastEditor) { + qCDebug(log) << '\t' << "about to compile"; + d->dirty = false; + compileContent(d->lastEditor->document()->contents()); + } +} + ExtraCompiler::ContentProvider ExtraCompiler::fromFileProvider() const { const auto provider = [fileName = source()] { @@ -179,6 +187,20 @@ bool ExtraCompiler::isDirty() const return d->dirty; } +void ExtraCompiler::block() +{ + qCDebug(log) << Q_FUNC_INFO; + d->lock.lock(); +} + +void ExtraCompiler::unblock() +{ + qCDebug(log) << Q_FUNC_INFO; + d->lock.unlock(); + if (!d->lock.isLocked() && !d->timer.isActive()) + d->timer.start(); +} + void ExtraCompiler::onTargetsBuilt(Project *project) { if (project != d->project || BuildManager::isBuilding(project)) @@ -278,6 +300,7 @@ Utils::FutureSynchronizer *ExtraCompiler::futureSynchronizer() const void ExtraCompiler::setContent(const FilePath &file, const QByteArray &contents) { + qCDebug(log).noquote() << Q_FUNC_INFO << contents; auto it = d->contents.find(file); if (it != d->contents.end()) { if (it.value() != contents) { diff --git a/src/plugins/projectexplorer/extracompiler.h b/src/plugins/projectexplorer/extracompiler.h index 7d595b8726..34993f3940 100644 --- a/src/plugins/projectexplorer/extracompiler.h +++ b/src/plugins/projectexplorer/extracompiler.h @@ -55,6 +55,8 @@ public: Utils::Tasking::TaskItem compileFileItem(); void compileFile(); bool isDirty() const; + void block(); + void unblock(); signals: void contentsChanged(const Utils::FilePath &file); @@ -76,6 +78,7 @@ private: ContentProvider fromFileProvider() const; void compileContent(const QByteArray &content); void compileImpl(const ContentProvider &provider); + void compileIfDirty(); virtual Utils::Tasking::TaskItem taskItemImpl(const ContentProvider &provider) = 0; const std::unique_ptr<ExtraCompilerPrivate> d; diff --git a/src/plugins/qbsprojectmanager/qbsproject.cpp b/src/plugins/qbsprojectmanager/qbsproject.cpp index ef22f02c8f..286219a9b7 100644 --- a/src/plugins/qbsprojectmanager/qbsproject.cpp +++ b/src/plugins/qbsprojectmanager/qbsproject.cpp @@ -586,6 +586,13 @@ void QbsBuildSystem::delayParsing() requestDelayedParse(); } +ExtraCompiler *QbsBuildSystem::extraCompilerForSource(const Utils::FilePath &source) +{ + return Utils::findOrDefault(m_extraCompilers, [source](ExtraCompiler *ec) { + return ec->source() == source; + }); +} + void QbsBuildSystem::parseCurrentBuildConfiguration() { m_parsingScheduled = false; diff --git a/src/plugins/qbsprojectmanager/qbsproject.h b/src/plugins/qbsprojectmanager/qbsproject.h index f33d725daa..4a21caa2a9 100644 --- a/src/plugins/qbsprojectmanager/qbsproject.h +++ b/src/plugins/qbsprojectmanager/qbsproject.h @@ -105,6 +105,8 @@ public: private: friend class QbsProject; + ProjectExplorer::ExtraCompiler *extraCompilerForSource(const Utils::FilePath &source) override; + void handleQbsParsingDone(bool success); void changeActiveTarget(ProjectExplorer::Target *t); void prepareForParsing(); diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index a1c34a528d..d7d0a854c4 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -2080,6 +2080,21 @@ QList<ExtraCompiler *> QmakeProFile::extraCompilers() const return m_extraCompilers; } +ExtraCompiler *QmakeProFile::extraCompilerForSource(const Utils::FilePath &sourceFile) +{ + for (ExtraCompiler * const ec : std::as_const(m_extraCompilers)) { + if (ec->source() == sourceFile) + return ec; + } + for (QmakePriFile * const priFile : std::as_const(m_children)) { + if (const auto proFile = dynamic_cast<QmakeProFile *>(priFile)) { + if (ExtraCompiler * const ec = proFile->extraCompilerForSource(sourceFile)) + return ec; + } + } + return nullptr; +} + void QmakeProFile::setupExtraCompiler(const FilePath &buildDir, const FileType &fileType, ExtraCompilerFactory *factory) { diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h index 86a3a97b49..f9700b7f54 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h @@ -301,6 +301,7 @@ public: const Utils::FilePath &sourceFile, const ProjectExplorer::FileType &sourceFileType) const; QList<ProjectExplorer::ExtraCompiler *> extraCompilers() const; + ProjectExplorer::ExtraCompiler *extraCompilerForSource(const Utils::FilePath &sourceFile); TargetInformation targetInformation() const; InstallsList installsList() const; diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index 5d021f9c56..84188df03f 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -532,6 +532,11 @@ void QmakeBuildSystem::scheduleUpdateAllNowOrLater() scheduleUpdateAll(QmakeProFile::ParseLater); } +ExtraCompiler *QmakeBuildSystem::extraCompilerForSource(const Utils::FilePath &source) +{ + return m_rootProFile->extraCompilerForSource(source); +} + QmakeBuildConfiguration *QmakeBuildSystem::qmakeBuildConfiguration() const { return static_cast<QmakeBuildConfiguration *>(BuildSystem::buildConfiguration()); diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.h b/src/plugins/qmakeprojectmanager/qmakeproject.h index b83468a108..edf2db0edf 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.h +++ b/src/plugins/qmakeprojectmanager/qmakeproject.h @@ -163,6 +163,8 @@ public: void scheduleUpdateAllNowOrLater(); private: + ProjectExplorer::ExtraCompiler *extraCompilerForSource(const Utils::FilePath &source) override; + void scheduleUpdateAll(QmakeProFile::AsyncUpdateDelay delay); void scheduleUpdateAllLater() { scheduleUpdateAll(QmakeProFile::ParseLater); } |