Commit 0b8df413 authored by Nikolai Kosjar's avatar Nikolai Kosjar

CppTools: Provide hints about chosen project part for editor document

Parse issues can have multiple reasons (invalid kit, not a project file,
actual parse issue) and we should be able to tell them apart. With this
change, we can distinguish between the fallback project part and a
ambiguous project part.

Follow up changes will use this to display more accurate diagnostics.

Change-Id: Icc8767607cc17dc14d6227b07f34e81ba5525a96
Reviewed-by: David Schulz's avatarDavid Schulz <david.schulz@qt.io>
parent 19eaf87e
......@@ -39,12 +39,13 @@ void ClangEditorDocumentParser::updateImpl(const QFutureInterface<void> &,
const UpdateParams &updateParams)
{
State state_ = state();
state_.projectPart = determineProjectPart(filePath(),
configuration(),
state_,
updateParams.activeProject,
updateParams.languagePreference,
updateParams.hasActiveProjectChanged);
state_.projectPartInfo = determineProjectPart(filePath(),
configuration(),
state_,
updateParams.activeProject,
updateParams.languagePreference,
updateParams.hasActiveProjectChanged);
emit projectPartInfoUpdated(state_.projectPartInfo);
setState(state_);
}
......
......@@ -80,6 +80,9 @@ ClangEditorDocumentProcessor::ClangEditorDocumentProcessor(
connect(&m_updateTranslationUnitTimer, &QTimer::timeout,
this, &ClangEditorDocumentProcessor::updateTranslationUnitIfProjectPartExists);
connect(m_parser.data(), &ClangEditorDocumentParser::projectPartInfoUpdated,
this, &BaseEditorDocumentProcessor::projectPartInfoUpdated);
// 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,
......@@ -302,7 +305,7 @@ static bool isProjectPartLoadedOrIsFallback(CppTools::ProjectPart::Ptr projectPa
void ClangEditorDocumentProcessor::updateProjectPartAndTranslationUnitForEditor()
{
const CppTools::ProjectPart::Ptr projectPart = m_parser->projectPart();
const CppTools::ProjectPart::Ptr projectPart = m_parser->projectPartInfo().projectPart;
if (isProjectPartLoadedOrIsFallback(projectPart)) {
registerTranslationUnitForEditor(projectPart.data());
......
......@@ -131,7 +131,7 @@ QStringList createClangOptions(const ProjectPart::Ptr &pPart, ProjectFile::Kind
ProjectPart::Ptr projectPartForFile(const QString &filePath)
{
if (const auto parser = CppTools::BaseEditorDocumentParser::get(filePath))
return parser->projectPart();
return parser->projectPartInfo().projectPart;
return ProjectPart::Ptr();
}
......
......@@ -1580,7 +1580,7 @@ void CppCodeModelInspectorDialog::refresh()
// Project Parts
const ProjectPart::Ptr editorsProjectPart = cppEditorDocument
? cppEditorDocument->processor()->parser()->projectPart()
? cppEditorDocument->processor()->parser()->projectPartInfo().projectPart
: ProjectPart::Ptr();
const QList<ProjectInfo> projectInfos = cmmi->projectInfos();
......
......@@ -274,7 +274,8 @@ void CppEditorDocument::setPreprocessorSettings(const CppTools::ProjectPart::Ptr
{
const auto parser = processor()->parser();
QTC_ASSERT(parser, return);
if (parser->projectPart() != projectPart || parser->configuration().editorDefines != defines) {
if (parser->projectPartInfo().projectPart != projectPart
|| parser->configuration().editorDefines != defines) {
CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration();
config.manuallySetProjectPart = projectPart;
config.editorDefines = defines;
......
......@@ -54,6 +54,8 @@ namespace CppTools {
BaseEditorDocumentParser::BaseEditorDocumentParser(const QString &filePath)
: m_filePath(filePath)
{
static int meta = qRegisterMetaType<ProjectPartInfo>("CppTools::ProjectPartInfo");
Q_UNUSED(meta);
}
BaseEditorDocumentParser::~BaseEditorDocumentParser()
......@@ -102,9 +104,9 @@ void BaseEditorDocumentParser::setState(const State &state)
m_state = state;
}
ProjectPart::Ptr BaseEditorDocumentParser::projectPart() const
ProjectPartInfo BaseEditorDocumentParser::projectPartInfo() const
{
return state().projectPart;
return state().projectPartInfo;
}
BaseEditorDocumentParser::Ptr BaseEditorDocumentParser::get(const QString &filePath)
......@@ -117,7 +119,7 @@ BaseEditorDocumentParser::Ptr BaseEditorDocumentParser::get(const QString &fileP
return BaseEditorDocumentParser::Ptr();
}
ProjectPart::Ptr BaseEditorDocumentParser::determineProjectPart(
ProjectPartInfo BaseEditorDocumentParser::determineProjectPart(
const QString &filePath,
const Configuration &config,
const State &state,
......@@ -125,7 +127,9 @@ ProjectPart::Ptr BaseEditorDocumentParser::determineProjectPart(
Language languagePreference,
bool hasActiveProjectChanged)
{
Internal::ProjectPartChooser chooser;
using Internal::ProjectPartChooser;
ProjectPartChooser chooser;
chooser.setFallbackProjectPart([](){
return CppModelManager::instance()->fallbackProjectPart();
});
......@@ -137,13 +141,16 @@ ProjectPart::Ptr BaseEditorDocumentParser::determineProjectPart(
return CppModelManager::instance()->projectPartFromDependencies(fileName);
});
return chooser.choose(filePath,
state.projectPart,
config.manuallySetProjectPart,
config.stickToPreviousProjectPart,
activeProject,
languagePreference,
hasActiveProjectChanged);
const ProjectPartInfo chooserResult
= chooser.choose(filePath,
state.projectPartInfo.projectPart,
config.manuallySetProjectPart,
config.stickToPreviousProjectPart,
activeProject,
languagePreference,
hasActiveProjectChanged);
return chooserResult;
}
} // namespace CppTools
......@@ -25,8 +25,8 @@
#pragma once
#include "cpplanguage.h"
#include "cpptools_global.h"
#include "cpptools_utils.h"
#include "cppworkingcopy.h"
#include "projectpart.h"
......@@ -82,22 +82,26 @@ public:
void update(const UpdateParams &updateParams);
void update(const QFutureInterface<void> &future, const UpdateParams &updateParams);
ProjectPart::Ptr projectPart() const;
ProjectPartInfo projectPartInfo() const;
signals:
void projectPartInfoUpdated(const CppTools::ProjectPartInfo &projectPartInfo);
protected:
struct State {
QByteArray editorDefines;
ProjectPart::Ptr projectPart;
ProjectPartInfo projectPartInfo;
};
State state() const;
void setState(const State &state);
static ProjectPart::Ptr determineProjectPart(const QString &filePath,
const Configuration &config,
const State &state,
const ProjectExplorer::Project *activeProject,
Language languagePreference,
bool hasActiveProjectChanged);
static ProjectPartInfo determineProjectPart(
const QString &filePath,
const Configuration &config,
const State &state,
const ProjectExplorer::Project *activeProject,
Language languagePreference,
bool hasActiveProjectChanged);
mutable QMutex m_stateAndConfigurationMutex;
......
......@@ -26,10 +26,10 @@
#include "baseeditordocumentprocessor.h"
#include "cppcodemodelsettings.h"
#include "cpplanguage.h"
#include "cppmodelmanager.h"
#include "cpptoolsbridge.h"
#include "cpptoolsreuse.h"
#include "cpptools_utils.h"
#include "editordocumenthandle.h"
#include <projectexplorer/session.h>
......
......@@ -74,8 +74,9 @@ public:
using HeaderErrorDiagnosticWidgetCreator = std::function<QWidget*()>;
signals:
// Signal interface to implement
void projectPartInfoUpdated(const CppTools::ProjectPartInfo &projectPartInfo);
void codeWarningsUpdated(unsigned revision,
const QList<QTextEdit::ExtraSelection> selections,
const HeaderErrorDiagnosticWidgetCreator &creator,
......
......@@ -77,19 +77,20 @@ void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface<void> &futur
QString projectConfigFile;
LanguageFeatures features = LanguageFeatures::defaultFeatures();
baseState.projectPart = determineProjectPart(filePath(),
baseConfig,
baseState,
updateParams.activeProject,
updateParams.languagePreference,
updateParams.hasActiveProjectChanged);
baseState.projectPartInfo = determineProjectPart(filePath(),
baseConfig,
baseState,
updateParams.activeProject,
updateParams.languagePreference,
updateParams.hasActiveProjectChanged);
emit projectPartInfoUpdated(baseState.projectPartInfo);
if (state.forceSnapshotInvalidation) {
invalidateSnapshot = true;
state.forceSnapshotInvalidation = false;
}
if (const ProjectPart::Ptr part = baseState.projectPart) {
if (const ProjectPart::Ptr part = baseState.projectPartInfo.projectPart) {
configFile += part->toolchainDefines;
configFile += overwrittenToolchainDefines(*part.data());
configFile += part->projectDefines;
......
......@@ -190,6 +190,8 @@ BuiltinEditorDocumentProcessor::BuiltinEditorDocumentProcessor(
});
}
connect(m_parser.data(), &BuiltinEditorDocumentParser::projectPartInfoUpdated,
this, &BaseEditorDocumentProcessor::projectPartInfoUpdated);
connect(m_parser.data(), &BuiltinEditorDocumentParser::finished,
this, &BuiltinEditorDocumentProcessor::onParserFinished);
connect(&m_semanticInfoUpdater, &SemanticInfoUpdater::updated,
......
......@@ -167,7 +167,7 @@ ProjectPart::Ptr projectPartOfEditorDocument(const QString &filePath)
{
auto *editorDocument = CppModelManager::instance()->cppEditorDocument(filePath);
QTC_ASSERT(editorDocument, return ProjectPart::Ptr());
return editorDocument->processor()->parser()->projectPart();
return editorDocument->processor()->parser()->projectPartInfo().projectPart;
}
} // anonymous namespace
......
......@@ -31,82 +31,144 @@
namespace CppTools {
namespace Internal {
static bool isPreferredLanguage(const ProjectPart &projectPart, Language preference)
class ProjectPartPrioritizer
{
const bool isCProjectPart = projectPart.languageVersion <= ProjectPart::LatestCVersion;
return (preference == Language::C && isCProjectPart)
|| (preference == Language::Cxx && !isCProjectPart);
}
public:
struct PrioritizedProjectPart
{
PrioritizedProjectPart(const ProjectPart::Ptr &projectPart, int priority)
: projectPart(projectPart) , priority(priority) {}
ProjectPart::Ptr projectPart;
int priority = 0;
};
static int priority(const ProjectPart &projectPart,
const ProjectExplorer::Project *activeProject,
Language languagePreference)
{
int thePriority = 0;
ProjectPartPrioritizer(const QList<ProjectPart::Ptr> &projectParts,
const ProjectExplorer::Project *activeProject,
Language languagePreference)
: m_projectParts(projectParts)
, m_activeProject(activeProject)
, m_languagePreference(languagePreference)
{
// Determine best project part
const QList<PrioritizedProjectPart> prioritized = prioritize();
m_projectPart = prioritized.first().projectPart;
// Determine ambiguity
m_isAmbiguous = prioritized.size() > 1
? prioritized[0].priority == prioritized[1].priority
: false;
}
if (projectPart.project == activeProject)
thePriority += 100;
ProjectPart::Ptr projectPart()
{
return m_projectPart;
}
if (projectPart.selectedForBuilding)
thePriority += 10;
bool isAmbiguous() const
{
return m_isAmbiguous;
}
if (isPreferredLanguage(projectPart, languagePreference))
thePriority += 1;
private:
QList<PrioritizedProjectPart> prioritize()
{
// Prioritize
QList<PrioritizedProjectPart> prioritized = Utils::transform(m_projectParts,
[&](const ProjectPart::Ptr &projectPart) {
return PrioritizedProjectPart{projectPart, priority(*projectPart)};
});
// Sort according to priority
const auto lessThan = [&] (const PrioritizedProjectPart &p1,
const PrioritizedProjectPart &p2) {
return p1.priority > p2.priority;
};
std::stable_sort(prioritized.begin(), prioritized.end(), lessThan);
return prioritized;
}
return thePriority;
}
int priority(const ProjectPart &projectPart) const
{
int thePriority = 0;
static ProjectPart::Ptr chooseFromMultiple(const QList<ProjectPart::Ptr> &projectParts,
const ProjectExplorer::Project *activeProject,
Language languagePreference)
{
QList<ProjectPart::Ptr> projectPartsPrioritized = projectParts;
const auto lessThan = [&] (const ProjectPart::Ptr &p1, const ProjectPart::Ptr &p2) {
return priority(*p1, activeProject, languagePreference)
> priority(*p2, activeProject, languagePreference);
};
std::stable_sort(projectPartsPrioritized.begin(), projectPartsPrioritized.end(), lessThan);
if (projectPart.project == m_activeProject)
thePriority += 100;
return projectPartsPrioritized.first();
}
if (projectPart.selectedForBuilding)
thePriority += 10;
if (isPreferredLanguage(projectPart))
thePriority += 1;
return thePriority;
}
bool isPreferredLanguage(const ProjectPart &projectPart) const
{
const bool isCProjectPart = projectPart.languageVersion <= ProjectPart::LatestCVersion;
return (m_languagePreference == Language::C && isCProjectPart)
|| (m_languagePreference == Language::Cxx && !isCProjectPart);
}
ProjectPart::Ptr ProjectPartChooser::choose(const QString &filePath,
const ProjectPart::Ptr &currentProjectPart,
const ProjectPart::Ptr &manuallySetProjectPart,
bool stickToPreviousProjectPart,
const ProjectExplorer::Project *activeProject,
Language languagePreference,
bool projectHasChanged) const
private:
const QList<ProjectPart::Ptr> m_projectParts;
const ProjectExplorer::Project *m_activeProject = nullptr;
Language m_languagePreference = Language::Cxx;
// Results
ProjectPart::Ptr m_projectPart;
bool m_isAmbiguous = false;
};
ProjectPartInfo ProjectPartChooser::choose(
const QString &filePath,
const ProjectPart::Ptr &currentProjectPart,
const ProjectPart::Ptr &manuallySetProjectPart,
bool stickToPreviousProjectPart,
const ProjectExplorer::Project *activeProject,
Language languagePreference,
bool projectHasChanged) const
{
QTC_CHECK(m_projectPartsForFile);
QTC_CHECK(m_projectPartsFromDependenciesForFile);
QTC_CHECK(m_fallbackProjectPart);
if (manuallySetProjectPart)
return manuallySetProjectPart;
return {manuallySetProjectPart, ProjectPartInfo::NoHint};
ProjectPart::Ptr projectPart = currentProjectPart;
ProjectPartInfo::Hint hint = ProjectPartInfo::NoHint;
QList<ProjectPart::Ptr> projectParts = m_projectPartsForFile(filePath);
if (projectParts.isEmpty()) {
if (projectPart && stickToPreviousProjectPart)
// File is not directly part of any project, but we got one before. We will re-use it,
// because re-calculating this can be expensive when the dependency table is big.
return projectPart;
return {projectPart, ProjectPartInfo::NoHint};
// Fall-back step 1: Get some parts through the dependency table:
projectParts = m_projectPartsFromDependenciesForFile(filePath);
if (projectParts.isEmpty())
if (projectParts.isEmpty()) {
// Fall-back step 2: Use fall-back part from the model manager:
projectPart = m_fallbackProjectPart();
else
projectPart = chooseFromMultiple(projectParts, activeProject, languagePreference);
hint = ProjectPartInfo::IsFallbackMatch;
} else {
ProjectPartPrioritizer prioritizer(projectParts, activeProject, languagePreference);
projectPart = prioritizer.projectPart();
}
} else {
if (projectHasChanged || !projectParts.contains(projectPart))
projectPart = chooseFromMultiple(projectParts, activeProject, languagePreference);
if (projectHasChanged || !projectParts.contains(projectPart)) {
ProjectPartPrioritizer prioritizer(projectParts, activeProject, languagePreference);
projectPart = prioritizer.projectPart();
hint = prioritizer.isAmbiguous()
? ProjectPartInfo::IsAmbiguousMatch
: ProjectPartInfo::NoHint;
}
}
return projectPart;
return {projectPart, hint};
}
void ProjectPartChooser::setFallbackProjectPart(const FallBackProjectPart &getter)
......
......@@ -25,7 +25,7 @@
#pragma once
#include "cpplanguage.h"
#include "cpptools_utils.h"
#include "projectpart.h"
#include <functional>
......@@ -48,13 +48,14 @@ public:
void setProjectPartsForFile(const ProjectPartsForFile &getter);
void setProjectPartsFromDependenciesForFile(const ProjectPartsFromDependenciesForFile &getter);
ProjectPart::Ptr choose(const QString &filePath,
const ProjectPart::Ptr &currentProjectPart,
const ProjectPart::Ptr &manuallySetProjectPart,
bool stickToPreviousProjectPart,
const ProjectExplorer::Project *activeProject,
Language languagePreference,
bool projectHasChanged) const;
ProjectPartInfo choose(
const QString &filePath,
const ProjectPart::Ptr &currentProjectPart,
const ProjectPart::Ptr &manuallySetProjectPart,
bool stickToPreviousProjectPart,
const ProjectExplorer::Project *activeProject,
Language languagePreference,
bool projectHasChanged) const;
private:
FallBackProjectPart m_fallbackProjectPart;
......
......@@ -34,7 +34,6 @@ HEADERS += \
cppfunctionsfilter.h \
cppincludesfilter.h \
cppindexingsupport.h \
cpplanguage.h \
cpplocalsymbols.h \
cpplocatordata.h \
cpplocatorfilter.h \
......@@ -50,6 +49,7 @@ HEADERS += \
cppsemanticinfoupdater.h \
cppsourceprocessor.h \
cpptools_global.h \
cpptools_utils.h \
cpptoolsconstants.h \
cpptoolsjsextension.h \
cpptoolsplugin.h \
......
......@@ -61,7 +61,6 @@ Project {
"cppfunctionsfilter.cpp", "cppfunctionsfilter.h",
"cppincludesfilter.cpp", "cppincludesfilter.h",
"cppindexingsupport.cpp", "cppindexingsupport.h",
"cpplanguage.h",
"cpplocalsymbols.cpp", "cpplocalsymbols.h",
"cpplocatordata.cpp", "cpplocatordata.h",
"cpplocatorfilter.cpp", "cpplocatorfilter.h",
......@@ -81,6 +80,7 @@ Project {
"cpptoolsbridgeqtcreatorimplementation.cpp", "cpptoolsbridgeqtcreatorimplementation.h",
"cpptools.qrc",
"cpptools_global.h",
"cpptools_utils.h",
"cpptoolsconstants.h",
"cpptoolsjsextension.cpp", "cpptoolsjsextension.h",
"cpptoolsplugin.cpp", "cpptoolsplugin.h",
......
......@@ -25,8 +25,23 @@
#pragma once
#include "projectpart.h"
namespace CppTools {
enum class Language { C, Cxx };
class ProjectPartInfo {
public:
enum Hint {
NoHint,
IsFallbackMatch,
IsAmbiguousMatch
};
public:
ProjectPart::Ptr projectPart;
Hint hint;
};
} // namespace CppTools
......@@ -47,7 +47,7 @@ namespace {
CppTools::ProjectPart::Ptr projectPartForFile(const QString &filePath)
{
if (const auto parser = BaseEditorDocumentParser::get(filePath))
return parser->projectPart();
return parser->projectPartInfo().projectPart;
return CppTools::ProjectPart::Ptr();
}
......
......@@ -25,12 +25,13 @@
#include "googletest.h"
#include <cpptools/cpplanguage.h>
#include <cpptools/cppprojectpartchooser.h>
#include <cpptools/cpptools_utils.h>
#include <cpptools/projectpart.h>
using CppTools::Internal::ProjectPartChooser;
using CppTools::ProjectPart;
using CppTools::ProjectPartInfo;
using CppTools::Language;
using testing::Eq;
......@@ -41,7 +42,7 @@ class ProjectPartChooser : public ::testing::Test
{
protected:
void SetUp() override;
const ProjectPart::Ptr choose() const;
const ProjectPartInfo choose() const;
static QList<ProjectPart::Ptr> createProjectPartsWithDifferentProjects();
static QList<ProjectPart::Ptr> createCAndCxxProjectParts();
......@@ -65,7 +66,7 @@ TEST_F(ProjectPartChooser, ChooseManuallySet)
{
manuallySetProjectPart.reset(new ProjectPart);
const ProjectPart::Ptr chosen = choose();
const ProjectPart::Ptr chosen = choose().projectPart;
ASSERT_THAT(chosen, Eq(manuallySetProjectPart));
}
......@@ -76,7 +77,7 @@ TEST_F(ProjectPartChooser, ForMultipleChoosePrevious)
projectPartsForFile += otherProjectPart;
projectPartsForFile += currentProjectPart;
const ProjectPart::Ptr chosen = choose();
const ProjectPart::Ptr chosen = choose().projectPart;
ASSERT_THAT(chosen, Eq(currentProjectPart));
}
......@@ -88,7 +89,7 @@ TEST_F(ProjectPartChooser, ForMultipleChooseFromActiveProject)
projectPartsForFile += projectParts;
activeProject = secondProjectPart->project;
const ProjectPart::Ptr chosen = choose();
const ProjectPart::Ptr chosen = choose().projectPart;
ASSERT_THAT(chosen, Eq(secondProjectPart));
}
......@@ -102,7 +103,7 @@ TEST_F(ProjectPartChooser, ForMultiplePreferSelectedForBuilding)
projectPartsForFile += firstProjectPart;
projectPartsForFile += secondProjectPart;
const ProjectPart::Ptr chosen = choose();
const ProjectPart::Ptr chosen = choose().projectPart;
ASSERT_THAT(chosen, Eq(secondProjectPart));
}
......@@ -114,7 +115,7 @@ TEST_F(ProjectPartChooser, ForMultipleFromDependenciesChooseFromActiveProject)
projectPartsFromDependenciesForFile += projectParts;
activeProject = secondProjectPart->project;
const ProjectPart::Ptr chosen = choose();
const ProjectPart::Ptr chosen = choose().projectPart;
ASSERT_THAT(chosen, Eq(secondProjectPart));
}
......@@ -129,7 +130,7 @@ TEST_F(ProjectPartChooser, ForMultipleCheckIfActiveProjectChanged)
activeProject = secondProjectPart->project;
projectHasChanged = true;
const ProjectPart::Ptr chosen = choose();
const ProjectPart::Ptr chosen = choose().projectPart;
ASSERT_THAT(chosen, Eq(secondProjectPart));
}
......@@ -140,7 +141,7 @@ TEST_F(ProjectPartChooser, ForMultipleAndAmbigiousHeaderPreferCProjectPart)
projectPartsForFile = createCAndCxxProjectParts();
const ProjectPart::Ptr cProjectPart = projectPartsForFile.at(0);
const ProjectPart::Ptr chosen = choose();
const ProjectPart::Ptr chosen = choose().projectPart;
ASSERT_THAT(chosen, Eq(cProjectPart));
}
......@@ -151,16 +152,27 @@ TEST_F(ProjectPartChooser, ForMultipleAndAmbigiousHeaderPreferCxxProjectPart)
projectPartsForFile = createCAndCxxProjectParts();