From 5f9c30cacfbfca694a4263954e7714ec61f32eb3 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Mon, 25 Jul 2022 15:25:56 +0200 Subject: [PATCH] ClangCodeModel: Do not make clangd open all ui headers This amends 01ceb3a3cb40561aad6aa0d59bf46ec85cb80921, where we failed to consider the case of projects with lots of UI headers, which cause excessive memory use by clangd with our current simplistic approach. Instead, we now only open ui headers that are used by currently open documents. Note that this approach will fail for indirect includes via header files, but people who do that do not deserve happiness. Change-Id: I1ef2add701e0f13dc0da79267d3c1367c1b496cc Reviewed-by: Qt CI Bot Reviewed-by: Reviewed-by: Cristian Adam Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 8 ++ src/plugins/clangcodemodel/clangdclient.h | 2 + src/plugins/languageclient/client.cpp | 109 +++++++++++++++----- src/plugins/languageclient/client.h | 2 + 4 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 4c72aba34c7..82b0944901e 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -1143,6 +1143,14 @@ DiagnosticManager *ClangdClient::createDiagnosticManager() return diagnosticManager; } +bool ClangdClient::referencesShadowFile(const TextEditor::TextDocument *doc, + const Utils::FilePath &candidate) +{ + const QRegularExpression includeRex("#include.*" + candidate.fileName() + R"([>"])"); + const QTextCursor includePos = doc->document()->find(includeRex); + return !includePos.isNull(); +} + RefactoringChangesData *ClangdClient::createRefactoringChangesBackend() const { return new CppEditor::CppRefactoringChangesData( diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 2aec6f8b570..850d6b41285 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -145,6 +145,8 @@ private: const CustomInspectorTabs createCustomInspectorTabs() override; TextEditor::RefactoringChangesData *createRefactoringChangesBackend() const override; LanguageClient::DiagnosticManager *createDiagnosticManager() override; + bool referencesShadowFile(const TextEditor::TextDocument *doc, + const Utils::FilePath &candidate) override; class Private; class VirtualFunctionAssistProcessor; diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index bb180467e5b..e57700133da 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -263,6 +263,12 @@ public: void sendOpenNotification(const FilePath &filePath, const QString &mimeType, const QString &content, int version); void sendCloseNotification(const FilePath &filePath); + void openRequiredShadowDocuments(const TextEditor::TextDocument *doc); + void closeRequiredShadowDocuments(const TextEditor::TextDocument *doc); + + using ShadowDocIterator = QMap>>::iterator; + void openShadowDocument(const TextEditor::TextDocument *requringDoc, ShadowDocIterator shadowIt); + void closeShadowDocument(ShadowDocIterator docIt); bool reset(); @@ -276,7 +282,9 @@ public: // Used for build system artifacts (e.g. UI headers) that Qt Creator "live-generates" ahead of // the build. - QMap m_shadowDocuments; + // The Value is the file content + the documents that require the shadow file to be open + // (empty <=> shadow document is not open). + QMap>> m_shadowDocuments; QSet m_postponedDocuments; QMap m_documentVersions; @@ -577,10 +585,12 @@ void Client::openDocument(TextEditor::TextDocument *document) } const FilePath &filePath = document->filePath(); - if (d->m_shadowDocuments.contains(filePath)) { - d->sendCloseNotification(filePath); + const auto shadowIt = d->m_shadowDocuments.find(filePath); + if (shadowIt != d->m_shadowDocuments.end()) { + d->closeShadowDocument(shadowIt); emit shadowDocumentSwitched(filePath); } + d->openRequiredShadowDocuments(document); const QString method(DidOpenTextDocumentNotification::methodName); if (Utils::optional registered = d->m_dynamicCapabilities.isRegistered(method)) { @@ -656,12 +666,20 @@ void Client::closeDocument(TextEditor::TextDocument *document) if (d->m_state != Initialized) return; - const auto shadowIt = d->m_shadowDocuments.constFind(document->filePath()); + d->closeRequiredShadowDocuments(document); + const auto shadowIt = d->m_shadowDocuments.find(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()); + QTC_CHECK(shadowIt.value().second.isEmpty()); + bool isReferenced = false; + for (auto it = d->m_openedDocument.cbegin(); it != d->m_openedDocument.cend(); ++it) { + if (referencesShadowFile(it.key(), shadowIt.key())) { + d->openShadowDocument(it.key(), shadowIt); + isReferenced = true; + } + } + if (isReferenced) + emit shadowDocumentSwitched(document->filePath()); } void ClientPrivate::updateCompletionProvider(TextEditor::TextDocument *document) @@ -871,6 +889,22 @@ void ClientPrivate::sendCloseNotification(const FilePath &filePath) Client::SendDocUpdates::Ignore); } +void ClientPrivate::openRequiredShadowDocuments(const TextEditor::TextDocument *doc) +{ + for (auto it = m_shadowDocuments.begin(); it != m_shadowDocuments.end(); ++it) { + if (!it.value().second.contains(doc) && q->referencesShadowFile(doc, it.key())) + openShadowDocument(doc, it); + } +} + +void ClientPrivate::closeRequiredShadowDocuments(const TextEditor::TextDocument *doc) +{ + for (auto it = m_shadowDocuments.begin(); it != m_shadowDocuments.end(); ++it) { + if (it.value().second.removeOne(doc) && it.value().second.isEmpty()) + closeShadowDocument(it); + } +} + bool Client::documentOpen(const TextEditor::TextDocument *document) const { return d->m_openedDocument.contains(const_cast(document)); @@ -888,24 +922,25 @@ TextEditor::TextDocument *Client::documentForFilePath(const Utils::FilePath &fil 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; + auto shadowIt = d->m_shadowDocuments.find(filePath); + if (shadowIt == d->m_shadowDocuments.end()) { + shadowIt = d->m_shadowDocuments.insert(filePath, {content, {}}); + } else { + shadowIt.value().first = content; + if (!shadowIt.value().second.isEmpty()) { + VersionedTextDocumentIdentifier docId(DocumentUri::fromFilePath(filePath)); + docId.setVersion(++d->m_documentVersions[filePath]); + const DidChangeTextDocumentParams params(docId, content); + sendMessage(DidChangeTextDocumentNotification(params), SendDocUpdates::Ignore); + return; + } + } if (documentForFilePath(filePath)) return; - const auto uri = DocumentUri::fromFilePath(filePath); - if (isNew) { - const QString mimeType = mimeTypeForFile(filePath, MimeMatchMode::MatchExtension).name(); - d->sendOpenNotification(filePath, mimeType, content, 0); + for (auto docIt = d->m_openedDocument.cbegin(); docIt != d->m_openedDocument.cend(); ++docIt) { + if (referencesShadowFile(docIt.key(), filePath)) + d->openShadowDocument(docIt.key(), shadowIt); } - - 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) @@ -913,10 +948,27 @@ void Client::removeShadowDocument(const Utils::FilePath &filePath) const auto it = d->m_shadowDocuments.find(filePath); if (it == d->m_shadowDocuments.end()) return; + if (!it.value().second.isEmpty()) + d->closeShadowDocument(it); d->m_shadowDocuments.erase(it); - if (documentForFilePath(filePath)) +} + +void ClientPrivate::openShadowDocument(const TextEditor::TextDocument *requringDoc, + ShadowDocIterator shadowIt) +{ + shadowIt.value().second << requringDoc; + if (shadowIt.value().second.size() > 1) return; - d->sendCloseNotification(filePath); + const auto uri = DocumentUri::fromFilePath(shadowIt.key()); + const QString mimeType = mimeTypeForFile(shadowIt.key(), MimeMatchMode::MatchExtension).name(); + sendOpenNotification(shadowIt.key(), mimeType, shadowIt.value().first, + ++m_documentVersions[shadowIt.key()]); +} + +void ClientPrivate::closeShadowDocument(ShadowDocIterator shadowIt) +{ + sendCloseNotification(shadowIt.key()); + shadowIt.value().second.clear(); } void Client::documentContentsSaved(TextEditor::TextDocument *document) @@ -948,6 +1000,7 @@ void Client::documentContentsSaved(TextEditor::TextDocument *document) return; DidSaveTextDocumentParams params( TextDocumentIdentifier(DocumentUri::fromFilePath(document->filePath()))); + d->openRequiredShadowDocuments(document); if (includeText) params.setText(document->plainText()); sendMessage(DidSaveTextDocumentNotification(params), SendDocUpdates::Send, Schedule::Now); @@ -1971,6 +2024,14 @@ QTextCursor Client::adjustedCursorForHighlighting(const QTextCursor &cursor, return cursor; } +bool Client::referencesShadowFile(const TextEditor::TextDocument *doc, + const Utils::FilePath &candidate) +{ + Q_UNUSED(doc) + Q_UNUSED(candidate) + return false; +} + } // namespace LanguageClient #include diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 6fcc4d5a984..d718012e49d 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -226,6 +226,8 @@ private: virtual void handleDocumentOpened(TextEditor::TextDocument *) {} virtual QTextCursor adjustedCursorForHighlighting(const QTextCursor &cursor, TextEditor::TextDocument *doc); + virtual bool referencesShadowFile(const TextEditor::TextDocument *doc, + const Utils::FilePath &candidate); }; } // namespace LanguageClient