// 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 "cppeditorplugin.h" #include "cppautocompleter.h" #include "cppcodemodelinspectordialog.h" #include "cppcodemodelsettings.h" #include "cppcodemodelsettingspage.h" #include "cppcodestylesettingspage.h" #include "cppeditorconstants.h" #include "cppeditordocument.h" #include "cppeditortr.h" #include "cppeditorwidget.h" #include "cppfilesettingspage.h" #include "cppincludehierarchy.h" #include "cppmodelmanager.h" #include "cppoutline.h" #include "cppprojectfile.h" #include "cppprojectupdater.h" #include "cppquickfixassistant.h" #include "cppquickfixes.h" #include "cppquickfixprojectsettingswidget.h" #include "cppquickfixsettingspage.h" #include "cpptoolsreuse.h" #include "cpptoolssettings.h" #include "cpptypehierarchy.h" #include "projectinfo.h" #include "resourcepreviewhoverhandler.h" #ifdef WITH_TESTS #include "compileroptionsbuilder_test.h" #include "cppcodegen_test.h" #include "cppcompletion_test.h" #include "cppdoxygen_test.h" #include "cppheadersource_test.h" #include "cpphighlighter.h" #include "cppincludehierarchy_test.h" #include "cppinsertvirtualmethods.h" #include "cpplocalsymbols_test.h" #include "cpplocatorfilter_test.h" #include "cppmodelmanager_test.h" #include "cpppointerdeclarationformatter_test.h" #include "cppquickfix_test.h" #include "cppsourceprocessor_test.h" #include "cppuseselections_test.h" #include "fileandtokenactions_test.h" #include "followsymbol_switchmethoddecldef_test.h" #include "functionutils.h" #include "includeutils.h" #include "projectinfo_test.h" #include "symbolsearcher_test.h" #include "typehierarchybuilder_test.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 CPlusPlus; using namespace Core; using namespace ProjectExplorer; using namespace TextEditor; using namespace Utils; namespace CppEditor { namespace Internal { enum { QUICKFIX_INTERVAL = 20 }; enum { debug = 0 }; static CppEditorWidget *currentCppEditorWidget() { if (IEditor *currentEditor = EditorManager::currentEditor()) return qobject_cast(currentEditor->widget()); return nullptr; } //////////////////////////// CppEditorFactory ///////////////////////////// class CppEditorFactory : public TextEditorFactory { public: CppEditorFactory() { setId(Constants::CPPEDITOR_ID); setDisplayName(::Core::Tr::tr("C++ Editor")); addMimeType(Constants::C_SOURCE_MIMETYPE); addMimeType(Constants::C_HEADER_MIMETYPE); addMimeType(Constants::CPP_SOURCE_MIMETYPE); addMimeType(Constants::CPP_HEADER_MIMETYPE); addMimeType(Constants::QDOC_MIMETYPE); addMimeType(Constants::MOC_MIMETYPE); setDocumentCreator([]() { return new CppEditorDocument; }); setEditorWidgetCreator([]() { return new CppEditorWidget; }); setEditorCreator([]() { const auto editor = new BaseTextEditor; editor->addContext(ProjectExplorer::Constants::CXX_LANGUAGE_ID); return editor; }); setAutoCompleterCreator([]() { return new CppAutoCompleter; }); setCommentDefinition(CommentDefinition::CppStyle); setCodeFoldingSupported(true); setParenthesesMatchingEnabled(true); setEditorActionHandlers(TextEditorActionHandler::Format | TextEditorActionHandler::UnCommentSelection | TextEditorActionHandler::UnCollapseAll | TextEditorActionHandler::FollowSymbolUnderCursor | TextEditorActionHandler::RenameSymbol | TextEditorActionHandler::FindUsage); } }; ///////////////////////////////// CppEditorPlugin ////////////////////////////////// class CppEditorPluginPrivate : public QObject { public: ~CppEditorPluginPrivate() { ExtensionSystem::PluginManager::removeObject(&m_cppProjectUpdaterFactory); delete m_clangdSettingsPage; } void initialize() { m_codeModelSettings.fromSettings(ICore::settings()); } void onTaskStarted(Utils::Id type); void onAllTasksFinished(Utils::Id type); void inspectCppCodeModel(); QAction *m_reparseExternallyChangedFiles = nullptr; QAction *m_findRefsCategorizedAction = nullptr; QAction *m_openTypeHierarchyAction = nullptr; QAction *m_openIncludeHierarchyAction = nullptr; CppQuickFixAssistProvider m_quickFixProvider; CppQuickFixSettingsPage m_quickFixSettingsPage; QPointer m_cppCodeModelInspectorDialog; QPointer m_currentEditor; CppOutlineWidgetFactory m_cppOutlineWidgetFactory; CppTypeHierarchyFactory m_cppTypeHierarchyFactory; CppIncludeHierarchyFactory m_cppIncludeHierarchyFactory; CppEditorFactory m_cppEditorFactory; CppModelManager modelManager; CppCodeModelSettings m_codeModelSettings; CppToolsSettings settings; CppFileSettings m_fileSettings; CppFileSettingsPage m_cppFileSettingsPage{&m_fileSettings}; CppCodeModelSettingsPage m_cppCodeModelSettingsPage{&m_codeModelSettings}; ClangdSettingsPage *m_clangdSettingsPage = nullptr; CppCodeStyleSettingsPage m_cppCodeStyleSettingsPage; CppProjectUpdaterFactory m_cppProjectUpdaterFactory; }; static CppEditorPlugin *m_instance = nullptr; static QHash m_headerSourceMapping; CppEditorPlugin::CppEditorPlugin() { m_instance = this; } CppEditorPlugin::~CppEditorPlugin() { destroyCppQuickFixes(); delete d; d = nullptr; m_instance = nullptr; } CppEditorPlugin *CppEditorPlugin::instance() { return m_instance; } CppQuickFixAssistProvider *CppEditorPlugin::quickFixProvider() const { return &d->m_quickFixProvider; } void CppEditorPlugin::initialize() { d = new CppEditorPluginPrivate; d->initialize(); CppModelManager::instance()->registerJsExtension(); ExtensionSystem::PluginManager::addObject(&d->m_cppProjectUpdaterFactory); // Menus ActionContainer *mtools = ActionManager::actionContainer(Core::Constants::M_TOOLS); ActionContainer *mcpptools = ActionManager::createMenu(Constants::M_TOOLS_CPP); QMenu *menu = mcpptools->menu(); menu->setTitle(Tr::tr("&C++")); menu->setEnabled(true); mtools->addMenu(mcpptools); // Actions Context context(Constants::CPPEDITOR_ID); QAction *switchAction = new QAction(Tr::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, [] { CppModelManager::switchHeaderSource(false); }); QAction *openInNextSplitAction = new QAction(Tr::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(HostOsInfo::isMacHost() ? Tr::tr("Meta+E, F4") : Tr::tr("Ctrl+E, F4"))); mcpptools->addAction(command); connect(openInNextSplitAction, &QAction::triggered, this, [] { CppModelManager::switchHeaderSource(true); }); MacroExpander *expander = globalMacroExpander(); expander->registerVariable("Cpp:LicenseTemplate", Tr::tr("The license template."), []() { return CppEditorPlugin::licenseTemplate(); }); expander->registerFileVariables("Cpp:LicenseTemplatePath", Tr::tr("The configured path to the license template"), []() { return CppEditorPlugin::licenseTemplatePath(); }); expander->registerVariable( "Cpp:PragmaOnce", Tr::tr("Insert \"#pragma once\" instead of \"#ifndef\" include guards into header file"), [] { return usePragmaOnce() ? QString("true") : QString(); }); const auto quickFixSettingsPanelFactory = new ProjectPanelFactory; quickFixSettingsPanelFactory->setPriority(100); quickFixSettingsPanelFactory->setId(Constants::QUICK_FIX_PROJECT_PANEL_ID); quickFixSettingsPanelFactory->setDisplayName(Tr::tr(Constants::QUICK_FIX_SETTINGS_DISPLAY_NAME)); quickFixSettingsPanelFactory->setCreateWidgetFunction([](Project *project) { return new CppQuickFixProjectSettingsWidget(project); }); ProjectPanelFactory::registerFactory(quickFixSettingsPanelFactory); SnippetProvider::registerGroup(Constants::CPP_SNIPPETS_GROUP_ID, Tr::tr("C++", "SnippetProvider"), &decorateCppEditor); createCppQuickFixes(); ActionContainer *contextMenu = ActionManager::createMenu(Constants::M_CONTEXT); contextMenu->insertGroup(Core::Constants::G_DEFAULT_ONE, Constants::G_CONTEXT_FIRST); Command *cmd; ActionContainer *cppToolsMenu = ActionManager::actionContainer(Constants::M_TOOLS_CPP); ActionContainer *touchBar = ActionManager::actionContainer(Core::Constants::TOUCH_BAR); cmd = ActionManager::command(Constants::SWITCH_HEADER_SOURCE); cmd->setTouchBarText(Tr::tr("Header/Source", "text on macOS touch bar")); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); touchBar->addAction(cmd, Core::Constants::G_TOUCHBAR_NAVIGATION); cmd = ActionManager::command(TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR); cmd->setTouchBarText(Tr::tr("Follow", "text on macOS touch bar")); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); touchBar->addAction(cmd, Core::Constants::G_TOUCHBAR_NAVIGATION); QAction *openPreprocessorDialog = new QAction(Tr::tr("Additional Preprocessor Directives..."), this); cmd = ActionManager::registerAction(openPreprocessorDialog, Constants::OPEN_PREPROCESSOR_DIALOG, context); cmd->setDefaultKeySequence(QKeySequence()); connect(openPreprocessorDialog, &QAction::triggered, this, &CppEditorPlugin::showPreProcessorDialog); cppToolsMenu->addAction(cmd); QAction *switchDeclarationDefinition = new QAction(Tr::tr("Switch Between Function Declaration/Definition"), this); cmd = ActionManager::registerAction(switchDeclarationDefinition, Constants::SWITCH_DECLARATION_DEFINITION, context, true); cmd->setDefaultKeySequence(QKeySequence(Tr::tr("Shift+F2"))); cmd->setTouchBarText(Tr::tr("Decl/Def", "text on macOS touch bar")); connect(switchDeclarationDefinition, &QAction::triggered, this, &CppEditorPlugin::switchDeclarationDefinition); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); touchBar->addAction(cmd, Core::Constants::G_TOUCHBAR_NAVIGATION); cmd = ActionManager::command(TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT); cppToolsMenu->addAction(cmd); QAction *openDeclarationDefinitionInNextSplit = new QAction(Tr::tr("Open Function Declaration/Definition in Next Split"), this); cmd = ActionManager::registerAction(openDeclarationDefinitionInNextSplit, Constants::OPEN_DECLARATION_DEFINITION_IN_NEXT_SPLIT, context, true); cmd->setDefaultKeySequence(QKeySequence(HostOsInfo::isMacHost() ? Tr::tr("Meta+E, Shift+F2") : Tr::tr("Ctrl+E, Shift+F2"))); connect(openDeclarationDefinitionInNextSplit, &QAction::triggered, this, &CppEditorPlugin::openDeclarationDefinitionInNextSplit); cppToolsMenu->addAction(cmd); QAction * const followSymbolToType = new QAction(Tr::tr("Follow Symbol Under Cursor to Type"), this); cmd = ActionManager::registerAction(followSymbolToType, Constants::FOLLOW_SYMBOL_TO_TYPE, context, true); cmd->setDefaultKeySequence(QKeySequence(Tr::tr("Ctrl+Shift+F2"))); connect(followSymbolToType, &QAction::triggered, this, []{ if (CppEditorWidget *editorWidget = currentCppEditorWidget()) editorWidget->followSymbolToType(false); }); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); QAction * const followSymbolToTypeInNextSplit = new QAction(Tr::tr("Follow Symbol to Type in Next Split"), this); cmd = ActionManager::registerAction(followSymbolToTypeInNextSplit, Constants::FOLLOW_SYMBOL_TO_TYPE_IN_NEXT_SPLIT, context, true); cmd->setDefaultKeySequence(QKeySequence(HostOsInfo::isMacHost() ? Tr::tr("Meta+E, Ctrl+Shift+F2") : Tr::tr("Ctrl+E, Ctrl+Shift+F2"))); connect(followSymbolToTypeInNextSplit, &QAction::triggered, this, []{ if (CppEditorWidget *editorWidget = currentCppEditorWidget()) editorWidget->followSymbolToType(true); }); cppToolsMenu->addAction(cmd); cmd = ActionManager::command(TextEditor::Constants::FIND_USAGES); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); d->m_findRefsCategorizedAction = new QAction(Tr::tr("Find References With Access Type"), this); cmd = ActionManager::registerAction(d->m_findRefsCategorizedAction, "CppEditor.FindRefsCategorized", context); connect(d->m_findRefsCategorizedAction, &QAction::triggered, this, [this] { if (const auto w = currentCppEditorWidget()) { codeModelSettings()->setCategorizeFindReferences(true); w->findUsages(); codeModelSettings()->setCategorizeFindReferences(false); } }); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); QAction * const showPreprocessedAction = new QAction(Tr::tr("Show Preprocessed Source"), this); command = ActionManager::registerAction(showPreprocessedAction, Constants::SHOW_PREPROCESSED_FILE, context); mcpptools->addAction(command); contextMenu->addAction(command, Constants::G_CONTEXT_FIRST); connect(showPreprocessedAction, &QAction::triggered, this, [] { CppModelManager::showPreprocessedFile(false); }); QAction * const showPreprocessedInSplitAction = new QAction (Tr::tr("Show Preprocessed Source in Next Split"), this); command = ActionManager::registerAction(showPreprocessedInSplitAction, Constants::SHOW_PREPROCESSED_FILE_SPLIT, context); mcpptools->addAction(command); connect(showPreprocessedInSplitAction, &QAction::triggered, this, [] { CppModelManager::showPreprocessedFile(true); }); QAction *const findUnusedFunctionsAction = new QAction(Tr::tr("Find Unused Functions"), this); command = ActionManager::registerAction(findUnusedFunctionsAction, "CppTools.FindUnusedFunctions"); mcpptools->addAction(command); connect(findUnusedFunctionsAction, &QAction::triggered, this, [] { CppModelManager::findUnusedFunctions({}); }); QAction *const findUnusedFunctionsInSubProjectAction = new QAction(Tr::tr("Find Unused C/C++ Functions"), this); command = ActionManager::registerAction(findUnusedFunctionsInSubProjectAction, "CppTools.FindUnusedFunctionsInSubProject"); for (ActionContainer *const projectContextMenu : {ActionManager::actionContainer(ProjectExplorer::Constants::M_SUBPROJECTCONTEXT), ActionManager::actionContainer(ProjectExplorer::Constants::M_PROJECTCONTEXT)}) { projectContextMenu->addSeparator(ProjectExplorer::Constants::G_PROJECT_TREE); projectContextMenu->addAction(command, ProjectExplorer::Constants::G_PROJECT_TREE); } connect(findUnusedFunctionsInSubProjectAction, &QAction::triggered, this, [] { if (const Node *const node = ProjectTree::currentNode(); node && node->asFolderNode()) CppModelManager::findUnusedFunctions(node->directory()); }); d->m_openTypeHierarchyAction = new QAction(Tr::tr("Open Type Hierarchy"), this); cmd = ActionManager::registerAction(d->m_openTypeHierarchyAction, Constants::OPEN_TYPE_HIERARCHY, context); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Shift+T") : Tr::tr("Ctrl+Shift+T"))); connect(d->m_openTypeHierarchyAction, &QAction::triggered, this, &CppEditorPlugin::openTypeHierarchy); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); d->m_openIncludeHierarchyAction = new QAction(Tr::tr("Open Include Hierarchy"), this); cmd = ActionManager::registerAction(d->m_openIncludeHierarchyAction, Constants::OPEN_INCLUDE_HIERARCHY, context); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Shift+I") : Tr::tr("Ctrl+Shift+I"))); connect(d->m_openIncludeHierarchyAction, &QAction::triggered, this, &CppEditorPlugin::openIncludeHierarchy); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); cmd = ActionManager::command(TextEditor::Constants::OPEN_CALL_HIERARCHY); contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); // Refactoring sub-menu Command *sep = contextMenu->addSeparator(); sep->action()->setObjectName(QLatin1String(Constants::M_REFACTORING_MENU_INSERTION_POINT)); contextMenu->addSeparator(); cppToolsMenu->addAction(ActionManager::command(TextEditor::Constants::RENAME_SYMBOL)); // Update context in global context cppToolsMenu->addSeparator(Core::Constants::G_DEFAULT_THREE); d->m_reparseExternallyChangedFiles = new QAction(Tr::tr("Reparse Externally Changed Files"), this); cmd = ActionManager::registerAction(d->m_reparseExternallyChangedFiles, Constants::UPDATE_CODEMODEL); CppModelManager *cppModelManager = CppModelManager::instance(); connect(d->m_reparseExternallyChangedFiles, &QAction::triggered, cppModelManager, &CppModelManager::updateModifiedSourceFiles); cppToolsMenu->addAction(cmd, Core::Constants::G_DEFAULT_THREE); ActionContainer *toolsDebug = ActionManager::actionContainer(Core::Constants::M_TOOLS_DEBUG); QAction *inspectCppCodeModel = new QAction(Tr::tr("Inspect C++ Code Model..."), this); cmd = ActionManager::registerAction(inspectCppCodeModel, Constants::INSPECT_CPP_CODEMODEL); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Shift+F12") : Tr::tr("Ctrl+Shift+F12"))); connect(inspectCppCodeModel, &QAction::triggered, d, &CppEditorPluginPrivate::inspectCppCodeModel); toolsDebug->addAction(cmd); contextMenu->addSeparator(context); cmd = ActionManager::command(TextEditor::Constants::AUTO_INDENT_SELECTION); contextMenu->addAction(cmd); cmd = ActionManager::command(TextEditor::Constants::UN_COMMENT_SELECTION); contextMenu->addAction(cmd); connect(ProgressManager::instance(), &ProgressManager::taskStarted, d, &CppEditorPluginPrivate::onTaskStarted); connect(ProgressManager::instance(), &ProgressManager::allTasksFinished, d, &CppEditorPluginPrivate::onAllTasksFinished); #ifdef WITH_TESTS addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); addTest(); #endif } void CppEditorPlugin::extensionsInitialized() { 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"); if (CppModelManager::instance()->isClangCodeModelActive()) { d->m_clangdSettingsPage = new ClangdSettingsPage; const auto clangdPanelFactory = new ProjectPanelFactory; clangdPanelFactory->setPriority(100); clangdPanelFactory->setDisplayName(Tr::tr("Clangd")); clangdPanelFactory->setCreateWidgetFunction([](Project *project) { return new ClangdProjectSettingsWidget(project); }); ProjectPanelFactory::registerFactory(clangdPanelFactory); } // Add the hover handler factories here instead of in initialize() // so that the Clang Code Model has a chance to hook in. d->m_cppEditorFactory.addHoverHandler(CppModelManager::instance()->createHoverHandler()); d->m_cppEditorFactory.addHoverHandler(new ColorPreviewHoverHandler); d->m_cppEditorFactory.addHoverHandler(new ResourcePreviewHoverHandler); FileIconProvider::registerIconOverlayForMimeType( creatorTheme()->imageFile(Theme::IconOverlayCppSource, ProjectExplorer::Constants::FILEOVERLAY_CPP), Constants::CPP_SOURCE_MIMETYPE); FileIconProvider::registerIconOverlayForMimeType( creatorTheme()->imageFile(Theme::IconOverlayCSource, ProjectExplorer::Constants::FILEOVERLAY_C), Constants::C_SOURCE_MIMETYPE); FileIconProvider::registerIconOverlayForMimeType( creatorTheme()->imageFile(Theme::IconOverlayCppHeader, ProjectExplorer::Constants::FILEOVERLAY_H), Constants::CPP_HEADER_MIMETYPE); } void CppEditorPlugin::switchDeclarationDefinition() { if (CppEditorWidget *editorWidget = currentCppEditorWidget()) editorWidget->switchDeclarationDefinition(/*inNextSplit*/ false); } void CppEditorPlugin::openDeclarationDefinitionInNextSplit() { if (CppEditorWidget *editorWidget = currentCppEditorWidget()) editorWidget->switchDeclarationDefinition(/*inNextSplit*/ true); } void CppEditorPlugin::renameSymbolUnderCursor() { if (CppEditorWidget *editorWidget = currentCppEditorWidget()) editorWidget->renameSymbolUnderCursor(); } void CppEditorPlugin::showPreProcessorDialog() { if (CppEditorWidget *editorWidget = currentCppEditorWidget()) editorWidget->showPreProcessorWidget(); } void CppEditorPluginPrivate::onTaskStarted(Id type) { if (type == Constants::TASK_INDEX) { ActionManager::command(TextEditor::Constants::FIND_USAGES)->action()->setEnabled(false); ActionManager::command(TextEditor::Constants::RENAME_SYMBOL)->action()->setEnabled(false); m_reparseExternallyChangedFiles->setEnabled(false); m_openTypeHierarchyAction->setEnabled(false); m_openIncludeHierarchyAction->setEnabled(false); } } void CppEditorPluginPrivate::onAllTasksFinished(Id type) { if (type == Constants::TASK_INDEX) { ActionManager::command(TextEditor::Constants::FIND_USAGES)->action()->setEnabled(true); ActionManager::command(TextEditor::Constants::RENAME_SYMBOL)->action()->setEnabled(true); m_reparseExternallyChangedFiles->setEnabled(true); m_openTypeHierarchyAction->setEnabled(true); m_openIncludeHierarchyAction->setEnabled(true); } } void CppEditorPluginPrivate::inspectCppCodeModel() { if (m_cppCodeModelInspectorDialog) { ICore::raiseWindow(m_cppCodeModelInspectorDialog); } else { m_cppCodeModelInspectorDialog = new CppCodeModelInspectorDialog(ICore::dialogParent()); ICore::registerWindow(m_cppCodeModelInspectorDialog, Context("CppEditor.Inspector")); m_cppCodeModelInspectorDialog->show(); } } void CppEditorPlugin::openTypeHierarchy() { if (currentCppEditorWidget()) { emit typeHierarchyRequested(); NavigationWidget::activateSubWidget(Constants::TYPE_HIERARCHY_ID, Side::Left); } } void CppEditorPlugin::openIncludeHierarchy() { if (currentCppEditorWidget()) { emit includeHierarchyRequested(); NavigationWidget::activateSubWidget(Constants::INCLUDE_HIERARCHY_ID, Side::Left); } } void CppEditorPlugin::clearHeaderSourceCache() { m_headerSourceMapping.clear(); } FilePath CppEditorPlugin::licenseTemplatePath() { return FilePath::fromString(m_instance->d->m_fileSettings.licenseTemplatePath); } QString CppEditorPlugin::licenseTemplate() { return CppFileSettings::licenseTemplate(); } bool CppEditorPlugin::usePragmaOnce() { return m_instance->d->m_fileSettings.headerPragmaOnce; } const QStringList &CppEditorPlugin::headerSearchPaths() { return m_instance->d->m_fileSettings.headerSearchPaths; } const QStringList &CppEditorPlugin::sourceSearchPaths() { return m_instance->d->m_fileSettings.sourceSearchPaths; } const QStringList &CppEditorPlugin::headerPrefixes() { return m_instance->d->m_fileSettings.headerPrefixes; } const QStringList &CppEditorPlugin::sourcePrefixes() { return m_instance->d->m_fileSettings.sourcePrefixes; } CppCodeModelSettings *CppEditorPlugin::codeModelSettings() { return &d->m_codeModelSettings; } CppFileSettings *CppEditorPlugin::fileSettings() { return &instance()->d->m_fileSettings; } static QStringList findFilesInProject(const QString &name, const Project *project) { if (debug) qDebug() << Q_FUNC_INFO << name << project; if (!project) return QStringList(); QString pattern = QString(1, QLatin1Char('/')); pattern += name; const QStringList projectFiles = transform(project->files(Project::AllFiles), &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, 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 mimeTypeForName(Constants::C_SOURCE_MIMETYPE).suffixes() + mimeTypeForName(Constants::CPP_SOURCE_MIMETYPE).suffixes() + mimeTypeForName(Constants::OBJECTIVE_C_SOURCE_MIMETYPE).suffixes() + mimeTypeForName(Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE).suffixes() + mimeTypeForName(Constants::CUDA_SOURCE_MIMETYPE).suffixes(); case ProjectFile::CSource: case ProjectFile::ObjCSource: return mimeTypeForName(Constants::C_HEADER_MIMETYPE).suffixes(); case ProjectFile::CXXSource: case ProjectFile::ObjCXXSource: case ProjectFile::CudaSource: case ProjectFile::OpenCLSource: return mimeTypeForName(Constants::CPP_HEADER_MIMETYPE).suffixes(); default: return QStringList(); } } static QStringList baseNameWithAllSuffixes(const QString &baseName, const QStringList &suffixes) { QStringList result; const QChar dot = QLatin1Char('.'); for (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(); for (const QString &name : baseNames) { for (const QString &prefix : isHeader ? headerPrefixes : sourcePrefixes) { if (name.startsWith(prefix)) { QString nameWithoutPrefix = name.mid(prefix.size()); result += nameWithoutPrefix; for (const QString &prefix : isHeader ? sourcePrefixes : headerPrefixes) result += prefix + nameWithoutPrefix; } } for (const QString &prefix : isHeader ? sourcePrefixes : headerPrefixes) result += prefix + name; } return result; } static QStringList baseDirWithAllDirectories(const QDir &baseDir, const QStringList &directories) { QStringList result; for (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 (HostOsInfo::fileNameCaseSensitivity() == Qt::CaseSensitive) { if (s1[i] != s2[i]) return i; } else { if (s1[i].toLower() != s2[i].toLower()) return i; } return length; } static FilePath correspondingHeaderOrSourceInProject(const QFileInfo &fileInfo, const QStringList &candidateFileNames, const Project *project, CacheUsage cacheUsage) { QString bestFileName; int compareValue = 0; const QString filePath = fileInfo.filePath(); for (const QString &candidateFileName : candidateFileNames) { const QStringList projectFiles = findFilesInProject(candidateFileName, project); // Find the file having the most common path with fileName for (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 {}); if (cacheUsage == CacheUsage::ReadWrite) { m_headerSourceMapping[fileInfo.absoluteFilePath()] = candidateFi.absoluteFilePath(); m_headerSourceMapping[candidateFi.absoluteFilePath()] = fileInfo.absoluteFilePath(); } return FilePath::fromString(candidateFi.absoluteFilePath()); } return {}; } } // namespace Internal using namespace Internal; FilePath correspondingHeaderOrSource(const FilePath &filePath, bool *wasHeader, CacheUsage cacheUsage) { const QString fileName = filePath.toString(); 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 FilePath::fromString(m_headerSourceMapping.value(fi.absoluteFilePath())); if (debug) qDebug() << Q_FUNC_INFO << fileName << kind; if (kind == ProjectFile::Unsupported) return {}; 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 for (const QString &candidateDir : std::as_const(candidateDirs)) { for (const QString &candidateFileName : std::as_const(candidateFileNames)) { const QString candidateFilePath = candidateDir + QLatin1Char('/') + candidateFileName; const QString normalized = FileUtils::normalizedPathName(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 FilePath::fromString(candidateFi.absoluteFilePath()); } } } // Find files in the current project Project *currentProject = ProjectTree::currentProject(); if (currentProject) { const FilePath path = correspondingHeaderOrSourceInProject(fi, candidateFileNames, currentProject, cacheUsage); if (!path.isEmpty()) return path; // Find files in other projects } else { CppModelManager *modelManager = CppModelManager::instance(); const QList projectInfos = modelManager->projectInfos(); for (const ProjectInfo::ConstPtr &projectInfo : projectInfos) { const Project *project = projectForProjectInfo(*projectInfo); if (project == currentProject) continue; // We have already checked the current project. const FilePath path = correspondingHeaderOrSourceInProject(fi, candidateFileNames, project, cacheUsage); if (!path.isEmpty()) return path; } } return {}; } } // namespace CppEditor