diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index ef813ff5ef4..acd6b4bbd28 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -27,7 +27,6 @@ add_qtc_plugin(ClangCodeModel clangmodelmanagersupport.cpp clangmodelmanagersupport.h clangpreprocessorassistproposalitem.cpp clangpreprocessorassistproposalitem.h clangtextmark.cpp clangtextmark.h - clanguiheaderondiskmanager.cpp clanguiheaderondiskmanager.h clangutils.cpp clangutils.h tasktimers.cpp tasktimers.h EXPLICIT_MOC clangcodemodelplugin.h diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index b16c685c99c..5f4cc2c1b6e 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -54,8 +54,6 @@ QtcPlugin { "clangpreprocessorassistproposalitem.h", "clangtextmark.cpp", "clangtextmark.h", - "clanguiheaderondiskmanager.cpp", - "clanguiheaderondiskmanager.h", "clangutils.cpp", "clangutils.h", "tasktimers.cpp", diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index b49c8fc3c7b..6bff13b8353 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -100,6 +100,30 @@ static const QList allCppDocuments() return Utils::qobject_container_cast(documents); } +static bool fileIsProjectBuildArtifact(const Client *client, const Utils::FilePath &filePath) +{ + if (const auto p = client->project()) { + if (const auto t = p->activeTarget()) { + if (const auto bc = t->activeBuildConfiguration()) { + if (filePath.isChildOf(bc->buildDirectory())) + return true; + } + } + } + return false; +} + +static Client *clientForGeneratedFile(const Utils::FilePath &filePath) +{ + for (Client * const client : LanguageClientManager::clients()) { + if (qobject_cast(client) && client->reachable() + && fileIsProjectBuildArtifact(client, filePath)) { + return client; + } + } + return nullptr; +} + ClangModelManagerSupport::ClangModelManagerSupport() { QTC_CHECK(!m_instance); @@ -344,7 +368,7 @@ void ClangModelManagerSupport::updateLanguageClient( if (Client * const oldClient = clientForProject(project)) LanguageClientManager::shutdownClient(oldClient); ClangdClient * const client = createClient(project, jsonDbDir); - connect(client, &Client::initialized, this, [client, project, projectInfo, jsonDbDir] { + connect(client, &Client::initialized, this, [this, client, project, projectInfo, jsonDbDir] { using namespace ProjectExplorer; if (!SessionManager::hasProject(project)) return; @@ -384,6 +408,23 @@ void ClangModelManagerSupport::updateLanguageClient( } } + for (auto it = m_queuedShadowDocuments.begin(); it != m_queuedShadowDocuments.end();) { + if (fileIsProjectBuildArtifact(client, it.key())) { + if (it.value().isEmpty()) + client->removeShadowDocument(it.key()); + else + client->setShadowDocument(it.key(), it.value()); + ClangdClient::handleUiHeaderChange(it.key().fileName()); + it = m_queuedShadowDocuments.erase(it); + } else { + ++it; + } + } + connect(client, &Client::shadowDocumentSwitched, this, + [](const Utils::FilePath &fp) { + ClangdClient::handleUiHeaderChange(fp.fileName()); + }); + if (client->state() == Client::Initialized) updateParserConfig(); else @@ -593,18 +634,28 @@ void ClangModelManagerSupport::onAbstractEditorSupportContentsUpdated(const QStr if (content.size() == 0) return; // Generation not yet finished. - - m_uiHeaderOnDiskManager.write(filePath, content); - ClangdClient::handleUiHeaderChange(Utils::FilePath::fromString(filePath).fileName()); + const auto fp = Utils::FilePath::fromString(filePath); + const QString stringContent = QString::fromUtf8(content); + if (Client * const client = clientForGeneratedFile(fp)) { + client->setShadowDocument(fp, stringContent); + ClangdClient::handleUiHeaderChange(fp.fileName()); + QTC_CHECK(m_queuedShadowDocuments.remove(fp) == 0); + } else { + m_queuedShadowDocuments.insert(fp, stringContent); + } } void ClangModelManagerSupport::onAbstractEditorSupportRemoved(const QString &filePath) { QTC_ASSERT(!filePath.isEmpty(), return); - if (!cppModelManager()->cppEditorDocument(filePath)) { - m_uiHeaderOnDiskManager.remove(filePath); - ClangdClient::handleUiHeaderChange(Utils::FilePath::fromString(filePath).fileName()); + const auto fp = Utils::FilePath::fromString(filePath); + if (Client * const client = clientForGeneratedFile(fp)) { + client->removeShadowDocument(fp); + ClangdClient::handleUiHeaderChange(fp.fileName()); + QTC_CHECK(m_queuedShadowDocuments.remove(fp) == 0); + } else { + m_queuedShadowDocuments.insert(fp, {}); } } @@ -738,16 +789,6 @@ ClangModelManagerSupport *ClangModelManagerSupport::instance() return m_instance; } -QString ClangModelManagerSupport::dummyUiHeaderOnDiskPath(const QString &filePath) const -{ - return m_uiHeaderOnDiskManager.mapPath(filePath); -} - -QString ClangModelManagerSupport::dummyUiHeaderOnDiskDirPath() const -{ - return m_uiHeaderOnDiskManager.directoryPath(); -} - QString ClangModelManagerSupportProvider::id() const { return QLatin1String(Constants::CLANG_MODELMANAGERSUPPORT_ID); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h index 7604dd5cc59..74723b2e29e 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h @@ -25,14 +25,14 @@ #pragma once -#include "clanguiheaderondiskmanager.h" - #include #include +#include #include #include +#include #include #include @@ -70,9 +70,6 @@ public: std::unique_ptr createOverviewModel() override; bool usesClangd(const TextEditor::TextDocument *document) const override; - QString dummyUiHeaderOnDiskDirPath() const; - QString dummyUiHeaderOnDiskPath(const QString &filePath) const; - ClangdClient *clientForProject(const ProjectExplorer::Project *project) const; ClangdClient *clientForFile(const Utils::FilePath &file) const; @@ -122,10 +119,9 @@ private: void watchForExternalChanges(); void watchForInternalChanges(); - UiHeaderOnDiskManager m_uiHeaderOnDiskManager; - Utils::FutureSynchronizer m_generatorSynchronizer; QList> m_clientsToRestart; + QHash m_queuedShadowDocuments; }; class ClangModelManagerSupportProvider : public CppEditor::ModelManagerSupportProvider diff --git a/src/plugins/clangcodemodel/clanguiheaderondiskmanager.cpp b/src/plugins/clangcodemodel/clanguiheaderondiskmanager.cpp deleted file mode 100644 index 696ced1c6f7..00000000000 --- a/src/plugins/clangcodemodel/clanguiheaderondiskmanager.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#include "clanguiheaderondiskmanager.h" - -#include -#include - -#include -#include - -namespace ClangCodeModel { -namespace Internal { - -UiHeaderOnDiskManager::UiHeaderOnDiskManager() : m_temporaryDir("clang-uiheader-XXXXXX") -{ - QTC_CHECK(m_temporaryDir.isValid()); -} - -QString UiHeaderOnDiskManager::write(const QString &filePath, const QByteArray &content) -{ - const QString mappedPath = mapPath(filePath); - QFile file(mappedPath); - const bool fileCreated = file.open(QFile::WriteOnly); - const qint64 bytesWritten = file.write(content); - QTC_CHECK(fileCreated && bytesWritten != -1); - - return mappedPath; -} - -QString UiHeaderOnDiskManager::remove(const QString &filePath) -{ - const QString mappedPath = mapPath(filePath); - if (QFileInfo::exists(mappedPath)) { - const bool fileRemoved = QFile::remove(mappedPath); - QTC_CHECK(fileRemoved); - } - - return mappedPath; -} - -QString UiHeaderOnDiskManager::directoryPath() const -{ - return m_temporaryDir.path().path(); -} - -QString UiHeaderOnDiskManager::mapPath(const QString &filePath) const -{ - return directoryPath() + '/' + QFileInfo(filePath).fileName(); -} - -} // namespace Internal -} // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clanguiheaderondiskmanager.h b/src/plugins/clangcodemodel/clanguiheaderondiskmanager.h deleted file mode 100644 index a244fdb1781..00000000000 --- a/src/plugins/clangcodemodel/clanguiheaderondiskmanager.h +++ /dev/null @@ -1,50 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#pragma once - -#include - -namespace ClangCodeModel { -namespace Internal { - -// TODO: Remove once libclang supports unsaved files that do not exist. -class UiHeaderOnDiskManager -{ -public: - UiHeaderOnDiskManager(); - - QString write(const QString &filePath, const QByteArray &content); - QString remove(const QString &filePath); - - QString mapPath(const QString &filePath) const; - QString directoryPath() const; - -private: - ::Utils::TemporaryDirectory m_temporaryDir; -}; - -} // namespace Internal -} // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangutils.cpp b/src/plugins/clangcodemodel/clangutils.cpp index d2d5e690702..a1c4393eab0 100644 --- a/src/plugins/clangcodemodel/clangutils.cpp +++ b/src/plugins/clangcodemodel/clangutils.cpp @@ -25,8 +25,6 @@ #include "clangutils.h" -#include "clangmodelmanagersupport.h" - #include #include #include @@ -355,12 +353,6 @@ CompilerOptionsBuilder clangOptionsBuilder(const ProjectPart &projectPart, useBuildSystemWarnings, clangIncludeDir); optionsBuilder.provideAdditionalMacros({ProjectExplorer::Macro("Q_CREATOR_RUN", "1")}); optionsBuilder.build(ProjectFile::Unclassified, UsePrecompiledHeaders::No); - const QString uiIncludePath - = ClangModelManagerSupport::instance()->dummyUiHeaderOnDiskDirPath(); - if (!uiIncludePath.isEmpty()) { - optionsBuilder.prepend(QDir::toNativeSeparators(uiIncludePath)); - optionsBuilder.prepend("-I"); - } optionsBuilder.add("-fmessage-length=0", /*gccOnlyOption=*/true); optionsBuilder.add("-fdiagnostics-show-note-include-stack", /*gccOnlyOption=*/true); optionsBuilder.add("-fretain-comments-from-system-headers", /*gccOnlyOption=*/true); diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index e997a564c5d..b45d8af37cb 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -260,6 +260,9 @@ public: const LanguageServerProtocol::Range &range, const QList &diagnostics); void documentClosed(Core::IDocument *document); + void sendOpenNotification(const FilePath &filePath, const QString &mimeType, + const QString &content, int version); + void sendCloseNotification(const FilePath &filePath); bool reset(); @@ -270,6 +273,11 @@ public: LanguageFilter m_languagFilter; QJsonObject m_initializationOptions; QMap m_openedDocument; + + // Used for build system artifacts (e.g. UI headers) that Qt Creator "live-generates" ahead of + // the build. + QMap m_shadowDocuments; + QSet m_postponedDocuments; QMap m_documentVersions; std::unordered_mapfilePath(); + if (d->m_shadowDocuments.contains(filePath)) { + d->sendCloseNotification(filePath); + emit shadowDocumentSwitched(filePath); + } + const QString method(DidOpenTextDocumentNotification::methodName); if (Utils::optional registered = d->m_dynamicCapabilities.isRegistered(method)) { if (!*registered) @@ -591,14 +604,10 @@ void Client::openDocument(TextEditor::TextDocument *document) [this, document](int position, int charsRemoved, int charsAdded) { documentContentsChanged(document, position, charsRemoved, charsAdded); }); - TextDocumentItem item; - item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType())); - item.setUri(DocumentUri::fromFilePath(filePath)); - item.setText(document->plainText()); if (!d->m_documentVersions.contains(filePath)) d->m_documentVersions[filePath] = 0; - item.setVersion(d->m_documentVersions[filePath]); - sendMessage(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item))); + d->sendOpenNotification(filePath, document->mimeType(), document->plainText(), + d->m_documentVersions[filePath]); handleDocumentOpened(document); const Client *currentClient = LanguageClientManager::clientForDocument(document); @@ -635,15 +644,21 @@ void Client::cancelRequest(const MessageId &id) void Client::closeDocument(TextEditor::TextDocument *document) { deactivateDocument(document); - const DocumentUri &uri = DocumentUri::fromFilePath(document->filePath()); d->m_postponedDocuments.remove(document); if (d->m_openedDocument.remove(document) != 0) { handleDocumentClosed(document); - if (d->m_state == Initialized) { - DidCloseTextDocumentParams params(TextDocumentIdentifier{uri}); - sendMessage(DidCloseTextDocumentNotification(params)); - } + if (d->m_state == Initialized) + d->sendCloseNotification(document->filePath()); } + + if (d->m_state != Initialized) + return; + const auto shadowIt = d->m_shadowDocuments.constFind(document->filePath()); + if (shadowIt == d->m_shadowDocuments.constEnd()) + return; + d->sendOpenNotification(document->filePath(), document->mimeType(), shadowIt.value(), + ++d->m_documentVersions[document->filePath()]); + emit shadowDocumentSwitched(document->filePath()); } void ClientPrivate::updateCompletionProvider(TextEditor::TextDocument *document) @@ -834,6 +849,25 @@ void ClientPrivate::documentClosed(Core::IDocument *document) q->closeDocument(textDocument); } +void ClientPrivate::sendOpenNotification(const FilePath &filePath, const QString &mimeType, + const QString &content, int version) +{ + TextDocumentItem item; + item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(mimeType)); + item.setUri(DocumentUri::fromFilePath(filePath)); + item.setText(content); + item.setVersion(version); + q->sendMessage(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)), + Client::SendDocUpdates::Ignore); +} + +void ClientPrivate::sendCloseNotification(const FilePath &filePath) +{ + q->sendMessage(DidCloseTextDocumentNotification(DidCloseTextDocumentParams( + TextDocumentIdentifier{DocumentUri::fromFilePath(filePath)})), + Client::SendDocUpdates::Ignore); +} + bool Client::documentOpen(const TextEditor::TextDocument *document) const { return d->m_openedDocument.contains(const_cast(document)); @@ -848,6 +882,41 @@ TextEditor::TextDocument *Client::documentForFilePath(const Utils::FilePath &fil return nullptr; } +void Client::setShadowDocument(const Utils::FilePath &filePath, const QString &content) +{ + QTC_ASSERT(reachable(), return); + const auto it = d->m_shadowDocuments.find(filePath); + const bool isNew = it == d->m_shadowDocuments.end(); + if (isNew) + d->m_shadowDocuments.insert(filePath, content); + else + it.value() = content; + if (documentForFilePath(filePath)) + return; + const auto uri = DocumentUri::fromFilePath(filePath); + if (isNew) { + const QString mimeType = mimeTypeForFile( + filePath.toString(), MimeMatchMode::MatchExtension).name(); + d->sendOpenNotification(filePath, mimeType, content, 0); + } + + VersionedTextDocumentIdentifier docId(uri); + docId.setVersion(++d->m_documentVersions[filePath]); + const DidChangeTextDocumentParams params(docId, content); + sendMessage(DidChangeTextDocumentNotification(params), SendDocUpdates::Ignore); +} + +void Client::removeShadowDocument(const Utils::FilePath &filePath) +{ + const auto it = d->m_shadowDocuments.find(filePath); + if (it == d->m_shadowDocuments.end()) + return; + d->m_shadowDocuments.erase(it); + if (documentForFilePath(filePath)) + return; + d->sendCloseNotification(filePath); +} + void Client::documentContentsSaved(TextEditor::TextDocument *document) { if (!d->m_openedDocument.contains(document)) @@ -1406,6 +1475,7 @@ bool ClientPrivate::reset() qDeleteAll(m_documentHighlightsTimer); m_documentHighlightsTimer.clear(); m_progressManager.reset(); + m_shadowDocuments.clear(); m_documentVersions.clear(); return true; } diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 679cf992193..6fcc4d5a984 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -136,6 +136,8 @@ public: void deactivateDocument(TextEditor::TextDocument *document); bool documentOpen(const TextEditor::TextDocument *document) const; TextEditor::TextDocument *documentForFilePath(const Utils::FilePath &file) const; + void setShadowDocument(const Utils::FilePath &filePath, const QString &contents); + void removeShadowDocument(const Utils::FilePath &filePath); void documentContentsSaved(TextEditor::TextDocument *document); void documentWillSave(Core::IDocument *document); void documentContentsChanged(TextEditor::TextDocument *document, @@ -205,6 +207,7 @@ signals: void capabilitiesChanged(const DynamicCapabilities &capabilities); void documentUpdated(TextEditor::TextDocument *document); void workDone(const LanguageServerProtocol::ProgressToken &token); + void shadowDocumentSwitched(const Utils::FilePath &filePath); void finished(); protected: diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index 747a6d04cae..cc439f1fdbf 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -181,7 +181,7 @@ Client *LanguageClientManager::startClient(const BaseSettings *setting, return client; } -QList LanguageClientManager::clients() +const QList LanguageClientManager::clients() { QTC_ASSERT(managerInstance, return {}); return managerInstance->m_clients; diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index c5c8e9e86ad..d1b1467f6ab 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -62,7 +62,7 @@ public: static void clientStarted(Client *client); static void clientFinished(Client *client); static Client *startClient(const BaseSettings *setting, ProjectExplorer::Project *project = nullptr); - static QList clients(); + static const QList clients(); static void addClient(Client *client); static void addExclusiveRequest(const LanguageServerProtocol::MessageId &id, Client *client); diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt index 502390cedfc..a6b0d3100de 100644 --- a/tests/unit/unittest/CMakeLists.txt +++ b/tests/unit/unittest/CMakeLists.txt @@ -332,7 +332,6 @@ extend_qtc_test(unittest SOURCES_PREFIX ../../../src/plugins/clangcodemodel SOURCES clangactivationsequenceprocessor.cpp clangactivationsequenceprocessor.h - clanguiheaderondiskmanager.cpp clanguiheaderondiskmanager.h ) find_package(yaml-cpp QUIET MODULE) diff --git a/tests/unit/unittest/unittest.qbs b/tests/unit/unittest/unittest.qbs index 598980bec18..00f3e2a8c64 100644 --- a/tests/unit/unittest/unittest.qbs +++ b/tests/unit/unittest/unittest.qbs @@ -203,8 +203,6 @@ Project { files: [ "clangactivationsequenceprocessor.cpp", "clangactivationsequenceprocessor.h", - "clanguiheaderondiskmanager.cpp", - "clanguiheaderondiskmanager.h", ] }