diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp index 05874085c08..1cb6790b44c 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp @@ -280,6 +280,13 @@ void ClangEditorDocumentProcessor::editorDocumentTimerRestarted() m_updateTranslationUnitTimer.stop(); // Wait for the next call to run(). } +void ClangEditorDocumentProcessor::setParserConfig( + const CppTools::BaseEditorDocumentParser::Configuration config) +{ + m_parser->setConfiguration(config); + m_builtinProcessor.parser()->setConfiguration(config); +} + ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const { return fileContainerWithArguments(m_projectPart.data()); diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h index 7f30299391d..ad598957641 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h @@ -82,6 +82,8 @@ public: void editorDocumentTimerRestarted() override; + void setParserConfig(const CppTools::BaseEditorDocumentParser::Configuration config) override; + ClangBackEnd::FileContainer fileContainerWithArguments() const; void clearDiagnosticsWithFixIts(); diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index be81648c233..a2c73661ff3 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -131,6 +131,8 @@ public: QScopedPointer m_followSymbolUnderCursor; + QAction *m_parseContextAction = nullptr; + ParseContextWidget *m_parseContextWidget = nullptr; QToolButton *m_preprocessorButton = nullptr; MinimizableInfoBars::Actions m_showInfoBarActions; @@ -222,7 +224,7 @@ void CppEditorWidget::finalizeInitialization() d->m_cppSelectionChanger.onCursorPositionChanged(textCursor()); }); - // Tool bar creation + // Toolbar: '#' Button d->m_preprocessorButton = new QToolButton(this); d->m_preprocessorButton->setText(QLatin1String("#")); Command *cmd = ActionManager::command(Constants::OPEN_PREPROCESSOR_DIALOG); @@ -231,13 +233,25 @@ void CppEditorWidget::finalizeInitialization() connect(d->m_preprocessorButton, &QAbstractButton::clicked, this, &CppEditorWidget::showPreProcessorWidget); insertExtraToolBarWidget(TextEditorWidget::Left, d->m_preprocessorButton); - // Actions to show minimized info bars + // Toolbar: Actions to show minimized info bars d->m_showInfoBarActions = MinimizableInfoBars::createShowInfoBarActions([this](QWidget *w) { return this->insertExtraToolBarWidget(TextEditorWidget::Left, w); }); connect(&cppEditorDocument()->minimizableInfoBars(), &MinimizableInfoBars::showAction, this, &CppEditorWidget::onShowInfoBarAction); + // Toolbar: Parse context + ParseContextModel &parseContextModel = cppEditorDocument()->parseContextModel(); + d->m_parseContextWidget = new ParseContextWidget(parseContextModel, this); + d->m_parseContextAction = insertExtraToolBarWidget(TextEditorWidget::Left, + d->m_parseContextWidget); + d->m_parseContextAction->setVisible(false); + connect(&parseContextModel, &ParseContextModel::updated, + this, [this](bool areMultipleAvailable) { + d->m_parseContextAction->setVisible(areMultipleAvailable); + }); + + // Toolbar: Outline/Overview combo box insertExtraToolBarWidget(TextEditorWidget::Left, d->m_cppEditorOutline->widget()); } @@ -252,6 +266,10 @@ void CppEditorWidget::finalizeInitializationAfterDuplication(TextEditorWidget *o d->m_cppEditorOutline->update(); const Id selectionKind = CodeWarningsSelection; setExtraSelections(selectionKind, cppEditorWidget->extraSelections(selectionKind)); + + d->m_parseContextWidget->syncToModel(); + d->m_parseContextAction->setVisible( + d->m_cppEditorDocument->parseContextModel().areMultipleAvailable()); } CppEditorWidget::~CppEditorWidget() @@ -972,20 +990,12 @@ void CppEditorWidget::abortDeclDefLink() void CppEditorWidget::showPreProcessorWidget() { - const Utils::FileName fileName = textDocument()->filePath(); + const QString filePath = textDocument()->filePath().toString(); - // Check if this editor belongs to a project - QList projectParts = d->m_modelManager->projectPart(fileName); - if (projectParts.isEmpty()) - projectParts = d->m_modelManager->projectPartFromDependencies(fileName); - if (projectParts.isEmpty()) - projectParts << d->m_modelManager->fallbackProjectPart(); - - CppPreProcessorDialog preProcessorDialog(this, textDocument()->filePath().toString(), projectParts); - if (preProcessorDialog.exec() == QDialog::Accepted) { - cppEditorDocument()->setPreprocessorSettings( - preProcessorDialog.projectPart(), - preProcessorDialog.additionalPreProcessorDirectives().toUtf8()); + CppPreProcessorDialog dialog(filePath, this); + if (dialog.exec() == QDialog::Accepted) { + const QByteArray extraDirectives = dialog.extraPreprocessorDirectives().toUtf8(); + cppEditorDocument()->setExtraPreprocessorDirectives(extraDirectives); cppEditorDocument()->scheduleProcessDocument(); } } diff --git a/src/plugins/cppeditor/cppeditor.pro b/src/plugins/cppeditor/cppeditor.pro index 5ffc65229d2..d7ae058c175 100644 --- a/src/plugins/cppeditor/cppeditor.pro +++ b/src/plugins/cppeditor/cppeditor.pro @@ -17,6 +17,7 @@ HEADERS += \ cppfunctiondecldeflink.h \ cpphighlighter.h \ cpphoverhandler.h \ + cppparsecontext.h \ cppincludehierarchy.h \ cppinsertvirtualmethods.h \ cpplocalrenaming.h \ @@ -46,6 +47,7 @@ SOURCES += \ cppfunctiondecldeflink.cpp \ cpphighlighter.cpp \ cpphoverhandler.cpp \ + cppparsecontext.cpp \ cppincludehierarchy.cpp \ cppinsertvirtualmethods.cpp \ cpplocalrenaming.cpp \ diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index d1d92ed29b3..e9cd5ed9e12 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -43,6 +43,7 @@ QtcPlugin { "cppminimizableinfobars.cpp", "cppminimizableinfobars.h", "cppoutline.cpp", "cppoutline.h", "cpppreprocessordialog.cpp", "cpppreprocessordialog.h", "cpppreprocessordialog.ui", + "cppparsecontext.cpp", "cppparsecontext.h", "cppquickfix.cpp", "cppquickfix.h", "cppquickfixassistant.cpp", "cppquickfixassistant.h", "cppquickfixes.cpp", "cppquickfixes.h", diff --git a/src/plugins/cppeditor/cppeditorconstants.h b/src/plugins/cppeditor/cppeditorconstants.h index 084a4f63c4e..44c1d2674be 100644 --- a/src/plugins/cppeditor/cppeditorconstants.h +++ b/src/plugins/cppeditor/cppeditorconstants.h @@ -37,6 +37,7 @@ 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 MULTIPLE_PARSE_CONTEXTS_AVAILABLE[] = "CppEditor.MultipleParseContextsAvailable"; const char NO_PROJECT_CONFIGURATION[] = "CppEditor.NoProjectConfiguration"; const char M_REFACTORING_MENU_INSERTION_POINT[] = "CppEditor.RefactorGroup"; const char UPDATE_CODEMODEL[] = "CppEditor.UpdateCodeModel"; @@ -50,7 +51,8 @@ const char OPEN_INCLUDE_HIERARCHY[] = "CppEditor.OpenIncludeHierarchy"; const char CPP_SNIPPETS_GROUP_ID[] = "C++"; -const char CPP_PREPROCESSOR_PROJECT_PREFIX[] = "CppPreprocessorProject-"; +const char EXTRA_PREPROCESSOR_DIRECTIVES[] = "CppEditor.ExtraPreprocessorDirectives-"; +const char PREFERRED_PARSE_CONTEXT[] = "CppEditor.PreferredParseContext-"; } // namespace Constants } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index 3c66c36baf5..6066e8da584 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -30,6 +30,8 @@ #include "cpphighlighter.h" #include "cppquickfixassistant.h" +#include + #include #include #include @@ -115,6 +117,9 @@ CppEditorDocument::CppEditorDocument() connect(this, &IDocument::filePathChanged, this, &CppEditorDocument::onFilePathChanged); + connect(&m_parseContextModel, &ParseContextModel::preferredParseContextChanged, + this, &CppEditorDocument::reparseWithPreferredParseContext); + // See also onFilePathChanged() for more initialization } @@ -204,6 +209,21 @@ void CppEditorDocument::onReloadFinished() m_fileIsBeingReloaded = false; } +void CppEditorDocument::reparseWithPreferredParseContext(const QString &parseContextId) +{ + using namespace CppTools; + + // Update parser + setPreferredParseContext(parseContextId); + + // Remember the setting + const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString(); + ProjectExplorer::SessionManager::setValue(key, parseContextId); + + // Reprocess + scheduleProcessDocument(); +} + void CppEditorDocument::onFilePathChanged(const Utils::FileName &oldPath, const Utils::FileName &newPath) { @@ -222,7 +242,8 @@ void CppEditorDocument::onFilePathChanged(const Utils::FileName &oldPath, m_editorDocumentHandle.reset(new CppEditorDocumentHandleImpl(this)); resetProcessor(); - updatePreprocessorSettings(); + applyPreferredParseContextFromSettings(); + applyExtraPreprocessorDirectivesFromSettings(); m_processorRevision = document()->revision(); processDocument(); } @@ -256,34 +277,51 @@ void CppEditorDocument::resetProcessor() processor(); // creates a new processor } -void CppEditorDocument::updatePreprocessorSettings() +void CppEditorDocument::applyPreferredParseContextFromSettings() { if (filePath().isEmpty()) return; - const QString prefix = QLatin1String(Constants::CPP_PREPROCESSOR_PROJECT_PREFIX); - const QString &projectPartId = ProjectExplorer::SessionManager::value( - prefix + filePath().toString()).toString(); - const QString directivesKey = projectPartId + QLatin1Char(',') + filePath().toString(); - const QByteArray additionalDirectives = ProjectExplorer::SessionManager::value( - directivesKey).toString().toUtf8(); + const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString(); + const QString parseContextId = ProjectExplorer::SessionManager::value(key).toString(); - setPreprocessorSettings(mm()->projectPartForId(projectPartId), additionalDirectives); + setPreferredParseContext(parseContextId); } -void CppEditorDocument::setPreprocessorSettings(const CppTools::ProjectPart::Ptr &projectPart, - const QByteArray &defines) +void CppEditorDocument::applyExtraPreprocessorDirectivesFromSettings() +{ + if (filePath().isEmpty()) + return; + + const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + filePath().toString(); + const QByteArray directives = ProjectExplorer::SessionManager::value(key).toString().toUtf8(); + + setExtraPreprocessorDirectives(directives); +} + +void CppEditorDocument::setExtraPreprocessorDirectives(const QByteArray &directives) { const auto parser = processor()->parser(); QTC_ASSERT(parser, return); - if (parser->projectPartInfo().projectPart != projectPart - || parser->configuration().editorDefines != defines) { - CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration(); - config.preferredProjectPartId = projectPart->id(); - config.editorDefines = defines; - parser->setConfiguration(config); - emit preprocessorSettingsChanged(!defines.trimmed().isEmpty()); + CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration(); + if (config.editorDefines != directives) { + config.editorDefines = directives; + processor()->setParserConfig(config); + + emit preprocessorSettingsChanged(!directives.trimmed().isEmpty()); + } +} + +void CppEditorDocument::setPreferredParseContext(const QString &parseContextId) +{ + const CppTools::BaseEditorDocumentParser::Ptr parser = processor()->parser(); + QTC_ASSERT(parser, return); + + CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration(); + if (config.preferredProjectPartId != parseContextId) { + config.preferredProjectPartId = parseContextId; + processor()->setParserConfig(config); } } @@ -299,6 +337,23 @@ void CppEditorDocument::releaseResources() m_processor.reset(); } +void CppEditorDocument::showHideInfoBarAboutMultipleParseContexts(bool show) +{ + const Core::Id id = Constants::MULTIPLE_PARSE_CONTEXTS_AVAILABLE; + + if (show) { + Core::InfoBarEntry info(id, + tr("Note: Multiple parse contexts are available for this file. " + "Choose the preferred one from the editor toolbar."), + Core::InfoBarEntry::GlobalSuppressionEnabled); + info.removeCancelButton(); + if (infoBar()->canInfoBeAdded(id)) + infoBar()->addInfo(info); + } else { + infoBar()->removeInfo(id); + } +} + void CppEditorDocument::initializeTimer() { m_processorTimer.setSingleShot(true); @@ -311,6 +366,11 @@ void CppEditorDocument::initializeTimer() Qt::UniqueConnection); } +ParseContextModel &CppEditorDocument::parseContextModel() +{ + return m_parseContextModel; +} + const MinimizableInfoBars &CppEditorDocument::minimizableInfoBars() const { return m_minimizableInfoBars; @@ -321,11 +381,15 @@ CppTools::BaseEditorDocumentProcessor *CppEditorDocument::processor() if (!m_processor) { m_processor.reset(mm()->editorDocumentProcessor(this)); connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::projectPartInfoUpdated, - [this] (const CppTools::ProjectPartInfo &projectPartInfo) + [this] (const CppTools::ProjectPartInfo &info) { - const bool hasProjectPart - = projectPartInfo.hint != CppTools::ProjectPartInfo::IsFallbackMatch; + using namespace CppTools; + const bool hasProjectPart = !(info.hints & ProjectPartInfo::IsFallbackMatch); m_minimizableInfoBars.processHasProjectPart(hasProjectPart); + m_parseContextModel.update(info); + const bool isAmbiguous = info.hints & ProjectPartInfo::IsAmbiguousMatch; + const bool isProjectFile = info.hints & ProjectPartInfo::IsFromProjectMatch; + showHideInfoBarAboutMultipleParseContexts(isAmbiguous && isProjectFile); }); connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::codeWarningsUpdated, [this] (unsigned revision, diff --git a/src/plugins/cppeditor/cppeditordocument.h b/src/plugins/cppeditor/cppeditordocument.h index 9551ccf567c..89c97b42ab6 100644 --- a/src/plugins/cppeditor/cppeditordocument.h +++ b/src/plugins/cppeditor/cppeditordocument.h @@ -26,6 +26,7 @@ #pragma once #include "cppminimizableinfobars.h" +#include "cppparsecontext.h" #include #include @@ -57,11 +58,13 @@ public: void recalculateSemanticInfoDetached(); CppTools::SemanticInfo recalculateSemanticInfo(); // TODO: Remove me - void setPreprocessorSettings(const CppTools::ProjectPart::Ptr &projectPart, - const QByteArray &defines); + void setPreferredParseContext(const QString &parseContextId); + void setExtraPreprocessorDirectives(const QByteArray &directives); + void scheduleProcessDocument(); const MinimizableInfoBars &minimizableInfoBars() const; + ParseContextModel &parseContextModel(); signals: void codeWarningsUpdated(unsigned contentsRevision, @@ -87,6 +90,8 @@ private: void onAboutToReload(); void onReloadFinished(); + void reparseWithPreferredParseContext(const QString &id); + void processDocument(); QByteArray contentsText() const; @@ -94,9 +99,12 @@ private: CppTools::BaseEditorDocumentProcessor *processor(); void resetProcessor(); - void updatePreprocessorSettings(); + void applyPreferredParseContextFromSettings(); + void applyExtraPreprocessorDirectivesFromSettings(); void releaseResources(); + void showHideInfoBarAboutMultipleParseContexts(bool show); + void initializeTimer(); private: @@ -118,6 +126,7 @@ private: QScopedPointer m_editorDocumentHandle; MinimizableInfoBars m_minimizableInfoBars; + ParseContextModel m_parseContextModel; }; } // namespace Internal diff --git a/src/plugins/cppeditor/cppparsecontext.cpp b/src/plugins/cppeditor/cppparsecontext.cpp new file mode 100644 index 00000000000..7be8b5beb10 --- /dev/null +++ b/src/plugins/cppeditor/cppparsecontext.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "cppparsecontext.h" + +#include +#include +#include + +#include +#include + +namespace CppEditor { +namespace Internal { + +void ParseContextModel::update(const CppTools::ProjectPartInfo &projectPartInfo) +{ + beginResetModel(); + reset(projectPartInfo); + endResetModel(); + + emit updated(areMultipleAvailable()); +} + +QString ParseContextModel::currentToolTip() const +{ + const QModelIndex index = createIndex(m_currentIndex, 0); + if (!index.isValid()) + return QString(); + + return tr("

