// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppcodemodelsettings.h" #include "clangdiagnosticconfigsmodel.h" #include "cppeditorconstants.h" #include "cppeditortr.h" #include "cpptoolsreuse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; namespace CppEditor { static Id initialClangDiagnosticConfigId() { return Constants::CPP_CLANG_DIAG_CONFIG_BUILDSYSTEM; } static CppCodeModelSettings::PCHUsage initialPchUsage() { return CppCodeModelSettings::PchUse_BuildSystem; } static QString enableLowerClazyLevelsKey() { return QLatin1String("enableLowerClazyLevels"); } static QString pchUsageKey() { return QLatin1String(Constants::CPPEDITOR_MODEL_MANAGER_PCH_USAGE); } static QString interpretAmbiguousHeadersAsCHeadersKey() { return QLatin1String(Constants::CPPEDITOR_INTERPRET_AMBIGIUOUS_HEADERS_AS_C_HEADERS); } static QString skipIndexingBigFilesKey() { return QLatin1String(Constants::CPPEDITOR_SKIP_INDEXING_BIG_FILES); } static QString ignoreFilesKey() { return QLatin1String(Constants::CPPEDITOR_IGNORE_FILES); } static QString ignorePatternKey() { return QLatin1String(Constants::CPPEDITOR_IGNORE_PATTERN); } static QString useBuiltinPreprocessorKey() { return QLatin1String(Constants::CPPEDITOR_USE_BUILTIN_PREPROCESSOR); } static QString indexerFileSizeLimitKey() { return QLatin1String(Constants::CPPEDITOR_INDEXER_FILE_SIZE_LIMIT); } static QString clangdSettingsKey() { return QLatin1String("ClangdSettings"); } static QString useClangdKey() { return QLatin1String("UseClangdV7"); } static QString clangdPathKey() { return QLatin1String("ClangdPath"); } static QString clangdIndexingKey() { return QLatin1String("ClangdIndexing"); } static QString clangdIndexingPriorityKey() { return QLatin1String("ClangdIndexingPriority"); } static QString clangdHeaderSourceSwitchModeKey() { return QLatin1String("ClangdHeaderSourceSwitchMode"); } static QString clangdHeaderInsertionKey() { return QLatin1String("ClangdHeaderInsertion"); } static QString clangdThreadLimitKey() { return QLatin1String("ClangdThreadLimit"); } static QString clangdDocumentThresholdKey() { return QLatin1String("ClangdDocumentThreshold"); } static QString clangdSizeThresholdEnabledKey() { return QLatin1String("ClangdSizeThresholdEnabled"); } static QString clangdSizeThresholdKey() { return QLatin1String("ClangdSizeThreshold"); } static QString clangdUseGlobalSettingsKey() { return QLatin1String("useGlobalSettings"); } static QString clangdblockIndexingSettingsKey() { return QLatin1String("blockIndexing"); } static QString sessionsWithOneClangdKey() { return QLatin1String("SessionsWithOneClangd"); } static QString diagnosticConfigIdKey() { return QLatin1String("diagnosticConfigId"); } static QString checkedHardwareKey() { return QLatin1String("checkedHardware"); } static QString completionResultsKey() { return QLatin1String("completionResults"); } static FilePath g_defaultClangdFilePath; static FilePath fallbackClangdFilePath() { if (g_defaultClangdFilePath.exists()) return g_defaultClangdFilePath; return Environment::systemEnvironment().searchInPath("clangd"); } void CppCodeModelSettings::fromSettings(QSettings *s) { s->beginGroup(QLatin1String(Constants::CPPEDITOR_SETTINGSGROUP)); setEnableLowerClazyLevels(s->value(enableLowerClazyLevelsKey(), true).toBool()); const QVariant pchUsageVariant = s->value(pchUsageKey(), initialPchUsage()); setPCHUsage(static_cast(pchUsageVariant.toInt())); const QVariant interpretAmbiguousHeadersAsCHeaders = s->value(interpretAmbiguousHeadersAsCHeadersKey(), false); setInterpretAmbigiousHeadersAsCHeaders(interpretAmbiguousHeadersAsCHeaders.toBool()); const QVariant skipIndexingBigFiles = s->value(skipIndexingBigFilesKey(), true); setSkipIndexingBigFiles(skipIndexingBigFiles.toBool()); const QVariant ignoreFiles = s->value(ignoreFilesKey(), false); setIgnoreFiles(ignoreFiles.toBool()); const QVariant ignorePattern = s->value(ignorePatternKey(), ""); setIgnorePattern(ignorePattern.toString()); setUseBuiltinPreprocessor(s->value(useBuiltinPreprocessorKey(), true).toBool()); const QVariant indexerFileSizeLimit = s->value(indexerFileSizeLimitKey(), 5); setIndexerFileSizeLimitInMb(indexerFileSizeLimit.toInt()); s->endGroup(); emit changed(); } void CppCodeModelSettings::toSettings(QSettings *s) { s->beginGroup(QLatin1String(Constants::CPPEDITOR_SETTINGSGROUP)); s->setValue(enableLowerClazyLevelsKey(), enableLowerClazyLevels()); s->setValue(pchUsageKey(), pchUsage()); s->setValue(interpretAmbiguousHeadersAsCHeadersKey(), interpretAmbigiousHeadersAsCHeaders()); s->setValue(skipIndexingBigFilesKey(), skipIndexingBigFiles()); s->setValue(ignoreFilesKey(), ignoreFiles()); s->setValue(ignorePatternKey(), QVariant(ignorePattern())); s->setValue(useBuiltinPreprocessorKey(), useBuiltinPreprocessor()); s->setValue(indexerFileSizeLimitKey(), indexerFileSizeLimitInMb()); s->endGroup(); emit changed(); } CppCodeModelSettings::PCHUsage CppCodeModelSettings::pchUsage() const { return m_pchUsage; } void CppCodeModelSettings::setPCHUsage(CppCodeModelSettings::PCHUsage pchUsage) { m_pchUsage = pchUsage; } bool CppCodeModelSettings::interpretAmbigiousHeadersAsCHeaders() const { return m_interpretAmbigiousHeadersAsCHeaders; } void CppCodeModelSettings::setInterpretAmbigiousHeadersAsCHeaders(bool yesno) { m_interpretAmbigiousHeadersAsCHeaders = yesno; } bool CppCodeModelSettings::skipIndexingBigFiles() const { return m_skipIndexingBigFiles; } void CppCodeModelSettings::setSkipIndexingBigFiles(bool yesno) { m_skipIndexingBigFiles = yesno; } int CppCodeModelSettings::indexerFileSizeLimitInMb() const { return m_indexerFileSizeLimitInMB; } void CppCodeModelSettings::setIndexerFileSizeLimitInMb(int sizeInMB) { m_indexerFileSizeLimitInMB = sizeInMB; } bool CppCodeModelSettings::ignoreFiles() const { return m_ignoreFiles; } void CppCodeModelSettings::setIgnoreFiles(bool ignoreFiles) { m_ignoreFiles = ignoreFiles; } QString CppCodeModelSettings::ignorePattern() const { return m_ignorePattern; } void CppCodeModelSettings::setIgnorePattern(const QString& ignorePattern) { m_ignorePattern = ignorePattern; } bool CppCodeModelSettings::enableLowerClazyLevels() const { return m_enableLowerClazyLevels; } void CppCodeModelSettings::setEnableLowerClazyLevels(bool yesno) { m_enableLowerClazyLevels = yesno; } QString ClangdSettings::priorityToString(const IndexingPriority &priority) { switch (priority) { case IndexingPriority::Background: return "background"; case IndexingPriority::Normal: return "normal"; case IndexingPriority::Low: return "low"; case IndexingPriority::Off: return {}; } return {}; } QString ClangdSettings::priorityToDisplayString(const IndexingPriority &priority) { switch (priority) { case IndexingPriority::Background: return Tr::tr("Background Priority"); case IndexingPriority::Normal: return Tr::tr("Normal Priority"); case IndexingPriority::Low: return Tr::tr("Low Priority"); case IndexingPriority::Off: return Tr::tr("Off"); } return {}; } QString ClangdSettings::headerSourceSwitchModeToDisplayString(HeaderSourceSwitchMode mode) { switch (mode) { case HeaderSourceSwitchMode::BuiltinOnly: return Tr::tr("Use Built-in Only"); case HeaderSourceSwitchMode::ClangdOnly: return Tr::tr("Use Clangd Only"); case HeaderSourceSwitchMode::Both: return Tr::tr("Try Both"); } return {}; } ClangdSettings &ClangdSettings::instance() { static ClangdSettings settings; return settings; } ClangdSettings::ClangdSettings() { loadSettings(); const auto sessionMgr = ProjectExplorer::SessionManager::instance(); connect(sessionMgr, &ProjectExplorer::SessionManager::sessionRemoved, this, [this](const QString &name) { m_data.sessionsWithOneClangd.removeOne(name); }); connect(sessionMgr, &ProjectExplorer::SessionManager::sessionRenamed, this, [this](const QString &oldName, const QString &newName) { const auto index = m_data.sessionsWithOneClangd.indexOf(oldName); if (index != -1) m_data.sessionsWithOneClangd[index] = newName; }); } bool ClangdSettings::useClangd() const { return m_data.useClangd && clangdVersion() >= QVersionNumber(14); } void ClangdSettings::setUseClangd(bool use) { instance().m_data.useClangd = use; } void ClangdSettings::setUseClangdAndSave(bool use) { setUseClangd(use); instance().saveSettings(); } bool ClangdSettings::hardwareFulfillsRequirements() { instance().m_data.haveCheckedHardwareReqirements = true; instance().saveSettings(); const quint64 minRam = quint64(12) * 1024 * 1024 * 1024; const std::optional totalRam = Utils::HostOsInfo::totalMemoryInstalledInBytes(); return !totalRam || *totalRam >= minRam; } bool ClangdSettings::haveCheckedHardwareRequirements() { return instance().data().haveCheckedHardwareReqirements; } void ClangdSettings::setDefaultClangdPath(const FilePath &filePath) { g_defaultClangdFilePath = filePath; } void ClangdSettings::setCustomDiagnosticConfigs(const ClangDiagnosticConfigs &configs) { if (instance().customDiagnosticConfigs() == configs) return; instance().m_data.customDiagnosticConfigs = configs; instance().saveSettings(); } FilePath ClangdSettings::clangdFilePath() const { if (!m_data.executableFilePath.isEmpty()) return m_data.executableFilePath; return fallbackClangdFilePath(); } bool ClangdSettings::sizeIsOkay(const Utils::FilePath &fp) const { return !sizeThresholdEnabled() || sizeThresholdInKb() * 1024 >= fp.fileSize(); } ClangDiagnosticConfigs ClangdSettings::customDiagnosticConfigs() const { return m_data.customDiagnosticConfigs; } Id ClangdSettings::diagnosticConfigId() const { if (!diagnosticConfigsModel().hasConfigWithId(m_data.diagnosticConfigId)) return initialClangDiagnosticConfigId(); return m_data.diagnosticConfigId; } ClangDiagnosticConfig ClangdSettings::diagnosticConfig() const { return diagnosticConfigsModel(customDiagnosticConfigs()).configWithId(diagnosticConfigId()); } ClangdSettings::Granularity ClangdSettings::granularity() const { if (m_data.sessionsWithOneClangd.contains(ProjectExplorer::SessionManager::activeSession())) return Granularity::Session; return Granularity::Project; } void ClangdSettings::setData(const Data &data) { if (this == &instance() && data != m_data) { m_data = data; saveSettings(); emit changed(); } } static FilePath getClangHeadersPathFromClang(const FilePath &clangdFilePath) { const FilePath clangFilePath = clangdFilePath.absolutePath().pathAppended("clang") .withExecutableSuffix(); if (!clangFilePath.exists()) return {}; Process clang; clang.setCommand({clangFilePath, {"-print-resource-dir"}}); clang.start(); if (!clang.waitForFinished()) return {}; const FilePath resourceDir = FilePath::fromUserInput(QString::fromLocal8Bit( clang.readAllRawStandardOutput().trimmed())); if (resourceDir.isEmpty() || !resourceDir.exists()) return {}; const FilePath includeDir = resourceDir.pathAppended("include"); if (!includeDir.exists()) return {}; return includeDir; } static FilePath getClangHeadersPath(const FilePath &clangdFilePath) { const FilePath headersPath = getClangHeadersPathFromClang(clangdFilePath); if (!headersPath.isEmpty()) return headersPath; const QVersionNumber version = Utils::clangdVersion(clangdFilePath); QTC_ASSERT(!version.isNull(), return {}); static const QStringList libDirs{"lib", "lib64"}; const QStringList versionStrings{QString::number(version.majorVersion()), version.toString()}; for (const QString &libDir : libDirs) { for (const QString &versionString : versionStrings) { const FilePath includePath = clangdFilePath.absolutePath().parentDir() .pathAppended(libDir).pathAppended("clang") .pathAppended(versionString).pathAppended("include"); if (includePath.exists()) return includePath; } } QTC_CHECK(false); return {}; } FilePath ClangdSettings::clangdIncludePath() const { QTC_ASSERT(useClangd(), return {}); FilePath clangdPath = clangdFilePath(); QTC_ASSERT(!clangdPath.isEmpty() && clangdPath.exists(), return {}); static QHash headersPathCache; const auto it = headersPathCache.constFind(clangdPath); if (it != headersPathCache.constEnd()) return *it; const FilePath headersPath = getClangHeadersPath(clangdPath); if (!headersPath.isEmpty()) headersPathCache.insert(clangdPath, headersPath); return headersPath; } FilePath ClangdSettings::clangdUserConfigFilePath() { return FilePath::fromString( QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)) / "clangd/config.yaml"; } void ClangdSettings::loadSettings() { const auto settings = Core::ICore::settings(); Utils::fromSettings(clangdSettingsKey(), {}, settings, &m_data); settings->beginGroup(QLatin1String(Constants::CPPEDITOR_SETTINGSGROUP)); m_data.customDiagnosticConfigs = diagnosticConfigsFromSettings(settings); // Pre-8.0 compat static const QString oldKey("ClangDiagnosticConfig"); const QVariant configId = settings->value(oldKey); if (configId.isValid()) { m_data.diagnosticConfigId = Id::fromSetting(configId); settings->setValue(oldKey, {}); } settings->endGroup(); } void ClangdSettings::saveSettings() { const auto settings = Core::ICore::settings(); Utils::toSettings(clangdSettingsKey(), {}, settings, &m_data); settings->beginGroup(QLatin1String(Constants::CPPEDITOR_SETTINGSGROUP)); diagnosticConfigsToSettings(settings, m_data.customDiagnosticConfigs); settings->endGroup(); } #ifdef WITH_TESTS void ClangdSettings::setClangdFilePath(const FilePath &filePath) { instance().m_data.executableFilePath = filePath; } #endif ClangdProjectSettings::ClangdProjectSettings(ProjectExplorer::Project *project) : m_project(project) { loadSettings(); } ClangdSettings::Data ClangdProjectSettings::settings() const { const ClangdSettings::Data globalData = ClangdSettings::instance().data(); ClangdSettings::Data data = globalData; if (!m_useGlobalSettings) { data = m_customSettings; // This property is global by definition. data.sessionsWithOneClangd = ClangdSettings::instance().data().sessionsWithOneClangd; // This list exists only once. data.customDiagnosticConfigs = ClangdSettings::instance().data().customDiagnosticConfigs; } if (m_blockIndexing) data.indexingPriority = ClangdSettings::IndexingPriority::Off; return data; } void ClangdProjectSettings::setSettings(const ClangdSettings::Data &data) { m_customSettings = data; saveSettings(); ClangdSettings::setCustomDiagnosticConfigs(data.customDiagnosticConfigs); emit ClangdSettings::instance().changed(); } void ClangdProjectSettings::setUseGlobalSettings(bool useGlobal) { m_useGlobalSettings = useGlobal; saveSettings(); emit ClangdSettings::instance().changed(); } void ClangdProjectSettings::setDiagnosticConfigId(Utils::Id configId) { m_customSettings.diagnosticConfigId = configId; saveSettings(); } void ClangdProjectSettings::blockIndexing() { if (m_blockIndexing) return; m_blockIndexing = true; saveSettings(); emit ClangdSettings::instance().changed(); } void ClangdProjectSettings::unblockIndexing() { if (!m_blockIndexing) return; m_blockIndexing = false; saveSettings(); // Do not emit changed here since that would restart clients with blocked indexing } void ClangdProjectSettings::loadSettings() { if (!m_project) return; const QVariantMap data = m_project->namedSettings(clangdSettingsKey()).toMap(); m_useGlobalSettings = data.value(clangdUseGlobalSettingsKey(), true).toBool(); m_blockIndexing = data.value(clangdblockIndexingSettingsKey(), false).toBool(); if (!m_useGlobalSettings) m_customSettings.fromMap(data); } void ClangdProjectSettings::saveSettings() { if (!m_project) return; QVariantMap data; if (!m_useGlobalSettings) data = m_customSettings.toMap(); data.insert(clangdUseGlobalSettingsKey(), m_useGlobalSettings); data.insert(clangdblockIndexingSettingsKey(), m_blockIndexing); m_project->setNamedSettings(clangdSettingsKey(), data); } QVariantMap ClangdSettings::Data::toMap() const { QVariantMap map; map.insert(useClangdKey(), useClangd); map.insert(clangdPathKey(), executableFilePath != fallbackClangdFilePath() ? executableFilePath.toString() : QString()); map.insert(clangdIndexingKey(), indexingPriority != IndexingPriority::Off); map.insert(clangdIndexingPriorityKey(), int(indexingPriority)); map.insert(clangdHeaderSourceSwitchModeKey(), int(headerSourceSwitchMode)); map.insert(clangdHeaderInsertionKey(), autoIncludeHeaders); map.insert(clangdThreadLimitKey(), workerThreadLimit); map.insert(clangdDocumentThresholdKey(), documentUpdateThreshold); map.insert(clangdSizeThresholdEnabledKey(), sizeThresholdEnabled); map.insert(clangdSizeThresholdKey(), sizeThresholdInKb); map.insert(sessionsWithOneClangdKey(), sessionsWithOneClangd); map.insert(diagnosticConfigIdKey(), diagnosticConfigId.toSetting()); map.insert(checkedHardwareKey(), true); map.insert(completionResultsKey(), completionResults); return map; } void ClangdSettings::Data::fromMap(const QVariantMap &map) { useClangd = map.value(useClangdKey(), true).toBool(); executableFilePath = FilePath::fromString(map.value(clangdPathKey()).toString()); indexingPriority = IndexingPriority( map.value(clangdIndexingPriorityKey(), int(this->indexingPriority)).toInt()); const auto it = map.find(clangdIndexingKey()); if (it != map.end() && !it->toBool()) indexingPriority = IndexingPriority::Off; headerSourceSwitchMode = HeaderSourceSwitchMode(map.value(clangdHeaderSourceSwitchModeKey(), int(headerSourceSwitchMode)).toInt()); autoIncludeHeaders = map.value(clangdHeaderInsertionKey(), false).toBool(); workerThreadLimit = map.value(clangdThreadLimitKey(), 0).toInt(); documentUpdateThreshold = map.value(clangdDocumentThresholdKey(), 500).toInt(); sizeThresholdEnabled = map.value(clangdSizeThresholdEnabledKey(), false).toBool(); sizeThresholdInKb = map.value(clangdSizeThresholdKey(), 1024).toLongLong(); sessionsWithOneClangd = map.value(sessionsWithOneClangdKey()).toStringList(); diagnosticConfigId = Id::fromSetting(map.value(diagnosticConfigIdKey(), initialClangDiagnosticConfigId().toSetting())); haveCheckedHardwareReqirements = map.value(checkedHardwareKey(), false).toBool(); completionResults = map.value(completionResultsKey(), defaultCompletionResults()).toInt(); } int ClangdSettings::Data::defaultCompletionResults() { // Default clangd --limit-results value is 100 bool ok = false; const int userValue = qtcEnvironmentVariableIntValue("QTC_CLANGD_COMPLETION_RESULTS", &ok); return ok ? userValue : 100; } } // namespace CppEditor