diff --git a/clangparsejob.cpp b/clangparsejob.cpp index be3fdd0721..cbbbe2077b 100644 --- a/clangparsejob.cpp +++ b/clangparsejob.cpp @@ -1,417 +1,362 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "clangsettings/clangsettingsmanager.h" #include "duchain/clanghelpers.h" #include "duchain/clangpch.h" #include "duchain/parsesession.h" #include "duchain/clangindex.h" #include "duchain/clangparsingenvironmentfile.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "clangsupport.h" #include "documentfinderhelpers.h" #include #include #include #include #include #include using namespace KDevelop; namespace { enum CustomFeatures { Rescheduled = (KDevelop::TopDUContext::LastFeature << 1) }; QString findConfigFile(const QString& forFile, const QString& configFileName) { QDir dir = QFileInfo(forFile).dir(); while (dir.exists()) { const QFileInfo customIncludePaths(dir, configFileName); if (customIncludePaths.exists()) { return customIncludePaths.absoluteFilePath(); } if (!dir.cdUp()) { break; } } return {}; } Path::List readPathListFile(const QString& filepath) { if (filepath.isEmpty()) { return {}; } QFile f(filepath); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { return {}; } const QString text = QString::fromLocal8Bit(f.readAll()); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); Path::List paths(lines.length()); std::transform(lines.begin(), lines.end(), paths.begin(), [] (const QString& line) { return Path(line); }); return paths; } /** * File should contain the header to precompile and use while parsing * @returns the first path in the file */ Path userDefinedPchIncludeForFile(const QString& sourcefile) { static const QString pchIncludeFilename = QStringLiteral(".kdev_pch_include"); const auto paths = readPathListFile(findConfigFile(sourcefile, pchIncludeFilename)); return paths.isEmpty() ? Path() : paths.first(); } ProjectFileItem* findProjectFileItem(const IndexedString& url, bool* hasBuildSystemInfo) { ProjectFileItem* file = nullptr; *hasBuildSystemInfo = false; for (auto project: ICore::self()->projectController()->projects()) { auto files = project->filesForPath(url); if (files.isEmpty()) { continue; } file = files.last(); // A file might be defined in different targets. // Prefer file items defined inside a target with non-empty includes. for (auto f: files) { if (!dynamic_cast(f->parent())) { continue; } file = f; if (!IDefinesAndIncludesManager::manager()->includes(f, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { break; } } } if (file && file->project()) { if (auto bsm = file->project()->buildSystemManager()) { *hasBuildSystemInfo = bsm->hasIncludesOrDefines(file); } } return file; } -QString languageStandard(ProjectFileItem* item) -{ - static const QString defaultLanguageStandard = QStringLiteral("c++11"); - struct PathStandard { - QString path; - QString standard; - }; - QList paths; - - auto cfg = item->project()->projectConfiguration(); - auto grp = cfg->group("CustomDefinesAndIncludes"); - for (const QString& grpName : grp.groupList()) { - if (grpName.startsWith(QLatin1String("ProjectPath"))) { - KConfigGroup pathgrp = grp.group(grpName); - PathStandard entry; - entry.path = pathgrp.readEntry("Path", ""); - - auto cgrp = pathgrp.group("Compiler"); - entry.standard = cgrp.readEntry("Standard", QString()); - if (entry.standard.isEmpty()) { - continue; - } - paths.append(entry); - } - } - - if (paths.isEmpty()) { - return defaultLanguageStandard; - } - - const Path itemPath = item->path(); - const Path rootDirectory = item->project()->path(); - - Path closestPath; - QString standard; - - for (const auto& entry : paths) { - Path targetDirectory = rootDirectory; - - targetDirectory.addPath(entry.path); - - if (targetDirectory == itemPath) { - return entry.standard;; - } - - if (targetDirectory.isParentOf(itemPath)) { - if (closestPath.isEmpty() || targetDirectory.segments().size() > closestPath.segments().size()) { - closestPath = targetDirectory; - standard = entry.standard; - } - } - } - - return standard.isEmpty() ? defaultLanguageStandard : standard; -} - ClangParsingEnvironmentFile* parsingEnvironmentFile(const TopDUContext* context) { return dynamic_cast(context->parsingEnvironmentFile().data()); } bool hasTracker(const IndexedString& url) { return ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); } ParseSessionData::Ptr findParseSession(const IndexedString &file) { DUChainReadLocker lock; const auto& context = DUChainUtils::standardContextForUrl(file.toUrl()); if (context) { return ParseSessionData::Ptr(dynamic_cast(context->ast().data())); } return {}; } } ClangParseJob::ClangParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : ParseJob(url, languageSupport) { const auto tuUrl = clang()->index()->translationUnitForUrl(url); bool hasBuildSystemInfo; if (auto file = findProjectFileItem(tuUrl, &hasBuildSystemInfo)) { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(file)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(file)); - m_environment.setLanguageStandard(languageStandard(file)); + m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(file)); } else { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(tuUrl.str())); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(tuUrl.str())); - m_environment.setLanguageStandard(QStringLiteral("c++11")); + m_environment.setParserSettings(ClangSettingsManager::self()->defaultParserSettings()); } const bool isSource = ClangHelpers::isSource(tuUrl.str()); m_environment.setQuality( isSource ? (hasBuildSystemInfo ? ClangParsingEnvironment::BuildSystem : ClangParsingEnvironment::Source) : ClangParsingEnvironment::Unknown ); m_environment.setTranslationUnitUrl(tuUrl); Path::List projectPaths; const auto& projects = ICore::self()->projectController()->projects(); projectPaths.reserve(projects.size()); foreach (auto project, projects) { projectPaths.append(project->path()); } m_environment.setProjectPaths(projectPaths); foreach(auto document, ICore::self()->documentController()->openDocuments()) { auto textDocument = document->textDocument(); if (!textDocument || !textDocument->isModified() || !textDocument->url().isLocalFile() || !DocumentFinderHelpers::mimeTypesList().contains(textDocument->mimeType())) { continue; } m_unsavedFiles << UnsavedFile(textDocument->url().toLocalFile(), textDocument->textLines(textDocument->documentRange())); const IndexedString indexedUrl(textDocument->url()); m_unsavedRevisions.insert(indexedUrl, ModificationRevision::revisionForFile(indexedUrl)); } } ClangSupport* ClangParseJob::clang() const { return static_cast(languageSupport()); } void ClangParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread */*thread*/) { QReadLocker parseLock(languageSupport()->parseLock()); if (abortRequested()) { return; } { const auto tuUrlStr = m_environment.translationUnitUrl().str(); m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includesInBackground(tuUrlStr)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->definesInBackground(tuUrlStr)); m_environment.setPchInclude(userDefinedPchIncludeForFile(tuUrlStr)); } if (abortRequested()) { return; } { UrlParseLock urlLock(document()); if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) { return; } } ParseSession session(findParseSession(document())); if (abortRequested()) { return; } if (!session.data() && document() != m_environment.translationUnitUrl()) { // no cached data found for the current file, but maybe // we are lucky and can grab it from the TU context // this happens e.g. when originally a .cpp file is open and then one // of its included files is opened in the editor. session.setData(findParseSession(m_environment.translationUnitUrl())); if (abortRequested()) { return; } } if (!session.data() || !session.reparse(m_unsavedFiles, m_environment)) { session.setData(createSessionData()); } if (!session.unit()) { // failed to parse file, unpin and don't try again clang()->index()->unpinTranslationUnitForUrl(document()); return; } if (!clang_getFile(session.unit(), document().byteArray().constData())) { // this parse job's document does not exist in the pinned translation unit // so we need to unpin and re-add this document // Ideally we'd reset m_environment and session, but this is much simpler // and shouldn't be a common case clang()->index()->unpinTranslationUnitForUrl(document()); if (!(minimumFeatures() & Rescheduled)) { auto features = static_cast(minimumFeatures() | Rescheduled); ICore::self()->languageController()->backgroundParser()->addDocument(document(), features, priority()); } return; } Imports imports = ClangHelpers::tuImports(session.unit()); IncludeFileContexts includedFiles; if (auto pch = clang()->index()->pch(m_environment)) { auto pchFile = pch->mapFile(session.unit()); includedFiles = pch->mapIncludes(session.unit()); includedFiles.insert(pchFile, pch->context()); auto tuFile = clang_getFile(session.unit(), m_environment.translationUnitUrl().byteArray().constData()); imports.insert(tuFile, { pchFile, CursorInRevision(0, 0) } ); } if (abortRequested()) { return; } auto context = ClangHelpers::buildDUChain(session.mainFile(), imports, session, minimumFeatures(), includedFiles, clang()->index()); setDuChain(context); if (abortRequested()) { return; } { if (minimumFeatures() & TopDUContext::AST) { DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } #ifdef QT_DEBUG DUChainReadLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); // verify that features and environment where properly set in ClangHelpers::buildDUChain Q_ASSERT(file->featuresSatisfied(TopDUContext::Features(minimumFeatures() & ~TopDUContext::ForceUpdateRecursive))); #endif } foreach(const auto& context, includedFiles) { if (!context) { continue; } { // prefer the editor modification revision, instead of the on-disk revision auto it = m_unsavedRevisions.find(context->url()); if (it != m_unsavedRevisions.end()) { DUChainWriteLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); file->setModificationRevision(it.value()); } } if (::hasTracker(context->url())) { if (clang()->index()->translationUnitForUrl(context->url()) == m_environment.translationUnitUrl()) { // cache the parse session and the contained translation unit for this chain // this then allows us to quickly reparse the document if it is changed by // the user // otherwise no editor component is open for this document and we can dispose // the TU to save memory // share the session data with all contexts that are pinned to this TU DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } languageSupport()->codeHighlighting()->highlightDUChain(context); } } } ParseSessionData::Ptr ClangParseJob::createSessionData() const { const bool skipFunctionBodies = (minimumFeatures() <= TopDUContext::VisibleDeclarationsAndContexts); return ParseSessionData::Ptr(new ParseSessionData(m_unsavedFiles, clang()->index(), m_environment, (skipFunctionBodies ? ParseSessionData::SkipFunctionBodies : ParseSessionData::NoOption))); } const ParsingEnvironment* ClangParseJob::environment() const { return &m_environment; } diff --git a/clangsettings/CMakeLists.txt b/clangsettings/CMakeLists.txt index f103f45623..72de1b39e6 100644 --- a/clangsettings/CMakeLists.txt +++ b/clangsettings/CMakeLists.txt @@ -1,35 +1,36 @@ add_library(settingsmanager STATIC clangsettingsmanager.cpp ) target_link_libraries(settingsmanager LINK_PUBLIC KDev::Project KDev::Util ) set(clangsettings_SRCS clangconfigpage.cpp clangsettingsplugin.cpp configwidget.cpp + pathsmodel.cpp sessionsettings/sessionsettings.cpp ) ki18n_wrap_ui(clangsettings_SRCS configwidget.ui sessionsettings/sessionsettings.ui ) kconfig_add_kcfg_files( clangsettings_SRCS clangprojectconfig.kcfgc sessionsettings/sessionconfig.kcfgc) kdevplatform_add_plugin(kdevclangsettings JSON kdevclangsettings.json SOURCES ${clangsettings_SRCS}) target_link_libraries(kdevclangsettings LINK_PUBLIC KDev::Project KDev::Util LINK_PRIVATE settingsmanager ) diff --git a/clangsettings/clangconfigpage.cpp b/clangsettings/clangconfigpage.cpp index 89a6bdc749..378e5dc440 100644 --- a/clangsettings/clangconfigpage.cpp +++ b/clangsettings/clangconfigpage.cpp @@ -1,67 +1,69 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "clangconfigpage.h" #include "configwidget.h" #include #include ClangConfigPage::ClangConfigPage(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent) : ProjectConfigPage(plugin, options, parent) { - configWidget = new ConfigWidget(this, project()); + m_configWidget = new ConfigWidget(this, project()); auto layout = new QVBoxLayout(this); - layout->addWidget(configWidget); + layout->addWidget(m_configWidget); + + connect(m_configWidget, &ConfigWidget::changed, this, &ClangConfigPage::changed); } ClangConfigPage::~ClangConfigPage() {} -void ClangConfigPage::reset() +void ClangConfigPage::defaults() { - ProjectConfigPage::reset(); - // TODO: clear parser settings + ProjectConfigPage::defaults(); + m_configWidget->defaults(); } void ClangConfigPage::apply() { ProjectConfigPage::apply(); configWidget->writeSettings(); } QString ClangConfigPage::name() const { return i18n("Clang command-line options"); } QString ClangConfigPage::fullName() const { return i18n("Configure Clang command-line options"); } QIcon ClangConfigPage::icon() const { return QIcon::fromTheme("kdevelop"); } diff --git a/clangsettings/clangconfigpage.h b/clangsettings/clangconfigpage.h index c3fb187f18..5103a9dd92 100644 --- a/clangsettings/clangconfigpage.h +++ b/clangsettings/clangconfigpage.h @@ -1,48 +1,49 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef KCM_CUSTOMBUILDSYSTEM_H #define KCM_CUSTOMBUILDSYSTEM_H #include #include "clangprojectconfig.h" class ClangConfigPage : public ProjectConfigPage { Q_OBJECT public: ClangConfigPage(KDevelop::IPlugin* plugin, const KDevelop::ProjectConfigOptions& options, QWidget* parent); virtual ~ClangConfigPage(); virtual QString name() const override; virtual QString fullName() const override; virtual QIcon icon() const override; virtual void apply() override; - virtual void reset() override; + virtual void defaults() override; + private: - class ConfigWidget* configWidget; + class ConfigWidget* m_configWidget; }; #endif diff --git a/clangsettings/clangsettingsmanager.cpp b/clangsettings/clangsettingsmanager.cpp index bacaaba6a4..cacb1242a8 100644 --- a/clangsettings/clangsettingsmanager.cpp +++ b/clangsettings/clangsettingsmanager.cpp @@ -1,180 +1,206 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "clangsettingsmanager.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { const QString settingsGroup = QStringLiteral("Clang Settings"); const QString parserGroup = QStringLiteral("group"); const QString parserPath = QStringLiteral("path"); const QString parserOptions = QStringLiteral("options"); const QString macros = QStringLiteral("macros"); const QString forwardDeclare = QStringLiteral("forwardDeclare"); AssistantsSettings readAssistantsSettings(KConfig* cfg) { auto grp = cfg->group(settingsGroup); AssistantsSettings settings; settings.forwardDeclare = grp.readEntry(forwardDeclare, true); return settings; } CodeCompletionSettings readCodeCompletionSettings(KConfig* cfg) { auto grp = cfg->group(settingsGroup); CodeCompletionSettings settings; settings.macros = grp.readEntry(macros, true); return settings; } -ParserSettings parserOptionsForItem(const QList& paths, const Path itemPath, const Path /*rootDirectory*/) +ParserSettings parserOptionsForItem(const QList& paths, const Path itemPath, const Path rootDirectory) { Path closestPath; ParserSettings settings; // find parser options configured to a path closest to the requested item for (const auto& entry : paths) { auto settingsEntry = entry; - // TODO: store paths without project path - Path targetDirectory = Path(entry.path);//rootDirectory; + Path targetDirectory = rootDirectory; - //targetDirectory.addPath(entry.path); + targetDirectory.addPath(entry.path); if (targetDirectory == itemPath){ return settingsEntry.settings; } if (targetDirectory.isParentOf(itemPath)) { if(settings.parserOptions.isEmpty() || targetDirectory.segments().size() > closestPath.segments().size()){ settings = settingsEntry.settings; closestPath = targetDirectory; } } } - return settings; + return settings.parserOptions.isEmpty() ? ClangSettingsManager::self()->defaultParserSettings() : settings; } } ClangSettingsManager* ClangSettingsManager::self() { static ClangSettingsManager manager; return &manager; } AssistantsSettings ClangSettingsManager::assistantsSettings() const { auto cfg = ICore::self()->activeSession()->config(); return readAssistantsSettings(cfg.data()); } CodeCompletionSettings ClangSettingsManager::codeCompletionSettings() const { auto cfg = ICore::self()->activeSession()->config(); return readCodeCompletionSettings(cfg.data()); } ParserSettings ClangSettingsManager::parserSettings(KDevelop::ProjectBaseItem* item) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { - // TODO: default value; - return {}; + return defaultParserSettings(); } return parserOptionsForItem(readPaths(item->project()), item->path(), item->project()->path()); } ClangSettingsManager::ClangSettingsManager() {} ParserSettings ClangSettingsManager::parserSettings(const QString& item, KDevelop::IProject* project) const { const Path itemPath(item); const Path rootDirectory = project->path(); return parserOptionsForItem(readPaths(project), itemPath, rootDirectory); } QList ClangSettingsManager::readPaths(KDevelop::IProject* project) const { auto cfg = project->projectConfiguration().data(); auto grp = cfg->group(settingsGroup); QList paths; for (const auto& grpName : grp.groupList()) { if (!grpName.startsWith(parserGroup)) { continue; } KConfigGroup pathgrp = grp.group(grpName); ParserSettingsEntry path; path.path = pathgrp.readEntry(parserPath, ""); if(path.path.isEmpty()){ continue; } - path.settings.parserOptions = pathgrp.readEntry(parserOptions, "-fspell-checking -Wdocumentation "); + path.settings.parserOptions = pathgrp.readEntry(parserOptions, defaultParserSettings().parserOptions); paths.append(path); } return paths; } void ClangSettingsManager::writePaths(KDevelop::IProject* project, const QList& paths) { auto cfg = project->projectConfiguration().data(); auto grp = cfg->group(settingsGroup); grp.deleteGroup(); int pathIndex = 0; for (const auto& path : paths) { auto pathgrp = grp.group(parserGroup + QString::number(pathIndex++)); pathgrp.writeEntry(parserPath, path.path); pathgrp.writeEntry(parserOptions, path.settings.parserOptions); } } + +ParserSettings ClangSettingsManager::defaultParserSettings() const +{ + return {"-fspell-checking -Wdocumentation -std=c++11 -Wall"}; +} + +bool ParserSettings::isCpp() const +{ + return parserOptions.contains(QStringLiteral("-std=c++")); +} + +QVector ParserSettings::toClangAPI() const +{ + auto list = parserOptions.split(' ', QString::SkipEmptyParts); + QVector result; + result.reserve(list.size()); + + std::transform(list.constBegin(), list.constEnd(), + std::back_inserter(result), + [] (const QString &argument) { return argument.toUtf8(); }); + + return result; +} + +bool ParserSettings::operator==(const ParserSettings& rhs) const +{ + return parserOptions == rhs.parserOptions; +} diff --git a/clangsettings/clangsettingsmanager.h b/clangsettings/clangsettingsmanager.h index 90fc6104cf..ed536b3132 100644 --- a/clangsettings/clangsettingsmanager.h +++ b/clangsettings/clangsettingsmanager.h @@ -1,78 +1,85 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef CLANGSETTINGSMANAGER_H #define CLANGSETTINGSMANAGER_H #include +#include class KConfig; namespace KDevelop { class ProjectBaseItem; class IProject; } struct ParserSettings { QString parserOptions; + bool isCpp() const; + QVector toClangAPI() const; + bool operator==(const ParserSettings& rhs) const; }; +Q_DECLARE_METATYPE(ParserSettings); struct ParserSettingsEntry { ParserSettings settings; QString path; }; struct CodeCompletionSettings { bool macros = true; }; struct AssistantsSettings { bool forwardDeclare = true; }; class ClangSettingsManager { public: static ClangSettingsManager* self(); AssistantsSettings assistantsSettings() const; CodeCompletionSettings codeCompletionSettings() const; ParserSettings parserSettings(KDevelop::ProjectBaseItem* item) const; ParserSettings parserSettings(const QString& item, KDevelop::IProject* project) const; + ParserSettings defaultParserSettings() const; + QList readPaths(KDevelop::IProject* project) const; void writePaths(KDevelop::IProject* project, const QList& paths); private: ClangSettingsManager(); }; #endif // CLANGSETTINGSMANAGER_H diff --git a/clangsettings/configwidget.cpp b/clangsettings/configwidget.cpp index e0b97445b8..877441bb7c 100644 --- a/clangsettings/configwidget.cpp +++ b/clangsettings/configwidget.cpp @@ -1,126 +1,160 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "configwidget.h" #include "ui_configwidget.h" -#include +#include "pathsmodel.h" #include #include -namespace -{ -int indexOfPath(const QList& paths, const QString& path) -{ - for (int i = 0; i < paths.size(); i++) { - if (paths[i].path == path) { - return i; - } - } - - return -1; -} +#include -QString parserOptions(const QList& paths, const QString& path, KDevelop::IProject* project) +QString languageStandard(const QString& arguments) { - int idx = indexOfPath(paths, path); - if (idx != -1) { - return paths[idx].settings.parserOptions; + int idx = arguments.indexOf("-std="); + if(idx == -1){ + return QStringLiteral("c++11"); } - return ClangSettingsManager::self()->parserSettings(path, project).parserOptions; -} - + idx += 5; + int end = arguments.indexOf(' ', idx) != -1 ? arguments.indexOf(' ', idx) : arguments.size(); + return arguments.mid(idx, end - idx); } ConfigWidget::ConfigWidget(QWidget* parent, KDevelop::IProject* project) : QWidget(parent) , m_ui(new Ui::ConfigWidget()) , m_project(project) - , m_paths(ClangSettingsManager::self()->readPaths(project)) { - const auto parentFolder = project->path().parent().toLocalFile(); const auto projectFolder = project->path().toLocalFile(); m_ui->setupUi(this); - auto model = new QFileSystemModel(this); - model->setRootPath(projectFolder); - model->setReadOnly(true); - - m_ui->pathView->setModel(model); - m_ui->pathView->setRootIndex(model->index(parentFolder)); - m_ui->pathView->setSortingEnabled(false); + m_model = new PathsModel(this); + m_model->setProjectPath(project->path()); - for (int i = model->columnCount() - 1; i >= 1; i--) { - m_ui->pathView->setColumnHidden(i, true); + m_ui->pathView->setModel(m_model); + auto paths = ClangSettingsManager::self()->readPaths(project); + if (paths.isEmpty()) { + paths.append({ClangSettingsManager::self()->parserSettings(projectFolder, project), QStringLiteral(".")}); } + m_model->setPaths(paths); - // TODO: hide somehow all other non project folders in the parent directory - m_ui->pathView->setCurrentIndex(model->index(projectFolder)); + m_ui->pathView->setCurrentIndex(m_model->index(0)); - connect(m_ui->pathView, &QTreeView::activated, this, &ConfigWidget::itemActivated); + connect(m_ui->pathView, &QListView::activated, this, &ConfigWidget::itemActivated); connect(m_ui->parserOptions, &QLineEdit::textEdited, this, &ConfigWidget::textEdited); + connect(m_model,&PathsModel::changed, this, &ConfigWidget::changed); + connect(m_model,&PathsModel::rowsInserted, this, &ConfigWidget::itemActivated); + connect(m_model,&PathsModel::rowsRemoved, this, &ConfigWidget::itemActivated); + m_ui->parserOptions->setText(ClangSettingsManager::self()->parserSettings(projectFolder, m_project).parserOptions); + + QAction* delDefAction = new QAction(i18n("Delete path"), this); + delDefAction->setShortcut(QKeySequence(Qt::Key_Delete)); + delDefAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); + delDefAction->setIcon(QIcon::fromTheme("list-remove")); + m_ui->pathView->addAction(delDefAction); + m_ui->pathView->setContextMenuPolicy(Qt::ActionsContextMenu); + connect(delDefAction, &QAction::triggered, this, &ConfigWidget::deletePath); + + connect(m_ui->addPath, &QPushButton::clicked, this, &ConfigWidget::addPath); + connect(m_ui->removePath, &QPushButton::clicked, this, &ConfigWidget::deletePath); + + m_ui->languageStandards->setCurrentText(languageStandard(m_ui->parserOptions->text())); + connect(m_ui->languageStandards, static_cast(&QComboBox::activated), this, &ConfigWidget::languageStandardChanged); +} + +void ConfigWidget::itemActivated() +{ + const auto selection = m_ui->pathView->selectionModel()->currentIndex(); + if(!selection.isValid()){ + m_ui->parserOptions->clear(); + return; + } + + auto settings = m_model->data(selection, PathsModel::ParserOptionsRole).value(); + + m_ui->parserOptions->setText(settings.parserOptions); + m_ui->languageStandards->setCurrentText(languageStandard(m_ui->parserOptions->text())); +} + +void ConfigWidget::writeSettings() +{ + ClangSettingsManager::self()->writePaths(m_project, m_model->paths()); +} + +void ConfigWidget::deletePath() +{ + const auto selection = m_ui->pathView->selectionModel()->currentIndex(); + + m_model->removeRow(selection.row()); } -void ConfigWidget::itemActivated(const QModelIndex& index) +void ConfigWidget::addPath() { - auto model = static_cast(index.model()); - auto path = model->filePath(index); + QFileDialog dlg(this, "", m_project->path().toLocalFile()); + dlg.setFileMode(QFileDialog::Directory); + dlg.setOption(QFileDialog::ReadOnly, true); + if(dlg.exec() == QDialog::Rejected){ + return; + } - auto options = parserOptions(m_paths, path, m_project); - m_ui->parserOptions->setText(options); + m_model->addPath(dlg.directoryUrl().toLocalFile()); + m_ui->pathView->setCurrentIndex(m_model->index(m_model->rowCount() - 1)); } void ConfigWidget::textEdited() { const auto parserOptions = m_ui->parserOptions->text(); const auto index = m_ui->pathView->selectionModel()->currentIndex(); - auto model = static_cast(index.model()); - const auto path = model->filePath(index); - if(path.isEmpty()){ - return; - } + m_model->setData(index, QVariant::fromValue(ParserSettings {parserOptions}), PathsModel::ParserOptionsRole); - auto currentSettings = ClangSettingsManager::self()->parserSettings(path, m_project); + m_ui->languageStandards->setCurrentText(languageStandard(m_ui->parserOptions->text())); +} - if(currentSettings.parserOptions == parserOptions){ - return; - } +void ConfigWidget::languageStandardChanged(const QString& standard) +{ + auto text = m_ui->parserOptions->text(); - int idx = indexOfPath(m_paths, path); - if (idx == -1) { - m_paths.append({{parserOptions}, path}); - } else { - m_paths[idx] = {{parserOptions}, path}; - } + auto currentStandard = languageStandard(text); + + m_ui->parserOptions->setText(text.replace(currentStandard, standard)); + + textEdited(); } -void ConfigWidget::writeSettings() +void ConfigWidget::defaults() { - ClangSettingsManager::self()->writePaths(m_project, m_paths); + ParserSettingsEntry entry; + entry.path = QStringLiteral("."); + entry.settings = ClangSettingsManager::self()->defaultParserSettings(); + m_model->setPaths({entry}); + + m_ui->parserOptions->setText(entry.settings.parserOptions); + m_ui->languageStandards->setCurrentText(languageStandard(m_ui->parserOptions->text())); } diff --git a/clangsettings/configwidget.h b/clangsettings/configwidget.h index e36271db58..36bcd800b6 100644 --- a/clangsettings/configwidget.h +++ b/clangsettings/configwidget.h @@ -1,58 +1,68 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef CONFIGWIDGET_H #define CONFIGWIDGET_H #include #include "clangsettingsmanager.h" namespace Ui { class ConfigWidget; } namespace KDevelop { class IProject; } +class PathsModel; + class ConfigWidget : public QWidget { + Q_OBJECT public: ConfigWidget(QWidget* parent, KDevelop::IProject* project); void writeSettings(); + void defaults(); + +Q_SIGNALS: + void changed(); private Q_SLOTS: - void itemActivated(const QModelIndex& index); + void itemActivated(); + void addPath(); + void deletePath(); void textEdited(); + void languageStandardChanged(const QString& standard); private: Ui::ConfigWidget* m_ui; KDevelop::IProject* m_project; - QList m_paths; + PathsModel* m_model; }; #endif // CONFIGWIDGET_H diff --git a/clangsettings/configwidget.ui b/clangsettings/configwidget.ui index d3ca2e85d7..1f7f7ee70b 100644 --- a/clangsettings/configwidget.ui +++ b/clangsettings/configwidget.ui @@ -1,108 +1,162 @@ ConfigWidget 0 0 662 489 Form 0 Parser settings - + - + + + + + &Add + + + + + + + &Remove + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - + Settings - - - + + + - + - Options + Language standard - + + + + c90 + + + + + c99 + + + + + c11 + + + + + c++98 + + + + + c++03 + + + + + c++11 + + + + + c++14 + + + - - - - - - Compiler - - - - - - - - + + + + - Configure... + Arguments - - - - Qt::Horizontal - - + + + - 40 - 20 + 100 + 0 - + - + Qt::Vertical 20 191 + + + diff --git a/clangsettings/pathsmodel.cpp b/clangsettings/pathsmodel.cpp new file mode 100644 index 0000000000..e0b48081fa --- /dev/null +++ b/clangsettings/pathsmodel.cpp @@ -0,0 +1,173 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "pathsmodel.h" + +#include "clangsettingsmanager.h" + +#include + +#include + +using namespace KDevelop; + +namespace +{ +QString toRelativePath(const QString& path, const Path& projectPath) +{ + auto relativePath = projectPath.relativePath(KDevelop::Path(path)); + return relativePath.isEmpty() ? QStringLiteral(".") : relativePath; +} +} + +PathsModel::PathsModel(QObject* parent) + : QAbstractListModel(parent) +{} + +QVariant PathsModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::EditRole && role != ParserOptionsRole)) { + return {}; + } + + if (index.row() < 0 || index.row() >= rowCount() || index.column() != 0) { + return {}; + } + + if (index.row() == m_paths.count()) { + return {}; + } + + if(role == ParserOptionsRole){ + return QVariant::fromValue(m_paths.at(index.row()).settings); + } + + return m_paths.at(index.row()).path; +} + +int PathsModel::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : m_paths.count(); +} + +bool PathsModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || (role != Qt::EditRole && role != ParserOptionsRole)) { + return false; + } + + if (index.row() < 0 || index.row() >= rowCount() || index.column() != 0) { + return false; + } + + if (role == ParserOptionsRole) { + auto settings = value.value(); + m_paths[index.row()].settings = settings; + } else { + auto path = value.toString().trimmed(); + if (path.isEmpty()) { + return false; + } + + m_paths[index.row()].path = path; + } + + emit dataChanged(index, index); + emit changed(); + return true; +} + +Qt::ItemFlags PathsModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) { + return 0; + } + + return Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); +} + +QList PathsModel::paths() const +{ + return m_paths; +} + +void PathsModel::setPaths(const QList& paths) +{ + beginResetModel(); + m_paths = paths; + endResetModel(); +} + +bool PathsModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (row < 0 || count <= 0 || row >= m_paths.count()) { + return false; + } + + beginRemoveRows(parent, row, row + count - 1); + for (int i = 0; i < count; ++i) { + if (m_paths[row].path != QStringLiteral(".")) { + m_paths.removeAt(row); + } + } + + endRemoveRows(); + emit changed(); + return true; +} + +void PathsModel::addPath(const QString& path) +{ + QFileInfo info(path); + auto canonicalPath = info.canonicalFilePath(); + if (!m_projectPath.isParentOf(KDevelop::Path(canonicalPath))) { + return; + } + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + addPathInternal(canonicalPath); + endInsertRows(); + emit changed(); +} + +void PathsModel::addPathInternal(const QString& path) +{ + if (path.isEmpty()) { + return; + } + + auto relativePath = toRelativePath(path, m_projectPath); + + for (const auto& existingConfig : m_paths) { + if (relativePath == existingConfig.path) { + return; + } + } + + m_paths.append({ClangSettingsManager::self()->defaultParserSettings(), relativePath}); +} + +void PathsModel::setProjectPath(const KDevelop::Path& path) +{ + m_projectPath = path; +} diff --git a/clangsettings/configwidget.h b/clangsettings/pathsmodel.h similarity index 51% copy from clangsettings/configwidget.h copy to clangsettings/pathsmodel.h index e36271db58..c45d039e23 100644 --- a/clangsettings/configwidget.h +++ b/clangsettings/pathsmodel.h @@ -1,58 +1,61 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ -#ifndef CONFIGWIDGET_H -#define CONFIGWIDGET_H - -#include +#ifndef PATHSMODEL_H +#define PATHSMODEL_H #include "clangsettingsmanager.h" +#include -namespace Ui -{ -class ConfigWidget; -} - -namespace KDevelop -{ -class IProject; -} +#include -class ConfigWidget : public QWidget +class PathsModel : public QAbstractListModel { + Q_OBJECT public: - ConfigWidget(QWidget* parent, KDevelop::IProject* project); + enum SpecialRoles { + ParserOptionsRole = Qt::UserRole + 1 + }; + + PathsModel(QObject* parent = 0); + void setPaths(const QList& paths); + QList paths() const; + void addPath(const QString& path); + void setProjectPath(const KDevelop::Path& path); - void writeSettings(); + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; -private Q_SLOTS: - void itemActivated(const QModelIndex& index); - void textEdited(); +Q_SIGNALS: void changed(); private: - Ui::ConfigWidget* m_ui; - KDevelop::IProject* m_project; + void addPathInternal(const QString& path); + QList m_paths; + KDevelop::Path m_projectPath; }; -#endif // CONFIGWIDGET_H +#endif // PATHSMODEL_H diff --git a/duchain/clangparsingenvironment.cpp b/duchain/clangparsingenvironment.cpp index 20a2dbfcb5..b515037766 100644 --- a/duchain/clangparsingenvironment.cpp +++ b/duchain/clangparsingenvironment.cpp @@ -1,147 +1,147 @@ /* * This file is part of KDevelop * * Copyright 2014 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangparsingenvironment.h" using namespace KDevelop; int ClangParsingEnvironment::type() const { return CppParsingEnvironment; } void ClangParsingEnvironment::setProjectPaths(const Path::List& projectPaths) { m_projectPaths = projectPaths; } Path::List ClangParsingEnvironment::projectPaths() const { return m_projectPaths; } void ClangParsingEnvironment::addIncludes(const Path::List& includes) { m_includes += includes; } ClangParsingEnvironment::IncludePaths ClangParsingEnvironment::includes() const { IncludePaths ret; ret.project.reserve(m_includes.size()); ret.system.reserve(m_includes.size()); foreach (const auto& path, m_includes) { bool inProject = false; foreach (const auto& project, m_projectPaths) { if (project.isParentOf(path) || project == path) { inProject = true; break; } } if (inProject) { ret.project.append(path); } else { ret.system.append(path); } } return ret; } void ClangParsingEnvironment::addDefines(const QHash& defines) { for (auto it = defines.constBegin(); it != defines.constEnd(); ++it) { m_defines[it.key()] = it.value(); } } QMap ClangParsingEnvironment::defines() const { return m_defines; } void ClangParsingEnvironment::setPchInclude(const Path& path) { m_pchInclude = path; } Path ClangParsingEnvironment::pchInclude() const { return m_pchInclude; } void ClangParsingEnvironment::setTranslationUnitUrl(const IndexedString& url) { m_tuUrl = url; } IndexedString ClangParsingEnvironment::translationUnitUrl() const { return m_tuUrl; } void ClangParsingEnvironment::setQuality(Quality quality) { m_quality = quality; } ClangParsingEnvironment::Quality ClangParsingEnvironment::quality() const { return m_quality; } uint ClangParsingEnvironment::hash() const { KDevHash hash; hash << m_defines.size(); for (auto it = m_defines.constBegin(); it != m_defines.constEnd(); ++it) { hash << qHash(it.key()) << qHash(it.value()); } hash << m_includes.size(); for (const auto& include : m_includes) { hash << qHash(include); } hash << qHash(m_pchInclude); - hash << qHash(m_languageStandard); + hash << qHash(m_parserSettings.parserOptions); return hash; } bool ClangParsingEnvironment::operator==(const ClangParsingEnvironment& other) const { return m_defines == other.m_defines && m_includes == other.m_includes && m_pchInclude == other.m_pchInclude && m_quality == other.m_quality && m_tuUrl == other.m_tuUrl - && m_languageStandard == other.m_languageStandard; + && m_parserSettings == other.m_parserSettings; } -void ClangParsingEnvironment::setLanguageStandard(const QString& standard) +void ClangParsingEnvironment::setParserSettings(const ParserSettings& parserSettings) { - m_languageStandard = standard; + m_parserSettings = parserSettings; } -QString ClangParsingEnvironment::languageStandard() const +ParserSettings ClangParsingEnvironment::parserSettings() const { - return m_languageStandard; + return m_parserSettings; } diff --git a/duchain/clangparsingenvironment.h b/duchain/clangparsingenvironment.h index 2e4ea8b39c..66a2d73d19 100644 --- a/duchain/clangparsingenvironment.h +++ b/duchain/clangparsingenvironment.h @@ -1,110 +1,112 @@ /* * This file is part of KDevelop * * Copyright 2014 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLANGPARSINGENVIRONMENT_H #define CLANGPARSINGENVIRONMENT_H #include #include #include +#include "clangsettings/clangsettingsmanager.h" + class KDEVCLANGDUCHAIN_EXPORT ClangParsingEnvironment : public KDevelop::ParsingEnvironment { public: virtual ~ClangParsingEnvironment() = default; virtual int type() const override; /** * Sets the list of project paths. * * Any include path outside these project paths is considered * to be a system include. */ void setProjectPaths(const KDevelop::Path::List& projectPaths); KDevelop::Path::List projectPaths() const; /** * Add the given list of @p include paths to this environment. */ void addIncludes(const KDevelop::Path::List& includes); struct IncludePaths { /// This list contains all include paths outside the known projects paths. KDevelop::Path::List system; /// This list contains all include paths inside the known projects paths. KDevelop::Path::List project; }; /** * Returns the list of includes, split into a list of system includes and project includes. */ IncludePaths includes() const; void addDefines(const QHash& defines); QMap defines() const; void setPchInclude(const KDevelop::Path& path); KDevelop::Path pchInclude() const; void setTranslationUnitUrl(const KDevelop::IndexedString& url); KDevelop::IndexedString translationUnitUrl() const; enum Quality { Unknown, Source, BuildSystem }; void setQuality(Quality quality); Quality quality() const; - void setLanguageStandard(const QString& standard); + void setParserSettings(const ParserSettings& arguments); - QString languageStandard() const; + ParserSettings parserSettings() const; /** * Hash all contents of this environment and return the result. * * This is useful for a quick comparison, and enough to store on-disk * to figure out if the environment changed or not. */ uint hash() const; bool operator==(const ClangParsingEnvironment& other) const; bool operator!=(const ClangParsingEnvironment& other) const { return !(*this == other); } private: KDevelop::Path::List m_projectPaths; KDevelop::Path::List m_includes; // NOTE: As elements in QHash stored in an unordered sequence, we're using QMap instead QMap m_defines; KDevelop::Path m_pchInclude; KDevelop::IndexedString m_tuUrl; Quality m_quality = Unknown; - QString m_languageStandard; + ParserSettings m_parserSettings; }; #endif // CLANGPARSINGENVIRONMENT_H diff --git a/duchain/parsesession.cpp b/duchain/parsesession.cpp index 8640f49ca4..e4b8e95c0d 100644 --- a/duchain/parsesession.cpp +++ b/duchain/parsesession.cpp @@ -1,344 +1,349 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2013 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsesession.h" #include "clangproblem.h" #include "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" #include "clangindex.h" #include "clangparsingenvironment.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include #include #include #include #include #include #include using namespace KDevelop; namespace { -QVector argsForSession(const QString& path, ParseSessionData::Options options, QByteArray& languageStandard) +QVector argsForSession(const QString& path, ParseSessionData::Options options, const ParserSettings& parserSettings) { QMimeDatabase db; if(db.mimeTypeForFile(path).name() == QStringLiteral("text/x-objcsrc")) { return {"-xobjective-c++"}; } - //this can happen for unit tests that use the ParseSession directly - if (languageStandard.isEmpty()) { - static const QByteArray defaultLanguageStandard("c++11"); - languageStandard = defaultLanguageStandard; + // this can happen for unit tests that use the ParseSession directly + if (parserSettings.parserOptions.isEmpty()) { + return {"-fspell-checking", "-Wdocumentation", "-std=c++11", "-xc++", "-Wall", "-nostdinc", "-nostdinc++"}; } - languageStandard = "-std=" + languageStandard; + auto result = parserSettings.toClangAPI(); + result.append("-nostdinc"); + result.append("-nostdinc++"); if (options & ParseSessionData::PrecompiledHeader) { - return {languageStandard.data(), languageStandard.contains("++") ? "-xc++-header" : "-xc-header", "-Wall", "-nostdinc", "-nostdinc++"}; + result.append(parserSettings.isCpp() ? "-xc++-header" : "-xc-header"); + return result; } - if(!languageStandard.contains("++")) { - return { languageStandard.data(), "-Wall", "-nostdinc", "-nostdinc++" }; - } - - return { languageStandard.data(), "-xc++", "-Wall", "-nostdinc", "-nostdinc++" }; + result.append(parserSettings.isCpp() ? "-xc++" : "-xc"); + return result; } void addIncludes(QVector* args, QVector* otherArgs, const Path::List& includes, const char* cliSwitch) { foreach (const Path& url, includes) { QFileInfo info(url.toLocalFile()); QByteArray path = url.toLocalFile().toUtf8(); if (info.isFile()) { path.prepend("-include"); } else { path.prepend(cliSwitch); } otherArgs->append(path); args->append(path.constData()); } } QVector toClangApi(const QVector& unsavedFiles) { QVector unsaved; unsaved.reserve(unsavedFiles.size()); std::transform(unsavedFiles.begin(), unsavedFiles.end(), std::back_inserter(unsaved), [] (const UnsavedFile& file) { return file.toClangApi(); }); return unsaved; } } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options) : m_file(nullptr) , m_unit(nullptr) { unsigned int flags = CXTranslationUnit_CXXChainedPCH | CXTranslationUnit_DetailedPreprocessingRecord; if (options.testFlag(SkipFunctionBodies)) { flags |= CXTranslationUnit_SkipFunctionBodies; } if (options.testFlag(PrecompiledHeader)) { flags |= CXTranslationUnit_ForSerialization; } else { flags |= CXTranslationUnit_CacheCompletionResults | CXTranslationUnit_PrecompiledPreamble | CXTranslationUnit_Incomplete; } const auto tuUrl = environment.translationUnitUrl(); Q_ASSERT(!tuUrl.isEmpty()); - auto languageStandard = environment.languageStandard().toLocal8Bit(); - QVector args = argsForSession(tuUrl.str(), options, languageStandard); + const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); + QVector clangArguments; const auto& includes = environment.includes(); const auto& pchInclude = environment.pchInclude(); + // uses QByteArray as smart-pointer for const char* ownership - QVector otherArgs; - otherArgs.reserve(includes.system.size() + includes.project.size() - + pchInclude.isValid() + 1); - args.reserve(args.size() + otherArgs.size()); + QVector smartArgs; + smartArgs.reserve(includes.system.size() + includes.project.size() + + pchInclude.isValid() + arguments.size() + 1); + clangArguments.reserve(smartArgs.size()); + + std::transform(arguments.constBegin(), arguments.constEnd(), + std::back_inserter(clangArguments), + [] (const QByteArray &argument) { return argument.constData(); }); + // NOTE: the PCH include must come before all other includes! if (pchInclude.isValid()) { - args << "-include"; + clangArguments << "-include"; QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); - otherArgs << pchFile; - args << pchFile.constData(); + smartArgs << pchFile; + clangArguments << pchFile.constData(); } - addIncludes(&args, &otherArgs, includes.system, "-isystem"); - addIncludes(&args, &otherArgs, includes.project, "-I"); + addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); + addIncludes(&clangArguments, &smartArgs, includes.project, "-I"); m_definesFile.open(); QTextStream definesStream(&m_definesFile); Q_ASSERT(m_definesFile.isWritable()); const auto& defines = environment.defines(); for (auto it = defines.begin(); it != defines.end(); ++it) { definesStream << QStringLiteral("#define ") << it.key() << ' ' << it.value() << '\n'; } definesStream.flush(); - otherArgs << m_definesFile.fileName().toUtf8(); - args << "-imacros" << otherArgs.last().constData(); + smartArgs << m_definesFile.fileName().toUtf8(); + clangArguments << "-imacros" << smartArgs.last().constData(); QVector unsaved; //For PrecompiledHeader, we don't want unsaved contents (and contents.isEmpty()) if (!options.testFlag(PrecompiledHeader)) { unsaved = toClangApi(unsavedFiles); } #if CINDEX_VERSION_MINOR >= 23 const CXErrorCode code = clang_parseTranslationUnit2( index->index(), tuUrl.byteArray().constData(), - args.constData(), args.size(), + clangArguments.constData(), clangArguments.size(), unsaved.data(), unsaved.size(), flags, &m_unit ); if (code != CXError_Success) { clangDebug() << "clang_parseTranslationUnit2 return with error code" << code; } #else m_unit = clang_parseTranslationUnit( index->index(), tuUrl.byteArray().constData(), - args.constData(), args.size(), + clangArguments.constData(), clangArguments.size(), unsaved.data(), unsaved.size(), flags ); #endif if (m_unit) { setUnit(m_unit); m_environment = environment; if (options.testFlag(PrecompiledHeader)) { clang_saveTranslationUnit(m_unit, (tuUrl.byteArray() + ".pch").constData(), CXSaveTranslationUnit_None); } } else { clangDebug() << "Failed to parse translation unit:" << tuUrl; } } ParseSessionData::~ParseSessionData() { clang_disposeTranslationUnit(m_unit); } void ParseSessionData::setUnit(CXTranslationUnit unit) { Q_ASSERT(!m_unit || unit == m_unit); m_unit = unit; const ClangString unitFile(clang_getTranslationUnitSpelling(unit)); m_file = clang_getFile(m_unit, unitFile.c_str()); } ClangParsingEnvironment ParseSessionData::environment() const { return m_environment; } ParseSession::ParseSession(const ParseSessionData::Ptr& data) : d(data) { if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSession::~ParseSession() { if (d) { d->m_mutex.unlock(); } } void ParseSession::setData(const ParseSessionData::Ptr& data) { if (data == d) { return; } if (d) { d->m_mutex.unlock(); } d = data; if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSessionData::Ptr ParseSession::data() const { return d; } IndexedString ParseSession::languageString() { static const IndexedString lang("Clang"); return lang; } QList ParseSession::problemsForFile(CXFile file) const { if (!d) { return {}; } QList problems; // extra clang diagnostics static const ClangDiagnosticEvaluator evaluator; const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); problems.reserve(numDiagnostics); for (uint i = 0; i < numDiagnostics; ++i) { auto diagnostic = clang_getDiagnostic(d->m_unit, i); CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); if (diagnosticFile != file) { continue; } ProblemPointer problem(evaluator.createProblem(diagnostic)); problems << problem; clang_disposeDiagnostic(diagnostic); } const QString path = QDir::cleanPath(ClangString(clang_getFileName(file)).toString()); const IndexedString indexedPath(path); TodoExtractor extractor(unit(), file); problems << extractor.problems(); // other problem sources if (ClangHelpers::isHeader(path) && !clang_isFileMultipleIncludeGuarded(unit(), file) && !clang_Location_isInSystemHeader(clang_getLocationForOffset(d->m_unit, file, 0))) { ProblemPointer problem(new Problem); problem->setSeverity(IProblem::Warning); problem->setDescription(i18n("Header is not guarded against multiple inclusions")); problem->setExplanation(i18n("The given header is not guarded against multiple inclusions, " "either with the conventional #ifndef/#define/#endif macro guards or with #pragma once.")); problem->setFinalLocation({indexedPath, KTextEditor::Range()}); problem->setSource(IProblem::Preprocessor); problems << problem; // TODO: Easy to add an assistant here that adds the guards -- any takers? } return problems; } CXTranslationUnit ParseSession::unit() const { return d ? d->m_unit : nullptr; } CXFile ParseSession::file(const QByteArray& path) const { return clang_getFile(unit(), path.constData()); } CXFile ParseSession::mainFile() const { return d ? d->m_file : nullptr; } bool ParseSession::reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment) { if (!d || environment != d->m_environment) { return false; } auto unsaved = toClangApi(unsavedFiles); if (clang_reparseTranslationUnit(d->m_unit, unsaved.size(), unsaved.data(), clang_defaultReparseOptions(d->m_unit)) == 0) { d->setUnit(d->m_unit); return true; } else { return false; } } ClangParsingEnvironment ParseSession::environment() const { return d->m_environment; }