diff --git a/doc/src/projects/creator-projects-custom-wizards-json.qdoc b/doc/src/projects/creator-projects-custom-wizards-json.qdoc index 04c6631ffac..ea75906386c 100644 --- a/doc/src/projects/creator-projects-custom-wizards-json.qdoc +++ b/doc/src/projects/creator-projects-custom-wizards-json.qdoc @@ -824,6 +824,9 @@ For example, to turn the first character in the line edit to upper case. + \li \c isPassword is a boolean value that specifies that the line edit + contains a password, which will be masked. + \endlist \section2 Path Chooser diff --git a/qbs/modules/qtc/qtc.qbs b/qbs/modules/qtc/qtc.qbs index b86dbb3edbe..69d2f6c8762 100644 --- a/qbs/modules/qtc/qtc.qbs +++ b/qbs/modules/qtc/qtc.qbs @@ -63,7 +63,8 @@ Module { "QT_CREATOR", 'IDE_LIBRARY_BASENAME="' + libDirName + '"', "QT_NO_CAST_TO_ASCII", - "QT_RESTRICTED_CAST_FROM_ASCII" + "QT_RESTRICTED_CAST_FROM_ASCII", + "QT_DISABLE_DEPRECATED_BEFORE=0x050600", ].concat(testsEnabled ? ["WITH_TESTS"] : []) Rule { diff --git a/qtcreator.pri b/qtcreator.pri index 8997b4a9961..1fcae0baf25 100644 --- a/qtcreator.pri +++ b/qtcreator.pri @@ -179,7 +179,11 @@ exists($$IDE_LIBRARY_PATH): LIBS *= -L$$IDE_LIBRARY_PATH # library path from ou DEFINES += IDE_LIBRARY_BASENAME=\\\"$$IDE_LIBRARY_BASENAME\\\" } -DEFINES += QT_CREATOR QT_NO_CAST_TO_ASCII QT_RESTRICTED_CAST_FROM_ASCII +DEFINES += \ + QT_CREATOR \ + QT_NO_CAST_TO_ASCII \ + QT_RESTRICTED_CAST_FROM_ASCII \ + QT_DISABLE_DEPRECATED_BEFORE=0x050600 !macx:DEFINES += QT_USE_FAST_OPERATOR_PLUS QT_USE_FAST_CONCATENATION unix { diff --git a/src/libs/clangbackendipc/documentannotationschangedmessage.cpp b/src/libs/clangbackendipc/documentannotationschangedmessage.cpp index b8b4b36601c..6c7ef0d5cb6 100644 --- a/src/libs/clangbackendipc/documentannotationschangedmessage.cpp +++ b/src/libs/clangbackendipc/documentannotationschangedmessage.cpp @@ -37,6 +37,7 @@ QDebug operator<<(QDebug debug, const DocumentAnnotationsChangedMessage &message debug.nospace() << "DocumentAnnotationsChangedMessage(" << message.fileContainer() << ", " << message.diagnostics().size() + << ", " << !message.firstHeaderErrorDiagnostic().text().isEmpty() << ", " << message.highlightingMarks().size() << ", " << message.skippedPreprocessorRanges().size() << ")"; @@ -49,6 +50,7 @@ void PrintTo(const DocumentAnnotationsChangedMessage &message, ::std::ostream* o *os << "DocumentAnnotationsChangedMessage("; PrintTo(message.fileContainer(), os); *os << "," << message.diagnostics().size(); + *os << "," << !message.firstHeaderErrorDiagnostic().text().isEmpty(); *os << "," << message.highlightingMarks().size(); *os << "," << message.skippedPreprocessorRanges().size(); *os << ")"; diff --git a/src/libs/clangbackendipc/documentannotationschangedmessage.h b/src/libs/clangbackendipc/documentannotationschangedmessage.h index 556794385d8..b121fc6ecd8 100644 --- a/src/libs/clangbackendipc/documentannotationschangedmessage.h +++ b/src/libs/clangbackendipc/documentannotationschangedmessage.h @@ -41,10 +41,12 @@ public: DocumentAnnotationsChangedMessage() = default; DocumentAnnotationsChangedMessage(const FileContainer &fileContainer, const QVector &diagnostics, + const DiagnosticContainer &firstHeaderErrorDiagnostic_, const QVector &highlightingMarks, const QVector &skippedPreprocessorRanges) : fileContainer_(fileContainer), diagnostics_(diagnostics), + firstHeaderErrorDiagnostic_(firstHeaderErrorDiagnostic_), highlightingMarks_(highlightingMarks), skippedPreprocessorRanges_(skippedPreprocessorRanges) { @@ -60,6 +62,11 @@ public: return diagnostics_; } + const DiagnosticContainer &firstHeaderErrorDiagnostic() const + { + return firstHeaderErrorDiagnostic_; + } + const QVector &highlightingMarks() const { return highlightingMarks_; @@ -74,6 +81,7 @@ public: { out << message.fileContainer_; out << message.diagnostics_; + out << message.firstHeaderErrorDiagnostic_; out << message.highlightingMarks_; out << message.skippedPreprocessorRanges_; @@ -84,6 +92,7 @@ public: { in >> message.fileContainer_; in >> message.diagnostics_; + in >> message.firstHeaderErrorDiagnostic_; in >> message.highlightingMarks_; in >> message.skippedPreprocessorRanges_; @@ -95,6 +104,7 @@ public: { return first.fileContainer_ == second.fileContainer_ && first.diagnostics_ == second.diagnostics_ + && first.firstHeaderErrorDiagnostic_ == second.firstHeaderErrorDiagnostic_ && first.highlightingMarks_ == second.highlightingMarks_ && first.skippedPreprocessorRanges_ == second.skippedPreprocessorRanges_; } @@ -102,6 +112,7 @@ public: private: FileContainer fileContainer_; QVector diagnostics_; + DiagnosticContainer firstHeaderErrorDiagnostic_; QVector highlightingMarks_; QVector skippedPreprocessorRanges_; }; diff --git a/src/plugins/clangcodemodel/clangbackendipcintegration.cpp b/src/plugins/clangcodemodel/clangbackendipcintegration.cpp index 9d2962cbef7..831a3bdea62 100644 --- a/src/plugins/clangcodemodel/clangbackendipcintegration.cpp +++ b/src/plugins/clangcodemodel/clangbackendipcintegration.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -68,6 +69,7 @@ #include +#include #include #include #include @@ -180,7 +182,9 @@ void IpcReceiver::documentAnnotationsChanged(const DocumentAnnotationsChangedMes const QString documentProjectPartId = CppTools::CppToolsBridge::projectPartIdForFile(filePath); if (projectPartId == documentProjectPartId) { const quint32 documentRevision = message.fileContainer().documentRevision(); - processor->updateCodeWarnings(message.diagnostics(), documentRevision); + processor->updateCodeWarnings(message.diagnostics(), + message.firstHeaderErrorDiagnostic(), + documentRevision); processor->updateHighlighting(message.highlightingMarks(), message.skippedPreprocessorRanges(), documentRevision); @@ -304,10 +308,16 @@ public: void updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &) override {} }; +enum { backEndStartTimeOutInMs = 10000 }; + IpcCommunicator::IpcCommunicator() : m_connection(&m_ipcReceiver) , m_ipcSender(new DummyIpcSender) { + m_backendStartTimeOut.setSingleShot(true); + connect(&m_backendStartTimeOut, &QTimer::timeout, + this, &IpcCommunicator::logStartTimeOut); + m_ipcReceiver.setAliveHandler([this]() { m_connection.resetProcessAliveTimer(); }); connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose, @@ -326,8 +336,11 @@ IpcCommunicator::~IpcCommunicator() void IpcCommunicator::initializeBackend() { const QString clangBackEndProcessPath = backendProcessPath(); + if (!QFileInfo(clangBackEndProcessPath).exists()) { + logExecutableDoesNotExist(); + return; + } qCDebug(log) << "Starting" << clangBackEndProcessPath; - QTC_ASSERT(QFileInfo(clangBackEndProcessPath).exists(), return); m_connection.setProcessAliveTimerInterval(30 * 1000); m_connection.setProcessPath(clangBackEndProcessPath); @@ -338,6 +351,7 @@ void IpcCommunicator::initializeBackend() this, &IpcCommunicator::setupDummySender); m_connection.startProcessAndConnectToServerAsynchronously(); + m_backendStartTimeOut.start(backEndStartTimeOutInMs); } static QStringList projectPartOptions(const CppTools::ProjectPart::Ptr &projectPart) @@ -622,11 +636,11 @@ void IpcCommunicator::updateUnsavedFile(Core::IDocument *document) void IpcCommunicator::onConnectedToBackend() { + m_backendStartTimeOut.stop(); + ++m_connectedCount; - if (m_connectedCount > 1) { - qWarning("Clang back end finished unexpectedly, restarted."); - qCDebug(log) << "Backend restarted, re-initializing with project data and unsaved files."; - } + if (m_connectedCount > 1) + logRestartedDueToUnexpectedFinish(); m_ipcReceiver.deleteAndClearWaitingAssistProcessors(); m_ipcSender.reset(new IpcSender(m_connection)); @@ -645,6 +659,42 @@ void IpcCommunicator::setupDummySender() m_ipcSender.reset(new DummyIpcSender); } +void IpcCommunicator::logExecutableDoesNotExist() +{ + const QString msg + = tr("Clang Code Model: Error: " + "The clangbackend executable \"%1\" does not exist.") + .arg(QDir::toNativeSeparators(backendProcessPath())); + + logError(msg); +} + +void IpcCommunicator::logStartTimeOut() +{ + const QString msg + = tr("Clang Code Model: Error: " + "The clangbackend executable \"%1\" could not be started (timeout after %2ms).") + .arg(QDir::toNativeSeparators(backendProcessPath())) + .arg(backEndStartTimeOutInMs); + + logError(msg); +} + +void IpcCommunicator::logRestartedDueToUnexpectedFinish() +{ + const QString msg + = tr("Clang Code Model: Error: " + "The clangbackend process has finished unexpectedly and was restarted."); + + logError(msg); +} + +void IpcCommunicator::logError(const QString &text) +{ + Core::MessageManager::write(text, Core::MessageManager::Flash); + qWarning("%s", qPrintable(text)); +} + void IpcCommunicator::initializeBackendWithCurrentData() { registerFallbackProjectPart(); diff --git a/src/plugins/clangcodemodel/clangbackendipcintegration.h b/src/plugins/clangcodemodel/clangbackendipcintegration.h index 891a8957f3c..60b915bfc25 100644 --- a/src/plugins/clangcodemodel/clangbackendipcintegration.h +++ b/src/plugins/clangcodemodel/clangbackendipcintegration.h @@ -171,12 +171,18 @@ private: void onDisconnectedFromBackend(); void onEditorAboutToClose(Core::IEditor *editor); + void logExecutableDoesNotExist(); + void logRestartedDueToUnexpectedFinish(); + void logStartTimeOut(); + void logError(const QString &text); + void updateTranslationUnitVisiblity(const Utf8String ¤tEditorFilePath, const Utf8StringVector &visibleEditorsFilePaths); private: IpcReceiver m_ipcReceiver; ClangBackEnd::ClangCodeModelConnectionClient m_connection; + QTimer m_backendStartTimeOut; QScopedPointer m_ipcSender; int m_connectedCount = 0; }; diff --git a/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp b/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp index 2bfa7baac7d..52c441443d6 100644 --- a/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp +++ b/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp @@ -41,6 +41,7 @@ #include #include +#include #include namespace { diff --git a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp index 52c097f305a..5b6c48c9536 100644 --- a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp +++ b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp @@ -129,9 +129,10 @@ enum IndentType { IndentDiagnostic, DoNotIndentDiagnostic }; QWidget *createDiagnosticLabel(const ClangBackEnd::DiagnosticContainer &diagnostic, const QString &mainFilePath, - IndentType indentType = DoNotIndentDiagnostic) + IndentType indentType = DoNotIndentDiagnostic, + bool enableClickableFixits = true) { - const bool hasFixit = !diagnostic.fixIts().isEmpty(); + const bool hasFixit = enableClickableFixits ? !diagnostic.fixIts().isEmpty() : false; const QString diagnosticText = diagnostic.text().toString().toHtmlEscaped(); const QString text = clickableLocation(mainFilePath, diagnostic.location()) + QStringLiteral(": ") @@ -159,25 +160,35 @@ class MainDiagnosticWidget : public QWidget { Q_OBJECT public: - MainDiagnosticWidget(const ClangBackEnd::DiagnosticContainer &diagnostic) + MainDiagnosticWidget(const ClangBackEnd::DiagnosticContainer &diagnostic, + const ClangCodeModel::Internal::DisplayHints &displayHints) { setContentsMargins(0, 0, 0, 0); auto *mainLayout = createLayout(); - // Set up header row: category + responsible option - const QString category = diagnostic.category(); - const QString responsibleOption = diagnostic.enableOption(); const ClangBackEnd::SourceLocationContainer location = diagnostic.location(); - auto *headerLayout = createLayout(); - headerLayout->addWidget(new QLabel(wrapInBoldTags(category)), 1); + // Set up header row: category + responsible option + if (displayHints.showMainDiagnosticHeader) { + const QString category = diagnostic.category(); + const QString responsibleOption = diagnostic.enableOption(); - auto *responsibleOptionLabel = new QLabel(wrapInColor(responsibleOption, "gray")); - headerLayout->addWidget(responsibleOptionLabel, 0); - mainLayout->addLayout(headerLayout); + auto *headerLayout = createLayout(); + headerLayout->addWidget(new QLabel(wrapInBoldTags(category)), 1); + + auto *responsibleOptionLabel = new QLabel(wrapInColor(responsibleOption, "gray")); + headerLayout->addWidget(responsibleOptionLabel, 0); + mainLayout->addLayout(headerLayout); + } // Set up main row: diagnostic text - mainLayout->addWidget(createDiagnosticLabel(diagnostic, location.filePath())); + const Utf8String mainFilePath = displayHints.showFileNameInMainDiagnostic + ? Utf8String() + : location.filePath(); + mainLayout->addWidget(createDiagnosticLabel(diagnostic, + mainFilePath, + DoNotIndentDiagnostic, + displayHints.enableClickableFixits)); setLayout(mainLayout); } @@ -186,26 +197,35 @@ public: void addChildrenToLayout(const QString &mainFilePath, const QVector::const_iterator first, const QVector::const_iterator last, + bool enableClickableFixits, QLayout &boxLayout) { - for (auto it = first; it != last; ++it) - boxLayout.addWidget(createDiagnosticLabel(*it, mainFilePath, IndentDiagnostic)); + for (auto it = first; it != last; ++it) { + boxLayout.addWidget(createDiagnosticLabel(*it, + mainFilePath, + IndentDiagnostic, + enableClickableFixits)); + } } void setupChildDiagnostics(const QString &mainFilePath, const QVector &diagnostics, + bool enableClickableFixits, QLayout &boxLayout) { if (diagnostics.size() <= 10) { - addChildrenToLayout(mainFilePath, diagnostics.begin(), diagnostics.end(), boxLayout); + addChildrenToLayout(mainFilePath, diagnostics.begin(), diagnostics.end(), + enableClickableFixits, boxLayout); } else { - addChildrenToLayout(mainFilePath, diagnostics.begin(), diagnostics.begin() + 7, boxLayout); + addChildrenToLayout(mainFilePath, diagnostics.begin(), diagnostics.begin() + 7, + enableClickableFixits, boxLayout); auto ellipsisLabel = new QLabel(QStringLiteral("...")); ellipsisLabel->setContentsMargins(childIndentationOnTheLeftInPixel, 0, 0, 0); boxLayout.addWidget(ellipsisLabel); - addChildrenToLayout(mainFilePath, diagnostics.end() - 3, diagnostics.end(), boxLayout); + addChildrenToLayout(mainFilePath, diagnostics.end() - 3, diagnostics.end(), + enableClickableFixits, boxLayout); } } @@ -214,13 +234,18 @@ void setupChildDiagnostics(const QString &mainFilePath, namespace ClangCodeModel { namespace Internal { -void addToolTipToLayout(const ClangBackEnd::DiagnosticContainer &diagnostic, QLayout *target) +void addToolTipToLayout(const ClangBackEnd::DiagnosticContainer &diagnostic, + QLayout *target, + const DisplayHints &displayHints) { // Set up header and text row for main diagnostic - target->addWidget(new MainDiagnosticWidget(diagnostic)); + target->addWidget(new MainDiagnosticWidget(diagnostic, displayHints)); // Set up child rows for notes - setupChildDiagnostics(diagnostic.location().filePath(), diagnostic.children(), *target); + setupChildDiagnostics(diagnostic.location().filePath(), + diagnostic.children(), + displayHints.enableClickableFixits, + *target); } } // namespace Internal diff --git a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h index 600ccf22d83..839952fc774 100644 --- a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h +++ b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h @@ -34,7 +34,15 @@ QT_END_NAMESPACE namespace ClangCodeModel { namespace Internal { -void addToolTipToLayout(const ClangBackEnd::DiagnosticContainer &diagnostic, QLayout *target); +struct DisplayHints { + bool showMainDiagnosticHeader = true; + bool showFileNameInMainDiagnostic = false; + bool enableClickableFixits = true; +}; + +void addToolTipToLayout(const ClangBackEnd::DiagnosticContainer &diagnostic, + QLayout *target, + const DisplayHints &displayHints = DisplayHints()); } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp index 8a4919e3786..09a7b76b6f1 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp @@ -57,6 +57,8 @@ #include #include +#include +#include namespace ClangCodeModel { namespace Internal { @@ -72,6 +74,11 @@ ClangEditorDocumentProcessor::ClangEditorDocumentProcessor( , m_semanticHighlighter(document) , m_builtinProcessor(document, /*enableSemanticHighlighter=*/ false) { + m_updateTranslationUnitTimer.setSingleShot(true); + m_updateTranslationUnitTimer.setInterval(350); + connect(&m_updateTranslationUnitTimer, &QTimer::timeout, + this, &ClangEditorDocumentProcessor::updateTranslationUnitIfProjectPartExists); + // Forwarding the semantic info from the builtin processor enables us to provide all // editor (widget) related features that are not yet implemented by the clang plugin. connect(&m_builtinProcessor, &CppTools::BuiltinEditorDocumentProcessor::cppDocumentUpdated, @@ -82,6 +89,8 @@ ClangEditorDocumentProcessor::ClangEditorDocumentProcessor( ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor() { + m_updateTranslationUnitTimer.stop(); + m_parserWatcher.cancel(); m_parserWatcher.waitForFinished(); @@ -93,7 +102,7 @@ ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor() void ClangEditorDocumentProcessor::run() { - updateTranslationUnitIfProjectPartExists(); + m_updateTranslationUnitTimer.start(); // Run clang parser disconnect(&m_parserWatcher, &QFutureWatcher::finished, @@ -160,14 +169,21 @@ void ClangEditorDocumentProcessor::clearProjectPart() m_projectPart.clear(); } -void ClangEditorDocumentProcessor::updateCodeWarnings(const QVector &diagnostics, - uint documentRevision) +void ClangEditorDocumentProcessor::updateCodeWarnings( + const QVector &diagnostics, + const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic, + uint documentRevision) { if (documentRevision == revision()) { m_diagnosticManager.processNewDiagnostics(diagnostics); const auto codeWarnings = m_diagnosticManager.takeExtraSelections(); const auto fixitAvailableMarkers = m_diagnosticManager.takeFixItAvailableMarkers(); - emit codeWarningsUpdated(revision(), codeWarnings, fixitAvailableMarkers); + const auto creator = creatorForHeaderErrorDiagnosticWidget(firstHeaderErrorDiagnostic); + + emit codeWarningsUpdated(revision(), + codeWarnings, + creator, + fixitAvailableMarkers); } } namespace { @@ -251,6 +267,11 @@ void ClangEditorDocumentProcessor::addDiagnosticToolTipToLayout(uint line, addToolTipToLayout(diagnostic, target); } +void ClangEditorDocumentProcessor::editorDocumentTimerRestarted() +{ + m_updateTranslationUnitTimer.stop(); // Wait for the next call to run(). +} + ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const { return fileContainerWithArguments(m_projectPart.data()); @@ -321,6 +342,38 @@ void ClangEditorDocumentProcessor::requestDocumentAnnotations(const QString &pro m_ipcCommunicator.requestDocumentAnnotations(fileContainer); } +static Internal::DisplayHints displayHintsForInfoBar() +{ + Internal::DisplayHints displayHints; + displayHints.showMainDiagnosticHeader = false; + displayHints.showFileNameInMainDiagnostic = true; + displayHints.enableClickableFixits = false; // Tool chain headers might be changed, so disable. + + return displayHints; +} + +CppTools::BaseEditorDocumentProcessor::HeaderErrorDiagnosticWidgetCreator +ClangEditorDocumentProcessor::creatorForHeaderErrorDiagnosticWidget( + const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic) +{ + if (firstHeaderErrorDiagnostic.text().isEmpty()) + return CppTools::BaseEditorDocumentProcessor::HeaderErrorDiagnosticWidgetCreator(); + + return [firstHeaderErrorDiagnostic]() { + auto vbox = new QVBoxLayout; + vbox->setMargin(0); + vbox->setContentsMargins(10, 0, 0, 2); + vbox->setSpacing(2); + + addToolTipToLayout(firstHeaderErrorDiagnostic, vbox, displayHintsForInfoBar()); + + auto widget = new QWidget; + widget->setLayout(vbox); + + return widget; + }; +} + static CppTools::ProjectPart projectPartForLanguageOption(CppTools::ProjectPart *projectPart) { if (projectPart) diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h index e9ec8ebd185..10355e349d7 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h @@ -32,6 +32,7 @@ #include #include +#include namespace ClangBackEnd { class DiagnosticContainer; @@ -67,6 +68,7 @@ public: void clearProjectPart(); void updateCodeWarnings(const QVector &diagnostics, + const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic, uint documentRevision); void updateHighlighting(const QVector &highlightingMarks, const QVector &skippedPreprocessorRanges, @@ -78,6 +80,8 @@ public: bool hasDiagnosticsAt(uint line, uint column) const override; void addDiagnosticToolTipToLayout(uint line, uint column, QLayout *target) const override; + void editorDocumentTimerRestarted() override; + ClangBackEnd::FileContainer fileContainerWithArguments() const; void clearDiagnosticsWithFixIts(); @@ -93,6 +97,8 @@ private: void registerTranslationUnitForEditor(CppTools::ProjectPart *projectPart); void updateTranslationUnitIfProjectPartExists(); void requestDocumentAnnotations(const QString &projectpartId); + HeaderErrorDiagnosticWidgetCreator creatorForHeaderErrorDiagnosticWidget( + const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic); ClangBackEnd::FileContainer fileContainerWithArguments(CppTools::ProjectPart *projectPart) const; ClangBackEnd::FileContainer fileContainerWithDocumentContent(const QString &projectpartId) const; @@ -102,6 +108,7 @@ private: QSharedPointer m_parser; CppTools::ProjectPart::Ptr m_projectPart; QFutureWatcher m_parserWatcher; + QTimer m_updateTranslationUnitTimer; unsigned m_parserRevision; CppTools::SemanticHighlighter m_semanticHighlighter; diff --git a/src/plugins/clangcodemodel/clanghighlightingmarksreporter.cpp b/src/plugins/clangcodemodel/clanghighlightingmarksreporter.cpp index 2e85c49c3cf..07d57419188 100644 --- a/src/plugins/clangcodemodel/clanghighlightingmarksreporter.cpp +++ b/src/plugins/clangcodemodel/clanghighlightingmarksreporter.cpp @@ -60,6 +60,8 @@ TextEditor::TextStyle toTextStyle(ClangBackEnd::HighlightingType type) return TextEditor::C_PREPROCESSOR; case HighlightingType::Declaration: return TextEditor::C_DECLARATION; + case HighlightingType::OutputArgument: + return TextEditor::C_OUTPUT_ARGUMENT; default: return TextEditor::C_TEXT; // never called } diff --git a/src/plugins/clangcodemodel/clangtextmark.cpp b/src/plugins/clangcodemodel/clangtextmark.cpp index 137787daa64..8057d66cb61 100644 --- a/src/plugins/clangcodemodel/clangtextmark.cpp +++ b/src/plugins/clangcodemodel/clangtextmark.cpp @@ -84,7 +84,7 @@ void ClangTextMark::setIcon(ClangBackEnd::DiagnosticSeverity severity) bool ClangTextMark::addToolTipContent(QLayout *target) { - Internal::addToolTipToLayout(m_diagnostic, target); + Internal::addToolTipToLayout(m_diagnostic, target, Internal::DisplayHints()); return true; } diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.cpp b/src/plugins/cmakeprojectmanager/builddirmanager.cpp index 2f874da3bde..5dfcf666fe7 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.cpp +++ b/src/plugins/cmakeprojectmanager/builddirmanager.cpp @@ -36,12 +36,16 @@ #include #include #include +#include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -247,6 +251,44 @@ void BuildDirManager::generateProjectTree(CMakeProjectNode *root) m_files.clear(); // Some of the FileNodes in files() were deleted! } +QSet BuildDirManager::updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) +{ + QSet languages; + ToolChain *tc = ToolChainKitInformation::toolChain(kit(), ToolChain::Language::Cxx); + const Utils::FileName sysroot = SysRootKitInformation::sysRoot(kit()); + + QHash targetDataCache; + foreach (const CMakeBuildTarget &cbt, buildTargets()) { + if (cbt.targetType == UtilityType) + continue; + + // CMake shuffles the include paths that it reports via the CodeBlocks generator + // So remove the toolchain include paths, so that at least those end up in the correct + // place. + QStringList cxxflags = getCXXFlagsFor(cbt, targetDataCache); + QSet tcIncludes; + foreach (const HeaderPath &hp, tc->systemHeaderPaths(cxxflags, sysroot)) + tcIncludes.insert(hp.path()); + QStringList includePaths; + foreach (const QString &i, cbt.includeFiles) { + if (!tcIncludes.contains(i)) + includePaths.append(i); + } + includePaths += buildDirectory().toString(); + ppBuilder.setIncludePaths(includePaths); + ppBuilder.setCFlags(cxxflags); + ppBuilder.setCxxFlags(cxxflags); + ppBuilder.setDefines(cbt.defines); + ppBuilder.setDisplayName(cbt.title); + + const QSet partLanguages + = QSet::fromList(ppBuilder.createProjectPartsForFiles(cbt.files)); + + languages.unite(partLanguages); + } + return languages; +} + void BuildDirManager::parse() { checkConfiguration(); @@ -545,6 +587,102 @@ void BuildDirManager::processCMakeError() }); } +QStringList BuildDirManager::getCXXFlagsFor(const CMakeBuildTarget &buildTarget, + QHash &cache) +{ + // check cache: + auto it = cache.constFind(buildTarget.title); + if (it != cache.constEnd()) + return *it; + + if (extractCXXFlagsFromMake(buildTarget, cache)) + return cache.value(buildTarget.title); + + if (extractCXXFlagsFromNinja(buildTarget, cache)) + return cache.value(buildTarget.title); + + cache.insert(buildTarget.title, QStringList()); + return QStringList(); +} + +bool BuildDirManager::extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget, + QHash &cache) +{ + QString makeCommand = QDir::fromNativeSeparators(buildTarget.makeCommand); + int startIndex = makeCommand.indexOf('\"'); + int endIndex = makeCommand.indexOf('\"', startIndex + 1); + if (startIndex != -1 && endIndex != -1) { + startIndex += 1; + QString makefile = makeCommand.mid(startIndex, endIndex - startIndex); + int slashIndex = makefile.lastIndexOf('/'); + makefile.truncate(slashIndex); + makefile.append("/CMakeFiles/" + buildTarget.title + ".dir/flags.make"); + QFile file(makefile); + if (file.exists()) { + file.open(QIODevice::ReadOnly | QIODevice::Text); + QTextStream stream(&file); + while (!stream.atEnd()) { + QString line = stream.readLine().trimmed(); + if (line.startsWith("CXX_FLAGS =")) { + // Skip past = + cache.insert(buildTarget.title, + line.mid(11).trimmed().split(' ', QString::SkipEmptyParts)); + return true; + } + } + } + } + return false; +} + +bool BuildDirManager::extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget, + QHash &cache) +{ + Q_UNUSED(buildTarget) + if (!cache.isEmpty()) // We fill the cache in one go! + return false; + + // Attempt to find build.ninja file and obtain FLAGS (CXX_FLAGS) from there if no suitable flags.make were + // found + // Get "all" target's working directory + QByteArray ninjaFile; + QString buildNinjaFile = QDir::fromNativeSeparators(buildTargets().at(0).workingDirectory); + buildNinjaFile += "/build.ninja"; + QFile buildNinja(buildNinjaFile); + if (buildNinja.exists()) { + buildNinja.open(QIODevice::ReadOnly | QIODevice::Text); + ninjaFile = buildNinja.readAll(); + buildNinja.close(); + } + + if (ninjaFile.isEmpty()) + return false; + + QTextStream stream(ninjaFile); + bool cxxFound = false; + const QString targetSignature = "# Object build statements for "; + QString currentTarget; + + while (!stream.atEnd()) { + // 1. Look for a block that refers to the current target + // 2. Look for a build rule which invokes CXX_COMPILER + // 3. Return the FLAGS definition + QString line = stream.readLine().trimmed(); + if (line.startsWith('#')) { + if (line.startsWith(targetSignature)) { + int pos = line.lastIndexOf(' '); + currentTarget = line.mid(pos + 1); + } + } else if (!currentTarget.isEmpty() && line.startsWith("build")) { + cxxFound = line.indexOf("CXX_COMPILER") != -1; + } else if (cxxFound && line.startsWith("FLAGS =")) { + // Skip past = + cache.insert(currentTarget, line.mid(7).trimmed().split(' ', QString::SkipEmptyParts)); + } + } + return !cache.isEmpty(); +} + void BuildDirManager::checkConfiguration() { if (m_tempDir) // always throw away changes in the tmpdir! @@ -656,6 +794,7 @@ CMakeConfig BuildDirManager::parseConfiguration(const Utils::FileName &cacheFile } QSet advancedSet; + QMap valuesMap; QByteArray documentation; while (!cache.atEnd()) { const QByteArray line = trimCMakeCacheLine(cache.readLine()); @@ -679,6 +818,8 @@ CMakeConfig BuildDirManager::parseConfiguration(const Utils::FileName &cacheFile if (key.endsWith("-ADVANCED") && value == "1") { advancedSet.insert(key.left(key.count() - 9 /* "-ADVANCED" */)); + } else if (key.endsWith("-STRINGS") && fromByteArray(type) == CMakeConfigItem::INTERNAL) { + valuesMap[key.left(key.count() - 8) /* "-STRINGS" */] = value; } else { CMakeConfigItem::Type t = fromByteArray(type); result << CMakeConfigItem(key, t, documentation, value); @@ -689,6 +830,13 @@ CMakeConfig BuildDirManager::parseConfiguration(const Utils::FileName &cacheFile for (int i = 0; i < result.count(); ++i) { CMakeConfigItem &item = result[i]; item.isAdvanced = advancedSet.contains(item.key); + + if (valuesMap.contains(item.key)) { + item.values = CMakeConfigItem::cmakeSplitValue(QString::fromUtf8(valuesMap[item.key])); + } else if (item.key == "CMAKE_BUILD_TYPE") { + // WA for known options + item.values << "" << "Debug" << "Release" << "MinSizeRel" << "RelWithDebInfo"; + } } Utils::sort(result, CMakeConfigItem::sortOperator()); diff --git a/src/plugins/cmakeprojectmanager/builddirmanager.h b/src/plugins/cmakeprojectmanager/builddirmanager.h index 9d1ee2b10ef..ec26d0c3bb0 100644 --- a/src/plugins/cmakeprojectmanager/builddirmanager.h +++ b/src/plugins/cmakeprojectmanager/builddirmanager.h @@ -45,6 +45,7 @@ QT_FORWARD_DECLARE_CLASS(QTemporaryDir); QT_FORWARD_DECLARE_CLASS(QFileSystemWatcher); namespace Core { class IDocument; } +namespace CppTools { class ProjectPartBuilder; } namespace ProjectExplorer { class FileNode; @@ -80,6 +81,7 @@ public: bool persistCMakeState(); void generateProjectTree(CMakeProjectNode *root); + QSet updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder); QList buildTargets() const; CMakeConfig parsedConfiguration() const; @@ -117,6 +119,10 @@ private: void processCMakeOutput(); void processCMakeError(); + QStringList getCXXFlagsFor(const CMakeBuildTarget &buildTarget, QHash &cache); + bool extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget, QHash &cache); + bool extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget, QHash &cache); + bool m_hasData = false; CMakeBuildConfiguration *m_buildConfiguration = nullptr; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 4291eec4de7..c5d6bdafc4b 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -211,6 +211,11 @@ void CMakeBuildConfiguration::generateProjectTree(CMakeProjectNode *root) const return m_buildDirManager->generateProjectTree(root); } +QSet CMakeBuildConfiguration::updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder) +{ + return m_buildDirManager->updateCodeModel(ppBuilder); +} + FileName CMakeBuildConfiguration::shadowBuildDirectory(const FileName &projectFilePath, const Kit *k, const QString &bcName, @@ -240,6 +245,7 @@ QList CMakeBuildConfiguration::completeCMakeConfiguration j.key = QString::fromUtf8(i.key); j.value = QString::fromUtf8(i.value); j.description = QString::fromUtf8(i.documentation); + j.values = i.values; j.isAdvanced = i.isAdvanced || i.type == CMakeConfigItem::INTERNAL; switch (i.type) { @@ -275,6 +281,7 @@ void CMakeBuildConfiguration::setCurrentCMakeConfiguration(const QList #include +namespace CppTools { class ProjectPartBuilder; } namespace ProjectExplorer { class ToolChain; } namespace CMakeProjectManager { @@ -82,6 +83,7 @@ public: QList buildTargets() const; void generateProjectTree(CMakeProjectNode *root) const; + QSet updateCodeModel(CppTools::ProjectPartBuilder &ppBuilder); static Utils::FileName shadowBuildDirectory(const Utils::FileName &projectFilePath, const ProjectExplorer::Kit *k, diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp index 013f2c2bb8e..4f0cd6ecbfc 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsettingswidget.cpp @@ -26,6 +26,7 @@ #include "cmakebuildsettingswidget.h" #include "configmodel.h" +#include "configmodelitemdelegate.h" #include "cmakeproject.h" #include "cmakebuildconfiguration.h" @@ -140,6 +141,7 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) m_configView->setSelectionBehavior(QAbstractItemView::SelectItems); m_configView->setFrameShape(QFrame::NoFrame); m_configView->hideColumn(2); // Hide isAdvanced column + m_configView->setItemDelegate(new ConfigModelItemDelegate(m_configView)); QFrame *findWrapper = Core::ItemViewFind::createSearchableWrapper(m_configView, Core::ItemViewFind::LightColored); findWrapper->setFrameStyle(QFrame::StyledPanel); diff --git a/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp b/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp index 6115b318679..ff1011ef819 100644 --- a/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeconfigitem.cpp @@ -42,7 +42,7 @@ CMakeConfigItem::CMakeConfigItem() = default; CMakeConfigItem::CMakeConfigItem(const CMakeConfigItem &other) : key(other.key), type(other.type), isAdvanced(other.isAdvanced), - value(other.value), documentation(other.documentation) + value(other.value), documentation(other.documentation), values(other.values) { } CMakeConfigItem::CMakeConfigItem(const QByteArray &k, Type t, diff --git a/src/plugins/cmakeprojectmanager/cmakeconfigitem.h b/src/plugins/cmakeprojectmanager/cmakeconfigitem.h index b800a547a21..1484cffda3e 100644 --- a/src/plugins/cmakeprojectmanager/cmakeconfigitem.h +++ b/src/plugins/cmakeprojectmanager/cmakeconfigitem.h @@ -61,6 +61,7 @@ public: bool isAdvanced = false; QByteArray value; // converted to string as needed QByteArray documentation; + QStringList values; }; using CMakeConfig = QList; diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 0737666457a..50dd058368f 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -94,102 +94,6 @@ CMakeProject::~CMakeProject() qDeleteAll(m_extraCompilers); } -QStringList CMakeProject::getCXXFlagsFor(const CMakeBuildTarget &buildTarget, - QHash &cache) -{ - // check cache: - auto it = cache.constFind(buildTarget.title); - if (it != cache.constEnd()) - return *it; - - if (extractCXXFlagsFromMake(buildTarget, cache)) - return cache.value(buildTarget.title); - - if (extractCXXFlagsFromNinja(buildTarget, cache)) - return cache.value(buildTarget.title); - - cache.insert(buildTarget.title, QStringList()); - return QStringList(); -} - -bool CMakeProject::extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget, - QHash &cache) -{ - QString makeCommand = QDir::fromNativeSeparators(buildTarget.makeCommand); - int startIndex = makeCommand.indexOf('\"'); - int endIndex = makeCommand.indexOf('\"', startIndex + 1); - if (startIndex != -1 && endIndex != -1) { - startIndex += 1; - QString makefile = makeCommand.mid(startIndex, endIndex - startIndex); - int slashIndex = makefile.lastIndexOf('/'); - makefile.truncate(slashIndex); - makefile.append("/CMakeFiles/" + buildTarget.title + ".dir/flags.make"); - QFile file(makefile); - if (file.exists()) { - file.open(QIODevice::ReadOnly | QIODevice::Text); - QTextStream stream(&file); - while (!stream.atEnd()) { - QString line = stream.readLine().trimmed(); - if (line.startsWith("CXX_FLAGS =")) { - // Skip past = - cache.insert(buildTarget.title, - line.mid(11).trimmed().split(' ', QString::SkipEmptyParts)); - return true; - } - } - } - } - return false; -} - -bool CMakeProject::extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget, - QHash &cache) -{ - Q_UNUSED(buildTarget) - if (!cache.isEmpty()) // We fill the cache in one go! - return false; - - // Attempt to find build.ninja file and obtain FLAGS (CXX_FLAGS) from there if no suitable flags.make were - // found - // Get "all" target's working directory - QByteArray ninjaFile; - QString buildNinjaFile = QDir::fromNativeSeparators(buildTargets().at(0).workingDirectory); - buildNinjaFile += "/build.ninja"; - QFile buildNinja(buildNinjaFile); - if (buildNinja.exists()) { - buildNinja.open(QIODevice::ReadOnly | QIODevice::Text); - ninjaFile = buildNinja.readAll(); - buildNinja.close(); - } - - if (ninjaFile.isEmpty()) - return false; - - QTextStream stream(ninjaFile); - bool cxxFound = false; - const QString targetSignature = "# Object build statements for "; - QString currentTarget; - - while (!stream.atEnd()) { - // 1. Look for a block that refers to the current target - // 2. Look for a build rule which invokes CXX_COMPILER - // 3. Return the FLAGS definition - QString line = stream.readLine().trimmed(); - if (line.startsWith('#')) { - if (line.startsWith(targetSignature)) { - int pos = line.lastIndexOf(' '); - currentTarget = line.mid(pos + 1); - } - } else if (!currentTarget.isEmpty() && line.startsWith("build")) { - cxxFound = line.indexOf("CXX_COMPILER") != -1; - } else if (cxxFound && line.startsWith("FLAGS =")) { - // Skip past = - cache.insert(currentTarget, line.mid(7).trimmed().split(' ', QString::SkipEmptyParts)); - } - } - return !cache.isEmpty(); -} - void CMakeProject::updateProjectData() { auto cmakeBc = qobject_cast(sender()); @@ -225,39 +129,11 @@ void CMakeProject::updateProjectData() activeQtVersion = CppTools::ProjectPart::Qt5; } - const FileName sysroot = SysRootKitInformation::sysRoot(k); - ppBuilder.setQtVersion(activeQtVersion); - QHash targetDataCache; - foreach (const CMakeBuildTarget &cbt, buildTargets()) { - if (cbt.targetType == UtilityType) - continue; - - // CMake shuffles the include paths that it reports via the CodeBlocks generator - // So remove the toolchain include paths, so that at least those end up in the correct - // place. - QStringList cxxflags = getCXXFlagsFor(cbt, targetDataCache); - QSet tcIncludes; - foreach (const HeaderPath &hp, tc->systemHeaderPaths(cxxflags, sysroot)) { - tcIncludes.insert(hp.path()); - } - QStringList includePaths; - foreach (const QString &i, cbt.includeFiles) { - if (!tcIncludes.contains(i)) - includePaths.append(i); - } - includePaths += projectDirectory().toString(); - ppBuilder.setIncludePaths(includePaths); - ppBuilder.setCFlags(cxxflags); - ppBuilder.setCxxFlags(cxxflags); - ppBuilder.setDefines(cbt.defines); - ppBuilder.setDisplayName(cbt.title); - - const QList languages = ppBuilder.createProjectPartsForFiles(cbt.files); - foreach (Core::Id language, languages) - setProjectLanguage(language, true); - } + const QSet languages = cmakeBc->updateCodeModel(ppBuilder); + for (const auto &lid : languages) + setProjectLanguage(lid, true); m_codeModelFuture.cancel(); pinfo.finish(); diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index 90435ff66bd..8ede082cd7f 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -120,9 +120,6 @@ private: QStringList filesGeneratedFrom(const QString &sourceFile) const final; void updateTargetRunConfigurations(ProjectExplorer::Target *t); void updateApplicationAndDeploymentTargets(); - QStringList getCXXFlagsFor(const CMakeBuildTarget &buildTarget, QHash &cache); - bool extractCXXFlagsFromMake(const CMakeBuildTarget &buildTarget, QHash &cache); - bool extractCXXFlagsFromNinja(const CMakeBuildTarget &buildTarget, QHash &cache); ProjectExplorer::Target *m_connectedTarget = nullptr; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index 4621c40c15d..82cd93df82a 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -28,7 +28,8 @@ HEADERS = builddirmanager.h \ cmakebuildsettingswidget.h \ cmakeindenter.h \ cmakeautocompleter.h \ - configmodel.h + configmodel.h \ + configmodelitemdelegate.h SOURCES = builddirmanager.cpp \ cmakebuildstep.cpp \ @@ -54,6 +55,7 @@ SOURCES = builddirmanager.cpp \ cmakebuildsettingswidget.cpp \ cmakeindenter.cpp \ cmakeautocompleter.cpp \ - configmodel.cpp + configmodel.cpp \ + configmodelitemdelegate.cpp RESOURCES += cmakeproject.qrc diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index b199483eaa0..99fe635b277 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -71,6 +71,8 @@ QtcPlugin { "cmakeautocompleter.h", "cmakeautocompleter.cpp", "configmodel.cpp", - "configmodel.h" + "configmodel.h", + "configmodelitemdelegate.cpp", + "configmodelitemdelegate.h" ] } diff --git a/src/plugins/cmakeprojectmanager/configmodel.cpp b/src/plugins/cmakeprojectmanager/configmodel.cpp index 292cd933bf5..4ef34dd7fab 100644 --- a/src/plugins/cmakeprojectmanager/configmodel.cpp +++ b/src/plugins/cmakeprojectmanager/configmodel.cpp @@ -88,6 +88,15 @@ QVariant ConfigModel::data(const QModelIndex &index, int role) const const InternalDataItem &item = m_configuration[index.row()]; + if (index.column() < 2) { + switch (role) { + case ItemTypeRole: + return item.type; + case ItemValuesRole: + return item.values; + } + } + switch (index.column()) { case 0: switch (role) { @@ -97,8 +106,6 @@ QVariant ConfigModel::data(const QModelIndex &index, int role) const return item.key; case Qt::ToolTipRole: return item.description; - case Qt::UserRole: - return item.type; case Qt::FontRole: { QFont font; font.setItalic(item.isCMakeChanged); @@ -126,8 +133,6 @@ QVariant ConfigModel::data(const QModelIndex &index, int role) const } case Qt::ToolTipRole: return item.description; - case Qt::UserRole: - return item.type; default: return QVariant(); } @@ -209,13 +214,15 @@ QVariant ConfigModel::headerData(int section, Qt::Orientation orientation, int r void ConfigModel::appendConfiguration(const QString &key, const QString &value, const ConfigModel::DataItem::Type type, - const QString &description) + const QString &description, + const QStringList &values) { DataItem item; item.key = key; item.type = type; item.value = value; item.description = description; + item.values = values; InternalDataItem internalItem(item); internalItem.isUserNew = true; diff --git a/src/plugins/cmakeprojectmanager/configmodel.h b/src/plugins/cmakeprojectmanager/configmodel.h index 5fe63baa0fe..05424817188 100644 --- a/src/plugins/cmakeprojectmanager/configmodel.h +++ b/src/plugins/cmakeprojectmanager/configmodel.h @@ -34,6 +34,11 @@ class ConfigModel : public QAbstractTableModel Q_OBJECT public: + enum Roles { + ItemTypeRole = Qt::UserRole, + ItemValuesRole + }; + class DataItem { public: enum Type { BOOLEAN, FILE, DIRECTORY, STRING, UNKNOWN}; @@ -43,6 +48,7 @@ public: bool isAdvanced = false; QString value; QString description; + QStringList values; }; explicit ConfigModel(QObject *parent = nullptr); @@ -58,7 +64,8 @@ public: void appendConfiguration(const QString &key, const QString &value = QString(), const DataItem::Type type = DataItem::UNKNOWN, - const QString &description = QString()); + const QString &description = QString(), + const QStringList &values = QStringList()); void setConfiguration(const QList &config); void flush(); void resetAllChanges(); diff --git a/src/plugins/cmakeprojectmanager/configmodelitemdelegate.cpp b/src/plugins/cmakeprojectmanager/configmodelitemdelegate.cpp new file mode 100644 index 00000000000..b2b19b837e2 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/configmodelitemdelegate.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alexander Drozdov. +** Contact: adrozdoff@gmail.com +** +** This file is part of CMakeProjectManager2 plugin. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +****************************************************************************/ + +#include "configmodelitemdelegate.h" +#include "configmodel.h" + +#include + +namespace CMakeProjectManager { + +ConfigModelItemDelegate::ConfigModelItemDelegate(QObject* parent) + : QStyledItemDelegate(parent) +{ } + +ConfigModelItemDelegate::~ConfigModelItemDelegate() +{ } + +QWidget* ConfigModelItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + // ComboBox ony in column 2 + if (index.column() != 1) + return QStyledItemDelegate::createEditor(parent, option, index); + + auto model = index.model(); + auto values = model->data(index, ConfigModel::ItemValuesRole).toStringList(); + if (values.isEmpty()) + return QStyledItemDelegate::createEditor(parent, option, index); + + // Create the combobox and populate it + auto cb = new QComboBox(parent); + cb->addItems(values); + cb->setEditable(true); + + return cb; +} + +void ConfigModelItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + if (QComboBox* cb = qobject_cast(editor)) { + // get the index of the text in the combobox that matches the current value of the itenm + QString currentText = index.data(Qt::EditRole).toString(); + int cbIndex = cb->findText(currentText); + // if it is valid, adjust the combobox + if (cbIndex >= 0) + cb->setCurrentIndex(cbIndex); + else + cb->setEditText(currentText); + } else { + QStyledItemDelegate::setEditorData(editor, index); + } +} + +void ConfigModelItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + if (QComboBox* cb = qobject_cast(editor)) + // save the current text of the combo box as the current value of the item + model->setData(index, cb->currentText(), Qt::EditRole); + else + QStyledItemDelegate::setModelData(editor, model, index); +} + +} // namespace CMakeProjectManager + diff --git a/src/plugins/cmakeprojectmanager/configmodelitemdelegate.h b/src/plugins/cmakeprojectmanager/configmodelitemdelegate.h new file mode 100644 index 00000000000..8c20f7b8313 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/configmodelitemdelegate.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alexander Drozdov. +** Contact: adrozdoff@gmail.com +** +** This file is part of CMakeProjectManager2 plugin. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace CMakeProjectManager { + +class ConfigModelItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + ConfigModelItemDelegate(QObject* parent=0); + ~ConfigModelItemDelegate(); + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; +}; + +} // namespace CMakeProjectManager diff --git a/src/plugins/coreplugin/infobar.cpp b/src/plugins/coreplugin/infobar.cpp index 405681c5809..9fcc7ce18ae 100644 --- a/src/plugins/coreplugin/infobar.cpp +++ b/src/plugins/coreplugin/infobar.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -67,6 +68,20 @@ void InfoBarEntry::setCancelButtonInfo(const QString &_cancelButtonText, CallBac m_cancelButtonCallBack = callBack; } +void InfoBarEntry::setSuppressionButtonInfo(InfoBarEntry::CallBack callback) +{ + m_suppressionButtonCallBack = callback; +} + +void InfoBarEntry::setShowDefaultCancelButton(bool yesno) +{ + m_showDefaultCancelButton = yesno; +} + +void InfoBarEntry::setDetailsWidgetCreator(const InfoBarEntry::DetailsWidgetCreator &creator) +{ + m_detailsWidgetCreator = creator; +} void InfoBar::addInfo(const InfoBarEntry &info) { @@ -124,10 +139,13 @@ void InfoBar::clear() void InfoBar::globallySuppressInfo(Id id) { globallySuppressed.insert(id); - QStringList list; - foreach (Id i, globallySuppressed) - list << QLatin1String(i.name()); - ICore::settings()->setValue(QLatin1String(C_SUPPRESSED_WARNINGS), list); + writeGloballySuppressedToSettings(); +} + +void InfoBar::globallyUnsuppressInfo(Id id) +{ + globallySuppressed.remove(id); + writeGloballySuppressedToSettings(); } void InfoBar::initializeGloballySuppressed() @@ -148,12 +166,17 @@ bool InfoBar::anyGloballySuppressed() return !globallySuppressed.isEmpty(); } +void InfoBar::writeGloballySuppressedToSettings() +{ + QStringList list; + foreach (Id i, globallySuppressed) + list << QLatin1String(i.name()); + ICore::settings()->setValue(QLatin1String(C_SUPPRESSED_WARNINGS), list); +} + InfoBarDisplay::InfoBarDisplay(QObject *parent) : QObject(parent) - , m_infoBar(0) - , m_boxLayout(0) - , m_boxIndex(0) { } @@ -209,13 +232,43 @@ void InfoBarDisplay::update() infoWidget->setLineWidth(1); infoWidget->setAutoFillBackground(true); - QHBoxLayout *hbox = new QHBoxLayout(infoWidget); + QHBoxLayout *hbox = new QHBoxLayout; hbox->setMargin(2); + auto *vbox = new QVBoxLayout(infoWidget); + vbox->setMargin(0); + vbox->addLayout(hbox); + QLabel *infoWidgetLabel = new QLabel(info.infoText); infoWidgetLabel->setWordWrap(true); hbox->addWidget(infoWidgetLabel); + if (info.m_detailsWidgetCreator) { + if (m_isShowingDetailsWidget) { + QWidget *detailsWidget = info.m_detailsWidgetCreator(); + vbox->addWidget(detailsWidget); + } + + auto *showDetailsButton = new QToolButton; + showDetailsButton->setCheckable(true); + showDetailsButton->setChecked(m_isShowingDetailsWidget); + showDetailsButton->setText(tr("&Show Details")); + connect(showDetailsButton, &QToolButton::clicked, [this, vbox, info] (bool) { + QWidget *detailsWidget = vbox->count() == 2 ? vbox->itemAt(1)->widget() : nullptr; + if (!detailsWidget) { + detailsWidget = info.m_detailsWidgetCreator(); + vbox->addWidget(detailsWidget); + } + + m_isShowingDetailsWidget = !m_isShowingDetailsWidget; + detailsWidget->setVisible(m_isShowingDetailsWidget); + }); + + hbox->addWidget(showDetailsButton); + } else { + m_isShowingDetailsWidget = false; + } + if (!info.buttonText.isEmpty()) { QToolButton *infoWidgetButton = new QToolButton; infoWidgetButton->setText(info.buttonText); @@ -229,7 +282,9 @@ void InfoBarDisplay::update() if (info.globalSuppression == InfoBarEntry::GlobalSuppressionEnabled) { infoWidgetSuppressButton = new QToolButton; infoWidgetSuppressButton->setText(tr("Do Not Show Again")); - connect(infoWidgetSuppressButton, &QAbstractButton::clicked, this, [this, id] { + connect(infoWidgetSuppressButton, &QAbstractButton::clicked, this, [this, info, id] { + if (info.m_suppressionButtonCallBack) + info.m_suppressionButtonCallBack(); m_infoBar->removeInfo(id); InfoBar::globallySuppressInfo(id); }); @@ -245,12 +300,17 @@ void InfoBarDisplay::update() }); if (info.cancelButtonText.isEmpty()) { - infoWidgetCloseButton->setAutoRaise(true); - infoWidgetCloseButton->setIcon(Utils::Icons::CLOSE_FOREGROUND.icon()); - infoWidgetCloseButton->setToolTip(tr("Close")); + if (info.m_showDefaultCancelButton) { + infoWidgetCloseButton->setAutoRaise(true); + infoWidgetCloseButton->setIcon(Utils::Icons::CLOSE_FOREGROUND.icon()); + infoWidgetCloseButton->setToolTip(tr("Close")); + } + if (infoWidgetSuppressButton) hbox->addWidget(infoWidgetSuppressButton); - hbox->addWidget(infoWidgetCloseButton); + + if (info.m_showDefaultCancelButton) + hbox->addWidget(infoWidgetCloseButton); } else { infoWidgetCloseButton->setText(info.cancelButtonText); hbox->addWidget(infoWidgetCloseButton); diff --git a/src/plugins/coreplugin/infobar.h b/src/plugins/coreplugin/infobar.h index 8c2cc7f211d..e832e570055 100644 --- a/src/plugins/coreplugin/infobar.h +++ b/src/plugins/coreplugin/infobar.h @@ -54,10 +54,15 @@ public: InfoBarEntry(Id _id, const QString &_infoText, GlobalSuppressionMode _globalSuppression = GlobalSuppressionDisabled); InfoBarEntry(const InfoBarEntry &other) { *this = other; } - typedef std::function CallBack; + using CallBack = std::function; void setCustomButtonInfo(const QString &_buttonText, CallBack callBack); void setCancelButtonInfo(CallBack callBack); void setCancelButtonInfo(const QString &_cancelButtonText, CallBack callBack); + void setSuppressionButtonInfo(CallBack callback); + void setShowDefaultCancelButton(bool yesno); + + using DetailsWidgetCreator = std::function; + void setDetailsWidgetCreator(const DetailsWidgetCreator &creator); private: Id id; @@ -66,7 +71,10 @@ private: CallBack m_buttonCallBack; QString cancelButtonText; CallBack m_cancelButtonCallBack; + CallBack m_suppressionButtonCallBack; GlobalSuppressionMode globalSuppression; + DetailsWidgetCreator m_detailsWidgetCreator; + bool m_showDefaultCancelButton = true; friend class InfoBar; friend class InfoBarDisplay; }; @@ -84,6 +92,7 @@ public: void enableInfo(Id id); void clear(); static void globallySuppressInfo(Id id); + static void globallyUnsuppressInfo(Id id); static void initializeGloballySuppressed(); static void clearGloballySuppressed(); static bool anyGloballySuppressed(); @@ -91,6 +100,9 @@ public: signals: void changed(); +private: + static void writeGloballySuppressedToSettings(); + private: QList m_infoBarEntries; QSet m_suppressed; @@ -113,9 +125,10 @@ private: void widgetDestroyed(); QList m_infoWidgets; - InfoBar *m_infoBar; - QBoxLayout *m_boxLayout; - int m_boxIndex; + InfoBar *m_infoBar = nullptr; + QBoxLayout *m_boxLayout = nullptr; + int m_boxIndex = 0; + bool m_isShowingDetailsWidget = false; }; } // namespace Core diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index c26a8b25f1a..0492635d1a6 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -77,6 +78,7 @@ #include #include #include +#include #include #include @@ -126,9 +128,13 @@ public: QSharedPointer m_declDefLink; QScopedPointer m_followSymbolUnderCursor; - QToolButton *m_preprocessorButton; + + QToolButton *m_preprocessorButton = nullptr; + QToolButton *m_headerErrorsIndicatorButton = nullptr; CppSelectionChanger m_cppSelectionChanger; + + CppEditorWidget::HeaderErrorDiagnosticWidgetCreator m_headerErrorDiagnosticWidgetCreator; }; CppEditorWidgetPrivate::CppEditorWidgetPrivate(CppEditorWidget *q) @@ -139,7 +145,6 @@ CppEditorWidgetPrivate::CppEditorWidgetPrivate(CppEditorWidget *q) , m_useSelectionsUpdater(q) , m_declDefLinkFinder(new FunctionDeclDefLinkFinder(q)) , m_followSymbolUnderCursor(new FollowSymbolUnderCursor(q)) - , m_preprocessorButton(0) , m_cppSelectionChanger() { } @@ -224,7 +229,15 @@ void CppEditorWidget::finalizeInitialization() connect(cmd, &Command::keySequenceChanged, this, &CppEditorWidget::updatePreprocessorButtonTooltip); updatePreprocessorButtonTooltip(); connect(d->m_preprocessorButton, &QAbstractButton::clicked, this, &CppEditorWidget::showPreProcessorWidget); + + d->m_headerErrorsIndicatorButton = new QToolButton(this); + d->m_headerErrorsIndicatorButton->setIcon(Utils::Icons::WARNING_TOOLBAR.pixmap()); + connect(d->m_headerErrorsIndicatorButton, &QAbstractButton::clicked, + this, &CppEditorWidget::showHeaderErrorInfoBar); + d->m_headerErrorsIndicatorButton->setEnabled(false); + insertExtraToolBarWidget(TextEditorWidget::Left, d->m_preprocessorButton); + insertExtraToolBarWidget(TextEditorWidget::Left, d->m_headerErrorsIndicatorButton); insertExtraToolBarWidget(TextEditorWidget::Left, d->m_cppEditorOutline->widget()); } @@ -287,6 +300,7 @@ void CppEditorWidget::onCppDocumentUpdated() void CppEditorWidget::onCodeWarningsUpdated(unsigned revision, const QList selections, + const HeaderErrorDiagnosticWidgetCreator &creator, const TextEditor::RefactorMarkers &refactorMarkers) { if (revision != documentRevision()) @@ -294,6 +308,9 @@ void CppEditorWidget::onCodeWarningsUpdated(unsigned revision, setExtraSelections(TextEditorWidget::CodeWarningsSelection, selections); setRefactorMarkers(refactorMarkersWithoutClangMarkers() + refactorMarkers); + + d->m_headerErrorDiagnosticWidgetCreator = creator; + updateHeaderErrorWidgets(); } void CppEditorWidget::onIfdefedOutBlocksUpdated(unsigned revision, @@ -304,6 +321,24 @@ void CppEditorWidget::onIfdefedOutBlocksUpdated(unsigned revision, setIfdefedOutBlocks(ifdefedOutBlocks); } +void CppEditorWidget::updateHeaderErrorWidgets() +{ + const Id id(Constants::ERRORS_IN_HEADER_FILES); + InfoBar *infoBar = textDocument()->infoBar(); + + infoBar->removeInfo(id); + + if (d->m_headerErrorDiagnosticWidgetCreator) { + if (infoBar->canInfoBeAdded(id)) { + addHeaderErrorInfoBarEntryAndHideIndicator(); + } else { + d->m_headerErrorsIndicatorButton->setEnabled(true); + } + } else { + d->m_headerErrorsIndicatorButton->setEnabled(false); + } +} + void CppEditorWidget::findUsages() { if (!d->m_modelManager) @@ -399,6 +434,25 @@ void CppEditorWidget::renameSymbolUnderCursorBuiltin() renameUsages(); // Rename non-local symbol or macro } +void CppEditorWidget::addHeaderErrorInfoBarEntryAndHideIndicator() const +{ + InfoBarEntry info(Constants::ERRORS_IN_HEADER_FILES, + tr("Warning: The code model could not parse an included file, " + "which might lead to slow or incorrect code completion and " + "highlighting, for example."), + InfoBarEntry::GlobalSuppressionEnabled); + info.setDetailsWidgetCreator(d->m_headerErrorDiagnosticWidgetCreator); + info.setShowDefaultCancelButton(false); + info.setSuppressionButtonInfo([this](){ + d->m_headerErrorsIndicatorButton->setEnabled(true); + }); + + InfoBar *infoBar = textDocument()->infoBar(); + infoBar->addInfo(info); + + d->m_headerErrorsIndicatorButton->setEnabled(false); +} + namespace { QList fetchProjectParts(CppTools::CppModelManager *modelManager, @@ -970,5 +1024,14 @@ void CppEditorWidget::showPreProcessorWidget() } } +void CppEditorWidget::showHeaderErrorInfoBar() +{ + const Id id(Constants::ERRORS_IN_HEADER_FILES); + QTC_CHECK(!textDocument()->infoBar()->canInfoBeAdded(id)); + + InfoBar::globallyUnsuppressInfo(id); + addHeaderErrorInfoBarEntryAndHideIndicator(); +} + } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppeditor.h b/src/plugins/cppeditor/cppeditor.h index 7fb26e93c36..cce22cebcd4 100644 --- a/src/plugins/cppeditor/cppeditor.h +++ b/src/plugins/cppeditor/cppeditor.h @@ -31,6 +31,10 @@ #include +namespace Core { +class InfoBarEntry; +} + namespace CppTools { class CppEditorOutline; class RefactoringEngineInterface; @@ -87,6 +91,7 @@ public: void switchDeclarationDefinition(bool inNextSplit); void showPreProcessorWidget(); + void showHeaderErrorInfoBar(); void findUsages(); void renameSymbolUnderCursor(); @@ -108,6 +113,9 @@ protected: void slotCodeStyleSettingsChanged(const QVariant &) override; +public: + using HeaderErrorDiagnosticWidgetCreator = std::function; + private: void updateFunctionDeclDefLink(); void updateFunctionDeclDefLinkNow(); @@ -118,10 +126,12 @@ private: void onCodeWarningsUpdated(unsigned revision, const QList selections, + const HeaderErrorDiagnosticWidgetCreator &creator, const TextEditor::RefactorMarkers &refactorMarkers); void onIfdefedOutBlocksUpdated(unsigned revision, const QList ifdefedOutBlocks); + void updateHeaderErrorWidgets(); void updateSemanticInfo(const CppTools::SemanticInfo &semanticInfo, bool updateUseSelectionSynchronously = false); void updatePreprocessorButtonTooltip(); @@ -140,6 +150,8 @@ private: void renameSymbolUnderCursorClang(); void renameSymbolUnderCursorBuiltin(); + void addHeaderErrorInfoBarEntryAndHideIndicator() const; + CppTools::ProjectPart *projectPart() const; private: diff --git a/src/plugins/cppeditor/cppeditorconstants.h b/src/plugins/cppeditor/cppeditorconstants.h index 1d72be7a7a4..761822d1012 100644 --- a/src/plugins/cppeditor/cppeditorconstants.h +++ b/src/plugins/cppeditor/cppeditorconstants.h @@ -36,6 +36,7 @@ const char OPEN_DECLARATION_DEFINITION_IN_NEXT_SPLIT[] = "CppEditor.OpenDeclarat const char RENAME_SYMBOL_UNDER_CURSOR[] = "CppEditor.RenameSymbolUnderCursor"; const char FIND_USAGES[] = "CppEditor.FindUsages"; const char OPEN_PREPROCESSOR_DIALOG[] = "CppEditor.OpenPreprocessorDialog"; +const char ERRORS_IN_HEADER_FILES[] = "CppEditor.ErrorsInHeaderFiles"; const char M_REFACTORING_MENU_INSERTION_POINT[] = "CppEditor.RefactorGroup"; const char UPDATE_CODEMODEL[] = "CppEditor.UpdateCodeModel"; const char INSPECT_CPP_CODEMODEL[] = "CppEditor.InspectCppCodeModel"; diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index 00ff24aca9b..91918f9d3bd 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -230,12 +230,14 @@ void CppEditorDocument::scheduleProcessDocument() { m_processorRevision = document()->revision(); m_processorTimer.start(); + processor()->editorDocumentTimerRestarted(); } void CppEditorDocument::processDocument() { if (processor()->isParserRunning() || m_processorRevision != contentsRevision()) { m_processorTimer.start(); + processor()->editorDocumentTimerRestarted(); return; } diff --git a/src/plugins/cppeditor/cppeditordocument.h b/src/plugins/cppeditor/cppeditordocument.h index 953e24de3e1..985e9a41b6a 100644 --- a/src/plugins/cppeditor/cppeditordocument.h +++ b/src/plugins/cppeditor/cppeditordocument.h @@ -59,9 +59,13 @@ public: const QByteArray &defines); void scheduleProcessDocument(); +public: + using HeaderErrorDiagnosticWidgetCreator = std::function; + signals: void codeWarningsUpdated(unsigned contentsRevision, const QList selections, + const HeaderErrorDiagnosticWidgetCreator &creator, const TextEditor::RefactorMarkers &refactorMarkers); void ifdefedOutBlocksUpdated(unsigned contentsRevision, diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.cpp b/src/plugins/cpptools/baseeditordocumentprocessor.cpp index 82aa058b90d..1ae7f11b98e 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.cpp +++ b/src/plugins/cpptools/baseeditordocumentprocessor.cpp @@ -67,6 +67,10 @@ void BaseEditorDocumentProcessor::addDiagnosticToolTipToLayout(uint, uint, QLayo { } +void BaseEditorDocumentProcessor::editorDocumentTimerRestarted() +{ +} + void BaseEditorDocumentProcessor::runParser(QFutureInterface &future, BaseEditorDocumentParser::Ptr parser, const WorkingCopy workingCopy) diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.h b/src/plugins/cpptools/baseeditordocumentprocessor.h index 77723fcd4ce..3ac8a3524d9 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.h +++ b/src/plugins/cpptools/baseeditordocumentprocessor.h @@ -37,6 +37,8 @@ #include +#include + namespace TextEditor { class TextDocument; class QuickFixOperations; @@ -67,10 +69,17 @@ public: virtual bool hasDiagnosticsAt(uint line, uint column) const; virtual void addDiagnosticToolTipToLayout(uint line, uint column, QLayout *layout) const; + virtual void editorDocumentTimerRestarted(); + +public: + using HeaderErrorDiagnosticWidgetCreator = std::function; + signals: + // Signal interface to implement void codeWarningsUpdated(unsigned revision, const QList selections, + const HeaderErrorDiagnosticWidgetCreator &creator, const TextEditor::RefactorMarkers &refactorMarkers); void ifdefedOutBlocksUpdated(unsigned revision, diff --git a/src/plugins/cpptools/builtineditordocumentprocessor.cpp b/src/plugins/cpptools/builtineditordocumentprocessor.cpp index a7a7ac1a338..284e1af8ae9 100644 --- a/src/plugins/cpptools/builtineditordocumentprocessor.cpp +++ b/src/plugins/cpptools/builtineditordocumentprocessor.cpp @@ -310,7 +310,10 @@ void BuiltinEditorDocumentProcessor::onCodeWarningsUpdated( m_codeWarnings += toTextEditorSelections(codeWarnings, textDocument()); m_codeWarningsUpdated = true; - emit codeWarningsUpdated(revision(), m_codeWarnings, TextEditor::RefactorMarkers()); + emit codeWarningsUpdated(revision(), + m_codeWarnings, + HeaderErrorDiagnosticWidgetCreator(), + TextEditor::RefactorMarkers()); } SemanticInfo::Source BuiltinEditorDocumentProcessor::createSemanticInfoSource(bool force) const diff --git a/src/plugins/help/helpplugin.cpp b/src/plugins/help/helpplugin.cpp index f50a8ee5b84..115bdfbca4c 100644 --- a/src/plugins/help/helpplugin.cpp +++ b/src/plugins/help/helpplugin.cpp @@ -622,25 +622,25 @@ void HelpPlugin::handleHelpRequest(const QUrl &url, HelpManager::HelpViewerLocat if (HelpViewer::launchWithExternalApp(url)) return; - QString address = url.toString(); if (!HelpManager::findFile(url).isValid()) { + const QString address = url.toString(); if (address.startsWith("qthelp://org.qt-project.") - || address.startsWith("qthelp://com.nokia.") - || address.startsWith("qthelp://com.trolltech.")) { - // local help not installed, resort to external web help - QString urlPrefix = "http://doc.qt.io/"; - if (url.authority() == "org.qt-project.qtcreator") - urlPrefix.append(QString::fromLatin1("qtcreator")); - else - urlPrefix.append("qt-5"); - address = urlPrefix + address.mid(address.lastIndexOf(QLatin1Char('/'))); + || address.startsWith("qthelp://com.nokia.") + || address.startsWith("qthelp://com.trolltech.")) { + // local help not installed, resort to external web help + QString urlPrefix = "http://doc.qt.io/"; + if (url.authority() == "org.qt-project.qtcreator") + urlPrefix.append(QString::fromLatin1("qtcreator")); + else + urlPrefix.append("qt-5"); + QDesktopServices::openUrl(QUrl(urlPrefix + address.mid(address.lastIndexOf(QLatin1Char('/'))))); + return; } } - const QUrl newUrl(address); HelpViewer *viewer = viewerForHelpViewerLocation(location); QTC_ASSERT(viewer, return); - viewer->setSource(newUrl); + viewer->setSource(url); ICore::raiseWindow(viewer); } diff --git a/src/plugins/help/macwebkithelpviewer.mm b/src/plugins/help/macwebkithelpviewer.mm index ca9095b6417..97982f49574 100644 --- a/src/plugins/help/macwebkithelpviewer.mm +++ b/src/plugins/help/macwebkithelpviewer.mm @@ -169,7 +169,7 @@ static QPoint flipPoint(const NSPoint &p) NSURL *resolvedURL = data.resolvedUrl.toNSURL(); NSString *mimeType = data.mimeType.toNSString(); - NSData *nsdata = QtMac::toNSData(data.data); // Qt 5.3 has this in QByteArray + NSData *nsdata = data.data.toNSData(); NSURLResponse *response = [[NSURLResponse alloc] initWithURL:resolvedURL MIMEType:mimeType expectedContentLength:data.data.length() diff --git a/src/plugins/help/remotehelpfilter.cpp b/src/plugins/help/remotehelpfilter.cpp index 74377a3bb4b..faa810e67e8 100644 --- a/src/plugins/help/remotehelpfilter.cpp +++ b/src/plugins/help/remotehelpfilter.cpp @@ -93,7 +93,8 @@ RemoteHelpFilter::RemoteHelpFilter() m_remoteUrls.append("https://www.bing.com/search?q=%1"); m_remoteUrls.append("https://www.google.com/search?q=%1"); m_remoteUrls.append("https://search.yahoo.com/search?p=%1"); - m_remoteUrls.append("https://www.cplusplus.com/reference/stl/%1"); + m_remoteUrls.append("https://stackoverflow.com/search?q=%1"); + m_remoteUrls.append("http://en.cppreference.com/mwiki/index.php?title=Special%3ASearch&search=%1"); m_remoteUrls.append("https://en.wikipedia.org/w/index.php?search=%1"); } diff --git a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp index fcabaaeed25..aeb799ae027 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp +++ b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp @@ -404,6 +404,7 @@ bool LineEditField::parseData(const QVariant &data, QString *errorMessage) QVariantMap tmp = data.toMap(); + m_isPassword = tmp.value("isPassword", false).toBool(); m_defaultText = JsonWizardFactory::localizedString(tmp.value(QLatin1String("trText")).toString()); m_disabledText = JsonWizardFactory::localizedString(tmp.value(QLatin1String("trDisabledText")).toString()); m_placeholderText = JsonWizardFactory::localizedString(tmp.value(QLatin1String("trPlaceholder")).toString()); @@ -439,6 +440,8 @@ QWidget *LineEditField::createWidget(const QString &displayName, JsonFieldPage * if (!m_historyId.isEmpty()) w->setHistoryCompleter(m_historyId, m_restoreLastHistoryItem); + w->setEchoMode(m_isPassword ? QLineEdit::Password : QLineEdit::Normal); + return w; } diff --git a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage_p.h b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage_p.h index 657fa518d5b..e02723a5ec6 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage_p.h +++ b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage_p.h @@ -105,6 +105,7 @@ private: bool m_isModified; bool m_isValidating; bool m_restoreLastHistoryItem; + bool m_isPassword; QString m_placeholderText; QString m_defaultText; QString m_disabledText; diff --git a/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp b/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp index 14288807a89..ed71f55b5ab 100644 --- a/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp +++ b/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp @@ -93,6 +93,7 @@ void QtProjectParameters::writeProFile(QTextStream &str) const case ConsoleApp: // Mac: Command line apps should not be bundles str << "CONFIG += console\nCONFIG -= app_bundle\n\n"; + // fallthrough case GuiApp: str << "TEMPLATE = app\n"; break; diff --git a/src/plugins/texteditor/texteditorconstants.cpp b/src/plugins/texteditor/texteditorconstants.cpp index d670895512a..48681e5cd06 100644 --- a/src/plugins/texteditor/texteditorconstants.cpp +++ b/src/plugins/texteditor/texteditorconstants.cpp @@ -102,6 +102,7 @@ const char *nameForStyle(TextStyle style) case C_WARNING_CONTEXT: return "WarningContext"; case C_DECLARATION: return "Declaration"; + case C_OUTPUT_ARGUMENT: return "C_OUTPUT_ARGUMENT"; case C_LAST_STYLE_SENTINEL: return "LastStyleSentinel"; } diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index f68b41cc0db..8dbaf93de4f 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -100,6 +100,7 @@ enum TextStyle : quint8 { C_ERROR_CONTEXT, C_DECLARATION, + C_OUTPUT_ARGUMENT, C_LAST_STYLE_SENTINEL }; diff --git a/src/plugins/texteditor/texteditorsettings.cpp b/src/plugins/texteditor/texteditorsettings.cpp index 67270d81bbd..0660f929f80 100644 --- a/src/plugins/texteditor/texteditorsettings.cpp +++ b/src/plugins/texteditor/texteditorsettings.cpp @@ -313,6 +313,10 @@ TextEditorSettings::TextEditorSettings(QObject *parent) tr("Declaration"), tr("Declaration of a function, variable, and so on."), FormatDescription::ShowFontUnderlineAndRelativeControls); + formatDescr.emplace_back(C_OUTPUT_ARGUMENT, + tr("Output Argument"), + tr("Writable arguments of a function call."), + FormatDescription::ShowFontUnderlineAndRelativeControls); d->m_fontSettingsPage = new FontSettingsPage(formatDescr, Constants::TEXT_EDITOR_FONT_SETTINGS, diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp index 89e7c40f6aa..ca51ee56dff 100644 --- a/src/plugins/valgrind/memchecktool.cpp +++ b/src/plugins/valgrind/memchecktool.cpp @@ -711,10 +711,11 @@ public: return m_tool->createRunControl(runConfiguration, mode); } - IRunConfigurationAspect *createRunConfigurationAspect(ProjectExplorer::RunConfiguration *rc) override - { - return createValgrindRunConfigurationAspect(rc); - } + // Do not create an aspect, let the Callgrind tool create one and use that, too. +// IRunConfigurationAspect *createRunConfigurationAspect(ProjectExplorer::RunConfiguration *rc) override +// { +// return createValgrindRunConfigurationAspect(rc); +// } public: MemcheckTool *m_tool; diff --git a/src/tools/clangbackend/ipcsource/clangasyncjob.h b/src/tools/clangbackend/ipcsource/clangasyncjob.h index b44227041e5..c8a7b799473 100644 --- a/src/tools/clangbackend/ipcsource/clangasyncjob.h +++ b/src/tools/clangbackend/ipcsource/clangasyncjob.h @@ -64,6 +64,11 @@ public: return future; } + void preventFinalization() override + { + m_futureWatcher.disconnect(); + } + private: Runner m_runner; QFutureWatcher m_futureWatcher; diff --git a/src/tools/clangbackend/ipcsource/clangbackend_global.h b/src/tools/clangbackend/ipcsource/clangbackend_global.h new file mode 100644 index 00000000000..0dccaa87ad4 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangbackend_global.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 + +namespace ClangBackEnd { + +enum class PreferredTranslationUnit +{ + RecentlyParsed, + PreviouslyParsed, + LastUninitialized, +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri index b247735deda..cf0a0e6682e 100644 --- a/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri +++ b/src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri @@ -29,6 +29,7 @@ HEADERS += $$PWD/clangcodemodelserver.h \ $$PWD/highlightingmarksiterator.h \ $$PWD/utf8positionfromlinecolumn.h \ $$PWD/clangasyncjob.h \ + $$PWD/clangbackend_global.h \ $$PWD/clangcompletecodejob.h \ $$PWD/clangcreateinitialdocumentpreamblejob.h \ $$PWD/clangfilepath.h \ @@ -41,7 +42,14 @@ HEADERS += $$PWD/clangcodemodelserver.h \ $$PWD/clangtranslationunit.h \ $$PWD/clangunsavedfilesshallowarguments.h \ $$PWD/clangupdatedocumentannotationsjob.h \ - $$PWD/clangexceptions.h + $$PWD/clangexceptions.h \ + $$PWD/clangdocumentprocessor.h \ + $$PWD/clangdocumentprocessors.h \ + $$PWD/clangtranslationunits.h \ + $$PWD/clangclock.h \ + $$PWD/clangsupportivetranslationunitinitializer.h \ + $$PWD/clangparsesupportivetranslationunitjob.h \ + $$PWD/clangreparsesupportivetranslationunitjob.h \ SOURCES += $$PWD/clangcodemodelserver.cpp \ $$PWD/codecompleter.cpp \ @@ -80,4 +88,10 @@ SOURCES += $$PWD/clangcodemodelserver.cpp \ $$PWD/clangtranslationunit.cpp \ $$PWD/clangunsavedfilesshallowarguments.cpp \ $$PWD/clangupdatedocumentannotationsjob.cpp \ - $$PWD/clangexceptions.cpp + $$PWD/clangexceptions.cpp \ + $$PWD/clangdocumentprocessor.cpp \ + $$PWD/clangdocumentprocessors.cpp \ + $$PWD/clangtranslationunits.cpp \ + $$PWD/clangsupportivetranslationunitinitializer.cpp \ + $$PWD/clangparsesupportivetranslationunitjob.cpp \ + $$PWD/clangreparsesupportivetranslationunitjob.cpp \ diff --git a/src/tools/clangbackend/ipcsource/clangclock.h b/src/tools/clangbackend/ipcsource/clangclock.h new file mode 100644 index 00000000000..68d91478f74 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangclock.h @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 ClangBackEnd { + +using Clock = std::chrono::steady_clock; +using Duration = std::chrono::steady_clock::duration; +using TimePoint = std::chrono::steady_clock::time_point; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp index 3c1cee59777..bea5eacea49 100644 --- a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp +++ b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp @@ -27,6 +27,7 @@ #include "clangdocuments.h" #include "clangfilesystemwatcher.h" +#include "clangtranslationunits.h" #include "codecompleter.h" #include "diagnosticset.h" #include "highlightingmarks.h" @@ -126,10 +127,16 @@ void ClangCodeModelServer::updateTranslationUnitsForEditor(const UpdateTranslati try { const auto newerFileContainers = documents.newerFileContainers(message.fileContainers()); if (newerFileContainers.size() > 0) { - documents.update(newerFileContainers); + const std::vector updateDocuments = documents.update(newerFileContainers); unsavedFiles.createOrUpdate(newerFileContainers); - updateDocumentAnnotationsTimer.start(updateDocumentAnnotationsTimeOutInMs); + // Start the jobs on the next event loop iteration since otherwise + // we might block the translation unit for a completion request + // that comes right after this message. + updateDocumentAnnotationsTimer.start(0); + QTimer::singleShot(0, [this, updateDocuments](){ + startInitializingSupportiveTranslationUnits(updateDocuments); + }); } } catch (const std::exception &exception) { qWarning() << "Error in ClangCodeModelServer::updateTranslationUnitsForEditor:" << exception.what(); @@ -141,6 +148,10 @@ void ClangCodeModelServer::unregisterTranslationUnitsForEditor(const ClangBackEn TIME_SCOPE_DURATION("ClangCodeModelServer::unregisterTranslationUnitsForEditor"); try { + for (const auto &fileContainer : message.fileContainers()) { + const Document &document = documents.document(fileContainer); + documentProcessors().remove(document); + } documents.remove(message.fileContainers()); unsavedFiles.remove(message.fileContainers()); } catch (const std::exception &exception) { @@ -211,8 +222,9 @@ void ClangCodeModelServer::completeCode(const ClangBackEnd::CompleteCodeMessage jobRequest.column = message.column(); jobRequest.ticketNumber = message.ticketNumber(); - jobs().add(jobRequest); - jobs().process(); + DocumentProcessor processor = documentProcessors().processor(document); + processor.addJob(jobRequest); + processor.process(); } catch (const std::exception &exception) { qWarning() << "Error in ClangCodeModelServer::completeCode:" << exception.what(); } @@ -229,8 +241,9 @@ void ClangCodeModelServer::requestDocumentAnnotations(const RequestDocumentAnnot const JobRequest jobRequest = createJobRequest(document, JobRequest::Type::RequestDocumentAnnotations); - jobs().add(jobRequest); - jobs().process(); + DocumentProcessor processor = documentProcessors().processor(document); + processor.addJob(jobRequest); + processor.process(); } catch (const std::exception &exception) { qWarning() << "Error in ClangCodeModelServer::requestDocumentAnnotations:" << exception.what(); } @@ -260,9 +273,14 @@ void ClangCodeModelServer::startDocumentAnnotationsTimerIfFileIsNotOpenAsDocumen updateDocumentAnnotationsTimer.start(0); } -const Jobs &ClangCodeModelServer::jobsForTestOnly() +QList ClangCodeModelServer::runningJobsForTestsOnly() { - return jobs(); + return documentProcessors().runningJobs(); +} + +int ClangCodeModelServer::queueSizeForTestsOnly() +{ + return documentProcessors().queueSize(); } bool ClangCodeModelServer::isTimerRunningForTestOnly() const @@ -270,32 +288,55 @@ bool ClangCodeModelServer::isTimerRunningForTestOnly() const return updateDocumentAnnotationsTimer.isActive(); } -void ClangCodeModelServer::addJobRequestsForDirtyAndVisibleDocuments() -{ - for (const auto &document : documents.documents()) { - if (document.isNeedingReparse() && document.isVisibleInEditor()) - jobs().add(createJobRequest(document, JobRequest::Type::UpdateDocumentAnnotations)); - } -} - void ClangCodeModelServer::processJobsForDirtyAndVisibleDocuments() { - addJobRequestsForDirtyAndVisibleDocuments(); - jobs().process(); + for (const auto &document : documents.documents()) { + if (document.isNeedingReparse() && document.isVisibleInEditor()) { + DocumentProcessor processor = documentProcessors().processor(document); + processor.addJob(createJobRequest(document, + JobRequest::Type::UpdateDocumentAnnotations, + PreferredTranslationUnit::PreviouslyParsed)); + } + } + + documentProcessors().process(); } void ClangCodeModelServer::processInitialJobsForDocuments(const std::vector &documents) { for (const auto &document : documents) { - jobs().add(createJobRequest(document, JobRequest::Type::UpdateDocumentAnnotations)); - jobs().add(createJobRequest(document, JobRequest::Type::CreateInitialDocumentPreamble)); - } + DocumentProcessor processor = documentProcessors().create(document); + const auto jobRequestCreator = [this](const Document &document, + JobRequest::Type jobRequestType, + PreferredTranslationUnit preferredTranslationUnit) { + return createJobRequest(document, jobRequestType, preferredTranslationUnit); + }; + processor.setJobRequestCreator(jobRequestCreator); - jobs().process(); + processor.addJob(createJobRequest(document, JobRequest::Type::UpdateDocumentAnnotations)); + processor.addJob(createJobRequest(document, JobRequest::Type::CreateInitialDocumentPreamble)); + processor.process(); + } } -JobRequest ClangCodeModelServer::createJobRequest(const Document &document, - JobRequest::Type type) const +void ClangCodeModelServer::startInitializingSupportiveTranslationUnits( + const std::vector &documents) +{ + for (const Document &document : documents) { + try { + DocumentProcessor processor = documentProcessors().processor(document); + if (!processor.hasSupportiveTranslationUnit()) + processor.startInitializingSupportiveTranslationUnit(); + } catch (const DocumentProcessorDoesNotExist &) { + // OK, document was already closed. + } + } +} + +JobRequest ClangCodeModelServer::createJobRequest( + const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit) const { JobRequest jobRequest; jobRequest.type = type; @@ -304,6 +345,7 @@ JobRequest ClangCodeModelServer::createJobRequest(const Document &document, jobRequest.projectPartId = document.projectPartId(); jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); jobRequest.documentRevision = document.documentRevision(); + jobRequest.preferredTranslationUnit = preferredTranslationUnit; const ProjectPart &projectPart = projects.project(document.projectPartId()); jobRequest.projectChangeTimePoint = projectPart.lastChangeTimePoint(); @@ -315,16 +357,16 @@ void ClangCodeModelServer::setUpdateDocumentAnnotationsTimeOutInMsForTestsOnly(i updateDocumentAnnotationsTimeOutInMs = value; } -Jobs &ClangCodeModelServer::jobs() +DocumentProcessors &ClangCodeModelServer::documentProcessors() { - if (!jobs_) { - // Jobs needs a reference to the client, but the client is not known at - // construction time of ClangCodeModelServer, so construct Jobs in a - // lazy manner. - jobs_.reset(new Jobs(documents, unsavedFiles, projects, *client())); + if (!documentProcessors_) { + // DocumentProcessors needs a reference to the client, but the client + // is not known at construction time of ClangCodeModelServer, so + // construct DocumentProcessors in a lazy manner. + documentProcessors_.reset(new DocumentProcessors(documents, unsavedFiles, projects, *client())); } - return *jobs_.data(); + return *documentProcessors_.data(); } } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangcodemodelserver.h b/src/tools/clangbackend/ipcsource/clangcodemodelserver.h index ff0db141f9c..ab4efdb7e29 100644 --- a/src/tools/clangbackend/ipcsource/clangcodemodelserver.h +++ b/src/tools/clangbackend/ipcsource/clangcodemodelserver.h @@ -31,8 +31,9 @@ #include "projects.h" #include "clangdocument.h" #include "clangdocuments.h" +#include "clangdocumentprocessors.h" +#include "clangjobrequest.h" #include "unsavedfiles.h" -#include "clangjobs.h" #include @@ -58,27 +59,33 @@ public: void updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message) override; void requestDocumentAnnotations(const RequestDocumentAnnotationsMessage &message) override; -public /*for tests*/: +public: // for tests const Documents &documentsForTestOnly() const; - const Jobs &jobsForTestOnly(); + QList runningJobsForTestsOnly(); + int queueSizeForTestsOnly(); bool isTimerRunningForTestOnly() const; void setUpdateDocumentAnnotationsTimeOutInMsForTestsOnly(int value); + DocumentProcessors &documentProcessors(); private: - Jobs &jobs(); void startDocumentAnnotationsTimerIfFileIsNotOpenAsDocument(const Utf8String &filePath); void addJobRequestsForDirtyAndVisibleDocuments(); void processJobsForDirtyAndVisibleDocuments(); void processInitialJobsForDocuments(const std::vector &documents); + void startInitializingSupportiveTranslationUnits(const std::vector &documents); - JobRequest createJobRequest(const Document &document, JobRequest::Type type) const; + JobRequest createJobRequest(const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit + = PreferredTranslationUnit::RecentlyParsed) const; private: ProjectParts projects; UnsavedFiles unsavedFiles; Documents documents; - QScopedPointer jobs_; + + QScopedPointer documentProcessors_; // Delayed initialization QTimer updateDocumentAnnotationsTimer; int updateDocumentAnnotationsTimeOutInMs; diff --git a/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp index ce9d190bec0..d45f53fdda0 100644 --- a/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp +++ b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp @@ -50,29 +50,28 @@ static CompleteCodeJob::AsyncResult runAsyncHelper(const TranslationUnit &transl return asyncResult; } -bool CompleteCodeJob::prepareAsyncRun() +IAsyncJob::AsyncPrepareResult CompleteCodeJob::prepareAsyncRun() { const JobRequest jobRequest = context().jobRequest; - QTC_ASSERT(jobRequest.type == JobRequest::Type::CompleteCode, return false); + QTC_ASSERT(jobRequest.type == JobRequest::Type::CompleteCode, return AsyncPrepareResult()); try { m_pinnedDocument = context().documentForJobRequest(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); const UnsavedFiles unsavedFiles = *context().unsavedFiles; const quint32 line = jobRequest.line; const quint32 column = jobRequest.column; setRunner([translationUnit, unsavedFiles, line, column]() { return runAsyncHelper(translationUnit, unsavedFiles, line, column); }); - + return AsyncPrepareResult{translationUnit.id()}; } catch (const std::exception &exception) { qWarning() << "Error in CompleteCodeJob::prepareAsyncRun:" << exception.what(); - return false; + return AsyncPrepareResult(); } - - return true; } void CompleteCodeJob::finalizeAsyncRun() diff --git a/src/tools/clangbackend/ipcsource/clangcompletecodejob.h b/src/tools/clangbackend/ipcsource/clangcompletecodejob.h index 30c0bb8e3f9..142988d7403 100644 --- a/src/tools/clangbackend/ipcsource/clangcompletecodejob.h +++ b/src/tools/clangbackend/ipcsource/clangcompletecodejob.h @@ -43,7 +43,7 @@ class CompleteCodeJob : public AsyncJob public: using AsyncResult = CompleteCodeJobResult; - bool prepareAsyncRun() override; + AsyncPrepareResult prepareAsyncRun() override; void finalizeAsyncRun() override; private: diff --git a/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp index dd5e1f267f8..a9bf6698661 100644 --- a/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp +++ b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.cpp @@ -39,28 +39,28 @@ static void runAsyncHelper(const TranslationUnit &translationUnit, translationUnit.reparse(translationUnitUpdateInput); } -bool CreateInitialDocumentPreambleJob::prepareAsyncRun() +IAsyncJob::AsyncPrepareResult CreateInitialDocumentPreambleJob::prepareAsyncRun() { const JobRequest jobRequest = context().jobRequest; - QTC_ASSERT(jobRequest.type == JobRequest::Type::CreateInitialDocumentPreamble, return false); + QTC_ASSERT(jobRequest.type == JobRequest::Type::CreateInitialDocumentPreamble, return AsyncPrepareResult()); try { m_pinnedDocument = context().documentForJobRequest(); m_pinnedFileContainer = m_pinnedDocument.fileContainer(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); setRunner([translationUnit, updateInput]() { return runAsyncHelper(translationUnit, updateInput); }); + return AsyncPrepareResult{translationUnit.id()}; } catch (const std::exception &exception) { qWarning() << "Error in CreateInitialDocumentPreambleJob::prepareAsyncRun:" << exception.what(); - return false; + return AsyncPrepareResult(); } - - return true; } void CreateInitialDocumentPreambleJob::finalizeAsyncRun() diff --git a/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h index 21fc875982b..570b746460e 100644 --- a/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h +++ b/src/tools/clangbackend/ipcsource/clangcreateinitialdocumentpreamblejob.h @@ -33,7 +33,7 @@ namespace ClangBackEnd { class CreateInitialDocumentPreambleJob : public AsyncJob { public: - bool prepareAsyncRun() override; + AsyncPrepareResult prepareAsyncRun() override; void finalizeAsyncRun() override; private: diff --git a/src/tools/clangbackend/ipcsource/clangdocument.cpp b/src/tools/clangbackend/ipcsource/clangdocument.cpp index 3e9b6971abf..49c1a3cef0a 100644 --- a/src/tools/clangbackend/ipcsource/clangdocument.cpp +++ b/src/tools/clangbackend/ipcsource/clangdocument.cpp @@ -32,6 +32,7 @@ #include "projectpart.h" #include "clangexceptions.h" #include "clangtranslationunit.h" +#include "clangtranslationunits.h" #include "clangtranslationunitupdater.h" #include "unsavedfiles.h" #include "unsavedfile.h" @@ -62,15 +63,14 @@ public: const Utf8StringVector fileArguments; ProjectPart projectPart; - time_point lastProjectPartChangeTimePoint; + TimePoint lastProjectPartChangeTimePoint; - CXTranslationUnit translationUnit = nullptr; - CXIndex index = nullptr; + TranslationUnits translationUnits; QSet dependedFilePaths; uint documentRevision = 0; - time_point needsToBeReparsedChangeTimePoint; + TimePoint needsToBeReparsedChangeTimePoint; bool hasParseOrReparseFailed = false; bool needsToBeReparsed = false; bool isUsedByCurrentEditor = false; @@ -85,16 +85,16 @@ DocumentData::DocumentData(const Utf8String &filePath, filePath(filePath), fileArguments(fileArguments), projectPart(projectPart), - lastProjectPartChangeTimePoint(std::chrono::steady_clock::now()), + lastProjectPartChangeTimePoint(Clock::now()), + translationUnits(filePath), needsToBeReparsedChangeTimePoint(lastProjectPartChangeTimePoint) { dependedFilePaths.insert(filePath); + translationUnits.createAndAppend(); } DocumentData::~DocumentData() { - clang_disposeTranslationUnit(translationUnit); - clang_disposeIndex(index); } Document::Document(const Utf8String &filePath, @@ -183,7 +183,7 @@ const ProjectPart &Document::projectPart() const return d->projectPart; } -const time_point Document::lastProjectPartChangeTimePoint() const +const TimePoint Document::lastProjectPartChangeTimePoint() const { checkIfNull(); @@ -239,7 +239,7 @@ void Document::setIsVisibleInEditor(bool isVisibleInEditor) d->isVisibleInEditor = isVisibleInEditor; } -time_point Document::isNeededReparseChangeTimePoint() const +TimePoint Document::isNeededReparseChangeTimePoint() const { checkIfNull(); @@ -282,8 +282,13 @@ TranslationUnitUpdateInput Document::createUpdateInput() const TranslationUnitUpdater Document::createUpdater() const { + TranslationUnit unit = translationUnit(); + const TranslationUnitUpdateInput updateInput = createUpdateInput(); - TranslationUnitUpdater updater(d->index, d->translationUnit, updateInput); + TranslationUnitUpdater updater(unit.id(), + unit.cxIndex(), + unit.cxTranslationUnit(), + updateInput); return updater; } @@ -304,9 +309,13 @@ void Document::incorporateUpdaterResult(const TranslationUnitUpdateResult &resul if (result.hasParsed()) d->lastProjectPartChangeTimePoint = result.parseTimePoint; - if (result.hasParsed() || result.hasReparsed()) + if (result.hasParsed() || result.hasReparsed()) { d->dependedFilePaths = result.dependedOnFilePaths; + const TimePoint timePoint = qMax(result.parseTimePoint, result.reparseTimePoint); + d->translationUnits.updateParseTimePoint(result.translationUnitId, timePoint); + } + d->documents.addWatchedFiles(d->dependedFilePaths); if (result.hasReparsed() @@ -315,11 +324,16 @@ void Document::incorporateUpdaterResult(const TranslationUnitUpdateResult &resul } } -TranslationUnit Document::translationUnit() const +TranslationUnit Document::translationUnit(PreferredTranslationUnit preferredTranslationUnit) const { checkIfNull(); - return TranslationUnit(d->filePath, d->index, d->translationUnit); + return d->translationUnits.get(preferredTranslationUnit); +} + +TranslationUnits &Document::translationUnits() const +{ + return d->translationUnits; } void Document::parse() const @@ -352,7 +366,7 @@ const QSet Document::dependedFilePaths() const void Document::setDirty() { - d->needsToBeReparsedChangeTimePoint = std::chrono::steady_clock::now(); + d->needsToBeReparsedChangeTimePoint = Clock::now(); d->needsToBeReparsed = true; } diff --git a/src/tools/clangbackend/ipcsource/clangdocument.h b/src/tools/clangbackend/ipcsource/clangdocument.h index 55c1bbb4d48..91112e9cddc 100644 --- a/src/tools/clangbackend/ipcsource/clangdocument.h +++ b/src/tools/clangbackend/ipcsource/clangdocument.h @@ -27,6 +27,7 @@ #include "clangtranslationunitupdater.h" +#include "clangbackend_global.h" #include "clangtranslationunit.h" #include @@ -36,7 +37,6 @@ #include #include -#include #include class Utf8String; @@ -44,14 +44,13 @@ class Utf8String; namespace ClangBackEnd { class TranslationUnit; +class TranslationUnits; class DocumentData; class TranslationUnitUpdateResult; class ProjectPart; class FileContainer; class Documents; -using time_point = std::chrono::steady_clock::time_point; - class Document { public: @@ -85,7 +84,7 @@ public: Utf8String projectPartId() const; const ProjectPart &projectPart() const; - const time_point lastProjectPartChangeTimePoint() const; + const TimePoint lastProjectPartChangeTimePoint() const; bool isProjectPartOutdated() const; uint documentRevision() const; @@ -104,7 +103,9 @@ public: TranslationUnitUpdateInput createUpdateInput() const; void incorporateUpdaterResult(const TranslationUnitUpdateResult &result) const; - TranslationUnit translationUnit() const; + TranslationUnit translationUnit(PreferredTranslationUnit preferredTranslationUnit + = PreferredTranslationUnit::RecentlyParsed) const; + TranslationUnits &translationUnits() const; public: // for tests void parse() const; @@ -112,7 +113,7 @@ public: // for tests const QSet dependedFilePaths() const; TranslationUnitUpdater createUpdater() const; void setHasParseOrReparseFailed(bool hasFailed); - time_point isNeededReparseChangeTimePoint() const; + TimePoint isNeededReparseChangeTimePoint() const; private: void setDirty(); diff --git a/src/tools/clangbackend/ipcsource/clangdocumentprocessor.cpp b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.cpp new file mode 100644 index 00000000000..b0a0e0122f1 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 "clangdocumentprocessor.h" + +#include "clangdocuments.h" +#include "clangjobs.h" +#include "clangsupportivetranslationunitinitializer.h" + +#include "clangdocument.h" +#include "clangtranslationunits.h" + +#include + +namespace ClangBackEnd { + +class DocumentProcessorData +{ +public: + DocumentProcessorData(const Document &document, + Documents &documents, + UnsavedFiles &unsavedFiles, + ProjectParts &projects, + ClangCodeModelClientInterface &client) + : document(document) + , documents(documents) + , jobs(documents, unsavedFiles, projects, client) + , supportiveTranslationUnitInitializer(document, jobs) + { + const auto isDocumentClosedChecker = [this](const Utf8String &filePath, + const Utf8String &projectPartId) { + return !this->documents.hasDocument(filePath, projectPartId); + }; + supportiveTranslationUnitInitializer.setIsDocumentClosedChecker(isDocumentClosedChecker); + } + +public: + Document document; + Documents &documents; + Jobs jobs; + + SupportiveTranslationUnitInitializer supportiveTranslationUnitInitializer; + JobRequestCreator jobRequestCreator; +}; + +DocumentProcessor::DocumentProcessor(const Document &document, + Documents &documents, + UnsavedFiles &unsavedFiles, + ProjectParts &projects, + ClangCodeModelClientInterface &client) + : d(std::make_shared(document, + documents, + unsavedFiles, + projects, + client)) +{ +} + +void DocumentProcessor::setJobRequestCreator(const JobRequestCreator &creator) +{ + d->supportiveTranslationUnitInitializer.setJobRequestCreator(creator); +} + +void DocumentProcessor::addJob(const JobRequest &jobRequest) +{ + d->jobs.add(jobRequest); +} + +JobRequests DocumentProcessor::process() +{ + return d->jobs.process(); +} + +Document DocumentProcessor::document() const +{ + return d->document; +} + +bool DocumentProcessor::hasSupportiveTranslationUnit() const +{ + return d->supportiveTranslationUnitInitializer.state() + != SupportiveTranslationUnitInitializer::State::NotInitialized; +} + +void DocumentProcessor::startInitializingSupportiveTranslationUnit() +{ + d->supportiveTranslationUnitInitializer.startInitializing(); +} + +bool DocumentProcessor::isSupportiveTranslationUnitInitialized() const +{ + return d->supportiveTranslationUnitInitializer.state() + == SupportiveTranslationUnitInitializer::State::Initialized; +} + +QList DocumentProcessor::runningJobs() const +{ + return d->jobs.runningJobs(); +} + +int DocumentProcessor::queueSize() const +{ + return d->jobs.queue().size(); +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangdocumentprocessor.h b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.h new file mode 100644 index 00000000000..364d1fecaea --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangdocumentprocessor.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 "clangjobrequest.h" +#include "clangjobs.h" + +#include + +namespace ClangBackEnd { + +class ClangCodeModelClientInterface; +class Document; +class Documents; +class DocumentProcessorData; +class JobRequest; +class ProjectParts; +class UnsavedFiles; + +class DocumentProcessor +{ +public: + DocumentProcessor(const Document &document, + Documents &documents, + UnsavedFiles &unsavedFiles, + ProjectParts &projects, + ClangCodeModelClientInterface &client); + + void setJobRequestCreator(const JobRequestCreator &creator); + + void addJob(const JobRequest &jobRequest); + JobRequests process(); + + Document document() const; + + bool hasSupportiveTranslationUnit() const; + void startInitializingSupportiveTranslationUnit(); + +public: // for tests + bool isSupportiveTranslationUnitInitialized() const; + QList runningJobs() const; + int queueSize() const; + +private: + std::shared_ptr d; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangdocumentprocessors.cpp b/src/tools/clangbackend/ipcsource/clangdocumentprocessors.cpp new file mode 100644 index 00000000000..3e465f8173d --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangdocumentprocessors.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** 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 "clangdocumentprocessors.h" +#include "clangdocument.h" +#include "clangexceptions.h" + +namespace ClangBackEnd { + +DocumentProcessors::DocumentProcessors(Documents &documents, + UnsavedFiles &unsavedFiles, + ProjectParts &projects, + ClangCodeModelClientInterface &client) + : m_documents(documents) + , m_unsavedFiles(unsavedFiles) + , m_projects(projects) + , m_client(client) +{ +} + +static bool operator<(const DocumentId &lhs, const DocumentId &rhs) +{ + return lhs.filePath < rhs.filePath + || (lhs.filePath == rhs.filePath && lhs.projectPartId < lhs.projectPartId); +} + +DocumentProcessor DocumentProcessors::create(const Document &document) +{ + const DocumentId id{document.filePath(), document.projectPartId()}; + if (m_processors.contains(id)) + throw DocumentProcessorAlreadyExists(document.filePath(), document.projectPartId()); + + const DocumentProcessor element(document, m_documents, m_unsavedFiles, m_projects, m_client); + m_processors.insert(id, element); + + return element; +} + +DocumentProcessor DocumentProcessors::processor(const Document &document) +{ + const DocumentId id{document.filePath(), document.projectPartId()}; + + const auto it = m_processors.find(id); + if (it == m_processors.end()) + throw DocumentProcessorDoesNotExist(document.filePath(), document.projectPartId()); + + return *it; +} + +QList DocumentProcessors::processors() const +{ + return m_processors.values(); +} + +void DocumentProcessors::remove(const Document &document) +{ + const DocumentId id{document.filePath(), document.projectPartId()}; + + const int itemsRemoved = m_processors.remove(id); + if (itemsRemoved != 1) + throw DocumentProcessorDoesNotExist(document.filePath(), document.projectPartId()); +} + +JobRequests DocumentProcessors::process() +{ + JobRequests jobsStarted; + for (auto &processor : m_processors) + jobsStarted += processor.process(); + + return jobsStarted; +} + +QList DocumentProcessors::runningJobs() const +{ + QList jobs; + for (auto &processor : m_processors) + jobs += processor.runningJobs(); + + return jobs; +} + +int DocumentProcessors::queueSize() const +{ + int total = 0; + for (auto &processor : m_processors) + total += processor.queueSize(); + + return total; +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangdocumentprocessors.h b/src/tools/clangbackend/ipcsource/clangdocumentprocessors.h new file mode 100644 index 00000000000..83e37f52766 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangdocumentprocessors.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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 "clangdocumentprocessor.h" +#include "clangjobs.h" + +#include + +#include + +namespace ClangBackEnd { + +class Document; +class DocumentProcessor; + +class DocumentId { +public: + Utf8String filePath; + Utf8String projectPartId; +}; + +class DocumentProcessors +{ +public: + DocumentProcessors(Documents &documents, + UnsavedFiles &unsavedFiles, + ProjectParts &projects, + ClangCodeModelClientInterface &client); + + DocumentProcessor create(const Document &document); + DocumentProcessor processor(const Document &document); + void remove(const Document &document); + + JobRequests process(); + +public: // for tests + QList processors() const; + QList runningJobs() const; + int queueSize() const; + +private: + Documents &m_documents; + UnsavedFiles &m_unsavedFiles; + ProjectParts &m_projects; + ClangCodeModelClientInterface &m_client; + + QMap m_processors; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangdocuments.cpp b/src/tools/clangbackend/ipcsource/clangdocuments.cpp index d85a0de49fd..6077505b573 100644 --- a/src/tools/clangbackend/ipcsource/clangdocuments.cpp +++ b/src/tools/clangbackend/ipcsource/clangdocuments.cpp @@ -68,14 +68,20 @@ std::vector Documents::create(const QVector &fileContai return createdDocuments; } -void Documents::update(const QVector &fileContainers) +std::vector Documents::update(const QVector &fileContainers) { checkIfDocumentsForFilePathsExist(fileContainers); + std::vector createdDocuments; + for (const FileContainer &fileContainer : fileContainers) { - updateDocument(fileContainer); + const std::vector documents = updateDocument(fileContainer); + createdDocuments.insert(createdDocuments.end(), documents.begin(), documents.end()); + updateDocumentsWithChangedDependency(fileContainer.filePath()); } + + return createdDocuments; } static bool removeFromFileContainer(QVector &fileContainers, const Document &document) @@ -205,12 +211,14 @@ Document Documents::createDocument(const FileContainer &fileContainer) return documents_.back(); } -void Documents::updateDocument(const FileContainer &fileContainer) +std::vector Documents::updateDocument(const FileContainer &fileContainer) { const auto documents = findAllDocumentsWithFilePath(fileContainer.filePath()); for (auto document : documents) document.setDocumentRevision(fileContainer.documentRevision()); + + return documents; } std::vector::iterator Documents::findDocument(const FileContainer &fileContainer) diff --git a/src/tools/clangbackend/ipcsource/clangdocuments.h b/src/tools/clangbackend/ipcsource/clangdocuments.h index 3af689145bd..90c905f16c4 100644 --- a/src/tools/clangbackend/ipcsource/clangdocuments.h +++ b/src/tools/clangbackend/ipcsource/clangdocuments.h @@ -45,7 +45,7 @@ public: Documents(ProjectParts &projectParts, UnsavedFiles &unsavedFiles); std::vector create(const QVector &fileContainers); - void update(const QVector &fileContainers); + std::vector update(const QVector &fileContainers); void remove(const QVector &fileContainers); void setUsedByCurrentEditor(const Utf8String &filePath); @@ -72,7 +72,7 @@ public: private: Document createDocument(const FileContainer &fileContainer); - void updateDocument(const FileContainer &fileContainer); + std::vector updateDocument(const FileContainer &fileContainer); std::vector::iterator findDocument(const FileContainer &fileContainer); std::vector findAllDocumentsWithFilePath(const Utf8String &filePath); std::vector::const_iterator findDocument(const Utf8String &filePath, const Utf8String &projectPartId) const; diff --git a/src/tools/clangbackend/ipcsource/clangexceptions.cpp b/src/tools/clangbackend/ipcsource/clangexceptions.cpp index e6d9218a943..187ce98d1a1 100644 --- a/src/tools/clangbackend/ipcsource/clangexceptions.cpp +++ b/src/tools/clangbackend/ipcsource/clangexceptions.cpp @@ -74,4 +74,31 @@ DocumentIsNullException::DocumentIsNullException() m_info = Utf8String::fromUtf8("Tried to access a null Document!"); } +DocumentProcessorAlreadyExists::DocumentProcessorAlreadyExists(const Utf8String &filePath, + const Utf8String &projectPartId) +{ + m_info = Utf8StringLiteral("Document processor for file '") + + filePath + + Utf8StringLiteral("' and project part id '") + + projectPartId + + Utf8StringLiteral("' already exists!"); +} + +DocumentProcessorDoesNotExist::DocumentProcessorDoesNotExist(const Utf8String &filePath, + const Utf8String &projectPartId) +{ + m_info = Utf8StringLiteral("Document processor for file '") + + filePath + + Utf8StringLiteral("' and project part id '") + + projectPartId + + Utf8StringLiteral("' does not exist!"); +} + +TranslationUnitDoesNotExist::TranslationUnitDoesNotExist(const Utf8String &filePath) +{ + m_info += Utf8StringLiteral("TranslationUnit for file '") + + filePath + + Utf8StringLiteral("' does not exist."); +} + } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangexceptions.h b/src/tools/clangbackend/ipcsource/clangexceptions.h index 6b1938cdfd8..ffc1c09da14 100644 --- a/src/tools/clangbackend/ipcsource/clangexceptions.h +++ b/src/tools/clangbackend/ipcsource/clangexceptions.h @@ -74,4 +74,24 @@ public: DocumentIsNullException(); }; +class DocumentProcessorAlreadyExists : public ClangBaseException +{ +public: + DocumentProcessorAlreadyExists(const Utf8String &filePath, + const Utf8String &projectPartId); +}; + +class DocumentProcessorDoesNotExist : public ClangBaseException +{ +public: + DocumentProcessorDoesNotExist(const Utf8String &filePath, + const Utf8String &projectPartId); +}; + +class TranslationUnitDoesNotExist : public ClangBaseException +{ +public: + TranslationUnitDoesNotExist(const Utf8String &filePath); +}; + } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp b/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp index 8c433c40928..d6fc0ff659f 100644 --- a/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp +++ b/src/tools/clangbackend/ipcsource/clangiasyncjob.cpp @@ -27,6 +27,8 @@ #include "clangcompletecodejob.h" #include "clangcreateinitialdocumentpreamblejob.h" +#include "clangparsesupportivetranslationunitjob.h" +#include "clangreparsesupportivetranslationunitjob.h" #include "clangrequestdocumentannotationsjob.h" #include "clangupdatedocumentannotationsjob.h" @@ -39,6 +41,10 @@ IAsyncJob *IAsyncJob::create(JobRequest::Type type) switch (type) { case JobRequest::Type::UpdateDocumentAnnotations: return new UpdateDocumentAnnotationsJob(); + case JobRequest::Type::ParseSupportiveTranslationUnit: + return new ParseSupportiveTranslationUnitJob(); + case JobRequest::Type::ReparseSupportiveTranslationUnit: + return new ReparseSupportiveTranslationUnitJob(); case JobRequest::Type::CreateInitialDocumentPreamble: return new CreateInitialDocumentPreambleJob(); case JobRequest::Type::CompleteCode: diff --git a/src/tools/clangbackend/ipcsource/clangiasyncjob.h b/src/tools/clangbackend/ipcsource/clangiasyncjob.h index 44f503f3cb8..98f61e857ab 100644 --- a/src/tools/clangbackend/ipcsource/clangiasyncjob.h +++ b/src/tools/clangbackend/ipcsource/clangiasyncjob.h @@ -41,6 +41,11 @@ class IAsyncJob public: static IAsyncJob *create(JobRequest::Type type); + struct AsyncPrepareResult { + operator bool() const { return !translationUnitId.isEmpty(); } + Utf8String translationUnitId; + }; + public: IAsyncJob(); virtual ~IAsyncJob(); @@ -52,10 +57,12 @@ public: FinishedHandler finishedHandler() const; void setFinishedHandler(const FinishedHandler &finishedHandler); - virtual bool prepareAsyncRun() = 0; + virtual AsyncPrepareResult prepareAsyncRun() = 0; virtual QFuture runAsync() = 0; virtual void finalizeAsyncRun() = 0; + virtual void preventFinalization() = 0; + public: // for tests bool isFinished() const; void setIsFinished(bool isFinished); diff --git a/src/tools/clangbackend/ipcsource/clangjobqueue.cpp b/src/tools/clangbackend/ipcsource/clangjobqueue.cpp index 1beb1cdace6..ceae5c070e6 100644 --- a/src/tools/clangbackend/ipcsource/clangjobqueue.cpp +++ b/src/tools/clangbackend/ipcsource/clangjobqueue.cpp @@ -27,6 +27,7 @@ #include "clangjobqueue.h" #include "clangdocument.h" #include "clangdocuments.h" +#include "clangtranslationunits.h" #include "projects.h" #include "unsavedfiles.h" @@ -40,11 +41,22 @@ JobQueue::JobQueue(Documents &documents, ProjectParts &projectParts) { } -void JobQueue::add(const JobRequest &job) +bool JobQueue::add(const JobRequest &job) { - qCDebug(jobsLog) << "Adding" << job; + if (m_queue.contains(job)) { + qCDebug(jobsLog) << "Not adding duplicate request" << job; + return false; + } + if (isJobRunningForJobRequest(job)) { + qCDebug(jobsLog) << "Not adding duplicate request for already running job" << job; + return false; + } + + qCDebug(jobsLog) << "Adding" << job; m_queue.append(job); + + return true; } int JobQueue::size() const @@ -164,50 +176,65 @@ void JobQueue::prioritizeRequests() JobRequests JobQueue::takeJobRequestsToRunNow() { JobRequests jobsToRun; - QSet documentsScheduledForThisRun; + using TranslationUnitIds = QSet; + TranslationUnitIds translationUnitsScheduledForThisRun; QMutableVectorIterator i(m_queue); while (i.hasNext()) { - const JobRequest &jobRequest = i.next(); + const JobRequest &request = i.next(); try { - const Document &document - = m_documents.document(jobRequest.filePath, - jobRequest.projectPartId); - const DocumentId documentId = DocumentId(jobRequest.filePath, jobRequest.projectPartId); + const Document &document = m_documents.document(request.filePath, + request.projectPartId); if (!document.isUsedByCurrentEditor() && !document.isVisibleInEditor()) continue; - if (documentsScheduledForThisRun.contains(documentId)) + const Utf8String id = document.translationUnit(request.preferredTranslationUnit).id(); + if (translationUnitsScheduledForThisRun.contains(id)) continue; - if (isJobRunningForDocument(documentId)) + if (isJobRunningForTranslationUnit(id)) continue; - documentsScheduledForThisRun.insert(documentId); - jobsToRun += jobRequest; + translationUnitsScheduledForThisRun.insert(id); + jobsToRun += request; i.remove(); } catch (const std::exception &exception) { qWarning() << "Error in Jobs::takeJobRequestsToRunNow for" - << jobRequest << ":" << exception.what(); + << request << ":" << exception.what(); } } return jobsToRun; } -bool JobQueue::isJobRunningForDocument(const JobQueue::DocumentId &documentId) +bool JobQueue::isJobRunningForTranslationUnit(const Utf8String &translationUnitId) { - if (m_isJobRunningHandler) - return m_isJobRunningHandler(documentId.first, documentId.second); + if (m_isJobRunningForTranslationUnitHandler) + return m_isJobRunningForTranslationUnitHandler(translationUnitId); return false; } -void JobQueue::setIsJobRunningHandler(const IsJobRunningHandler &isJobRunningHandler) +bool JobQueue::isJobRunningForJobRequest(const JobRequest &jobRequest) { - m_isJobRunningHandler = isJobRunningHandler; + if (m_isJobRunningForJobRequestHandler) + return m_isJobRunningForJobRequestHandler(jobRequest); + + return false; +} + +void JobQueue::setIsJobRunningForTranslationUnitHandler( + const IsJobRunningForTranslationUnitHandler &isJobRunningHandler) +{ + m_isJobRunningForTranslationUnitHandler = isJobRunningHandler; +} + +void JobQueue::setIsJobRunningForJobRequestHandler( + const JobQueue::IsJobRunningForJobRequestHandler &isJobRunningHandler) +{ + m_isJobRunningForJobRequestHandler = isJobRunningHandler; } JobRequests JobQueue::queue() const diff --git a/src/tools/clangbackend/ipcsource/clangjobqueue.h b/src/tools/clangbackend/ipcsource/clangjobqueue.h index 1be38ceaf07..d48f96aaaeb 100644 --- a/src/tools/clangbackend/ipcsource/clangjobqueue.h +++ b/src/tools/clangbackend/ipcsource/clangjobqueue.h @@ -39,12 +39,17 @@ class JobQueue public: JobQueue(Documents &documents, ProjectParts &projects); - void add(const JobRequest &job); + bool add(const JobRequest &job); JobRequests processQueue(); - using IsJobRunningHandler = std::function; - void setIsJobRunningHandler(const IsJobRunningHandler &isJobRunningHandler); + using IsJobRunningForTranslationUnitHandler = std::function; + void setIsJobRunningForTranslationUnitHandler( + const IsJobRunningForTranslationUnitHandler &isJobRunningHandler); + + using IsJobRunningForJobRequestHandler = std::function; + void setIsJobRunningForJobRequestHandler( + const IsJobRunningForJobRequestHandler &isJobRunningHandler); public: // for tests JobRequests queue() const; @@ -52,8 +57,8 @@ public: // for tests void prioritizeRequests(); private: - using DocumentId = QPair; - bool isJobRunningForDocument(const DocumentId &documentId); + bool isJobRunningForTranslationUnit(const Utf8String &translationUnitId); + bool isJobRunningForJobRequest(const JobRequest &jobRequest); JobRequests takeJobRequestsToRunNow(); void removeOutDatedRequests(); bool isJobRequestOutDated(const JobRequest &jobRequest) const; @@ -62,7 +67,8 @@ private: Documents &m_documents; ProjectParts &m_projectParts; - IsJobRunningHandler m_isJobRunningHandler; + IsJobRunningForTranslationUnitHandler m_isJobRunningForTranslationUnitHandler; + IsJobRunningForJobRequestHandler m_isJobRunningForJobRequestHandler; JobRequests m_queue; }; diff --git a/src/tools/clangbackend/ipcsource/clangjobrequest.cpp b/src/tools/clangbackend/ipcsource/clangjobrequest.cpp index 4c3d692d08c..1065267762e 100644 --- a/src/tools/clangbackend/ipcsource/clangjobrequest.cpp +++ b/src/tools/clangbackend/ipcsource/clangjobrequest.cpp @@ -25,6 +25,8 @@ #include "clangjobrequest.h" +#include + namespace ClangBackEnd { #define RETURN_TEXT_FOR_CASE(enumValue) case JobRequest::Type::enumValue: return #enumValue @@ -32,6 +34,8 @@ static const char *JobRequestTypeToText(JobRequest::Type type) { switch (type) { RETURN_TEXT_FOR_CASE(UpdateDocumentAnnotations); + RETURN_TEXT_FOR_CASE(ParseSupportiveTranslationUnit); + RETURN_TEXT_FOR_CASE(ReparseSupportiveTranslationUnit); RETURN_TEXT_FOR_CASE(CreateInitialDocumentPreamble); RETURN_TEXT_FOR_CASE(CompleteCode); RETURN_TEXT_FOR_CASE(RequestDocumentAnnotations); @@ -41,6 +45,19 @@ static const char *JobRequestTypeToText(JobRequest::Type type) } #undef RETURN_TEXT_FOR_CASE +#define RETURN_TEXT_FOR_CASE(enumValue) case PreferredTranslationUnit::enumValue: return #enumValue +const char *preferredTranslationUnitToText(PreferredTranslationUnit type) +{ + switch (type) { + RETURN_TEXT_FOR_CASE(RecentlyParsed); + RETURN_TEXT_FOR_CASE(PreviouslyParsed); + RETURN_TEXT_FOR_CASE(LastUninitialized); + } + + return "UnhandledPreferredTranslationUnitType"; +} +#undef RETURN_TEXT_FOR_CASE + QDebug operator<<(QDebug debug, JobRequest::Type type) { debug << JobRequestTypeToText(type); @@ -53,12 +70,14 @@ QDebug operator<<(QDebug debug, const JobRequest &jobRequest) debug.nospace() << "Job<" << jobRequest.id << "," + << QFileInfo(jobRequest.filePath).fileName() + << "," << JobRequestTypeToText(jobRequest.type) << "," - << jobRequest.filePath + << preferredTranslationUnitToText(jobRequest.preferredTranslationUnit) << ">"; - return debug; + return debug.space(); } JobRequest::JobRequest() @@ -67,6 +86,23 @@ JobRequest::JobRequest() id = ++idCounter; } +bool JobRequest::operator==(const JobRequest &other) const +{ + return type == other.type + && requirements == other.requirements + + && filePath == other.filePath + && projectPartId == other.projectPartId + && unsavedFilesChangeTimePoint == other.unsavedFilesChangeTimePoint + && projectChangeTimePoint == other.projectChangeTimePoint + && documentRevision == other.documentRevision + && preferredTranslationUnit == other.preferredTranslationUnit + + && line == other.line + && column == other.column + && ticketNumber == other.ticketNumber; +} + JobRequest::Requirements JobRequest::requirementsForType(Type type) { switch (type) { @@ -77,6 +113,8 @@ JobRequest::Requirements JobRequest::requirementsForType(Type type) |JobRequest::CurrentDocumentRevision); case JobRequest::Type::CompleteCode: case JobRequest::Type::CreateInitialDocumentPreamble: + case JobRequest::Type::ParseSupportiveTranslationUnit: + case JobRequest::Type::ReparseSupportiveTranslationUnit: return JobRequest::Requirements(JobRequest::DocumentValid); } diff --git a/src/tools/clangbackend/ipcsource/clangjobrequest.h b/src/tools/clangbackend/ipcsource/clangjobrequest.h index ca75e4ec675..258763de945 100644 --- a/src/tools/clangbackend/ipcsource/clangjobrequest.h +++ b/src/tools/clangbackend/ipcsource/clangjobrequest.h @@ -25,17 +25,20 @@ #pragma once +#include "clangbackend_global.h" +#include "clangclock.h" + #include #include #include #include -#include +#include namespace ClangBackEnd { -using time_point = std::chrono::steady_clock::time_point; +class Document; class JobRequest { @@ -43,6 +46,10 @@ public: enum class Type { UpdateDocumentAnnotations, CreateInitialDocumentPreamble, + + ParseSupportiveTranslationUnit, + ReparseSupportiveTranslationUnit, + CompleteCode, RequestDocumentAnnotations, }; @@ -64,6 +71,8 @@ public: JobRequest(); + bool operator==(const JobRequest &other) const; + public: quint64 id = 0; Type type; @@ -72,9 +81,10 @@ public: // General Utf8String filePath; Utf8String projectPartId; - time_point unsavedFilesChangeTimePoint; - time_point projectChangeTimePoint; + TimePoint unsavedFilesChangeTimePoint; + TimePoint projectChangeTimePoint; uint documentRevision = 0; + PreferredTranslationUnit preferredTranslationUnit = PreferredTranslationUnit::RecentlyParsed; // For code completion quint32 line = 0; @@ -83,6 +93,9 @@ public: }; using JobRequests = QVector; +using JobRequestCreator = std::function; QDebug operator<<(QDebug debug, const JobRequest &jobRequest); diff --git a/src/tools/clangbackend/ipcsource/clangjobs.cpp b/src/tools/clangbackend/ipcsource/clangjobs.cpp index ac787c36fa5..10cc1293157 100644 --- a/src/tools/clangbackend/ipcsource/clangjobs.cpp +++ b/src/tools/clangbackend/ipcsource/clangjobs.cpp @@ -45,17 +45,25 @@ Jobs::Jobs(Documents &documents, , m_client(client) , m_queue(documents, projectParts) { - m_queue.setIsJobRunningHandler([this](const Utf8String &filePath, - const Utf8String &projectPartId) { - return isJobRunning(filePath, projectPartId); + m_queue.setIsJobRunningForTranslationUnitHandler([this](const Utf8String &translationUnitId) { + return isJobRunningForTranslationUnit(translationUnitId); + }); + m_queue.setIsJobRunningForJobRequestHandler([this](const JobRequest &jobRequest) { + return isJobRunningForJobRequest(jobRequest); }); } Jobs::~Jobs() { + foreach (IAsyncJob *asyncJob, m_running.keys()) + asyncJob->preventFinalization(); + QFutureSynchronizer waitForFinishedJobs; foreach (const RunningJob &runningJob, m_running.values()) waitForFinishedJobs.addFuture(runningJob.future); + + foreach (IAsyncJob *asyncJob, m_running.keys()) + delete asyncJob; } void Jobs::add(const JobRequest &job) @@ -87,22 +95,25 @@ JobRequests Jobs::runJobs(const JobRequests &jobsRequests) bool Jobs::runJob(const JobRequest &jobRequest) { - if (IAsyncJob *asyncJob = IAsyncJob::create(jobRequest.type)) { - JobContext context(jobRequest, &m_documents, &m_unsavedFiles, &m_client); - asyncJob->setContext(context); + IAsyncJob *asyncJob = IAsyncJob::create(jobRequest.type); + QTC_ASSERT(asyncJob, return false); - if (asyncJob->prepareAsyncRun()) { - qCDebug(jobsLog) << "Running" << jobRequest; + JobContext context(jobRequest, &m_documents, &m_unsavedFiles, &m_client); + asyncJob->setContext(context); - asyncJob->setFinishedHandler([this](IAsyncJob *asyncJob){ onJobFinished(asyncJob); }); - const QFuture future = asyncJob->runAsync(); + if (const IAsyncJob::AsyncPrepareResult prepareResult = asyncJob->prepareAsyncRun()) { + qCDebug(jobsLog) << "Running" << jobRequest + << "with TranslationUnit" << prepareResult.translationUnitId; - m_running.insert(asyncJob, RunningJob{jobRequest, future}); - return true; - } else { - qCDebug(jobsLog) << "Preparation failed for " << jobRequest; - delete asyncJob; - } + asyncJob->setFinishedHandler([this](IAsyncJob *asyncJob){ onJobFinished(asyncJob); }); + const QFuture future = asyncJob->runAsync(); + + const RunningJob runningJob{jobRequest, prepareResult.translationUnitId, future}; + m_running.insert(asyncJob, runningJob); + return true; + } else { + qCDebug(jobsLog) << "Preparation failed for " << jobRequest; + delete asyncJob; } return false; @@ -112,15 +123,25 @@ void Jobs::onJobFinished(IAsyncJob *asyncJob) { qCDebug(jobsLog) << "Finishing" << asyncJob->context().jobRequest; + if (m_jobFinishedCallback) { + const RunningJob runningJob = m_running.value(asyncJob); + m_jobFinishedCallback(runningJob); + } + m_running.remove(asyncJob); delete asyncJob; process(); } -int Jobs::runningJobs() const +void Jobs::setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback) { - return m_running.size(); + m_jobFinishedCallback = jobFinishedCallback; +} + +QList Jobs::runningJobs() const +{ + return m_running.values(); } JobRequests Jobs::queue() const @@ -128,12 +149,19 @@ JobRequests Jobs::queue() const return m_queue.queue(); } -bool Jobs::isJobRunning(const Utf8String &filePath, const Utf8String &projectPartId) const +bool Jobs::isJobRunningForTranslationUnit(const Utf8String &translationUnitId) const { - const auto hasJobRequest = [filePath, projectPartId](const RunningJob &runningJob) { - const JobRequest &jobRequest = runningJob.jobRequest; - return filePath == jobRequest.filePath - && projectPartId == jobRequest.projectPartId; + const auto hasTranslationUnitId = [translationUnitId](const RunningJob &runningJob) { + return runningJob.translationUnitId == translationUnitId; + }; + + return Utils::anyOf(m_running.values(), hasTranslationUnitId); +} + +bool Jobs::isJobRunningForJobRequest(const JobRequest &jobRequest) const +{ + const auto hasJobRequest = [jobRequest](const RunningJob &runningJob) { + return runningJob.jobRequest == jobRequest; }; return Utils::anyOf(m_running.values(), hasJobRequest); diff --git a/src/tools/clangbackend/ipcsource/clangjobs.h b/src/tools/clangbackend/ipcsource/clangjobs.h index 70b6dd80f6f..02e8ff249d1 100644 --- a/src/tools/clangbackend/ipcsource/clangjobs.h +++ b/src/tools/clangbackend/ipcsource/clangjobs.h @@ -31,6 +31,8 @@ #include +#include + namespace ClangBackEnd { class ClangCodeModelClientInterface; @@ -43,9 +45,12 @@ class Jobs public: struct RunningJob { JobRequest jobRequest; + Utf8String translationUnitId; QFuture future; }; + using RunningJobs = QHash; + using JobFinishedCallback = std::function; public: Jobs(Documents &documents, @@ -58,10 +63,13 @@ public: JobRequests process(); + void setJobFinishedCallback(const JobFinishedCallback &jobFinishedCallback); + public /*for tests*/: - int runningJobs() const; + QList runningJobs() const; JobRequests queue() const; - bool isJobRunning(const Utf8String &filePath, const Utf8String &projectPartId) const; + bool isJobRunningForTranslationUnit(const Utf8String &translationUnitId) const; + bool isJobRunningForJobRequest(const JobRequest &jobRequest) const; private: JobRequests runJobs(const JobRequests &jobRequest); @@ -75,6 +83,7 @@ private: JobQueue m_queue; RunningJobs m_running; + JobFinishedCallback m_jobFinishedCallback; }; } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.cpp b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.cpp new file mode 100644 index 00000000000..fa8937c1951 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 "clangparsesupportivetranslationunitjob.h" + +#include + +#include + +namespace ClangBackEnd { + +static ParseSupportiveTranslationUnitJob::AsyncResult runAsyncHelper( + const TranslationUnit &translationUnit, + const TranslationUnitUpdateInput &translationUnitUpdateInput) +{ + TIME_SCOPE_DURATION("ParseSupportiveTranslationUnitJob"); + + TranslationUnitUpdateInput updateInput = translationUnitUpdateInput; + updateInput.parseNeeded = true; + + ParseSupportiveTranslationUnitJob::AsyncResult asyncResult; + asyncResult.updateResult = translationUnit.update(updateInput); + + return asyncResult; +} + +IAsyncJob::AsyncPrepareResult ParseSupportiveTranslationUnitJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::ParseSupportiveTranslationUnit, return AsyncPrepareResult()); + + try { + m_pinnedDocument = context().documentForJobRequest(); + m_pinnedFileContainer = m_pinnedDocument.fileContainer(); + + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); + const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); + setRunner([translationUnit, updateInput]() { + return runAsyncHelper(translationUnit, updateInput); + }); + return AsyncPrepareResult{translationUnit.id()}; + + } catch (const std::exception &exception) { + qWarning() << "Error in ParseForSupportiveTranslationUnitJob::prepareAsyncRun:" + << exception.what(); + return AsyncPrepareResult(); + } +} + +void ParseSupportiveTranslationUnitJob::finalizeAsyncRun() +{ +} + +} // namespace ClangBackEnd + diff --git a/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.h b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.h new file mode 100644 index 00000000000..6d4d01131e2 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangparsesupportivetranslationunitjob.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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 "clangasyncjob.h" +#include "clangdocument.h" + +namespace ClangBackEnd { + +struct ParseSupportiveTranslationUnitJobResult +{ + TranslationUnitUpdateResult updateResult; +}; + +class ParseSupportiveTranslationUnitJob : public AsyncJob +{ +public: + using AsyncResult = ParseSupportiveTranslationUnitJobResult; + + AsyncPrepareResult prepareAsyncRun() override; + void finalizeAsyncRun() override; + +private: + Document m_pinnedDocument; + FileContainer m_pinnedFileContainer; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.cpp b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.cpp new file mode 100644 index 00000000000..da922db8e9d --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 "clangreparsesupportivetranslationunitjob.h" + +#include + +#include + +namespace ClangBackEnd { + +static ReparseSupportiveTranslationUnitJob::AsyncResult runAsyncHelper( + const TranslationUnit &translationUnit, + const TranslationUnitUpdateInput &translationUnitUpdateInput) +{ + TIME_SCOPE_DURATION("ReparseSupportiveTranslationUnitJob"); + + TranslationUnitUpdateInput updateInput = translationUnitUpdateInput; + updateInput.reparseNeeded = true; + + ReparseSupportiveTranslationUnitJob::AsyncResult asyncResult; + asyncResult.updateResult = translationUnit.reparse(updateInput); + + return asyncResult; +} + +IAsyncJob::AsyncPrepareResult ReparseSupportiveTranslationUnitJob::prepareAsyncRun() +{ + const JobRequest jobRequest = context().jobRequest; + QTC_ASSERT(jobRequest.type == JobRequest::Type::ReparseSupportiveTranslationUnit, return AsyncPrepareResult()); + + try { + m_pinnedDocument = context().documentForJobRequest(); + m_pinnedFileContainer = m_pinnedDocument.fileContainer(); + + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); + const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); + setRunner([translationUnit, updateInput]() { + return runAsyncHelper(translationUnit, updateInput); + }); + return AsyncPrepareResult{translationUnit.id()}; + + } catch (const std::exception &exception) { + qWarning() << "Error in ReparseSupportiveTranslationUnitJob::prepareAsyncRun:" + << exception.what(); + return AsyncPrepareResult(); + } +} + +void ReparseSupportiveTranslationUnitJob::finalizeAsyncRun() +{ + if (!context().isOutdated()) { + const AsyncResult result = asyncResult(); + m_pinnedDocument.incorporateUpdaterResult(result.updateResult); + } +} + +} // namespace ClangBackEnd + diff --git a/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.h b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.h new file mode 100644 index 00000000000..1b352c2c361 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangreparsesupportivetranslationunitjob.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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 "clangasyncjob.h" +#include "clangdocument.h" + +namespace ClangBackEnd { + +struct ReparseSupportiveTranslationUnitJobResult +{ + TranslationUnitUpdateResult updateResult; +}; + +class ReparseSupportiveTranslationUnitJob : public AsyncJob +{ +public: + using AsyncResult = ReparseSupportiveTranslationUnitJobResult; + + AsyncPrepareResult prepareAsyncRun() override; + void finalizeAsyncRun() override; + +private: + Document m_pinnedDocument; + FileContainer m_pinnedFileContainer; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp index 689fe2b9f19..45b06c02037 100644 --- a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp +++ b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.cpp @@ -40,33 +40,35 @@ static RequestDocumentAnnotationsJob::AsyncResult runAsyncHelper( RequestDocumentAnnotationsJob::AsyncResult asyncResult; - translationUnit.extractDocumentAnnotations(asyncResult.diagnostics, + translationUnit.extractDocumentAnnotations(asyncResult.firstHeaderErrorDiagnostic, + asyncResult.diagnostics, asyncResult.highlightingMarks, asyncResult.skippedSourceRanges); return asyncResult; } -bool RequestDocumentAnnotationsJob::prepareAsyncRun() +IAsyncJob::AsyncPrepareResult RequestDocumentAnnotationsJob::prepareAsyncRun() { const JobRequest jobRequest = context().jobRequest; - QTC_ASSERT(jobRequest.type == JobRequest::Type::RequestDocumentAnnotations, return false); + QTC_ASSERT(jobRequest.type == JobRequest::Type::RequestDocumentAnnotations, + return AsyncPrepareResult()); try { m_pinnedDocument = context().documentForJobRequest(); m_pinnedFileContainer = m_pinnedDocument.fileContainer(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); setRunner([translationUnit]() { return runAsyncHelper(translationUnit); }); + return AsyncPrepareResult{translationUnit.id()}; } catch (const std::exception &exception) { qWarning() << "Error in RequestDocumentAnnotationsJob::prepareAsyncRun:" << exception.what(); - return false; + return AsyncPrepareResult(); } - - return true; } void RequestDocumentAnnotationsJob::finalizeAsyncRun() @@ -82,6 +84,7 @@ void RequestDocumentAnnotationsJob::sendAnnotations( { const DocumentAnnotationsChangedMessage message(m_pinnedFileContainer, result.diagnostics, + result.firstHeaderErrorDiagnostic, result.highlightingMarks, result.skippedSourceRanges); diff --git a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h index 04bdd7cfc3a..e6bb4f9edca 100644 --- a/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h +++ b/src/tools/clangbackend/ipcsource/clangrequestdocumentannotationsjob.h @@ -36,6 +36,7 @@ namespace ClangBackEnd { struct RequestDocumentAnnotationsJobResult { + ClangBackEnd::DiagnosticContainer firstHeaderErrorDiagnostic; QVector diagnostics; QVector highlightingMarks; QVector skippedSourceRanges; @@ -46,7 +47,7 @@ class RequestDocumentAnnotationsJob : public AsyncJob + +namespace ClangBackEnd { + + // TODO: Check translation unit id? + +SupportiveTranslationUnitInitializer::SupportiveTranslationUnitInitializer( + const Document &document, + Jobs &jobs) + : m_document(document) + , m_jobs(jobs) +{ +} + +void SupportiveTranslationUnitInitializer::setJobRequestCreator(const JobRequestCreator &creator) +{ + m_jobRequestCreator = creator; +} + +void SupportiveTranslationUnitInitializer::setIsDocumentClosedChecker( + const IsDocumentClosedChecker &isDocumentClosedChecker) +{ + m_isDocumentClosedChecker = isDocumentClosedChecker; +} + +SupportiveTranslationUnitInitializer::State SupportiveTranslationUnitInitializer::state() const +{ + return m_state; +} + +void SupportiveTranslationUnitInitializer::startInitializing() +{ + QTC_CHECK(m_state == State::NotInitialized); + if (abortIfDocumentIsClosed()) + return; + + m_document.translationUnits().createAndAppend(); + + m_jobs.setJobFinishedCallback([this](const Jobs::RunningJob &runningJob) { + checkIfParseJobFinished(runningJob); + }); + addJob(JobRequest::Type::ParseSupportiveTranslationUnit); + m_jobs.process(); + + m_state = State::WaitingForParseJob; +} + +void SupportiveTranslationUnitInitializer::checkIfParseJobFinished(const Jobs::RunningJob &job) +{ + QTC_CHECK(m_state == State::WaitingForParseJob); + if (abortIfDocumentIsClosed()) + return; + + if (job.jobRequest.type == JobRequest::Type::ParseSupportiveTranslationUnit) { + m_jobs.setJobFinishedCallback([this](const Jobs::RunningJob &runningJob) { + checkIfReparseJobFinished(runningJob); + }); + + addJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + + m_state = State::WaitingForReparseJob; + } +} + +void SupportiveTranslationUnitInitializer::checkIfReparseJobFinished(const Jobs::RunningJob &job) +{ + QTC_CHECK(m_state == State::WaitingForReparseJob); + if (abortIfDocumentIsClosed()) + return; + + if (job.jobRequest.type == JobRequest::Type::ReparseSupportiveTranslationUnit) { + if (m_document.translationUnits().areAllTranslationUnitsParsed()) { + m_jobs.setJobFinishedCallback(nullptr); + m_state = State::Initialized; + } else { + // The supportive translation unit was reparsed, but the document + // revision changed in the meanwhile, so try again. + addJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + } + } +} + +bool SupportiveTranslationUnitInitializer::abortIfDocumentIsClosed() +{ + QTC_CHECK(m_isDocumentClosedChecker); + + if (m_isDocumentClosedChecker(m_document.filePath(), m_document.projectPartId())) { + m_state = State::Aborted; + return true; + } + + return false; +} + +void SupportiveTranslationUnitInitializer::addJob(JobRequest::Type jobRequestType) +{ + QTC_CHECK(m_jobRequestCreator); + + const JobRequest jobRequest = m_jobRequestCreator(m_document, + jobRequestType, + PreferredTranslationUnit::LastUninitialized); + + m_jobs.add(jobRequest); +} + +void SupportiveTranslationUnitInitializer::setState(const State &state) +{ + m_state = state; +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.h b/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.h new file mode 100644 index 00000000000..f65cc4afd22 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangsupportivetranslationunitinitializer.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 "clangdocument.h" +#include "clangjobrequest.h" +#include "clangjobs.h" + +#include + +#pragma once + +namespace ClangBackEnd { + +class SupportiveTranslationUnitInitializer +{ +public: + using IsDocumentClosedChecker = std::function; + + enum class State { + NotInitialized, + WaitingForParseJob, + WaitingForReparseJob, + Initialized, + Aborted + }; + +public: + SupportiveTranslationUnitInitializer(const Document &document, Jobs &jobs); + + void setJobRequestCreator(const JobRequestCreator &creator); + void setIsDocumentClosedChecker(const IsDocumentClosedChecker &isDocumentClosedChecker); + + State state() const; + void startInitializing(); + +public: // for tests + void setState(const State &state); + void checkIfParseJobFinished(const Jobs::RunningJob &job); + void checkIfReparseJobFinished(const Jobs::RunningJob &job); + +private: + + bool abortIfDocumentIsClosed(); + void addJob(JobRequest::Type jobRequestType); + +private: + Document m_document; + Jobs &m_jobs; + + State m_state = State::NotInitialized; + JobRequestCreator m_jobRequestCreator; + IsDocumentClosedChecker m_isDocumentClosedChecker; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp index 37bdf088912..f4d3390038f 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp @@ -38,10 +38,12 @@ namespace ClangBackEnd { -TranslationUnit::TranslationUnit(const Utf8String &filepath, +TranslationUnit::TranslationUnit(const Utf8String &id, + const Utf8String &filepath, CXIndex &cxIndex, CXTranslationUnit &cxTranslationUnit) - : m_filePath(filepath) + : m_id(id) + , m_filePath(filepath) , m_cxIndex(cxIndex) , m_cxTranslationUnit(cxTranslationUnit) { @@ -49,7 +51,12 @@ TranslationUnit::TranslationUnit(const Utf8String &filepath, bool TranslationUnit::isNull() const { - return !m_cxTranslationUnit || !m_cxIndex || m_filePath.isEmpty(); + return !m_cxTranslationUnit || !m_cxIndex || m_filePath.isEmpty() || m_id.isEmpty(); +} + +Utf8String TranslationUnit::id() const +{ + return m_id; } Utf8String TranslationUnit::filePath() const @@ -70,7 +77,7 @@ CXTranslationUnit &TranslationUnit::cxTranslationUnit() const TranslationUnitUpdateResult TranslationUnit::update( const TranslationUnitUpdateInput &parseInput) const { - TranslationUnitUpdater updater(cxIndex(), cxTranslationUnit(), parseInput); + TranslationUnitUpdater updater(id(), cxIndex(), cxTranslationUnit(), parseInput); return updater.update(TranslationUnitUpdater::UpdateMode::AsNeeded); } @@ -78,7 +85,7 @@ TranslationUnitUpdateResult TranslationUnit::update( TranslationUnitUpdateResult TranslationUnit::parse( const TranslationUnitUpdateInput &parseInput) const { - TranslationUnitUpdater updater(cxIndex(), cxTranslationUnit(), parseInput); + TranslationUnitUpdater updater(id(), cxIndex(), cxTranslationUnit(), parseInput); return updater.update(TranslationUnitUpdater::UpdateMode::ParseIfNeeded); } @@ -86,7 +93,7 @@ TranslationUnitUpdateResult TranslationUnit::parse( TranslationUnitUpdateResult TranslationUnit::reparse( const TranslationUnitUpdateInput &parseInput) const { - TranslationUnitUpdater updater(cxIndex(), cxTranslationUnit(), parseInput); + TranslationUnitUpdater updater(id(), cxIndex(), cxTranslationUnit(), parseInput); return updater.update(TranslationUnitUpdater::UpdateMode::ForceReparse); } @@ -105,11 +112,12 @@ TranslationUnit::CodeCompletionResult TranslationUnit::complete( } void TranslationUnit::extractDocumentAnnotations( - QVector &diagnostics, + DiagnosticContainer &firstHeaderErrorDiagnostic, + QVector &mainFileDiagnostics, QVector &highlightingMarks, QVector &skippedSourceRanges) const { - diagnostics = mainFileDiagnostics(); + extractDiagnostics(firstHeaderErrorDiagnostic, mainFileDiagnostics); highlightingMarks = this->highlightingMarks().toHighlightingMarksContainers(); skippedSourceRanges = this->skippedSourceRanges().toSourceRangeContainers(); } @@ -119,15 +127,6 @@ DiagnosticSet TranslationUnit::diagnostics() const return DiagnosticSet(clang_getDiagnosticSetFromTU(m_cxTranslationUnit)); } -QVector TranslationUnit::mainFileDiagnostics() const -{ - const auto isMainFileDiagnostic = [this](const Diagnostic &diagnostic) { - return diagnostic.location().filePath() == m_filePath; - }; - - return diagnostics().toDiagnosticContainers(isMainFileDiagnostic); -} - SourceLocation TranslationUnit::sourceLocationAt(uint line,uint column) const { return SourceLocation(m_cxTranslationUnit, m_filePath, line, column); @@ -186,4 +185,35 @@ SkippedSourceRanges TranslationUnit::skippedSourceRanges() const return SkippedSourceRanges(m_cxTranslationUnit, m_filePath.constData()); } +static bool isMainFileDiagnostic(const Utf8String &mainFilePath, const Diagnostic &diagnostic) +{ + return diagnostic.location().filePath() == mainFilePath; +} + +static bool isHeaderErrorDiagnostic(const Utf8String &mainFilePath, const Diagnostic &diagnostic) +{ + const bool isCritical = diagnostic.severity() == DiagnosticSeverity::Error + || diagnostic.severity() == DiagnosticSeverity::Fatal; + return isCritical && diagnostic.location().filePath() != mainFilePath; +} + +void TranslationUnit::extractDiagnostics(DiagnosticContainer &firstHeaderErrorDiagnostic, + QVector &mainFileDiagnostics) const +{ + mainFileDiagnostics.clear(); + mainFileDiagnostics.reserve(int(diagnostics().size())); + + bool hasFirstHeaderErrorDiagnostic = false; + + for (const Diagnostic &diagnostic : diagnostics()) { + if (!hasFirstHeaderErrorDiagnostic && isHeaderErrorDiagnostic(m_filePath, diagnostic)) { + hasFirstHeaderErrorDiagnostic = true; + firstHeaderErrorDiagnostic = diagnostic.toDiagnosticContainer(); + } + + if (isMainFileDiagnostic(m_filePath, diagnostic)) + mainFileDiagnostics.push_back(diagnostic.toDiagnosticContainer()); + } +} + } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.h b/src/tools/clangbackend/ipcsource/clangtranslationunit.h index 3b7e81f6180..69574750923 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.h +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.h @@ -57,12 +57,15 @@ public: }; public: - TranslationUnit(const Utf8String &filePath, + TranslationUnit(const Utf8String &id, + const Utf8String &filePath, CXIndex &cxIndex, CXTranslationUnit &cxTranslationUnit); bool isNull() const; + Utf8String id() const; + Utf8String filePath() const; CXIndex &cxIndex() const; CXTranslationUnit &cxTranslationUnit() const; @@ -73,12 +76,14 @@ public: CodeCompletionResult complete(UnsavedFiles &unsavedFiles, uint line, uint column) const; - void extractDocumentAnnotations(QVector &diagnostics, + void extractDiagnostics(DiagnosticContainer &firstHeaderErrorDiagnostic, + QVector &mainFileDiagnostics) const; + void extractDocumentAnnotations(DiagnosticContainer &firstHeaderErrorDiagnostic, + QVector &mainFileDiagnostics, QVector &highlightingMarks, QVector &skippedSourceRanges) const; DiagnosticSet diagnostics() const; - QVector mainFileDiagnostics() const; SourceLocation sourceLocationAt(uint line, uint column) const; SourceLocation sourceLocationAt(const Utf8String &filePath, uint line, uint column) const; @@ -94,6 +99,7 @@ public: SkippedSourceRanges skippedSourceRanges() const; private: + const Utf8String m_id; const Utf8String m_filePath; CXIndex &m_cxIndex; CXTranslationUnit &m_cxTranslationUnit; diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp new file mode 100644 index 00000000000..7f65a9bf247 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangtranslationunits.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** 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 "clangtranslationunits.h" + +#include "clangexceptions.h" +#include "clangtranslationunit.h" + +#include +#include + +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(tuLog, "qtc.clangbackend.translationunits"); + +namespace ClangBackEnd { + +TranslationUnits::TranslationUnits(const Utf8String &filePath) + : m_filePath(filePath) +{ +} + +TranslationUnits::~TranslationUnits() +{ + foreach (const TranslationUnitData &unit, m_tuDatas) { + clang_disposeTranslationUnit(unit.cxTranslationUnit); + clang_disposeIndex(unit.cxIndex); + } +} + +TranslationUnit TranslationUnits::createAndAppend() +{ + const Utf8String id = Utf8String::fromByteArray(QUuid::createUuid().toByteArray()); + qCDebug(tuLog) << "Creating TranslationUnit" << id << "for" << QFileInfo(m_filePath).fileName(); + + m_tuDatas.append(TranslationUnitData(id)); + TranslationUnitData &translationUnitData = m_tuDatas.last(); + + return toTranslationUnit(translationUnitData); +} + +TranslationUnit TranslationUnits::get(PreferredTranslationUnit type) +{ + if (m_tuDatas.isEmpty()) + throw TranslationUnitDoesNotExist(m_filePath); + + if (m_tuDatas.size() == 1) + return toTranslationUnit(m_tuDatas.first()); + + if (areAllTranslationUnitsParsed()) + return getPreferredTranslationUnit(type); + else if (type == PreferredTranslationUnit::LastUninitialized) + return toTranslationUnit(m_tuDatas.last()); + + return toTranslationUnit(m_tuDatas.first()); +} + +void TranslationUnits::updateParseTimePoint(const Utf8String &translationUnitId, + TimePoint timePoint) +{ + TranslationUnitData &unit = findUnit(translationUnitId); + + QTC_CHECK(timePoint != TimePoint()); + unit.parseTimePoint = timePoint; + + qCDebug(tuLog) << "Updated" << translationUnitId << "for" << QFileInfo(m_filePath).fileName() + << "RecentlyParsed:" << get(PreferredTranslationUnit::RecentlyParsed).id() + << "PreviouslyParsed:" << get(PreferredTranslationUnit::PreviouslyParsed).id(); +} + +TimePoint TranslationUnits::parseTimePoint(const Utf8String &translationUnitId) +{ + return findUnit(translationUnitId).parseTimePoint; +} + +bool TranslationUnits::areAllTranslationUnitsParsed() const +{ + return Utils::allOf(m_tuDatas, [](const TranslationUnitData &unit) { + return unit.parseTimePoint != TimePoint(); + }); +} + +int TranslationUnits::size() const +{ + return m_tuDatas.size(); +} + +TranslationUnit TranslationUnits::getPreferredTranslationUnit(PreferredTranslationUnit type) +{ + using TuData = TranslationUnitData; + + const auto lessThan = [](const TuData &a, const TuData &b) { + return a.parseTimePoint < b.parseTimePoint; + }; + auto translationUnitData = type == PreferredTranslationUnit::RecentlyParsed + ? std::max_element(m_tuDatas.begin(), m_tuDatas.end(), lessThan) + : std::min_element(m_tuDatas.begin(), m_tuDatas.end(), lessThan); + + if (translationUnitData == m_tuDatas.end()) + throw TranslationUnitDoesNotExist(m_filePath); + + return toTranslationUnit(*translationUnitData); +} + +TranslationUnits::TranslationUnitData &TranslationUnits::findUnit( + const Utf8String &translationUnitId) +{ + for (TranslationUnitData &unit : m_tuDatas) { + if (translationUnitId == unit.id) + return unit; + } + + throw TranslationUnitDoesNotExist(m_filePath); +} + +TranslationUnit TranslationUnits::toTranslationUnit(TranslationUnits::TranslationUnitData &unit) +{ + return TranslationUnit(unit.id, + m_filePath, + unit.cxIndex, + unit.cxTranslationUnit); +} + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunits.h b/src/tools/clangbackend/ipcsource/clangtranslationunits.h new file mode 100644 index 00000000000..5a9d02f0e37 --- /dev/null +++ b/src/tools/clangbackend/ipcsource/clangtranslationunits.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 "clangbackend_global.h" +#include "clangclock.h" + +#include + +#include + +#include + +namespace ClangBackEnd { + +class TranslationUnit; + +class TranslationUnits +{ +public: + class TranslationUnitData { + public: + TranslationUnitData(const Utf8String &id) + : id(id) + {} + + Utf8String id; + + CXTranslationUnit cxTranslationUnit = nullptr; + CXIndex cxIndex = nullptr; + + TimePoint parseTimePoint; + }; + +public: + TranslationUnits(const Utf8String &filePath); + ~TranslationUnits(); + + TranslationUnit createAndAppend(); + TranslationUnit get(PreferredTranslationUnit type = PreferredTranslationUnit::RecentlyParsed); + void updateParseTimePoint(const Utf8String &translationUnitId, TimePoint timePoint); + + bool areAllTranslationUnitsParsed() const; + +public: // for tests + int size() const; + TimePoint parseTimePoint(const Utf8String &translationUnitId); + +private: + TranslationUnit getPreferredTranslationUnit(PreferredTranslationUnit type); + TranslationUnitData &findUnit(const Utf8String &translationUnitId); + TranslationUnit toTranslationUnit(TranslationUnitData &unit); + +private: + Utf8String m_filePath; + QList m_tuDatas; +}; + +} // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp index 51e34dc53c8..e65ce61fa1d 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp +++ b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.cpp @@ -40,13 +40,15 @@ static bool isVerboseModeEnabled() namespace ClangBackEnd { -TranslationUnitUpdater::TranslationUnitUpdater(CXIndex &index, +TranslationUnitUpdater::TranslationUnitUpdater(const Utf8String translationUnitId, + CXIndex &index, CXTranslationUnit &cxTranslationUnit, const TranslationUnitUpdateInput &updateData) : m_cxIndex(index) , m_cxTranslationUnit(cxTranslationUnit) , m_in(updateData) { + m_out.translationUnitId = translationUnitId; } TranslationUnitUpdateResult TranslationUnitUpdater::update(UpdateMode mode) @@ -122,7 +124,7 @@ void TranslationUnitUpdater::createTranslationUnitIfNeeded() if (parseWasSuccessful()) { updateIncludeFilePaths(); - m_out.parseTimePoint = std::chrono::steady_clock::now(); + m_out.parseTimePoint = Clock::now(); } else { qWarning() << "Parsing" << m_in.filePath << "failed:" << errorCodeToText(m_parseErrorCode); @@ -151,7 +153,7 @@ void TranslationUnitUpdater::reparse() if (reparseWasSuccessful()) { updateIncludeFilePaths(); - m_out.reparseTimePoint = std::chrono::steady_clock::now(); + m_out.reparseTimePoint = Clock::now(); m_out.needsToBeReparsedChangeTimePoint = m_in.needsToBeReparsedChangeTimePoint; } else { qWarning() << "Reparsing" << m_in.filePath << "failed:" << m_reparseErrorCode; diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h index 6802daf7eb6..547c6838bc3 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h +++ b/src/tools/clangbackend/ipcsource/clangtranslationunitupdater.h @@ -25,6 +25,8 @@ #pragma once +#include "clangclock.h" + #include "commandlinearguments.h" #include "unsavedfiles.h" #include "utf8stringvector.h" @@ -33,18 +35,14 @@ #include -#include - namespace ClangBackEnd { -using time_point = std::chrono::steady_clock::time_point; - class TranslationUnitUpdateInput { public: bool parseNeeded = false; bool reparseNeeded = false; - time_point needsToBeReparsedChangeTimePoint; + TimePoint needsToBeReparsedChangeTimePoint; Utf8String filePath; Utf8StringVector fileArguments; @@ -57,17 +55,18 @@ public: class TranslationUnitUpdateResult { public: bool hasParsed() const - { return parseTimePoint != time_point(); } + { return parseTimePoint != TimePoint(); } bool hasReparsed() const - { return reparseTimePoint != time_point(); } + { return reparseTimePoint != TimePoint(); } public: - bool hasParseOrReparseFailed = false; + Utf8String translationUnitId; - time_point parseTimePoint; - time_point reparseTimePoint; - time_point needsToBeReparsedChangeTimePoint; + bool hasParseOrReparseFailed = false; + TimePoint parseTimePoint; + TimePoint reparseTimePoint; + TimePoint needsToBeReparsedChangeTimePoint; QSet dependedOnFilePaths; }; @@ -81,7 +80,8 @@ public: }; public: - TranslationUnitUpdater(CXIndex &index, + TranslationUnitUpdater(const Utf8String translationUnitId, + CXIndex &index, CXTranslationUnit &cxTranslationUnit, const TranslationUnitUpdateInput &in); diff --git a/src/tools/clangbackend/ipcsource/clangtype.cpp b/src/tools/clangbackend/ipcsource/clangtype.cpp index 54fe1aba2d6..13ab8c9c6c0 100644 --- a/src/tools/clangbackend/ipcsource/clangtype.cpp +++ b/src/tools/clangbackend/ipcsource/clangtype.cpp @@ -69,7 +69,7 @@ bool Type::isReferencingConstant() const return (isPointer() || isLValueReference()) && pointeeType().isConstant(); } -bool Type::isOutputParameter() const +bool Type::isOutputArgument() const { return (isPointer() || isLValueReference()) && !pointeeType().isConstant(); } diff --git a/src/tools/clangbackend/ipcsource/clangtype.h b/src/tools/clangbackend/ipcsource/clangtype.h index e9a19e92db9..6a51245ce6b 100644 --- a/src/tools/clangbackend/ipcsource/clangtype.h +++ b/src/tools/clangbackend/ipcsource/clangtype.h @@ -49,7 +49,7 @@ public: bool isConstantPointer() const; bool isLValueReference() const; bool isReferencingConstant() const; - bool isOutputParameter() const; + bool isOutputArgument() const; Utf8String utf8Spelling() const; ClangString spelling() const; diff --git a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp index 8c78f60ff18..fee622964d8 100644 --- a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp +++ b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.cpp @@ -45,34 +45,36 @@ static UpdateDocumentAnnotationsJob::AsyncResult runAsyncHelper( asyncResult.updateResult = translationUnit.update(translationUnitUpdatInput); // Collect - translationUnit.extractDocumentAnnotations(asyncResult.diagnostics, + translationUnit.extractDocumentAnnotations(asyncResult.firstHeaderErrorDiagnostic, + asyncResult.diagnostics, asyncResult.highlightingMarks, asyncResult.skippedSourceRanges); return asyncResult; } -bool UpdateDocumentAnnotationsJob::prepareAsyncRun() +IAsyncJob::AsyncPrepareResult UpdateDocumentAnnotationsJob::prepareAsyncRun() { const JobRequest jobRequest = context().jobRequest; - QTC_ASSERT(jobRequest.type == JobRequest::Type::UpdateDocumentAnnotations, return false); + QTC_ASSERT(jobRequest.type == JobRequest::Type::UpdateDocumentAnnotations, + return AsyncPrepareResult()); try { m_pinnedDocument = context().documentForJobRequest(); m_pinnedFileContainer = m_pinnedDocument.fileContainer(); - const TranslationUnit translationUnit = m_pinnedDocument.translationUnit(); + const TranslationUnit translationUnit + = m_pinnedDocument.translationUnit(jobRequest.preferredTranslationUnit); const TranslationUnitUpdateInput updateInput = m_pinnedDocument.createUpdateInput(); setRunner([translationUnit, updateInput]() { return runAsyncHelper(translationUnit, updateInput); }); + return AsyncPrepareResult{translationUnit.id()}; } catch (const std::exception &exception) { qWarning() << "Error in UpdateDocumentAnnotationsJob::prepareAsyncRun:" << exception.what(); - return false; + return AsyncPrepareResult(); } - - return true; } void UpdateDocumentAnnotationsJob::finalizeAsyncRun() @@ -94,6 +96,7 @@ void UpdateDocumentAnnotationsJob::sendAnnotations(const AsyncResult &result) { const DocumentAnnotationsChangedMessage message(m_pinnedFileContainer, result.diagnostics, + result.firstHeaderErrorDiagnostic, result.highlightingMarks, result.skippedSourceRanges); diff --git a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h index c2ca0d13aa5..00fe3be3110 100644 --- a/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h +++ b/src/tools/clangbackend/ipcsource/clangupdatedocumentannotationsjob.h @@ -39,6 +39,7 @@ struct UpdateDocumentAnnotationsJobResult { TranslationUnitUpdateResult updateResult; + ClangBackEnd::DiagnosticContainer firstHeaderErrorDiagnostic; QVector diagnostics; QVector highlightingMarks; QVector skippedSourceRanges; @@ -49,7 +50,7 @@ class UpdateDocumentAnnotationsJob : public AsyncJob &outputArguments) + +bool isNotUnexposedLValueReference(const Cursor &argument, const Type &argumentType) { - auto callExpressionType = callExpression.referenced().type(); - auto argumentCount = callExpression.argumentCount(); - outputArguments.reserve(argumentCount); + return !(argument.isUnexposed() && argumentType.isLValueReference()); +} + +} + +void Cursor::collectOutputArgumentRangesTo(std::vector &outputArgumentRanges) const +{ + const Type callExpressionType = referenced().type(); + const int argumentCount = this->argumentCount(); + const std::size_t maxSize = std::size_t(std::max(0, argumentCount)) + + outputArgumentRanges.size(); + outputArgumentRanges.reserve(maxSize); for (int argumentIndex = 0; argumentIndex < argumentCount; ++argumentIndex) { - auto argument = callExpression.argument(argumentIndex); - auto argumentType = callExpressionType.argument(argumentIndex); + const Cursor argument = this->argument(argumentIndex); + const Type argumentType = callExpressionType.argument(argumentIndex); - if (!argument.isUnexposed() && argumentType.isOutputParameter()) - outputArguments.push_back(callExpression.argument(argumentIndex)); + if (isNotUnexposedLValueReference(argument, argumentType) + && argumentType.isOutputArgument()) { + outputArgumentRanges.push_back(argument.cxSourceRange()); + } } } -} -std::vector Cursor::outputArguments() const +std::vector Cursor::outputArgumentRanges() const { - std::vector outputArguments; + std::vector outputArgumentRanges; - if (kind() == CXCursor_CallExpr) - collectOutputArguments(*this, outputArguments); + collectOutputArgumentRangesTo(outputArgumentRanges); - return outputArguments; + return outputArgumentRanges; } CXCursorKind Cursor::kind() const @@ -317,6 +343,11 @@ bool operator==(const Cursor &first, const Cursor &second) return clang_equalCursors(first.cxCursor, second.cxCursor); } +bool operator!=(const Cursor &first, const Cursor &second) +{ + return !(first == second); +} + void PrintTo(CXCursorKind cursorKind, ::std::ostream *os) { ClangString cursorKindSpelling(clang_getCursorKindSpelling(cursorKind)); diff --git a/src/tools/clangbackend/ipcsource/cursor.h b/src/tools/clangbackend/ipcsource/cursor.h index 6711ab98d51..8f294c7e26c 100644 --- a/src/tools/clangbackend/ipcsource/cursor.h +++ b/src/tools/clangbackend/ipcsource/cursor.h @@ -78,8 +78,11 @@ public: Type nonPointerTupe() const; SourceLocation sourceLocation() const; + CXSourceLocation cxSourceLocation() const; SourceRange sourceRange() const; + CXSourceRange cxSourceRange() const; SourceRange commentRange() const; + bool hasSameSourceLocationAs(const Cursor &other) const; Cursor definition() const; Cursor canonical() const; @@ -90,7 +93,9 @@ public: Cursor functionBaseDeclaration() const; Cursor functionBase() const; Cursor argument(int index) const; - std::vector outputArguments() const; + void collectOutputArgumentRangesTo( + std::vector &outputArgumentRanges) const; + std::vector outputArgumentRanges() const; CXCursorKind kind() const; @@ -114,6 +119,7 @@ void Cursor::visit(VisitorCallback visitorCallback) const } bool operator==(const Cursor &first, const Cursor &second); +bool operator!=(const Cursor &first, const Cursor &second); void PrintTo(CXCursorKind cursorKind, ::std::ostream *os); void PrintTo(const Cursor &cursor, ::std::ostream* os); diff --git a/src/tools/clangbackend/ipcsource/highlightingmark.cpp b/src/tools/clangbackend/ipcsource/highlightingmark.cpp index 18ceca91579..d4744f78534 100644 --- a/src/tools/clangbackend/ipcsource/highlightingmark.cpp +++ b/src/tools/clangbackend/ipcsource/highlightingmark.cpp @@ -30,6 +30,7 @@ #include "highlightingmark.h" #include "sourcelocation.h" #include "sourcerange.h" +#include "sourcerangecontainer.h" #include #include @@ -39,16 +40,19 @@ namespace ClangBackEnd { HighlightingMark::HighlightingMark(const CXCursor &cxCursor, - CXToken *cxToken, - CXTranslationUnit cxTranslationUnit) + CXToken *cxToken, + CXTranslationUnit cxTranslationUnit, + std::vector ¤tOutputArgumentRanges) + : currentOutputArgumentRanges(¤tOutputArgumentRanges), + originalCursor(cxCursor) { const SourceRange sourceRange = clang_getTokenExtent(cxTranslationUnit, *cxToken); const auto start = sourceRange.start(); const auto end = sourceRange.end(); - originalCursor = cxCursor; line = start.line(); column = start.column(); + offset = start.offset(); length = end.offset() - start.offset(); collectKinds(cxToken, originalCursor); } @@ -159,6 +163,17 @@ void HighlightingMark::variableKind(const Cursor &cursor) types.mainHighlightingType = HighlightingType::LocalVariable; else types.mainHighlightingType = HighlightingType::GlobalVariable; + + if (isOutputArgument()) + types.mixinHighlightingTypes.push_back(HighlightingType::OutputArgument); +} + +void HighlightingMark::fieldKind(const Cursor &) +{ + types.mainHighlightingType = HighlightingType::Field; + + if (isOutputArgument()) + types.mixinHighlightingTypes.push_back(HighlightingType::OutputArgument); } bool HighlightingMark::isVirtualMethodDeclarationOrDefinition(const Cursor &cursor) const @@ -185,6 +200,68 @@ void HighlightingMark::addExtraTypeIfFirstPass(HighlightingType type, types.mixinHighlightingTypes.push_back(type); } +bool HighlightingMark::isArgumentInCurrentOutputArgumentLocations() const +{ + auto originalSourceLocation = originalCursor.cxSourceLocation(); + + const auto isNotSameOutputArgument = [&] (const CXSourceRange ¤tSourceRange) { + return !(originalSourceLocation.int_data >= currentSourceRange.begin_int_data + && originalSourceLocation.int_data <= currentSourceRange.end_int_data); + }; + + auto partitionPoint = std::partition(currentOutputArgumentRanges->begin(), + currentOutputArgumentRanges->end(), + isNotSameOutputArgument); + + bool isOutputArgument = partitionPoint != currentOutputArgumentRanges->end(); + + if (isOutputArgument) + currentOutputArgumentRanges->erase(partitionPoint, currentOutputArgumentRanges->end()); + + return isOutputArgument; +} + +bool HighlightingMark::isOutputArgument() const +{ + if (currentOutputArgumentRanges->empty()) + return false; + + return isArgumentInCurrentOutputArgumentLocations(); +} + +void HighlightingMark::collectOutputArguments(const Cursor &cursor) +{ + cursor.collectOutputArgumentRangesTo(*currentOutputArgumentRanges); + filterOutPreviousOutputArguments(); +} + +namespace { + +uint getStart(CXSourceRange cxSourceRange) +{ + CXSourceLocation startSourceLocation = clang_getRangeStart(cxSourceRange); + + uint startOffset; + + clang_getFileLocation(startSourceLocation, nullptr, nullptr, nullptr, &startOffset); + + return startOffset; +} +} + +void HighlightingMark::filterOutPreviousOutputArguments() +{ + auto isAfterLocation = [this] (CXSourceRange outputRange) { + return getStart(outputRange) > offset; + }; + + auto precedingBegin = std::partition(currentOutputArgumentRanges->begin(), + currentOutputArgumentRanges->end(), + isAfterLocation); + + currentOutputArgumentRanges->erase(precedingBegin, currentOutputArgumentRanges->end()); +} + void HighlightingMark::functionKind(const Cursor &cursor, Recursion recursion) { if (isRealDynamicCall(cursor) || isVirtualMethodDeclarationOrDefinition(cursor)) @@ -204,12 +281,13 @@ void HighlightingMark::identifierKind(const Cursor &cursor, Recursion recursion) case CXCursor_CallExpr: case CXCursor_CXXMethod: functionKind(cursor, recursion); break; case CXCursor_NonTypeTemplateParameter: - case CXCursor_ParmDecl: types.mainHighlightingType = HighlightingType::LocalVariable; break; + case CXCursor_CompoundStmt: types.mainHighlightingType = HighlightingType::LocalVariable; break; + case CXCursor_ParmDecl: case CXCursor_VarDecl: variableKind(cursor); break; case CXCursor_DeclRefExpr: identifierKind(cursor.referenced(), Recursion::RecursivePass); break; case CXCursor_MemberRefExpr: memberReferenceKind(cursor); break; case CXCursor_FieldDecl: - case CXCursor_MemberRef: + case CXCursor_MemberRef: fieldKind(cursor); break; case CXCursor_ObjCIvarDecl: case CXCursor_ObjCPropertyDecl: case CXCursor_ObjCClassMethodDecl: @@ -282,15 +360,17 @@ HighlightingType operatorKind(const Cursor &cursor) return HighlightingType::Invalid; } -HighlightingType punctationKind(const Cursor &cursor) +} + +HighlightingType HighlightingMark::punctuationKind(const Cursor &cursor) { switch (cursor.kind()) { case CXCursor_DeclRefExpr: return operatorKind(cursor); + case CXCursor_CallExpr: collectOutputArguments(cursor); default: return HighlightingType::Invalid; } } -} void HighlightingMark::collectKinds(CXToken *cxToken, const Cursor &cursor) { auto cxTokenKind = clang_getTokenKind(*cxToken); @@ -299,7 +379,7 @@ void HighlightingMark::collectKinds(CXToken *cxToken, const Cursor &cursor) switch (cxTokenKind) { case CXToken_Keyword: types.mainHighlightingType = HighlightingType::Keyword; break; - case CXToken_Punctuation: types.mainHighlightingType = punctationKind(cursor); break; + case CXToken_Punctuation: types.mainHighlightingType = punctuationKind(cursor); break; case CXToken_Identifier: identifierKind(cursor, Recursion::FirstPass); break; case CXToken_Comment: types.mainHighlightingType = HighlightingType::Comment; break; case CXToken_Literal: types.mainHighlightingType = literalKind(cursor); break; diff --git a/src/tools/clangbackend/ipcsource/highlightingmark.h b/src/tools/clangbackend/ipcsource/highlightingmark.h index 7f2009810b5..17408527305 100644 --- a/src/tools/clangbackend/ipcsource/highlightingmark.h +++ b/src/tools/clangbackend/ipcsource/highlightingmark.h @@ -45,7 +45,10 @@ class HighlightingMark }; public: - HighlightingMark(const CXCursor &cxCursor, CXToken *cxToken, CXTranslationUnit cxTranslationUnit); + HighlightingMark(const CXCursor &cxCursor, + CXToken *cxToken, + CXTranslationUnit cxTranslationUnit, + std::vector ¤tOutputArgumentRanges); HighlightingMark(uint line, uint column, uint length, HighlightingTypes types); HighlightingMark(uint line, uint column, uint length, HighlightingType type); @@ -61,18 +64,26 @@ private: void identifierKind(const Cursor &cursor, Recursion recursion); void referencedTypeKind(const Cursor &cursor); void variableKind(const Cursor &cursor); + void fieldKind(const Cursor &cursor); bool isVirtualMethodDeclarationOrDefinition(const Cursor &cursor) const; void functionKind(const Cursor &cursor, Recursion recursion); void memberReferenceKind(const Cursor &cursor); + HighlightingType punctuationKind(const Cursor &cursor); void collectKinds(CXToken *cxToken, const Cursor &cursor); bool isRealDynamicCall(const Cursor &cursor) const; void addExtraTypeIfFirstPass(HighlightingType type, Recursion recursion); + bool isOutputArgument() const; + void collectOutputArguments(const Cursor &cursor); + void filterOutPreviousOutputArguments(); + bool isArgumentInCurrentOutputArgumentLocations() const; private: + std::vector *currentOutputArgumentRanges = nullptr; Cursor originalCursor; uint line; uint column; uint length; + uint offset = 0; HighlightingTypes types; }; diff --git a/src/tools/clangbackend/ipcsource/highlightingmarks.cpp b/src/tools/clangbackend/ipcsource/highlightingmarks.cpp index 10cb2b5e82b..725e6eb3169 100644 --- a/src/tools/clangbackend/ipcsource/highlightingmarks.cpp +++ b/src/tools/clangbackend/ipcsource/highlightingmarks.cpp @@ -47,12 +47,18 @@ HighlightingMarks::~HighlightingMarks() HighlightingMarks::const_iterator HighlightingMarks::begin() const { - return const_iterator(cxCursor.cbegin(), cxToken, cxTranslationUnit); + return const_iterator(cxCursor.cbegin(), + cxToken, + cxTranslationUnit, + currentOutputArgumentRanges); } HighlightingMarks::const_iterator HighlightingMarks::end() const { - return const_iterator(cxCursor.cend(), cxToken + cxTokenCount, cxTranslationUnit); + return const_iterator(cxCursor.cend(), + cxToken + cxTokenCount, + cxTranslationUnit, + currentOutputArgumentRanges); } QVector HighlightingMarks::toHighlightingMarksContainers() const @@ -67,11 +73,19 @@ QVector HighlightingMarks::toHighlightingMarksContain && !highlightMark.hasMainType(HighlightingType::Comment); }; - std::copy_if(begin(), end(), std::back_inserter(containers), isValidHighlightMark); + for (const HighlightingMark &highlightMark : *this) { + if (isValidHighlightMark(highlightMark)) + containers.push_back(highlightMark); + } return containers; } +bool HighlightingMarks::currentOutputArgumentRangesAreEmpty() const +{ + return currentOutputArgumentRanges.empty(); +} + bool HighlightingMarks::isEmpty() const { return cxTokenCount == 0; @@ -89,7 +103,10 @@ uint HighlightingMarks::size() const HighlightingMark HighlightingMarks::operator[](size_t index) const { - return HighlightingMark(cxCursor[index], cxToken + index, cxTranslationUnit); + return HighlightingMark(cxCursor[index], + cxToken + index, + cxTranslationUnit, + currentOutputArgumentRanges); } } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/highlightingmarks.h b/src/tools/clangbackend/ipcsource/highlightingmarks.h index 2d77bb86c53..6904bb31847 100644 --- a/src/tools/clangbackend/ipcsource/highlightingmarks.h +++ b/src/tools/clangbackend/ipcsource/highlightingmarks.h @@ -58,7 +58,10 @@ public: QVector toHighlightingMarksContainers() const; + bool currentOutputArgumentRangesAreEmpty() const; + private: + mutable std::vector currentOutputArgumentRanges; CXTranslationUnit cxTranslationUnit = nullptr; CXToken *const cxToken = nullptr; const uint cxTokenCount = 0; diff --git a/src/tools/clangbackend/ipcsource/highlightingmarksiterator.h b/src/tools/clangbackend/ipcsource/highlightingmarksiterator.h index 9cbfcabaad6..daeada47a76 100644 --- a/src/tools/clangbackend/ipcsource/highlightingmarksiterator.h +++ b/src/tools/clangbackend/ipcsource/highlightingmarksiterator.h @@ -43,11 +43,13 @@ class HighlightingMarksIterator : public std::iterator::const_iterator cxCursorIterator, - CXToken *cxToken, - CXTranslationUnit cxTranslationUnit) + CXToken *cxToken, + CXTranslationUnit cxTranslationUnit, + std::vector ¤tOutputArgumentRanges) : cxCursorIterator(cxCursorIterator), cxToken(cxToken), - cxTranslationUnit(cxTranslationUnit) + cxTranslationUnit(cxTranslationUnit), + currentOutputArgumentRanges(currentOutputArgumentRanges) {} HighlightingMarksIterator& operator++() @@ -60,7 +62,10 @@ public: HighlightingMarksIterator operator++(int) { - return HighlightingMarksIterator(cxCursorIterator++, cxToken++, cxTranslationUnit); + return HighlightingMarksIterator(cxCursorIterator++, + cxToken++, + cxTranslationUnit, + currentOutputArgumentRanges); } bool operator==(HighlightingMarksIterator other) const @@ -75,13 +80,17 @@ public: HighlightingMark operator*() { - return HighlightingMark(*cxCursorIterator, cxToken, cxTranslationUnit); + return HighlightingMark(*cxCursorIterator, + cxToken, + cxTranslationUnit, + currentOutputArgumentRanges); } private: std::vector::const_iterator cxCursorIterator; CXToken *cxToken; CXTranslationUnit cxTranslationUnit; + std::vector ¤tOutputArgumentRanges; }; } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/projectpart.cpp b/src/tools/clangbackend/ipcsource/projectpart.cpp index b9060df2975..556aa4847c9 100644 --- a/src/tools/clangbackend/ipcsource/projectpart.cpp +++ b/src/tools/clangbackend/ipcsource/projectpart.cpp @@ -39,13 +39,13 @@ public: ~ProjectPartData(); public: - time_point lastChangeTimePoint; + TimePoint lastChangeTimePoint; Utf8StringVector arguments; Utf8String projectPartId; }; ProjectPartData::ProjectPartData(const Utf8String &projectPartId) - : lastChangeTimePoint(std::chrono::steady_clock::now()), + : lastChangeTimePoint(Clock::now()), projectPartId(projectPartId) { } @@ -111,14 +111,14 @@ const Utf8StringVector ProjectPart::arguments() const return d->arguments; } -const time_point &ProjectPart::lastChangeTimePoint() const +const TimePoint &ProjectPart::lastChangeTimePoint() const { return d->lastChangeTimePoint; } void ProjectPart::updateLastChangeTimePoint() { - d->lastChangeTimePoint = std::chrono::steady_clock::now(); + d->lastChangeTimePoint = Clock::now(); } bool operator==(const ProjectPart &first, const ProjectPart &second) diff --git a/src/tools/clangbackend/ipcsource/projectpart.h b/src/tools/clangbackend/ipcsource/projectpart.h index d359751c4cc..9cbea8e563a 100644 --- a/src/tools/clangbackend/ipcsource/projectpart.h +++ b/src/tools/clangbackend/ipcsource/projectpart.h @@ -25,9 +25,10 @@ #pragma once +#include "clangclock.h" + #include -#include #include class Utf8StringVector; @@ -37,8 +38,6 @@ namespace ClangBackEnd { class ProjectPartContainer; class ProjectPartData; -using time_point = std::chrono::steady_clock::time_point; - class ProjectPart { public: @@ -61,7 +60,7 @@ public: void setArguments(const Utf8StringVector &arguments_); const Utf8StringVector arguments() const; - const time_point &lastChangeTimePoint() const; + const TimePoint &lastChangeTimePoint() const; private: void updateLastChangeTimePoint(); diff --git a/src/tools/clangbackend/ipcsource/unsavedfiles.cpp b/src/tools/clangbackend/ipcsource/unsavedfiles.cpp index 5224e14e9a1..64f2afe3ff0 100644 --- a/src/tools/clangbackend/ipcsource/unsavedfiles.cpp +++ b/src/tools/clangbackend/ipcsource/unsavedfiles.cpp @@ -40,12 +40,12 @@ public: UnsavedFilesData(); public: - time_point lastChangeTimePoint; + TimePoint lastChangeTimePoint; QVector unsavedFiles; }; UnsavedFilesData::UnsavedFilesData() - : lastChangeTimePoint(std::chrono::steady_clock::now()) + : lastChangeTimePoint(Clock::now()) { } @@ -116,7 +116,7 @@ UnsavedFilesShallowArguments UnsavedFiles::shallowArguments() const return UnsavedFilesShallowArguments(*this); } -const time_point UnsavedFiles::lastChangeTimePoint() const +const TimePoint UnsavedFiles::lastChangeTimePoint() const { return d->lastChangeTimePoint; } @@ -154,7 +154,7 @@ void UnsavedFiles::addOrUpdateUnsavedFile(const FileContainer &fileContainer) void UnsavedFiles::updateLastChangeTimePoint() { - d->lastChangeTimePoint = std::chrono::steady_clock::now(); + d->lastChangeTimePoint = Clock::now(); } } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/ipcsource/unsavedfiles.h b/src/tools/clangbackend/ipcsource/unsavedfiles.h index 541d6c3a6a9..eacab0bc342 100644 --- a/src/tools/clangbackend/ipcsource/unsavedfiles.h +++ b/src/tools/clangbackend/ipcsource/unsavedfiles.h @@ -25,6 +25,8 @@ #pragma once +#include "clangclock.h" + #include #include @@ -32,12 +34,8 @@ #include -#include - namespace ClangBackEnd { -using time_point = std::chrono::steady_clock::time_point; - class UnsavedFile; class UnsavedFilesData; class UnsavedFilesShallowArguments; @@ -61,7 +59,7 @@ public: UnsavedFilesShallowArguments shallowArguments() const; - const time_point lastChangeTimePoint() const; + const TimePoint lastChangeTimePoint() const; private: void updateUnsavedFileWithFileContainer(const FileContainer &fileContainer); diff --git a/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp b/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp index 16559edc457..e51355826fa 100644 --- a/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp +++ b/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp @@ -44,6 +44,7 @@ #include #include +#include #include #include @@ -1663,7 +1664,7 @@ void generateASTPatternBuilder_h(const QDir &cplusplusDir) } QStringList classesList = classesSet.toList(); - qSort(classesList); + Utils::sort(classesList); foreach (const QString &className, classesList) { const QString methodName = className.left(className.length() - 3); const QString elementName = className.left(className.length() - 7) + QLatin1String("AST"); diff --git a/tests/unit/unittest/clangcompareoperators.h b/tests/unit/unittest/clangcompareoperators.h new file mode 100644 index 00000000000..39351946beb --- /dev/null +++ b/tests/unit/unittest/clangcompareoperators.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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 + +inline +bool operator==(const CXSourceLocation &first, const CXSourceLocation &second) +{ + return clang_equalLocations(first, second); +} + +inline +bool operator==(const CXSourceRange &first, const CXSourceRange &second) +{ + return clang_equalRanges(first, second); +} diff --git a/tests/unit/unittest/clangdocument-test.cpp b/tests/unit/unittest/clangdocument-test.cpp index 010964fb2fd..a6a32dcf939 100644 --- a/tests/unit/unittest/clangdocument-test.cpp +++ b/tests/unit/unittest/clangdocument-test.cpp @@ -25,8 +25,11 @@ #include "googletest.h" +#include #include #include +#include +#include #include #include #include @@ -45,9 +48,10 @@ #include -#include #include +using ClangBackEnd::Clock; +using ClangBackEnd::Duration; using ClangBackEnd::FileContainer; using ClangBackEnd::FilePath; using ClangBackEnd::Document; @@ -56,6 +60,8 @@ using ClangBackEnd::ProjectPart; using ClangBackEnd::ProjectPartContainer; using ClangBackEnd::Documents; using ClangBackEnd::TranslationUnitUpdateResult; +using ClangBackEnd::TranslationUnit; +using ClangBackEnd::TranslationUnits; using testing::IsNull; using testing::NotNull; @@ -161,7 +167,7 @@ TEST_F(Document, LastCommandLineArgumentIsFilePath) TEST_F(Document, TimeStampForProjectPartChangeIsUpdatedAsNewCxTranslationUnitIsGenerated) { auto lastChangeTimePoint = document.lastProjectPartChangeTimePoint(); - std::this_thread::sleep_for(std::chrono::steady_clock::duration(1)); + std::this_thread::sleep_for(Duration(1)); document.parse(); @@ -173,7 +179,7 @@ TEST_F(Document, TimeStampForProjectPartChangeIsUpdatedAsProjectPartIsCleared) ProjectPart projectPart = document.projectPart(); document.parse(); auto lastChangeTimePoint = document.lastProjectPartChangeTimePoint(); - std::this_thread::sleep_for(std::chrono::steady_clock::duration(1)); + std::this_thread::sleep_for(Duration(1)); projectPart.clear(); document.parse(); @@ -330,8 +336,9 @@ TEST_F(Document, IncorporateUpdaterResultResetsDirtyness) { document.setDirtyIfDependencyIsMet(document.filePath()); TranslationUnitUpdateResult result; - result.reparseTimePoint = std::chrono::steady_clock::now(); + result.reparseTimePoint = Clock::now(); result.needsToBeReparsedChangeTimePoint = document.isNeededReparseChangeTimePoint(); + result.translationUnitId = document.translationUnit().id(); document.incorporateUpdaterResult(result); @@ -341,8 +348,9 @@ TEST_F(Document, IncorporateUpdaterResultResetsDirtyness) TEST_F(Document, IncorporateUpdaterResultDoesNotResetDirtynessIfItWasChanged) { TranslationUnitUpdateResult result; - result.reparseTimePoint = std::chrono::steady_clock::now(); - result.needsToBeReparsedChangeTimePoint = std::chrono::steady_clock::now(); + result.reparseTimePoint = Clock::now(); + result.needsToBeReparsedChangeTimePoint = Clock::now(); + result.translationUnitId = document.translationUnit().id(); document.setDirtyIfDependencyIsMet(document.filePath()); document.incorporateUpdaterResult(result); @@ -350,6 +358,25 @@ TEST_F(Document, IncorporateUpdaterResultDoesNotResetDirtynessIfItWasChanged) ASSERT_TRUE(document.isNeedingReparse()); } +TEST_F(Document, IncorporateUpdaterResultUpdatesTranslationUnitsReparseTimePoint) +{ + TranslationUnits &translationUnits = document.translationUnits(); + const TranslationUnit initialTranslationUnit = translationUnits.get(); + translationUnits.updateParseTimePoint(initialTranslationUnit.id(), Clock::now()); + const TranslationUnit alternativeTranslationUnit = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(alternativeTranslationUnit.id(), Clock::now()); + TranslationUnitUpdateResult result; + result.reparseTimePoint = Clock::now(); + result.needsToBeReparsedChangeTimePoint = Clock::now(); + result.translationUnitId = initialTranslationUnit.id(); + document.setDirtyIfDependencyIsMet(document.filePath()); + ASSERT_THAT(translationUnits.get().id(), Eq(alternativeTranslationUnit.id())); + + document.incorporateUpdaterResult(result); + + ASSERT_THAT(translationUnits.get().id(), Eq(initialTranslationUnit.id())); +} + void Document::SetUp() { projects.createOrUpdate({ProjectPartContainer(projectPartId)}); diff --git a/tests/unit/unittest/clangdocumentprocessor-test.cpp b/tests/unit/unittest/clangdocumentprocessor-test.cpp new file mode 100644 index 00000000000..1674b224781 --- /dev/null +++ b/tests/unit/unittest/clangdocumentprocessor-test.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 "clangiasyncjob.h" +#include "dummyclangipcclient.h" +#include "processevents-utilities.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using namespace ClangBackEnd; + +namespace { + +class DocumentProcessor : public ::testing::Test +{ +protected: + void SetUp() override; + void TearDown() override; + + ClangBackEnd::JobRequest createJobRequest(ClangBackEnd::JobRequest::Type type) const; + + bool waitUntilAllJobsFinished(int timeOutInMs = 10000) const; + +protected: + ClangBackEnd::ProjectParts projects; + ClangBackEnd::UnsavedFiles unsavedFiles; + ClangBackEnd::Documents documents{projects, unsavedFiles}; + ClangBackEnd::Document document; + + DummyIpcClient dummyIpcClient; + + Utf8String filePath{Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp")}; + Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")}; + + ClangBackEnd::DocumentProcessor documentProcessor{document, + documents, + unsavedFiles, + projects, + dummyIpcClient}; +}; + +TEST_F(DocumentProcessor, ProcessEmpty) +{ + const JobRequests jobsStarted = documentProcessor.process(); + + ASSERT_THAT(jobsStarted.size(), 0); +} + +TEST_F(DocumentProcessor, ProcessSingleJob) +{ + const JobRequest jobRequest = createJobRequest(JobRequest::Type::UpdateDocumentAnnotations); + documentProcessor.addJob(jobRequest); + + const JobRequests jobsStarted = documentProcessor.process(); + + ASSERT_THAT(jobsStarted.size(), 1); +} + +void DocumentProcessor::SetUp() +{ + projects.createOrUpdate({ProjectPartContainer(projectPartId)}); + + const QVector fileContainer{FileContainer(filePath, projectPartId)}; + document = documents.create(fileContainer).front(); + documents.setVisibleInEditors({filePath}); + documents.setUsedByCurrentEditor(filePath); +} + +void DocumentProcessor::TearDown() +{ + ASSERT_TRUE(waitUntilAllJobsFinished()); // QFuture/QFutureWatcher is implemented with events +} + +JobRequest DocumentProcessor::createJobRequest(JobRequest::Type type) const +{ + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = filePath; + jobRequest.projectPartId = projectPartId; + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = document.documentRevision(); + jobRequest.projectChangeTimePoint = projects.project(projectPartId).lastChangeTimePoint(); + + return jobRequest; +} + +bool DocumentProcessor::waitUntilAllJobsFinished(int timeOutInMs) const +{ + const auto noJobsRunningAnymore = [this](){ return documentProcessor.runningJobs().isEmpty(); }; + + return ProcessEventUtilities::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); +} + +} // anonymous diff --git a/tests/unit/unittest/clangdocumentprocessors-test.cpp b/tests/unit/unittest/clangdocumentprocessors-test.cpp new file mode 100644 index 00000000000..7c36146aaba --- /dev/null +++ b/tests/unit/unittest/clangdocumentprocessors-test.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** 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 "clangiasyncjob.h" +#include "dummyclangipcclient.h" +#include "processevents-utilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using testing::Eq; + +using namespace ClangBackEnd; + +namespace { + +class DocumentProcessors : public ::testing::Test +{ +protected: + void SetUp() override; + void TearDown() override; + + ClangBackEnd::JobRequest createJobRequest(ClangBackEnd::JobRequest::Type type) const; + + bool waitUntilAllJobsFinished(int timeOutInMs = 10000) const; + +protected: + ClangBackEnd::ProjectParts projects; + ClangBackEnd::UnsavedFiles unsavedFiles; + ClangBackEnd::Documents documents{projects, unsavedFiles}; + ClangBackEnd::Document document; + + DummyIpcClient dummyIpcClient; + + Utf8String filePath{Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp")}; + Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")}; + + ClangBackEnd::JobRequest jobRequest; + ClangBackEnd::JobContext jobContext; + + ClangBackEnd::DocumentProcessors documentProcessors{documents, + unsavedFiles, + projects, + dummyIpcClient}; +}; + +TEST_F(DocumentProcessors, HasNoItemsInitially) +{ + ASSERT_TRUE(documentProcessors.processors().empty()); +} + +TEST_F(DocumentProcessors, CreateAddsADocumentProcessor) +{ + documentProcessors.create(document); + + ASSERT_THAT(documentProcessors.processors().size(), Eq(1)); +} + +TEST_F(DocumentProcessors, CreateReturnsDocumentProcessor) +{ + const DocumentProcessor documentProcessor = documentProcessors.create(document); + + ASSERT_THAT(documentProcessor.document(), Eq(document)); +} + +TEST_F(DocumentProcessors, CreateThrowsForAlreadyExisting) +{ + documentProcessors.create(document); + + ASSERT_THROW(documentProcessors.create(document), + ClangBackEnd::DocumentProcessorAlreadyExists); +} + +TEST_F(DocumentProcessors, Access) +{ + documentProcessors.create(document); + + const DocumentProcessor documentProcessor = documentProcessors.processor(document); + + ASSERT_THAT(documentProcessor.document(), Eq(document)); +} + +TEST_F(DocumentProcessors, AccessThrowsForNotExisting) +{ + ASSERT_THROW(documentProcessors.processor(document), + ClangBackEnd::DocumentProcessorDoesNotExist); +} + +TEST_F(DocumentProcessors, Remove) +{ + documentProcessors.create(document); + + documentProcessors.remove(document); + + ASSERT_TRUE(documentProcessors.processors().empty()); +} + +TEST_F(DocumentProcessors, RemoveThrowsForNotExisting) +{ + ASSERT_THROW(documentProcessors.remove(document), + ClangBackEnd::DocumentProcessorDoesNotExist); +} + +TEST_F(DocumentProcessors, ProcessEmpty) +{ + documentProcessors.create(document); + + const JobRequests jobsStarted = documentProcessors.process(); + + ASSERT_TRUE(jobsStarted.isEmpty()); +} + +TEST_F(DocumentProcessors, ProcessSingle) +{ + DocumentProcessor documentProcessor = documentProcessors.create(document); + const JobRequest jobRequest = createJobRequest(JobRequest::Type::UpdateDocumentAnnotations); + documentProcessor.addJob(jobRequest); + + const JobRequests jobsStarted = documentProcessors.process(); + + ASSERT_THAT(jobsStarted.size(), 1); +} + +void DocumentProcessors::SetUp() +{ + projects.createOrUpdate({ProjectPartContainer(projectPartId)}); + + const QVector fileContainer{FileContainer(filePath, projectPartId)}; + document = documents.create(fileContainer).front(); + documents.setVisibleInEditors({filePath}); + documents.setUsedByCurrentEditor(filePath); +} + +void DocumentProcessors::TearDown() +{ + ASSERT_TRUE(waitUntilAllJobsFinished()); // QFuture/QFutureWatcher is implemented with events +} + +JobRequest DocumentProcessors::createJobRequest(JobRequest::Type type) const +{ + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = filePath; + jobRequest.projectPartId = projectPartId; + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = document.documentRevision(); + jobRequest.projectChangeTimePoint = projects.project(projectPartId).lastChangeTimePoint(); + + return jobRequest; +} + +bool DocumentProcessors::waitUntilAllJobsFinished(int timeOutInMs) const +{ + const auto noJobsRunningAnymore = [this](){ return documentProcessors.runningJobs().isEmpty(); }; + + return ProcessEventUtilities::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); +} + +} // anonymous diff --git a/tests/unit/unittest/clangdocuments-test.cpp b/tests/unit/unittest/clangdocuments-test.cpp index 0be8c6281a2..58159924413 100644 --- a/tests/unit/unittest/clangdocuments-test.cpp +++ b/tests/unit/unittest/clangdocuments-test.cpp @@ -43,6 +43,7 @@ using ClangBackEnd::ProjectPartContainer; using testing::IsNull; using testing::NotNull; using testing::Gt; +using testing::Eq; using testing::Not; using testing::Contains; @@ -158,6 +159,18 @@ TEST_F(Documents, UpdateSingle) IsDocument(filePath, projectPartId, 75u)); } +TEST_F(Documents, UpdateReturnsUpdatedDocument) +{ + ClangBackEnd::FileContainer createFileContainer(filePath, projectPartId, Utf8StringVector(), 74u); + ClangBackEnd::FileContainer updateFileContainer(filePath, Utf8String(), Utf8StringVector(), 75u); + documents.create({createFileContainer}); + + const std::vector updatedDocuments = documents.update({updateFileContainer}); + + ASSERT_THAT(updatedDocuments.size(), Eq(1u)); + ASSERT_THAT(updatedDocuments.front().documentRevision(), Eq(75u)); +} + TEST_F(Documents, UpdateMultiple) { ClangBackEnd::FileContainer fileContainer(filePath, projectPartId, Utf8StringVector(), 74u); diff --git a/tests/unit/unittest/clangipcserver-test.cpp b/tests/unit/unittest/clangipcserver-test.cpp index 44e631323af..bcff6e95327 100644 --- a/tests/unit/unittest/clangipcserver-test.cpp +++ b/tests/unit/unittest/clangipcserver-test.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,7 @@ #include #include +#include #include using testing::Property; @@ -135,6 +137,8 @@ protected: void completeCodeInFileA(); void completeCodeInFileB(); + bool isSupportiveTranslationUnitInitialized(const Utf8String &filePath); + void expectDocumentAnnotationsChanged(int count); void expectCompletion(const CodeCompletion &completion); void expectCompletionFromFileA(); @@ -286,6 +290,64 @@ TEST_F(ClangClangCodeModelServer, SetCurrentAndVisibleEditor) ASSERT_TRUE(functionDocument.isVisibleInEditor()); } +TEST_F(ClangClangCodeModelServer, StartCompletionJobFirstOnEditThatTriggersCompletion) +{ + registerProjectAndFile(filePathA, 2); + ASSERT_TRUE(waitUntilAllJobsFinished()); + expectCompletionFromFileA(); + + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1); + completeCodeInFileA(); + + const QList jobs = clangServer.runningJobsForTestsOnly(); + ASSERT_THAT(jobs.size(), Eq(1)); + ASSERT_THAT(jobs.first().jobRequest.type, Eq(JobRequest::Type::CompleteCode)); +} + +TEST_F(ClangClangCodeModelServer, SupportiveTranslationUnitNotInitializedAfterRegister) +{ + registerProjectAndFile(filePathA, 1); + + ASSERT_TRUE(waitUntilAllJobsFinished()); + ASSERT_FALSE(isSupportiveTranslationUnitInitialized(filePathA)); +} + +TEST_F(ClangClangCodeModelServer, SupportiveTranslationUnitIsSetupAfterFirstEdit) +{ + registerProjectAndFile(filePathA, 2); + ASSERT_TRUE(waitUntilAllJobsFinished()); + + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1); + + ASSERT_TRUE(waitUntilAllJobsFinished()); + ASSERT_TRUE(isSupportiveTranslationUnitInitialized(filePathA)); +} + +TEST_F(ClangClangCodeModelServer, DoNotRunDuplicateJobs) +{ + registerProjectAndFile(filePathA, 3); + ASSERT_TRUE(waitUntilAllJobsFinished()); + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 1); + ASSERT_TRUE(waitUntilAllJobsFinished()); + ASSERT_TRUE(isSupportiveTranslationUnitInitialized(filePathA)); + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), 2); + QCoreApplication::processEvents(); // adds + runs a job + updateVisibilty(Utf8String(), Utf8String()); + + updateVisibilty(filePathA, filePathA); // triggers adding + runnings job on next processevents() +} + +TEST_F(ClangClangCodeModelServer, OpenDocumentAndEdit) +{ + registerProjectAndFile(filePathA, 4); + ASSERT_TRUE(waitUntilAllJobsFinished()); + + for (unsigned revision = 1; revision <= 3; ++revision) { + updateUnsavedContent(filePathA, unsavedContent(filePathAUnsavedVersion2), revision); + ASSERT_TRUE(waitUntilAllJobsFinished()); + } +} + TEST_F(ClangClangCodeModelServer, IsNotCurrentCurrentAndVisibleEditorAnymore) { registerProjectAndFilesAndWaitForFinished(); @@ -323,8 +385,8 @@ void ClangClangCodeModelServer::TearDown() bool ClangClangCodeModelServer::waitUntilAllJobsFinished(int timeOutInMs) { const auto noJobsRunningAnymore = [this]() { - return clangServer.jobsForTestOnly().runningJobs() == 0 - && clangServer.jobsForTestOnly().queue().size() == 0 + return clangServer.runningJobsForTestsOnly().isEmpty() + && clangServer.queueSizeForTestsOnly() == 0 && !clangServer.isTimerRunningForTestOnly(); }; @@ -411,6 +473,16 @@ void ClangClangCodeModelServer::completeCodeInFileB() completeCode(filePathB, 35, 1); } +bool ClangClangCodeModelServer::isSupportiveTranslationUnitInitialized(const Utf8String &filePath) +{ + Document document = clangServer.documentsForTestOnly().document(filePath, projectPartId); + DocumentProcessor documentProcessor = clangServer.documentProcessors().processor(document); + + return document.translationUnits().size() == 2 + && documentProcessor.hasSupportiveTranslationUnit() + && documentProcessor.isSupportiveTranslationUnitInitialized(); +} + void ClangClangCodeModelServer::expectCompletion(const CodeCompletion &completion) { EXPECT_CALL(mockClangCodeModelClient, diff --git a/tests/unit/unittest/clangjobqueue-test.cpp b/tests/unit/unittest/clangjobqueue-test.cpp index b3fbafb7534..85ebfe5a1a8 100644 --- a/tests/unit/unittest/clangjobqueue-test.cpp +++ b/tests/unit/unittest/clangjobqueue-test.cpp @@ -28,6 +28,8 @@ #include #include +#include +#include #include #include #include @@ -60,7 +62,9 @@ protected: Utf8String createTranslationUnitForDeletedFile(); JobRequest createJobRequest(const Utf8String &filePath, - JobRequest::Type type) const; + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit + = PreferredTranslationUnit::RecentlyParsed) const; void updateDocumentRevision(); void updateUnsavedFiles(); @@ -91,6 +95,29 @@ TEST_F(JobQueue, AddJob) ASSERT_THAT(jobQueue.queue().size(), Eq(1)); } +TEST_F(JobQueue, DoNotAddDuplicate) +{ + const JobRequest request = createJobRequest(filePath1, + JobRequest::Type::UpdateDocumentAnnotations); + jobQueue.add(request); + + const bool added = jobQueue.add(request); + + ASSERT_FALSE(added); +} + +TEST_F(JobQueue, DoNotAddDuplicateForWhichAJobIsAlreadyRunning) +{ + jobQueue.setIsJobRunningForJobRequestHandler([](const JobRequest &) { + return true; + }); + + const bool added = jobQueue.add(createJobRequest(filePath1, + JobRequest::Type::UpdateDocumentAnnotations)); + + ASSERT_FALSE(added); +} + TEST_F(JobQueue, ProcessEmpty) { jobQueue.processQueue(); @@ -111,7 +138,7 @@ TEST_F(JobQueue, ProcessSingleJob) TEST_F(JobQueue, ProcessUntilEmpty) { jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); - jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CreateInitialDocumentPreamble)); JobRequests jobsToRun; ASSERT_THAT(jobQueue.size(), Eq(2)); @@ -231,7 +258,7 @@ TEST_F(JobQueue, PrioritizeCurrentDocumentOverVisible) TEST_F(JobQueue, RunNothingForNotCurrentOrVisibleDocument) { jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); - jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CreateInitialDocumentPreamble)); documents.setVisibleInEditors({}); documents.setUsedByCurrentEditor(Utf8StringLiteral("aNonExistingFilePath")); @@ -240,10 +267,10 @@ TEST_F(JobQueue, RunNothingForNotCurrentOrVisibleDocument) ASSERT_THAT(jobsToRun.size(), Eq(0)); } -TEST_F(JobQueue, RunOnlyOneJobPerDocumentIfMultipleAreInQueue) +TEST_F(JobQueue, RunOnlyOneJobPerTranslationUnitIfMultipleAreInQueue) { jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); - jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CreateInitialDocumentPreamble)); const JobRequests jobsToRun = jobQueue.processQueue(); @@ -251,12 +278,30 @@ TEST_F(JobQueue, RunOnlyOneJobPerDocumentIfMultipleAreInQueue) ASSERT_THAT(jobQueue.size(), Eq(1)); } -TEST_F(JobQueue, DoNotRunJobForDocumentThatIsBeingProcessed) +TEST_F(JobQueue, RunJobsForDistinctTranslationUnits) +{ + const TranslationUnit initialTu = document.translationUnit(); + document.translationUnits().updateParseTimePoint(initialTu.id(), std::chrono::steady_clock::now()); + const TranslationUnit alternativeTu = document.translationUnits().createAndAppend(); + document.translationUnits().updateParseTimePoint(alternativeTu.id(), std::chrono::steady_clock::now()); + jobQueue.add(createJobRequest(filePath1, + JobRequest::Type::UpdateDocumentAnnotations, + PreferredTranslationUnit::RecentlyParsed)); + jobQueue.add(createJobRequest(filePath1, + JobRequest::Type::UpdateDocumentAnnotations, + PreferredTranslationUnit::PreviouslyParsed)); + + const JobRequests jobsToRun = jobQueue.processQueue(); + + ASSERT_THAT(jobsToRun.size(), Eq(2)); + ASSERT_THAT(jobQueue.size(), Eq(0)); +} +TEST_F(JobQueue, DoNotRunJobForTranslationUnittThatIsBeingProcessed) { jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); - jobQueue.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); + jobQueue.add(createJobRequest(filePath1, JobRequest::Type::CreateInitialDocumentPreamble)); JobRequests jobsToRun = jobQueue.processQueue(); - jobQueue.setIsJobRunningHandler([](const Utf8String &, const Utf8String &) { + jobQueue.setIsJobRunningForTranslationUnitHandler([](const Utf8String &) { return true; }); @@ -397,8 +442,10 @@ Utf8String JobQueue::createTranslationUnitForDeletedFile() return temporaryFilePath; } -JobRequest JobQueue::createJobRequest(const Utf8String &filePath, - JobRequest::Type type) const +JobRequest JobQueue::createJobRequest( + const Utf8String &filePath, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit) const { JobRequest jobRequest; jobRequest.type = type; @@ -407,6 +454,7 @@ JobRequest JobQueue::createJobRequest(const Utf8String &filePath, jobRequest.projectPartId = projectPartId; jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); jobRequest.documentRevision = document.documentRevision(); + jobRequest.preferredTranslationUnit = preferredTranslationUnit; jobRequest.projectChangeTimePoint = projects.project(projectPartId).lastChangeTimePoint(); return jobRequest; diff --git a/tests/unit/unittest/clangjobs-test.cpp b/tests/unit/unittest/clangjobs-test.cpp index 3c642343351..6285fdd017f 100644 --- a/tests/unit/unittest/clangjobs-test.cpp +++ b/tests/unit/unittest/clangjobs-test.cpp @@ -79,7 +79,7 @@ TEST_F(Jobs, ProcessEmptyQueue) const JobRequests jobsStarted = jobs.process(); ASSERT_THAT(jobsStarted.size(), Eq(0)); - ASSERT_THAT(jobs.runningJobs(), Eq(0)); + ASSERT_TRUE(jobs.runningJobs().isEmpty()); } TEST_F(Jobs, ProcessQueueWithSingleJob) @@ -89,7 +89,7 @@ TEST_F(Jobs, ProcessQueueWithSingleJob) const JobRequests jobsStarted = jobs.process(); ASSERT_THAT(jobsStarted.size(), Eq(1)); - ASSERT_THAT(jobs.runningJobs(), Eq(1)); + ASSERT_THAT(jobs.runningJobs().size(), Eq(1)); } TEST_F(Jobs, ProcessQueueUntilEmpty) @@ -108,7 +108,7 @@ TEST_F(Jobs, IsJobRunning) jobs.add(createJobRequest(filePath1, JobRequest::Type::UpdateDocumentAnnotations)); jobs.process(); - const bool isJobRunning = jobs.isJobRunning(filePath1, projectPartId); + const bool isJobRunning = jobs.isJobRunningForTranslationUnit(document.translationUnit().id()); ASSERT_TRUE(isJobRunning); } @@ -130,7 +130,7 @@ void Jobs::TearDown() bool Jobs::waitUntilAllJobsFinished(int timeOutInMs) const { - const auto noJobsRunningAnymore = [this](){ return jobs.runningJobs() == 0; }; + const auto noJobsRunningAnymore = [this](){ return jobs.runningJobs().isEmpty(); }; return ProcessEventUtilities::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); } @@ -138,7 +138,7 @@ bool Jobs::waitUntilAllJobsFinished(int timeOutInMs) const bool Jobs::waitUntilJobChainFinished(int timeOutInMs) const { const auto noJobsRunningAnymore = [this]() { - return jobs.runningJobs() == 0 && jobs.queue().isEmpty(); + return jobs.runningJobs().isEmpty() && jobs.queue().isEmpty(); }; return ProcessEventUtilities::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); diff --git a/tests/unit/unittest/clangparsesupportivetranslationunitjobtest.cpp b/tests/unit/unittest/clangparsesupportivetranslationunitjobtest.cpp new file mode 100644 index 00000000000..4b7d5bc23bf --- /dev/null +++ b/tests/unit/unittest/clangparsesupportivetranslationunitjobtest.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 "clangasyncjob-base.h" + +#include +#include + +using namespace ClangBackEnd; + +using testing::Eq; +using testing::Not; +using testing::_; + +namespace { + +class ParseSupportiveTranslationUnitJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::ParseSupportiveTranslationUnit, job); } + + TimePoint parseTimePointOfDocument(); + +protected: + ClangBackEnd::ParseSupportiveTranslationUnitJob job; +}; + +TEST_F(ParseSupportiveTranslationUnitJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(ParseSupportiveTranslationUnitJob, RunAsync) +{ + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(ParseSupportiveTranslationUnitJob, DoNotIncorporateUpdaterResult) +{ + const TimePoint parseTimePointBefore = parseTimePointOfDocument(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); + ASSERT_THAT(parseTimePointOfDocument(), Eq(parseTimePointBefore)); +} + +TimePoint ParseSupportiveTranslationUnitJob::parseTimePointOfDocument() +{ + const Utf8String translationUnitId = document.translationUnit().id(); + + return document.translationUnits().parseTimePoint(translationUnitId); +} + +} // anonymous diff --git a/tests/unit/unittest/clangreparsesupportivetranslationunitjobtest.cpp b/tests/unit/unittest/clangreparsesupportivetranslationunitjobtest.cpp new file mode 100644 index 00000000000..1796b183c42 --- /dev/null +++ b/tests/unit/unittest/clangreparsesupportivetranslationunitjobtest.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 "clangasyncjob-base.h" + +#include +#include + +using namespace ClangBackEnd; + +using testing::Eq; +using testing::Not; +using testing::_; + +namespace { + +class ReparseSupportiveTranslationUnitJob : public ClangAsyncJobTest +{ +protected: + void SetUp() override { BaseSetUp(JobRequest::Type::ReparseSupportiveTranslationUnit, job); } + + TimePoint parseTimePointOfDocument(); + void parse(); + +protected: + ClangBackEnd::ReparseSupportiveTranslationUnitJob job; +}; + +TEST_F(ReparseSupportiveTranslationUnitJob, PrepareAsyncRun) +{ + job.setContext(jobContext); + + ASSERT_TRUE(job.prepareAsyncRun()); +} + +TEST_F(ReparseSupportiveTranslationUnitJob, RunAsync) +{ + parse(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); +} + +TEST_F(ReparseSupportiveTranslationUnitJob, IncorporateUpdaterResult) +{ + parse(); + const TimePoint parseTimePointBefore = parseTimePointOfDocument(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + + ASSERT_TRUE(waitUntilJobFinished(job)); + ASSERT_THAT(parseTimePointOfDocument(), Not(Eq(parseTimePointBefore))); +} + +TEST_F(ReparseSupportiveTranslationUnitJob, DoNotIncorporateUpdaterResultIfDocumentWasClosed) +{ + parse(); + const TimePoint parseTimePointBefore = parseTimePointOfDocument(); + job.setContext(jobContext); + job.prepareAsyncRun(); + + job.runAsync(); + documents.remove({FileContainer{filePath, projectPartId}}); + + ASSERT_TRUE(waitUntilJobFinished(job)); + ASSERT_THAT(parseTimePointOfDocument(), Eq(parseTimePointBefore)); +} + +TimePoint ReparseSupportiveTranslationUnitJob::parseTimePointOfDocument() +{ + const Utf8String translationUnitId = document.translationUnit().id(); + + return document.translationUnits().parseTimePoint(translationUnitId); +} + +void ReparseSupportiveTranslationUnitJob::parse() +{ + projects.createOrUpdate({ProjectPartContainer{projectPartId, Utf8StringVector()}}); + document.parse(); +} + +} // anonymous diff --git a/tests/unit/unittest/clangsupportivetranslationunitinitializertest.cpp b/tests/unit/unittest/clangsupportivetranslationunitinitializertest.cpp new file mode 100644 index 00000000000..dcb970e4100 --- /dev/null +++ b/tests/unit/unittest/clangsupportivetranslationunitinitializertest.cpp @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** 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 "dummyclangipcclient.h" +#include "processevents-utilities.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using namespace ClangBackEnd; + +using testing::Eq; + +namespace { + +class Data { +public: + Data() + { + projects.createOrUpdate({ProjectPartContainer(projectPartId)}); + + const QVector fileContainer{FileContainer(filePath, projectPartId)}; + document = documents.create(fileContainer).front(); + documents.setVisibleInEditors({filePath}); + documents.setUsedByCurrentEditor(filePath); + + const auto isDocumentClosed = [this](const Utf8String &filePath, + const Utf8String &projectPartId) { + return !documents.hasDocument(filePath, projectPartId); + }; + const auto jobRequestCreator = [this](const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit) { + return createJobRequest(document, type, preferredTranslationUnit); + }; + initializer.reset(new ClangBackEnd::SupportiveTranslationUnitInitializer{document, jobs}); + initializer->setIsDocumentClosedChecker(isDocumentClosed); + initializer->setJobRequestCreator(jobRequestCreator); + } + + JobRequest createJobRequest(const Document &document, + JobRequest::Type type, + PreferredTranslationUnit preferredTranslationUnit) const + { + JobRequest jobRequest; + jobRequest.type = type; + jobRequest.requirements = JobRequest::requirementsForType(type); + jobRequest.filePath = document.filePath(); + jobRequest.projectPartId = document.projectPartId(); + jobRequest.unsavedFilesChangeTimePoint = unsavedFiles.lastChangeTimePoint(); + jobRequest.documentRevision = document.documentRevision(); + jobRequest.preferredTranslationUnit = preferredTranslationUnit; + const ProjectPart &projectPart = projects.project(document.projectPartId()); + jobRequest.projectChangeTimePoint = projectPart.lastChangeTimePoint(); + + return jobRequest; + } + +public: + Utf8String filePath{Utf8StringLiteral(TESTDATA_DIR"/translationunits.cpp")}; + Utf8String projectPartId{Utf8StringLiteral("/path/to/projectfile")}; + + ProjectParts projects; + UnsavedFiles unsavedFiles; + Documents documents{projects, unsavedFiles}; + Document document; + DummyIpcClient dummyClientInterface; + + Jobs jobs{documents, unsavedFiles, projects, dummyClientInterface}; + + std::unique_ptr initializer; +}; + +class SupportiveTranslationUnitInitializer : public ::testing::Test +{ +protected: + void parse(); + Jobs::RunningJob createRunningJob(JobRequest::Type type) const; + + void assertNoJobIsRunningAndEmptyQueue(); + void assertSingleJobRunningAndEmptyQueue(); + + bool waitUntilJobChainFinished(int timeOutInMs = 10000) const; + +protected: + Data d; + + Utf8String &filePath{d.filePath}; + Utf8String &projectPartId{d.projectPartId}; + + ProjectParts projects{d.projects}; + Document &document{d.document}; + Documents &documents{d.documents}; + Jobs &jobs{d.jobs}; + ClangBackEnd::SupportiveTranslationUnitInitializer &initializer{*d.initializer}; +}; + +TEST_F(SupportiveTranslationUnitInitializer, HasInitiallyNotInitializedState) +{ + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::NotInitialized)); +} + +TEST_F(SupportiveTranslationUnitInitializer, StartInitializingAbortsIfDocumentIsClosed) +{ + documents.remove({FileContainer(filePath, projectPartId)}); + + initializer.startInitializing(); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted)); +} + +TEST_F(SupportiveTranslationUnitInitializer, StartInitializingAddsTranslationUnit) +{ + initializer.startInitializing(); + + ASSERT_THAT(document.translationUnits().size(), Eq(2)); + ASSERT_FALSE(document.translationUnits().areAllTranslationUnitsParsed()); +} + +TEST_F(SupportiveTranslationUnitInitializer, StartInitializingStartsJob) +{ + initializer.startInitializing(); + + assertSingleJobRunningAndEmptyQueue(); + const Jobs::RunningJob runningJob = jobs.runningJobs().first(); + ASSERT_THAT(runningJob.jobRequest.type, JobRequest::Type::ParseSupportiveTranslationUnit); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfParseJobFinishedAbortsIfDocumentIsClosed) +{ + documents.remove({FileContainer(filePath, projectPartId)}); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForParseJob); + const Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ParseSupportiveTranslationUnit); + + initializer.checkIfParseJobFinished(runningJob); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted)); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfParseJobFinishedStartsJob) +{ + parse(); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForParseJob); + Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ParseSupportiveTranslationUnit); + + initializer.checkIfParseJobFinished(runningJob); + jobs.process(); + + assertSingleJobRunningAndEmptyQueue(); + runningJob = jobs.runningJobs().first(); + ASSERT_THAT(runningJob.jobRequest.type, JobRequest::Type::ReparseSupportiveTranslationUnit); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfReparseJobFinishedAbortsIfDocumentIsClosed) +{ + documents.remove({FileContainer(filePath, projectPartId)}); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForReparseJob); + const Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + + initializer.checkIfReparseJobFinished(runningJob); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Aborted)); +} + +TEST_F(SupportiveTranslationUnitInitializer, CheckIfReparseJobFinishedStartsJob) +{ + parse(); + initializer.setState(ClangBackEnd::SupportiveTranslationUnitInitializer::State::WaitingForReparseJob); + Jobs::RunningJob runningJob = createRunningJob(JobRequest::Type::ReparseSupportiveTranslationUnit); + + initializer.checkIfReparseJobFinished(runningJob); + jobs.process(); + + assertNoJobIsRunningAndEmptyQueue(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Initialized)); +} + +TEST_F(SupportiveTranslationUnitInitializer, FullRun) +{ + parse(); + initializer.startInitializing(); + + waitUntilJobChainFinished(); + ASSERT_THAT(initializer.state(), Eq(ClangBackEnd::SupportiveTranslationUnitInitializer::State::Initialized)); +} + +void SupportiveTranslationUnitInitializer::parse() +{ + projects.createOrUpdate({ProjectPartContainer{projectPartId, Utf8StringVector()}}); + document.parse(); +} + +Jobs::RunningJob SupportiveTranslationUnitInitializer::createRunningJob(JobRequest::Type type) const +{ + const JobRequest jobRequest = d.createJobRequest(document, + type, + PreferredTranslationUnit::LastUninitialized); + return Jobs::RunningJob{jobRequest, Utf8String(), QFuture()}; +} + +void SupportiveTranslationUnitInitializer::assertNoJobIsRunningAndEmptyQueue() +{ + ASSERT_TRUE(jobs.runningJobs().isEmpty()); + ASSERT_TRUE(jobs.queue().isEmpty()); +} + +void SupportiveTranslationUnitInitializer::assertSingleJobRunningAndEmptyQueue() +{ + ASSERT_THAT(jobs.runningJobs().size(), Eq(1)); + ASSERT_TRUE(jobs.queue().isEmpty()); +} + +bool SupportiveTranslationUnitInitializer::waitUntilJobChainFinished(int timeOutInMs) const +{ + const auto noJobsRunningAnymore = [this]() { + return jobs.runningJobs().isEmpty() && jobs.queue().isEmpty(); + }; + + return ProcessEventUtilities::processEventsUntilTrue(noJobsRunningAnymore, timeOutInMs); +} + +} // anonymous namespace diff --git a/tests/unit/unittest/clangtranslationunit-test.cpp b/tests/unit/unittest/clangtranslationunit-test.cpp new file mode 100644 index 00000000000..10fee318ab9 --- /dev/null +++ b/tests/unit/unittest/clangtranslationunit-test.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using ClangBackEnd::DiagnosticContainer; +using ClangBackEnd::TranslationUnit; +using ClangBackEnd::TranslationUnitUpdateInput; +using ClangBackEnd::TranslationUnitUpdateResult; + +using testing::ContainerEq; +using testing::Eq; + +namespace { + +class TranslationUnit : public ::testing::Test +{ +protected: + void SetUp() override; + void TearDown() override; + + void parse(); + void reparse(); + + DiagnosticContainer createDiagnostic(const QString &text, + ClangBackEnd::DiagnosticSeverity severity, + uint line, + uint column, + const QString &filePath) const; + QVector diagnosticsFromMainFile() const; + QVector errorDiagnosticsFromHeaders() const; + +protected: + Utf8String sourceFilePath = Utf8StringLiteral(TESTDATA_DIR"/diagnostic_erroneous_source.cpp"); + Utf8String headerFilePath = Utf8StringLiteral(TESTDATA_DIR"/diagnostic_erroneous_header.h"); + CXIndex cxIndex = nullptr; + CXTranslationUnit cxTranslationUnit = nullptr; + ::TranslationUnit translationUnit{Utf8String(), sourceFilePath, cxIndex, cxTranslationUnit}; + + DiagnosticContainer extractedFirstHeaderErrorDiagnostic; + QVector extractedMainFileDiagnostics; +}; + +TEST_F(TranslationUnit, HasExpectedMainFileDiagnostics) +{ + translationUnit.extractDiagnostics(extractedFirstHeaderErrorDiagnostic, + extractedMainFileDiagnostics); + + ASSERT_THAT(extractedMainFileDiagnostics, ContainerEq(diagnosticsFromMainFile())); +} + +TEST_F(TranslationUnit, HasExpectedMainFileDiagnosticsAfterReparse) +{ + reparse(); + + translationUnit.extractDiagnostics(extractedFirstHeaderErrorDiagnostic, + extractedMainFileDiagnostics); + + ASSERT_THAT(extractedMainFileDiagnostics, ContainerEq(diagnosticsFromMainFile())); +} + +TEST_F(TranslationUnit, HasErrorDiagnosticsInHeaders) +{ + translationUnit.extractDiagnostics(extractedFirstHeaderErrorDiagnostic, + extractedMainFileDiagnostics); + + ASSERT_THAT(extractedFirstHeaderErrorDiagnostic, + Eq(errorDiagnosticsFromHeaders().first())); +} + +TEST_F(TranslationUnit, HasErrorDiagnosticsInHeadersAfterReparse) +{ + reparse(); + + translationUnit.extractDiagnostics(extractedFirstHeaderErrorDiagnostic, + extractedMainFileDiagnostics); + + ASSERT_THAT(extractedFirstHeaderErrorDiagnostic, + Eq(errorDiagnosticsFromHeaders().first())); +} + +void TranslationUnit::SetUp() +{ + parse(); +} + +void TranslationUnit::TearDown() +{ + clang_disposeTranslationUnit(cxTranslationUnit); + clang_disposeIndex(cxIndex); +} + +void TranslationUnit::parse() +{ + TranslationUnitUpdateInput parseInput; + parseInput.filePath = sourceFilePath; + parseInput.parseNeeded = true; + const TranslationUnitUpdateResult parseResult = translationUnit.update(parseInput); + ASSERT_TRUE(parseResult.hasParsed()); +} + +void TranslationUnit::reparse() +{ + TranslationUnitUpdateInput parseInput; + parseInput.filePath = sourceFilePath; + parseInput.reparseNeeded = true; + const TranslationUnitUpdateResult parseResult = translationUnit.update(parseInput); + ASSERT_TRUE(parseResult.hasReparsed()); +} + +DiagnosticContainer TranslationUnit::createDiagnostic(const QString &text, + ClangBackEnd::DiagnosticSeverity severity, + uint line, + uint column, + const QString &filePath) const +{ + return DiagnosticContainer( + text, + Utf8StringLiteral(""), + {}, + severity, + {filePath, line, column}, + {}, + {}, + {} + ); +} + +QVector TranslationUnit::diagnosticsFromMainFile() const +{ + return { + createDiagnostic( + QStringLiteral("warning: enumeration value 'Three' not handled in switch"), + ClangBackEnd::DiagnosticSeverity::Warning, + 7, + 13, + sourceFilePath), + createDiagnostic( + QStringLiteral("error: void function 'g' should not return a value"), + ClangBackEnd::DiagnosticSeverity::Error, + 15, + 5, + sourceFilePath), + createDiagnostic( + QStringLiteral("warning: using the result of an assignment as a condition without parentheses"), + ClangBackEnd::DiagnosticSeverity::Warning, + 21, + 11, + sourceFilePath), + }; +} + +QVector TranslationUnit::errorDiagnosticsFromHeaders() const +{ + return { + createDiagnostic( + QStringLiteral("error: C++ requires a type specifier for all declarations"), + ClangBackEnd::DiagnosticSeverity::Error, + 11, + 1, + headerFilePath), + }; +} + +} // anonymous namespace diff --git a/tests/unit/unittest/clangtranslationunits-test.cpp b/tests/unit/unittest/clangtranslationunits-test.cpp new file mode 100644 index 00000000000..d89f3ba83e0 --- /dev/null +++ b/tests/unit/unittest/clangtranslationunits-test.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include "gtest-qt-printing.h" + +using ClangBackEnd::Clock; +using ClangBackEnd::TranslationUnit; +using ClangBackEnd::TranslationUnits; +using ClangBackEnd::TranslationUnitDoesNotExist; +using ClangBackEnd::PreferredTranslationUnit; + +using testing::Eq; + +namespace { + +class TranslationUnits : public ::testing::Test +{ +protected: + Utf8String someFilePath = Utf8StringLiteral("someFilePath"); + ClangBackEnd::TranslationUnits translationUnits{someFilePath}; +}; + +TEST_F(TranslationUnits, CreatedUnitIsNull) +{ + TranslationUnit translationUnit = translationUnits.createAndAppend(); + + ASSERT_TRUE(translationUnit.isNull()); +} + +TEST_F(TranslationUnits, GetThrowsForNotExisting) +{ + ASSERT_THROW(translationUnits.get(), TranslationUnitDoesNotExist); +} + +TEST_F(TranslationUnits, GetForSingleTranslationUnit) +{ + const TranslationUnit created = translationUnits.createAndAppend(); + + const TranslationUnit queried = translationUnits.get(); + + ASSERT_THAT(queried.id(), Eq(created.id())); +} + +TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnits) +{ + const TranslationUnit created1 = translationUnits.createAndAppend(); + translationUnits.createAndAppend(); + + const TranslationUnit queried = translationUnits.get(); + + ASSERT_THAT(queried.id(), Eq(created1.id())); +} + +TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnitsAndOnlyFirstParsed) +{ + const TranslationUnit created1 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created1.id(), Clock::now()); + translationUnits.createAndAppend(); + + const TranslationUnit queried = translationUnits.get(); + + ASSERT_THAT(queried.id(), Eq(created1.id())); +} + +TEST_F(TranslationUnits, GetFirstForMultipleTranslationUnitsAndOnlySecondParsed) +{ + const TranslationUnit created1 = translationUnits.createAndAppend(); + const TranslationUnit created2 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created2.id(), Clock::now()); + + const TranslationUnit queried = translationUnits.get(); + + ASSERT_THAT(queried.id(), Eq(created1.id())); +} + +TEST_F(TranslationUnits, GetLastUnitializedForMultipleTranslationUnits) +{ + const TranslationUnit created1 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created1.id(), Clock::now()); + const TranslationUnit created2 = translationUnits.createAndAppend(); + + const TranslationUnit queried = translationUnits.get(PreferredTranslationUnit::LastUninitialized); + + ASSERT_THAT(queried.id(), Eq(created2.id())); +} + +TEST_F(TranslationUnits, GetRecentForMultipleTranslationUnits) +{ + const TranslationUnit created1 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created1.id(), Clock::now()); + const TranslationUnit created2 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created2.id(), Clock::now()); + + const TranslationUnit queried = translationUnits.get(PreferredTranslationUnit::RecentlyParsed); + + ASSERT_THAT(queried.id(), Eq(created2.id())); +} + +TEST_F(TranslationUnits, GetPreviousForMultipleTranslationUnits) +{ + const TranslationUnit created1 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created1.id(), Clock::now()); + const TranslationUnit created2 = translationUnits.createAndAppend(); + translationUnits.updateParseTimePoint(created2.id(), Clock::now()); + + const TranslationUnit queried = translationUnits.get(PreferredTranslationUnit::PreviouslyParsed); + + ASSERT_THAT(queried.id(), Eq(created1.id())); +} + +TEST_F(TranslationUnits, UpdateThrowsForNotExisting) +{ + ClangBackEnd::TranslationUnits otherTranslationUnits{someFilePath}; + const TranslationUnit translationUnit = otherTranslationUnits.createAndAppend(); + + ASSERT_THROW(translationUnits.updateParseTimePoint(translationUnit.id(), Clock::now()), + TranslationUnitDoesNotExist); +} + +} // anonymous namespace diff --git a/tests/unit/unittest/clangupdatedocumentannotationsjob-test.cpp b/tests/unit/unittest/clangupdatedocumentannotationsjob-test.cpp index a76e0f8703c..291f2e298c3 100644 --- a/tests/unit/unittest/clangupdatedocumentannotationsjob-test.cpp +++ b/tests/unit/unittest/clangupdatedocumentannotationsjob-test.cpp @@ -97,7 +97,7 @@ TEST_F(UpdateDocumentAnnotationsJob, DontSendAnnotationsIfDocumentRevisionChange TEST_F(UpdateDocumentAnnotationsJob, UpdatesTranslationUnit) { - const time_point timePointBefore = document.lastProjectPartChangeTimePoint(); + const TimePoint timePointBefore = document.lastProjectPartChangeTimePoint(); const QSet dependendOnFilesBefore = document.dependedFilePaths(); job.setContext(jobContext); job.prepareAsyncRun(); diff --git a/tests/unit/unittest/clientserverinprocess-test.cpp b/tests/unit/unittest/clientserverinprocess-test.cpp index 37d1e46570c..d4a1cab16dd 100644 --- a/tests/unit/unittest/clientserverinprocess-test.cpp +++ b/tests/unit/unittest/clientserverinprocess-test.cpp @@ -277,6 +277,7 @@ TEST_F(ClientServerInProcess, SendDocumentAnnotationsChangedMessage) ClangBackEnd::DocumentAnnotationsChangedMessage message(fileContainer, {diagnostic}, + {}, {highlightingMark}, QVector()); diff --git a/tests/unit/unittest/cursor-test.cpp b/tests/unit/unittest/cursor-test.cpp index 6fa4249c725..77df29a17a5 100644 --- a/tests/unit/unittest/cursor-test.cpp +++ b/tests/unit/unittest/cursor-test.cpp @@ -25,6 +25,8 @@ #include "googletest.h" +#include "clangcompareoperators.h" + #include #include #include @@ -68,6 +70,7 @@ struct Data { {}, documents}; TranslationUnit translationUnit{filePath, + filePath, document.translationUnit().cxIndex(), document.translationUnit().cxTranslationUnit()}; }; @@ -84,6 +87,8 @@ protected: const TranslationUnit &translationUnit = d->translationUnit; }; + + TEST_F(Cursor, CreateNullCursor) { ::Cursor cursor; @@ -488,21 +493,21 @@ TEST_F(Cursor, HasNotFinaAttributeInClass) TEST_F(Cursor, HasOutputValues) { auto callExpressionCursor = translationUnit.cursorAt(117, 19); - auto outputArgumentExpectedCursor = translationUnit.cursorAt(117, 20); + auto outputArgumentExpectedSourceLocation = translationUnit.cursorAt(117, 20).cxSourceRange(); - auto outputArguments = callExpressionCursor.outputArguments(); + auto outputArgumentLocations = callExpressionCursor.outputArgumentRanges(); - ASSERT_THAT(outputArguments.size(), 1); - ASSERT_THAT(outputArguments[0], outputArgumentExpectedCursor); + ASSERT_THAT(outputArgumentLocations.size(), 2); + ASSERT_THAT(outputArgumentLocations[0], outputArgumentExpectedSourceLocation); } TEST_F(Cursor, HasOnlyInputValues) { auto callExpressionCursor = translationUnit.cursorAt(118, 18); - auto outputArguments = callExpressionCursor.outputArguments(); + auto outputArgumentLocations = callExpressionCursor.outputArgumentRanges(); - ASSERT_THAT(outputArguments, IsEmpty()); + ASSERT_THAT(outputArgumentLocations, IsEmpty()); } TEST_F(Cursor, ArgumentCountIsZero) @@ -747,58 +752,58 @@ TEST_F(Cursor, PointerIsNotRefencingConstant) ASSERT_FALSE(argument.isReferencingConstant()); } -TEST_F(Cursor, PointerIsOutputParameter) +TEST_F(Cursor, PointerIsOutputArgument) { auto callExpressionCursor = translationUnit.cursorAt(127, 13); auto argument = callExpressionCursor.type().argument(0); - ASSERT_TRUE(argument.isOutputParameter()); + ASSERT_TRUE(argument.isOutputArgument()); } -TEST_F(Cursor, ConstantReferenceIsNotOutputParameter) +TEST_F(Cursor, ConstantReferenceIsNotOutputArgument) { auto callExpressionCursor = translationUnit.cursorAt(125, 26); auto argument = callExpressionCursor.type().argument(0); - ASSERT_FALSE(argument.isOutputParameter()); + ASSERT_FALSE(argument.isOutputArgument()); } -TEST_F(Cursor, PointerToConstantIsNotOutputParameter) +TEST_F(Cursor, PointerToConstantIsNotOutputArgument) { auto callExpressionCursor = translationUnit.cursorAt(126, 20); auto argument = callExpressionCursor.type().argument(0); - ASSERT_FALSE(argument.isOutputParameter()) << argument.isConstant() << argument.pointeeType().isConstant(); + ASSERT_FALSE(argument.isOutputArgument()) << argument.isConstant() << argument.pointeeType().isConstant(); } -TEST_F(Cursor, ConstantPointerIsNotOutputParameter) +TEST_F(Cursor, ConstantPointerIsNotOutputArgument) { auto callExpressionCursor = translationUnit.cursorAt(128, 21); auto argument = callExpressionCursor.type().argument(0); - ASSERT_TRUE(argument.isOutputParameter()); + ASSERT_TRUE(argument.isOutputArgument()); } -TEST_F(Cursor, ReferenceIsOutputParameter) +TEST_F(Cursor, ReferenceIsOutputArgument) { auto callExpressionCursor = translationUnit.cursorAt(124, 21); auto argument = callExpressionCursor.type().argument(0); - ASSERT_TRUE(argument.isOutputParameter()); + ASSERT_TRUE(argument.isOutputArgument()); } -TEST_F(Cursor, ConstReferenceIsNotOutputParameter) +TEST_F(Cursor, ConstReferenceIsNotOutputArgument) { auto callExpressionCursor = translationUnit.cursorAt(125, 26); auto argument = callExpressionCursor.type().argument(0); - ASSERT_FALSE(argument.isOutputParameter()); + ASSERT_FALSE(argument.isOutputArgument()); } Data *Cursor::d; diff --git a/tests/unit/unittest/data/highlightingmarks.cpp b/tests/unit/unittest/data/highlightingmarks.cpp index 0fb2ea3bebc..d37f3e297af 100644 --- a/tests/unit/unittest/data/highlightingmarks.cpp +++ b/tests/unit/unittest/data/highlightingmarks.cpp @@ -278,12 +278,12 @@ void FinalClass::FinalClassThisCall() } -void OutputParameter(int &one, const int &two, int *three=0); +void OutputArgument(int &one, const int &two, int *three=0); -void f12() +void f12b() { int One; - OutputParameter(One, 2); + OutputArgument(One, 2); } #include @@ -445,3 +445,83 @@ struct LambdaTester lambda(var2); } }; + +void NonConstReferenceArgument(int &argument); + +void f22() +{ + int x = 1; + + NonConstReferenceArgument(x); +} + +void ConstReferenceArgument(const int &argument); + +void f23() +{ + int x = 1; + + ConstReferenceArgument(x); +} + +void RValueReferenceArgument(int &&argument); + +void f24() +{ + int x = 1; + + RValueReferenceArgument(static_cast(x)); +} + +void NonConstPointerArgument(int *argument); + +void f25() +{ + int *x; + + NonConstPointerArgument(x); +} + +void ConstPointerArgument(const int *argument); + +void f26() +{ + int *x; + + ConstPointerArgument(x); +} + +void NonConstReferenceArgumentCallInsideCall(int x, int &argument); +int GetArgument(int x); + +void f27() +{ + int x = 1; + + NonConstReferenceArgumentCallInsideCall(GetArgument(x), x); +} + +void f28(int &Reference) +{ + NonConstReferenceArgument(Reference); +} + +void f29() +{ + int x; + + NonConstPointerArgument(&x); +} + +struct NonConstPointerArgumentAsMemberOfClass +{ + int member; +}; + +void f30() +{ + NonConstPointerArgumentAsMemberOfClass instance; + + NonConstReferenceArgument(instance.member); +} + diff --git a/tests/unit/unittest/highlightingmarks-test.cpp b/tests/unit/unittest/highlightingmarks-test.cpp index de79d379fd9..9433bf9249e 100644 --- a/tests/unit/unittest/highlightingmarks-test.cpp +++ b/tests/unit/unittest/highlightingmarks-test.cpp @@ -107,10 +107,11 @@ struct Data { Utf8String filePath{Utf8StringLiteral(TESTDATA_DIR"/highlightingmarks.cpp")}; Document document{filePath, ProjectPart(Utf8StringLiteral("projectPartId"), - {Utf8StringLiteral("-std=c++14")}), + {Utf8StringLiteral("-std=c++14"), Utf8StringLiteral("-I" TESTDATA_DIR)}), {}, documents}; TranslationUnit translationUnit{filePath, + filePath, document.translationUnit().cxIndex(), document.translationUnit().cxTranslationUnit()}; }; @@ -958,6 +959,106 @@ TEST_F(HighlightingMarks, TypeDefDeclarationUsage) ASSERT_THAT(infos[0], HasOnlyType(HighlightingType::Type)); } +TEST_F(HighlightingMarks, NonConstReferenceArgument) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(455, 35)); + + infos[1]; + + ASSERT_THAT(infos[2], + HasTwoTypes(HighlightingType::LocalVariable, HighlightingType::OutputArgument)); +} + +TEST_F(HighlightingMarks, ConstReferenceArgument) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(464, 32)); + + infos[1]; + + ASSERT_THAT(infos[2], + HasOnlyType(HighlightingType::LocalVariable)); +} + +TEST_F(HighlightingMarks, RValueReferenceArgument) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(473, 52)); + + infos[1]; + + ASSERT_THAT(infos[8], + HasOnlyType(HighlightingType::LocalVariable)); +} + +TEST_F(HighlightingMarks, NonConstPointerArgument) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(482, 33)); + + infos[1]; + + ASSERT_THAT(infos[2], + HasTwoTypes(HighlightingType::LocalVariable, HighlightingType::OutputArgument)); +} + +TEST_F(HighlightingMarks, ConstPointerArgument) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(491, 30)); + + infos[1]; + + ASSERT_THAT(infos[2], + HasOnlyType(HighlightingType::LocalVariable)); +} + +TEST_F(HighlightingMarks, NonConstReferenceArgumentCallInsideCall) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(501, 64)); + infos[1]; + + infos[3]; + + ASSERT_THAT(infos[7], + HasTwoTypes(HighlightingType::LocalVariable, HighlightingType::OutputArgument)); +} + +TEST_F(HighlightingMarks, OutputArgumentsAreEmptyAfterIteration) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(501, 63)); + + for (const auto &info : infos ) {} + + ASSERT_TRUE(infos.currentOutputArgumentRangesAreEmpty()); +} + +TEST_F(HighlightingMarks, NonConstReferenceArgumentFromFunctionParameter) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(506, 42)); + + infos[1]; + + ASSERT_THAT(infos[2], + HasTwoTypes(HighlightingType::LocalVariable, HighlightingType::OutputArgument)); +} + +TEST_F(HighlightingMarks, NonConstPointerArgumentAsExpression) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(513, 33)); + + infos[1]; + + ASSERT_THAT(infos[3], + HasTwoTypes(HighlightingType::LocalVariable, HighlightingType::OutputArgument)); +} + +TEST_F(HighlightingMarks, NonConstPointerArgumentAsMemberOfClass) +{ + const auto infos = translationUnit.highlightingMarksInRange(sourceRange(525, 46)); + + infos[1]; + + ASSERT_THAT(infos[4], + HasTwoTypes(HighlightingType::Field, HighlightingType::OutputArgument)); +} + TEST_F(HighlightingMarks, DISABLED_EnumerationTypeDef) { const auto infos = translationUnit.highlightingMarksInRange(sourceRange(424, 41)); diff --git a/tests/unit/unittest/projectpart-test.cpp b/tests/unit/unittest/projectpart-test.cpp index 7595cedbfc2..ec5d99f9b43 100644 --- a/tests/unit/unittest/projectpart-test.cpp +++ b/tests/unit/unittest/projectpart-test.cpp @@ -25,12 +25,12 @@ #include "googletest.h" +#include #include #include #include #include -#include #include using testing::ElementsAre; @@ -82,7 +82,7 @@ TEST(ProjectPart, TimeStampIsUpdatedAsArgumentChanged) { ClangBackEnd::ProjectPart project(Utf8StringLiteral("/tmp/blah.pro")); auto lastChangeTimePoint = project.lastChangeTimePoint(); - std::this_thread::sleep_for(std::chrono::steady_clock::duration(1)); + std::this_thread::sleep_for(ClangBackEnd::Duration(1)); project.setArguments(Utf8StringVector({Utf8StringLiteral("-O"), Utf8StringLiteral("-fast")})); @@ -163,7 +163,7 @@ TEST(ProjectPart, ProjectPartIsClearedAfterRemove) projects.createOrUpdate({projectContainer}); ClangBackEnd::ProjectPart project = *projects.findProjectPart(projectContainer.projectPartId()); const auto lastChangeTimePoint = project.lastChangeTimePoint(); - std::this_thread::sleep_for(std::chrono::steady_clock::duration(1)); + std::this_thread::sleep_for(ClangBackEnd::Duration(1)); projects.remove({projectContainer.projectPartId()}); diff --git a/tests/unit/unittest/readandwritemessageblock-test.cpp b/tests/unit/unittest/readandwritemessageblock-test.cpp index d45a3a347ae..ace9effadb4 100644 --- a/tests/unit/unittest/readandwritemessageblock-test.cpp +++ b/tests/unit/unittest/readandwritemessageblock-test.cpp @@ -186,6 +186,7 @@ TEST_F(ReadAndWriteMessageBlock, CompareDocumentAnnotationsChangedMessage) CompareMessage(ClangBackEnd::DocumentAnnotationsChangedMessage(fileContainer, {diagnostic}, + {}, {highlightingMark}, QVector())); } diff --git a/tests/unit/unittest/skippedsourceranges-test.cpp b/tests/unit/unittest/skippedsourceranges-test.cpp index e775961a84b..1e1d9d4ee6f 100644 --- a/tests/unit/unittest/skippedsourceranges-test.cpp +++ b/tests/unit/unittest/skippedsourceranges-test.cpp @@ -99,6 +99,7 @@ struct Data { {}, documents}; TranslationUnit translationUnit{filePath, + filePath, document.translationUnit().cxIndex(), document.translationUnit().cxTranslationUnit()}; }; diff --git a/tests/unit/unittest/sourcerange-test.cpp b/tests/unit/unittest/sourcerange-test.cpp index a9adee64138..c50bb98dc21 100644 --- a/tests/unit/unittest/sourcerange-test.cpp +++ b/tests/unit/unittest/sourcerange-test.cpp @@ -103,6 +103,7 @@ struct Data { Utf8StringVector(), documents}; TranslationUnit translationUnit{filePath, + filePath, document.translationUnit().cxIndex(), document.translationUnit().cxTranslationUnit()}; diff --git a/tests/unit/unittest/translationunitupdater-test.cpp b/tests/unit/unittest/translationunitupdater-test.cpp index 40ca8909713..dd490cda98c 100644 --- a/tests/unit/unittest/translationunitupdater-test.cpp +++ b/tests/unit/unittest/translationunitupdater-test.cpp @@ -25,14 +25,18 @@ #include "googletest.h" +#include #include #include +using ClangBackEnd::Clock; +using ClangBackEnd::TimePoint; using ClangBackEnd::TranslationUnitUpdater; using ClangBackEnd::TranslationUnitUpdateInput; using ClangBackEnd::TranslationUnitUpdateResult; +using testing::Eq; using testing::Gt; namespace { @@ -42,7 +46,8 @@ class TranslationUnitUpdater : public ::testing::Test protected: void TearDown() override; - ::TranslationUnitUpdater createUpdater(const TranslationUnitUpdateInput &input); + ::TranslationUnitUpdater createUpdater(const TranslationUnitUpdateInput &input, + const Utf8String &translationUnitId = Utf8String()); enum ReparseMode { SetReparseNeeded, DoNotSetReparseNeeded }; TranslationUnitUpdateInput createInput(ReparseMode reparseMode = DoNotSetReparseNeeded); @@ -73,10 +78,20 @@ TEST_F(TranslationUnitUpdater, ReparsesIfNeeded) ASSERT_TRUE(result.hasReparsed()); } +TEST_F(TranslationUnitUpdater, PropagatesTranslationUnitId) +{ + const Utf8String translationUnitId = Utf8StringLiteral("myId"); + ::TranslationUnitUpdater updater = createUpdater(createInput(SetReparseNeeded), translationUnitId); + + TranslationUnitUpdateResult result = updater.update(::TranslationUnitUpdater::UpdateMode::AsNeeded); + + ASSERT_THAT(result.translationUnitId, Eq(translationUnitId)); +} + TEST_F(TranslationUnitUpdater, UpdatesParseTimePoint) { ::TranslationUnitUpdater updater = createUpdater(createInput()); - const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const TimePoint now = Clock::now(); TranslationUnitUpdateResult result = updater.update(::TranslationUnitUpdater::UpdateMode::AsNeeded); @@ -111,9 +126,10 @@ void TranslationUnitUpdater::TearDown() } ::TranslationUnitUpdater -TranslationUnitUpdater::createUpdater(const TranslationUnitUpdateInput &input) +TranslationUnitUpdater::createUpdater(const TranslationUnitUpdateInput &input, + const Utf8String &translationUnitId) { - return ::TranslationUnitUpdater(cxIndex, cxTranslationUnit, input); + return ::TranslationUnitUpdater(translationUnitId, cxIndex, cxTranslationUnit, input); } TranslationUnitUpdateInput diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 6c40fbb8ece..aa4b560fb6b 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -49,13 +49,20 @@ SOURCES += \ clangdiagnosticfilter-test.cpp \ clangdocuments-test.cpp \ clangdocument-test.cpp \ + clangdocumentprocessor-test.cpp \ + clangdocumentprocessors-test.cpp \ clangfixitoperation-test.cpp \ clangipcserver-test.cpp \ clangisdiagnosticrelatedtolocation-test.cpp \ clangjobqueue-test.cpp \ clangjobs-test.cpp \ + clangparsesupportivetranslationunitjobtest.cpp \ + clangreparsesupportivetranslationunitjobtest.cpp \ clangrequestdocumentannotationsjob-test.cpp \ + clangsupportivetranslationunitinitializertest.cpp \ clangstring-test.cpp \ + clangtranslationunit-test.cpp \ + clangtranslationunits-test.cpp \ clangupdatedocumentannotationsjob-test.cpp \ codecompletionsextractor-test.cpp \ codecompletion-test.cpp \ @@ -111,6 +118,7 @@ HEADERS += \ chunksreportedmonitor.h \ clangasyncjob-base.h \ diagnosticcontainer-matcher.h \ + clangcompareoperators.h \ dummyclangipcclient.h \ mockclangcodemodelclient.h \ mockclangcodemodelserver.h