/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "cpptoolsplugin.h" #include "cppcodemodelsettingspage.h" #include "cppcodestylesettingspage.h" #include "cppfilesettingspage.h" #include "cppmodelmanager.h" #include "cppprojectfile.h" #include "cppprojectupdater.h" #include "cpptoolsbridge.h" #include "cpptoolsbridgeqtcreatorimplementation.h" #include "cpptoolsconstants.h" #include "cpptoolsjsextension.h" #include "cpptoolsreuse.h" #include "cpptoolssettings.h" #include "projectinfo.h" #include "stringtable.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace CPlusPlus; using namespace Utils; namespace CppTools { namespace Internal { enum { debug = 0 }; static CppToolsPlugin *m_instance = nullptr; static QHash m_headerSourceMapping; class CppToolsPluginPrivate { public: CppToolsPluginPrivate() {} void initialize() { m_codeModelSettings.fromSettings(ICore::settings()); } ~CppToolsPluginPrivate() { ExtensionSystem::PluginManager::removeObject(&m_cppProjectUpdaterFactory); } StringTable stringTable; CppModelManager modelManager; CppCodeModelSettings m_codeModelSettings; CppToolsSettings settings; CppFileSettings m_fileSettings; CppFileSettingsPage m_cppFileSettingsPage{&m_fileSettings}; CppCodeModelSettingsPage m_cppCodeModelSettingsPage{&m_codeModelSettings}; CppCodeStyleSettingsPage m_cppCodeStyleSettingsPage; CppProjectUpdaterFactory m_cppProjectUpdaterFactory; }; CppToolsPlugin::CppToolsPlugin() { m_instance = this; CppToolsBridge::setCppToolsBridgeImplementation(std::make_unique()); } CppToolsPlugin::~CppToolsPlugin() { delete d; d = nullptr; m_instance = nullptr; } CppToolsPlugin *CppToolsPlugin::instance() { return m_instance; } void CppToolsPlugin::clearHeaderSourceCache() { m_headerSourceMapping.clear(); } Utils::FilePath CppToolsPlugin::licenseTemplatePath() { return Utils::FilePath::fromString(m_instance->d->m_fileSettings.licenseTemplatePath); } QString CppToolsPlugin::licenseTemplate() { return m_instance->d->m_fileSettings.licenseTemplate(); } bool CppToolsPlugin::usePragmaOnce() { return m_instance->d->m_fileSettings.headerPragmaOnce; } const QStringList &CppToolsPlugin::headerSearchPaths() { return m_instance->d->m_fileSettings.headerSearchPaths; } const QStringList &CppToolsPlugin::sourceSearchPaths() { return m_instance->d->m_fileSettings.sourceSearchPaths; } const QStringList &CppToolsPlugin::headerPrefixes() { return m_instance->d->m_fileSettings.headerPrefixes; } const QStringList &CppToolsPlugin::sourcePrefixes() { return m_instance->d->m_fileSettings.sourcePrefixes; } bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error) { Q_UNUSED(arguments) Q_UNUSED(error) d = new CppToolsPluginPrivate; d->initialize(); JsExpander::registerGlobalObject("Cpp"); ExtensionSystem::PluginManager::addObject(&d->m_cppProjectUpdaterFactory); // Menus ActionContainer *mtools = ActionManager::actionContainer(Core::Constants::M_TOOLS); ActionContainer *mcpptools = ActionManager::createMenu(CppTools::Constants::M_TOOLS_CPP); QMenu *menu = mcpptools->menu(); menu->setTitle(tr("&C++")); menu->setEnabled(true); mtools->addMenu(mcpptools); // Actions Context context(CppEditor::Constants::CPPEDITOR_ID); QAction *switchAction = new QAction(tr("Switch Header/Source"), this); Command *command = ActionManager::registerAction(switchAction, Constants::SWITCH_HEADER_SOURCE, context, true); command->setDefaultKeySequence(QKeySequence(Qt::Key_F4)); mcpptools->addAction(command); connect(switchAction, &QAction::triggered, this, &CppToolsPlugin::switchHeaderSource); QAction *openInNextSplitAction = new QAction(tr("Open Corresponding Header/Source in Next Split"), this); command = ActionManager::registerAction(openInNextSplitAction, Constants::OPEN_HEADER_SOURCE_IN_NEXT_SPLIT, context, true); command->setDefaultKeySequence(QKeySequence(Utils::HostOsInfo::isMacHost() ? tr("Meta+E, F4") : tr("Ctrl+E, F4"))); mcpptools->addAction(command); connect(openInNextSplitAction, &QAction::triggered, this, &CppToolsPlugin::switchHeaderSourceInNextSplit); Utils::MacroExpander *expander = Utils::globalMacroExpander(); expander->registerVariable("Cpp:LicenseTemplate", tr("The license template."), []() { return CppToolsPlugin::licenseTemplate(); }); expander->registerFileVariables("Cpp:LicenseTemplatePath", tr("The configured path to the license template"), []() { return CppToolsPlugin::licenseTemplatePath().toString(); }); expander->registerVariable( "Cpp:PragmaOnce", tr("Insert \"#pragma once\" instead of \"#ifndef\" include guards into header file"), [] { return usePragmaOnce() ? QString("true") : QString(); }); return true; } void CppToolsPlugin::extensionsInitialized() { // The Cpp editor plugin, which is loaded later on, registers the Cpp mime types, // so, apply settings here d->m_fileSettings.fromSettings(ICore::settings()); if (!d->m_fileSettings.applySuffixesToMimeDB()) qWarning("Unable to apply cpp suffixes to mime database (cpp mime types not found).\n"); } CppCodeModelSettings *CppToolsPlugin::codeModelSettings() { return &d->m_codeModelSettings; } CppFileSettings *CppToolsPlugin::fileSettings() { return &d->m_fileSettings; } void CppToolsPlugin::switchHeaderSource() { CppTools::switchHeaderSource(); } void CppToolsPlugin::switchHeaderSourceInNextSplit() { QString otherFile = correspondingHeaderOrSource( EditorManager::currentDocument()->filePath().toString()); if (!otherFile.isEmpty()) EditorManager::openEditor(otherFile, Id(), EditorManager::OpenInOtherSplit); } static QStringList findFilesInProject(const QString &name, const ProjectExplorer::Project *project) { if (debug) qDebug() << Q_FUNC_INFO << name << project; if (!project) return QStringList(); QString pattern = QString(1, QLatin1Char('/')); pattern += name; const QStringList projectFiles = Utils::transform(project->files(ProjectExplorer::Project::AllFiles), &Utils::FilePath::toString); const QStringList::const_iterator pcend = projectFiles.constEnd(); QStringList candidateList; for (QStringList::const_iterator it = projectFiles.constBegin(); it != pcend; ++it) { if (it->endsWith(pattern, Utils::HostOsInfo::fileNameCaseSensitivity())) candidateList.append(*it); } return candidateList; } // Return the suffixes that should be checked when trying to find a // source belonging to a header and vice versa static QStringList matchingCandidateSuffixes(ProjectFile::Kind kind) { switch (kind) { case ProjectFile::AmbiguousHeader: case ProjectFile::CHeader: case ProjectFile::CXXHeader: case ProjectFile::ObjCHeader: case ProjectFile::ObjCXXHeader: return Utils::mimeTypeForName(QLatin1String(Constants::C_SOURCE_MIMETYPE)).suffixes() + Utils::mimeTypeForName(QLatin1String(Constants::CPP_SOURCE_MIMETYPE)).suffixes() + Utils::mimeTypeForName(QLatin1String(Constants::OBJECTIVE_C_SOURCE_MIMETYPE)).suffixes() + Utils::mimeTypeForName(QLatin1String(Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE)).suffixes(); case ProjectFile::CSource: case ProjectFile::ObjCSource: return Utils::mimeTypeForName(QLatin1String(Constants::C_HEADER_MIMETYPE)).suffixes(); case ProjectFile::CXXSource: case ProjectFile::ObjCXXSource: case ProjectFile::CudaSource: case ProjectFile::OpenCLSource: return Utils::mimeTypeForName(QLatin1String(Constants::CPP_HEADER_MIMETYPE)).suffixes(); default: return QStringList(); } } static QStringList baseNameWithAllSuffixes(const QString &baseName, const QStringList &suffixes) { QStringList result; const QChar dot = QLatin1Char('.'); foreach (const QString &suffix, suffixes) { QString fileName = baseName; fileName += dot; fileName += suffix; result += fileName; } return result; } static QStringList baseNamesWithAllPrefixes(const QStringList &baseNames, bool isHeader) { QStringList result; const QStringList &sourcePrefixes = m_instance->sourcePrefixes(); const QStringList &headerPrefixes = m_instance->headerPrefixes(); foreach (const QString &name, baseNames) { foreach (const QString &prefix, isHeader ? headerPrefixes : sourcePrefixes) { if (name.startsWith(prefix)) { QString nameWithoutPrefix = name.mid(prefix.size()); result += nameWithoutPrefix; foreach (const QString &prefix, isHeader ? sourcePrefixes : headerPrefixes) result += prefix + nameWithoutPrefix; } } foreach (const QString &prefix, isHeader ? sourcePrefixes : headerPrefixes) result += prefix + name; } return result; } static QStringList baseDirWithAllDirectories(const QDir &baseDir, const QStringList &directories) { QStringList result; foreach (const QString &dir, directories) result << QDir::cleanPath(baseDir.absoluteFilePath(dir)); return result; } static int commonFilePathLength(const QString &s1, const QString &s2) { int length = qMin(s1.length(), s2.length()); for (int i = 0; i < length; ++i) if (Utils::HostOsInfo::fileNameCaseSensitivity() == Qt::CaseSensitive) { if (s1[i] != s2[i]) return i; } else { if (s1[i].toLower() != s2[i].toLower()) return i; } return length; } static QString correspondingHeaderOrSourceInProject(const QFileInfo &fileInfo, const QStringList &candidateFileNames, const ProjectExplorer::Project *project, CacheUsage cacheUsage) { QString bestFileName; int compareValue = 0; const QString filePath = fileInfo.filePath(); foreach (const QString &candidateFileName, candidateFileNames) { const QStringList projectFiles = findFilesInProject(candidateFileName, project); // Find the file having the most common path with fileName foreach (const QString &projectFile, projectFiles) { int value = commonFilePathLength(filePath, projectFile); if (value > compareValue) { compareValue = value; bestFileName = projectFile; } } } if (!bestFileName.isEmpty()) { const QFileInfo candidateFi(bestFileName); QTC_ASSERT(candidateFi.isFile(), return QString()); if (cacheUsage == CacheUsage::ReadWrite) { m_headerSourceMapping[fileInfo.absoluteFilePath()] = candidateFi.absoluteFilePath(); m_headerSourceMapping[candidateFi.absoluteFilePath()] = fileInfo.absoluteFilePath(); } return candidateFi.absoluteFilePath(); } return QString(); } } // namespace Internal QString correspondingHeaderOrSource(const QString &fileName, bool *wasHeader, CacheUsage cacheUsage) { using namespace Internal; const QFileInfo fi(fileName); ProjectFile::Kind kind = ProjectFile::classify(fileName); const bool isHeader = ProjectFile::isHeader(kind); if (wasHeader) *wasHeader = isHeader; if (m_headerSourceMapping.contains(fi.absoluteFilePath())) return m_headerSourceMapping.value(fi.absoluteFilePath()); if (debug) qDebug() << Q_FUNC_INFO << fileName << kind; if (kind == ProjectFile::Unsupported) return QString(); const QString baseName = fi.completeBaseName(); const QString privateHeaderSuffix = QLatin1String("_p"); const QStringList suffixes = matchingCandidateSuffixes(kind); QStringList candidateFileNames = baseNameWithAllSuffixes(baseName, suffixes); if (isHeader) { if (baseName.endsWith(privateHeaderSuffix)) { QString sourceBaseName = baseName; sourceBaseName.truncate(sourceBaseName.size() - privateHeaderSuffix.size()); candidateFileNames += baseNameWithAllSuffixes(sourceBaseName, suffixes); } } else { QString privateHeaderBaseName = baseName; privateHeaderBaseName.append(privateHeaderSuffix); candidateFileNames += baseNameWithAllSuffixes(privateHeaderBaseName, suffixes); } const QDir absoluteDir = fi.absoluteDir(); QStringList candidateDirs(absoluteDir.absolutePath()); // If directory is not root, try matching against its siblings const QStringList searchPaths = isHeader ? m_instance->sourceSearchPaths() : m_instance->headerSearchPaths(); candidateDirs += baseDirWithAllDirectories(absoluteDir, searchPaths); candidateFileNames += baseNamesWithAllPrefixes(candidateFileNames, isHeader); // Try to find a file in the same or sibling directories first foreach (const QString &candidateDir, candidateDirs) { foreach (const QString &candidateFileName, candidateFileNames) { const QString candidateFilePath = candidateDir + QLatin1Char('/') + candidateFileName; const QString normalized = Utils::FileUtils::normalizePathName(candidateFilePath); const QFileInfo candidateFi(normalized); if (candidateFi.isFile()) { if (cacheUsage == CacheUsage::ReadWrite) { m_headerSourceMapping[fi.absoluteFilePath()] = candidateFi.absoluteFilePath(); if (!isHeader || !baseName.endsWith(privateHeaderSuffix)) m_headerSourceMapping[candidateFi.absoluteFilePath()] = fi.absoluteFilePath(); } return candidateFi.absoluteFilePath(); } } } // Find files in the current project ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectTree::currentProject(); if (currentProject) { const QString path = correspondingHeaderOrSourceInProject(fi, candidateFileNames, currentProject, cacheUsage); if (!path.isEmpty()) return path; // Find files in other projects } else { CppModelManager *modelManager = CppModelManager::instance(); QList projectInfos = modelManager->projectInfos(); foreach (const ProjectInfo &projectInfo, projectInfos) { const ProjectExplorer::Project *project = projectInfo.project().data(); if (project == currentProject) continue; // We have already checked the current project. const QString path = correspondingHeaderOrSourceInProject(fi, candidateFileNames, project, cacheUsage); if (!path.isEmpty()) return path; } } return QString(); } } // namespace CppTools