Active Parse Context:
%1

" + "

Multiple parse contexts (set of defines, include paths, and so on) " + "are available for this file.

" + "

Choose a parse context to set it as the preferred one. " + "Clear the preference from the context menu.

") + .arg(data(index, Qt::ToolTipRole).toString()); +} + +void ParseContextModel::setPreferred(int index) +{ + if (index < 0) + return; + + emit preferredParseContextChanged(m_projectParts[index]->id()); +} + +void ParseContextModel::clearPreferred() +{ + emit preferredParseContextChanged(QString()); +} + +bool ParseContextModel::areMultipleAvailable() const +{ + return m_projectParts.size() >= 2; +} + +void ParseContextModel::reset(const CppTools::ProjectPartInfo &projectPartInfo) +{ + // Sort + m_hints = projectPartInfo.hints; + m_projectParts = projectPartInfo.projectParts; + Utils::sort(m_projectParts, &CppTools::ProjectPart::displayName); + + // Determine index for current + const QString id = projectPartInfo.projectPart->id(); + m_currentIndex = Utils::indexOf(m_projectParts, [id](const CppTools::ProjectPart::Ptr &pp) { + return pp->id() == id; + }); + QTC_CHECK(m_currentIndex >= 0); +} + +int ParseContextModel::currentIndex() const +{ + return m_currentIndex; +} + +bool ParseContextModel::isCurrentPreferred() const +{ + return m_hints & CppTools::ProjectPartInfo::IsPreferredMatch; +} + +QString ParseContextModel::currentId() const +{ + if (m_currentIndex < 0) + return QString(); + + return m_projectParts[m_currentIndex]->id(); +} + +int ParseContextModel::rowCount(const QModelIndex &) const +{ + return m_projectParts.size(); +} + +QVariant ParseContextModel::data(const QModelIndex &index, int role) const +{ + if (m_projectParts.isEmpty()) + return QVariant(); + + const int row = index.row(); + if (role == Qt::DisplayRole) + return m_projectParts[row]->displayName; + else if (role == Qt::ToolTipRole) + return QDir::toNativeSeparators(m_projectParts[row]->projectFile); + + return QVariant(); +} + +ParseContextWidget::ParseContextWidget(ParseContextModel &parseContextModel, QWidget *parent) + : QComboBox(parent) + , m_parseContextModel(parseContextModel) +{ + // Set up context menu with a clear action + setContextMenuPolicy(Qt::ActionsContextMenu); + m_clearPreferredAction = new QAction(tr("Clear Preferred Parse Context"), this); + connect(m_clearPreferredAction, &QAction::triggered,[&]() { + m_parseContextModel.clearPreferred(); + }); + addAction(m_clearPreferredAction); + + // Set up sync of this widget and model in both directions + connect(this, + static_cast(&QComboBox::activated), + &m_parseContextModel, + &ParseContextModel::setPreferred); + connect(&m_parseContextModel, &ParseContextModel::updated, + this, &ParseContextWidget::syncToModel); + + // Set up model + setModel(&m_parseContextModel); +} + +void ParseContextWidget::syncToModel() +{ + const int index = m_parseContextModel.currentIndex(); + if (index < 0) + return; // E.g. editor was duplicated but no project context was determined yet. + + if (currentIndex() != index) + setCurrentIndex(index); + + setToolTip(m_parseContextModel.currentToolTip()); + + const bool isPreferred = m_parseContextModel.isCurrentPreferred(); + m_clearPreferredAction->setEnabled(isPreferred); + QComboBox::setProperty("highlightWidget", isPreferred); +} + +} // namespace Internal +} // namespace CppEditor diff --git a/src/plugins/cppeditor/cppparsecontext.h b/src/plugins/cppeditor/cppparsecontext.h new file mode 100644 index 00000000000..4c6cc95e9dc --- /dev/null +++ b/src/plugins/cppeditor/cppparsecontext.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +#include +#include + +namespace CppEditor { +namespace Internal { + +class ParseContextModel : public QAbstractListModel +{ + Q_OBJECT + +public: + void update(const CppTools::ProjectPartInfo &projectPartInfo); + + void setPreferred(int index); + void clearPreferred(); + + int currentIndex() const; + bool isCurrentPreferred() const; + + QString currentId() const; + QString currentToolTip() const; + + bool areMultipleAvailable() const; + +signals: + void updated(bool areMultipleAvailable); + void preferredParseContextChanged(const QString &id); + +private: + void reset(const CppTools::ProjectPartInfo &projectPartInfo); + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + +private: + CppTools::ProjectPartInfo::Hints m_hints; + QList m_projectParts; + int m_currentIndex = -1; +}; + +class ParseContextWidget : public QComboBox +{ + Q_OBJECT + +public: + ParseContextWidget(ParseContextModel &parseContextModel, QWidget *parent); + void syncToModel(); + +private: + ParseContextModel &m_parseContextModel; + QAction *m_clearPreferredAction = nullptr; +}; + +} // namespace Internal +} // namespace CppEditor diff --git a/src/plugins/cppeditor/cpppreprocessordialog.cpp b/src/plugins/cppeditor/cpppreprocessordialog.cpp index 0e770405c6c..bbc7c49e4c6 100644 --- a/src/plugins/cppeditor/cpppreprocessordialog.cpp +++ b/src/plugins/cppeditor/cpppreprocessordialog.cpp @@ -31,18 +31,9 @@ #include -#include - using namespace CppEditor::Internal; -static bool projectPartLessThan(const CppTools::ProjectPart::Ptr &projectPart1, - const CppTools::ProjectPart::Ptr &projectPart2) -{ - return projectPart1->displayName < projectPart2->displayName; -} - -CppPreProcessorDialog::CppPreProcessorDialog(QWidget *parent, const QString &filePath, - const QList &projectParts) +CppPreProcessorDialog::CppPreProcessorDialog(const QString &filePath, QWidget *parent) : QDialog(parent) , m_ui(new Ui::CppPreProcessorDialog()) , m_filePath(filePath) @@ -55,32 +46,9 @@ CppPreProcessorDialog::CppPreProcessorDialog(QWidget *parent, const QString &fil CppSnippetProvider().decorateEditor(m_ui->editWidget); - const QString &projectPartIdToUse = ProjectExplorer::SessionManager::value( - QLatin1String(Constants::CPP_PREPROCESSOR_PROJECT_PREFIX) + m_filePath).toString(); - int currentIndex = 0; - - QList sortedProjectParts(projectParts); - std::stable_sort(sortedProjectParts.begin(), sortedProjectParts.end(), projectPartLessThan); - - foreach (CppTools::ProjectPart::Ptr projectPart, sortedProjectParts) { - m_ui->projectComboBox->addItem(projectPart->displayName); - ProjectPartAddition addition; - addition.projectPart = projectPart; - addition.additionalDirectives = ProjectExplorer::SessionManager::value( - projectPart->id() + QLatin1Char(',') + m_filePath).toString(); - if (projectPart->id() == projectPartIdToUse) - currentIndex = m_ui->projectComboBox->count() - 1; - m_partAdditions << addition; - } - if (m_ui->projectComboBox->count() <= 1) - m_ui->projectComboBox->setEnabled(false); - m_ui->projectComboBox->setCurrentIndex(currentIndex); - m_ui->editWidget->setPlainText(m_partAdditions.value(currentIndex).additionalDirectives); - - connect(m_ui->projectComboBox, - static_cast(&QComboBox::currentIndexChanged), - this, &CppPreProcessorDialog::projectChanged); - connect(m_ui->editWidget, &QPlainTextEdit::textChanged, this, &CppPreProcessorDialog::textChanged); + const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + m_filePath; + const QString directives = ProjectExplorer::SessionManager::value(key).toString(); + m_ui->editWidget->setPlainText(directives); } CppPreProcessorDialog::~CppPreProcessorDialog() @@ -93,41 +61,13 @@ int CppPreProcessorDialog::exec() if (QDialog::exec() == Rejected) return Rejected; - ProjectExplorer::SessionManager::setValue( - QLatin1String(Constants::CPP_PREPROCESSOR_PROJECT_PREFIX) + m_filePath, - m_partAdditions[m_ui->projectComboBox->currentIndex()].projectPart->id()); + const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + m_filePath; + ProjectExplorer::SessionManager::setValue(key, extraPreprocessorDirectives()); - foreach (ProjectPartAddition partAddition, m_partAdditions) { - const QString &previousDirectives = ProjectExplorer::SessionManager::value( - partAddition.projectPart->id() - + QLatin1Char(',') - + m_filePath).toString(); - if (previousDirectives != partAddition.additionalDirectives) { - ProjectExplorer::SessionManager::setValue( - partAddition.projectPart->id() + QLatin1Char(',') + m_filePath, - partAddition.additionalDirectives); - } - } return Accepted; } -CppTools::ProjectPart::Ptr CppPreProcessorDialog::projectPart() const -{ - return m_partAdditions[m_ui->projectComboBox->currentIndex()].projectPart; -} - -QString CppPreProcessorDialog::additionalPreProcessorDirectives() const +QString CppPreProcessorDialog::extraPreprocessorDirectives() const { return m_ui->editWidget->toPlainText(); } - -void CppPreProcessorDialog::projectChanged(int index) -{ - m_ui->editWidget->setPlainText(m_partAdditions[index].additionalDirectives); -} - -void CppPreProcessorDialog::textChanged() -{ - m_partAdditions[m_ui->projectComboBox->currentIndex()].additionalDirectives - = m_ui->editWidget->toPlainText(); -} diff --git a/src/plugins/cppeditor/cpppreprocessordialog.h b/src/plugins/cppeditor/cpppreprocessordialog.h index 8a0c8865ac5..2bca50f4011 100644 --- a/src/plugins/cppeditor/cpppreprocessordialog.h +++ b/src/plugins/cppeditor/cpppreprocessordialog.h @@ -25,9 +25,8 @@ #pragma once -#include - #include +#include namespace CppEditor { namespace Internal { @@ -38,27 +37,17 @@ class CppPreProcessorDialog : public QDialog Q_OBJECT public: - explicit CppPreProcessorDialog(QWidget *parent, const QString &filePath, - const QList &projectParts); + explicit CppPreProcessorDialog(const QString &filePath, QWidget *parent); ~CppPreProcessorDialog(); int exec(); - CppTools::ProjectPart::Ptr projectPart() const; - QString additionalPreProcessorDirectives() const; + QString extraPreprocessorDirectives() const; private: - void projectChanged(int index); - void textChanged(); - - struct ProjectPartAddition { - CppTools::ProjectPart::Ptr projectPart; - QString additionalDirectives; - }; - Ui::CppPreProcessorDialog *m_ui; - QList m_partAdditions; - QString m_filePath; + const QString m_filePath; + const QString m_projectPartId; }; } // namespace Internal diff --git a/src/plugins/cppeditor/cpppreprocessordialog.ui b/src/plugins/cppeditor/cpppreprocessordialog.ui index 38609011565..4dbcd968b8c 100644 --- a/src/plugins/cppeditor/cpppreprocessordialog.ui +++ b/src/plugins/cppeditor/cpppreprocessordialog.ui @@ -14,34 +14,6 @@ Additional C++ Preprocessor Directives - - - - - - Project: - - - - - - - - 0 - 0 - - - - - - - - - - Qt::Horizontal - - - diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.cpp b/src/plugins/cpptools/baseeditordocumentprocessor.cpp index ad26fa653c3..2bf77787d76 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.cpp +++ b/src/plugins/cpptools/baseeditordocumentprocessor.cpp @@ -87,6 +87,12 @@ void BaseEditorDocumentProcessor::editorDocumentTimerRestarted() { } +void BaseEditorDocumentProcessor::setParserConfig( + const BaseEditorDocumentParser::Configuration config) +{ + parser()->setConfiguration(config); +} + void BaseEditorDocumentProcessor::runParser(QFutureInterface &future, BaseEditorDocumentParser::Ptr parser, BaseEditorDocumentParser::UpdateParams updateParams) diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.h b/src/plugins/cpptools/baseeditordocumentprocessor.h index 2f2fdc3af20..155edac6750 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.h +++ b/src/plugins/cpptools/baseeditordocumentprocessor.h @@ -70,6 +70,8 @@ public: virtual void editorDocumentTimerRestarted(); + virtual void setParserConfig(const BaseEditorDocumentParser::Configuration config); + public: using HeaderErrorDiagnosticWidgetCreator = std::function; diff --git a/src/plugins/cpptools/cppprojectpartchooser.cpp b/src/plugins/cpptools/cppprojectpartchooser.cpp index a79b44540a7..bbf9ca35594 100644 --- a/src/plugins/cpptools/cppprojectpartchooser.cpp +++ b/src/plugins/cpptools/cppprojectpartchooser.cpp @@ -37,7 +37,7 @@ public: struct PrioritizedProjectPart { PrioritizedProjectPart(const ProjectPart::Ptr &projectPart, int priority) - : projectPart(projectPart) , priority(priority) {} + : projectPart(projectPart), priority(priority) {} ProjectPart::Ptr projectPart; int priority = 0; @@ -46,38 +46,42 @@ public: ProjectPartPrioritizer(const QList &projectParts, const QString &preferredProjectPartId, const ProjectExplorer::Project *activeProject, - Language languagePreference) - : m_projectParts(projectParts) - , m_preferredProjectPartId(preferredProjectPartId) + Language languagePreference, + bool areProjectPartsFromDependencies) + : m_preferredProjectPartId(preferredProjectPartId) , m_activeProject(activeProject) , m_languagePreference(languagePreference) { - // Determine best project part - const QList prioritized = prioritize(); - m_projectPart = prioritized.first().projectPart; + // Prioritize + const QList prioritized = prioritize(projectParts); + for (const PrioritizedProjectPart &ppp : prioritized) + m_info.projectParts << ppp.projectPart; - // Determine ambiguity - m_isAmbiguous = prioritized.size() > 1 - ? prioritized[0].priority == prioritized[1].priority - : false; + // Best project part + m_info.projectPart = m_info.projectParts.first(); + + // Hints + if (m_info.projectParts.size() > 1) + m_info.hints |= ProjectPartInfo::IsAmbiguousMatch; + if (prioritized.first().priority > 1000) + m_info.hints |= ProjectPartInfo::IsPreferredMatch; + if (areProjectPartsFromDependencies) + m_info.hints |= ProjectPartInfo::IsFromDependenciesMatch; + else + m_info.hints |= ProjectPartInfo::IsFromProjectMatch; } - ProjectPart::Ptr projectPart() + ProjectPartInfo info() const { - return m_projectPart; - } - - bool isAmbiguous() const - { - return m_isAmbiguous; + return m_info; } private: - QList prioritize() + QList prioritize(const QList &projectParts) { // Prioritize - QList prioritized = Utils::transform(m_projectParts, - [&](const ProjectPart::Ptr &projectPart) { + QList prioritized = Utils::transform(projectParts, + [&](const ProjectPart::Ptr &projectPart) { return PrioritizedProjectPart{projectPart, priority(*projectPart)}; }); @@ -118,14 +122,12 @@ private: } private: - const QList m_projectParts; const QString m_preferredProjectPartId; const ProjectExplorer::Project *m_activeProject = nullptr; Language m_languagePreference = Language::Cxx; // Results - ProjectPart::Ptr m_projectPart; - bool m_isAmbiguous = false; + ProjectPartInfo m_info; }; ProjectPartInfo ProjectPartChooser::choose( @@ -141,40 +143,30 @@ ProjectPartInfo ProjectPartChooser::choose( QTC_CHECK(m_fallbackProjectPart); ProjectPart::Ptr projectPart = currentProjectPartInfo.projectPart; - ProjectPartInfo::Hint hint = ProjectPartInfo::NoHint; - QList projectParts = m_projectPartsForFile(filePath); + bool areProjectPartsFromDependencies = false; + if (projectParts.isEmpty()) { if (!projectsUpdated && projectPart - && currentProjectPartInfo.hint == ProjectPartInfo::IsFallbackMatch) + && currentProjectPartInfo.hints & ProjectPartInfo::IsFallbackMatch) // Avoid re-calculating the expensive dependency table for non-project files. - return {projectPart, ProjectPartInfo::IsFallbackMatch}; + return ProjectPartInfo(projectPart, {projectPart}, ProjectPartInfo::IsFallbackMatch); // Fall-back step 1: Get some parts through the dependency table: projectParts = m_projectPartsFromDependenciesForFile(filePath); if (projectParts.isEmpty()) { // Fall-back step 2: Use fall-back part from the model manager: projectPart = m_fallbackProjectPart(); - hint = ProjectPartInfo::IsFallbackMatch; - } else { - ProjectPartPrioritizer prioritizer(projectParts, - preferredProjectPartId, - activeProject, - languagePreference); - projectPart = prioritizer.projectPart(); + return ProjectPartInfo(projectPart, {projectPart}, ProjectPartInfo::IsFallbackMatch); } - } else { - ProjectPartPrioritizer prioritizer(projectParts, - preferredProjectPartId, - activeProject, - languagePreference); - projectPart = prioritizer.projectPart(); - hint = prioritizer.isAmbiguous() - ? ProjectPartInfo::IsAmbiguousMatch - : ProjectPartInfo::NoHint; + areProjectPartsFromDependencies = true; } - return {projectPart, hint}; + return ProjectPartPrioritizer(projectParts, + preferredProjectPartId, + activeProject, + languagePreference, + areProjectPartsFromDependencies).info(); } void ProjectPartChooser::setFallbackProjectPart(const FallBackProjectPart &getter) diff --git a/src/plugins/cpptools/cpptools_utils.h b/src/plugins/cpptools/cpptools_utils.h index a90805a7ab0..7c775998d6d 100644 --- a/src/plugins/cpptools/cpptools_utils.h +++ b/src/plugins/cpptools/cpptools_utils.h @@ -34,14 +34,29 @@ enum class Language { C, Cxx }; class ProjectPartInfo { public: enum Hint { - NoHint, - IsFallbackMatch, - IsAmbiguousMatch + NoHint = 0, + IsFallbackMatch = 1 << 0, + IsAmbiguousMatch = 1 << 1, + IsPreferredMatch = 1 << 2, + IsFromProjectMatch = 1 << 3, + IsFromDependenciesMatch = 1 << 4, }; + Q_DECLARE_FLAGS(Hints, Hint) + + ProjectPartInfo() = default; + ProjectPartInfo(const ProjectPart::Ptr &projectPart, + const QList &projectParts, + Hints hints) + : projectPart(projectPart) + , projectParts(projectParts) + , hints(hints) + { + } public: ProjectPart::Ptr projectPart; - Hint hint; + QList projectParts; // The one above as first plus alternatives. + Hints hints = NoHint; }; } // namespace CppTools diff --git a/tests/unit/unittest/cppprojectpartchooser-test.cpp b/tests/unit/unittest/cppprojectpartchooser-test.cpp index b2f3918cbc2..8020d1a35cc 100644 --- a/tests/unit/unittest/cppprojectpartchooser-test.cpp +++ b/tests/unit/unittest/cppprojectpartchooser-test.cpp @@ -49,7 +49,9 @@ protected: protected: QString filePath; - ProjectPartInfo currentProjectPartInfo{ProjectPart::Ptr(new ProjectPart), + ProjectPart::Ptr currentProjectPart{new ProjectPart}; + ProjectPartInfo currentProjectPartInfo{currentProjectPart, + {currentProjectPart}, ProjectPartInfo::NoHint}; QString preferredProjectPartId; const ProjectExplorer::Project *activeProject = nullptr; @@ -74,6 +76,37 @@ TEST_F(ProjectPartChooser, ChooseManuallySet) ASSERT_THAT(chosen, Eq(p2)); } +TEST_F(ProjectPartChooser, IndicateManuallySet) +{ + ProjectPart::Ptr p1(new ProjectPart); + ProjectPart::Ptr p2(new ProjectPart); + p2->projectFile = preferredProjectPartId = "someId"; + projectPartsForFile += {p1, p2}; + + const ProjectPartInfo::Hints hints = choose().hints; + + ASSERT_TRUE(hints & ProjectPartInfo::IsPreferredMatch); +} + +TEST_F(ProjectPartChooser, IndicateManuallySetForFallbackToProjectPartFromDependencies) +{ + ProjectPart::Ptr p1(new ProjectPart); + ProjectPart::Ptr p2(new ProjectPart); + p2->projectFile = preferredProjectPartId = "someId"; + projectPartsFromDependenciesForFile += {p1, p2}; + + const ProjectPartInfo::Hints hints = choose().hints; + + ASSERT_TRUE(hints & ProjectPartInfo::IsPreferredMatch); +} + +TEST_F(ProjectPartChooser, DoNotIndicateNotManuallySet) +{ + const ProjectPartInfo::Hints hints = choose().hints; + + ASSERT_FALSE(hints & ProjectPartInfo::IsPreferredMatch); +} + TEST_F(ProjectPartChooser, ForMultipleChooseFromActiveProject) { const QList projectParts = createProjectPartsWithDifferentProjects(); @@ -154,9 +187,20 @@ TEST_F(ProjectPartChooser, IndicateMultiple) const ProjectPart::Ptr p2{new ProjectPart}; projectPartsForFile += { p1, p2 }; - const ProjectPartInfo::Hint hint = choose().hint; + const ProjectPartInfo::Hints hints = choose().hints; - ASSERT_THAT(hint, Eq(ProjectPartInfo::Hint::IsAmbiguousMatch)); + ASSERT_TRUE(hints & ProjectPartInfo::IsAmbiguousMatch); +} + +TEST_F(ProjectPartChooser, IndicateMultipleForFallbackToProjectPartFromDependencies) +{ + const ProjectPart::Ptr p1{new ProjectPart}; + const ProjectPart::Ptr p2{new ProjectPart}; + projectPartsFromDependenciesForFile += { p1, p2 }; + + const ProjectPartInfo::Hints hints = choose().hints; + + ASSERT_TRUE(hints & ProjectPartInfo::IsAmbiguousMatch); } TEST_F(ProjectPartChooser, ForMultipleChooseNewIfPreviousIsGone) @@ -193,7 +237,7 @@ TEST_F(ProjectPartChooser, ContinueUsingFallbackFromModelManagerIfProjectDoesNot // ...without re-calculating the dependency table. fallbackProjectPart.reset(new ProjectPart); currentProjectPartInfo.projectPart = fallbackProjectPart; - currentProjectPartInfo.hint = ProjectPartInfo::IsFallbackMatch; + currentProjectPartInfo.hints |= ProjectPartInfo::IsFallbackMatch; projectPartsFromDependenciesForFile += ProjectPart::Ptr(new ProjectPart); const ProjectPart::Ptr chosen = choose().projectPart; @@ -205,7 +249,7 @@ TEST_F(ProjectPartChooser, StopUsingFallbackFromModelManagerIfProjectChanges1) { fallbackProjectPart.reset(new ProjectPart); currentProjectPartInfo.projectPart = fallbackProjectPart; - currentProjectPartInfo.hint = ProjectPartInfo::IsFallbackMatch; + currentProjectPartInfo.hints |= ProjectPartInfo::IsFallbackMatch; const ProjectPart::Ptr addedProject(new ProjectPart); projectPartsForFile += addedProject; @@ -218,7 +262,7 @@ TEST_F(ProjectPartChooser, StopUsingFallbackFromModelManagerIfProjectChanges2) { fallbackProjectPart.reset(new ProjectPart); currentProjectPartInfo.projectPart = fallbackProjectPart; - currentProjectPartInfo.hint = ProjectPartInfo::IsFallbackMatch; + currentProjectPartInfo.hints |= ProjectPartInfo::IsFallbackMatch; const ProjectPart::Ptr addedProject(new ProjectPart); projectPartsFromDependenciesForFile += addedProject; projectsChanged = true; @@ -232,9 +276,27 @@ TEST_F(ProjectPartChooser, IndicateFallbacktoProjectPartFromModelManager) { fallbackProjectPart.reset(new ProjectPart); - const ProjectPartInfo::Hint hint = choose().hint; + const ProjectPartInfo::Hints hints = choose().hints; - ASSERT_THAT(hint, Eq(ProjectPartInfo::Hint::IsFallbackMatch)); + ASSERT_TRUE(hints & ProjectPartInfo::IsFallbackMatch); +} + +TEST_F(ProjectPartChooser, IndicateFromDependencies) +{ + projectPartsFromDependenciesForFile += ProjectPart::Ptr(new ProjectPart); + + const ProjectPartInfo::Hints hints = choose().hints; + + ASSERT_TRUE(hints & ProjectPartInfo::IsFromDependenciesMatch); +} + +TEST_F(ProjectPartChooser, DoNotIndicateFromDependencies) +{ + projectPartsForFile += ProjectPart::Ptr(new ProjectPart); + + const ProjectPartInfo::Hints hints = choose().hints; + + ASSERT_FALSE(hints & ProjectPartInfo::IsFromDependenciesMatch); } void ProjectPartChooser::SetUp()