1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "cpptoolsplugin.h"
27 #include "cppcodemodelsettingspage.h"
28 #include "cppcodestylesettingspage.h"
29 #include "cppfilesettingspage.h"
30 #include "cppmodelmanager.h"
31 #include "cppprojectfile.h"
32 #include "cppprojectupdater.h"
33 #include "cpptoolsbridge.h"
34 #include "cpptoolsbridgeqtcreatorimplementation.h"
35 #include "cpptoolsconstants.h"
36 #include "cpptoolsreuse.h"
37 #include "cpptoolssettings.h"
38 #include "projectinfo.h"
39 #include "stringtable.h"
40 
41 #include <coreplugin/actionmanager/actioncontainer.h>
42 #include <coreplugin/actionmanager/actionmanager.h>
43 #include <coreplugin/coreconstants.h>
44 #include <coreplugin/documentmanager.h>
45 #include <coreplugin/editormanager/editormanager.h>
46 #include <coreplugin/icore.h>
47 #include <coreplugin/idocument.h>
48 #include <coreplugin/vcsmanager.h>
49 #include <cppeditor/cppeditorconstants.h>
50 #include <extensionsystem/pluginmanager.h>
51 #include <projectexplorer/project.h>
52 #include <projectexplorer/projectpanelfactory.h>
53 #include <projectexplorer/projecttree.h>
54 
55 #include <utils/algorithm.h>
56 #include <utils/fileutils.h>
57 #include <utils/hostosinfo.h>
58 #include <utils/macroexpander.h>
59 #include <utils/mimetypes/mimedatabase.h>
60 #include <utils/qtcassert.h>
61 
62 #include <QFileInfo>
63 #include <QDir>
64 #include <QDebug>
65 #include <QMenu>
66 #include <QAction>
67 
68 using namespace Core;
69 using namespace CPlusPlus;
70 using namespace Utils;
71 
72 namespace CppTools {
73 namespace Internal {
74 
75 enum { debug = 0 };
76 
77 static CppToolsPlugin *m_instance = nullptr;
78 static QHash<QString, QString> m_headerSourceMapping;
79 
80 class CppToolsPluginPrivate
81 {
82 public:
CppToolsPluginPrivate()83     CppToolsPluginPrivate() {}
84 
initialize()85     void initialize() { m_codeModelSettings.fromSettings(ICore::settings()); }
86 
~CppToolsPluginPrivate()87     ~CppToolsPluginPrivate()
88     {
89         ExtensionSystem::PluginManager::removeObject(&m_cppProjectUpdaterFactory);
90         delete m_clangdSettingsPage;
91     }
92 
93     StringTable stringTable;
94     CppModelManager modelManager;
95     CppCodeModelSettings m_codeModelSettings;
96     CppToolsSettings settings;
97     CppFileSettings m_fileSettings;
98     CppFileSettingsPage m_cppFileSettingsPage{&m_fileSettings};
99     CppCodeModelSettingsPage m_cppCodeModelSettingsPage{&m_codeModelSettings};
100     ClangdSettingsPage *m_clangdSettingsPage = nullptr;
101     CppCodeStyleSettingsPage m_cppCodeStyleSettingsPage;
102     CppProjectUpdaterFactory m_cppProjectUpdaterFactory;
103 };
104 
CppToolsPlugin()105 CppToolsPlugin::CppToolsPlugin()
106 {
107     m_instance = this;
108     CppToolsBridge::setCppToolsBridgeImplementation(std::make_unique<CppToolsBridgeQtCreatorImplementation>());
109 }
110 
~CppToolsPlugin()111 CppToolsPlugin::~CppToolsPlugin()
112 {
113     delete d;
114     d = nullptr;
115     m_instance = nullptr;
116 }
117 
instance()118 CppToolsPlugin *CppToolsPlugin::instance()
119 {
120     return m_instance;
121 }
122 
clearHeaderSourceCache()123 void CppToolsPlugin::clearHeaderSourceCache()
124 {
125     m_headerSourceMapping.clear();
126 }
127 
licenseTemplatePath()128 Utils::FilePath CppToolsPlugin::licenseTemplatePath()
129 {
130     return Utils::FilePath::fromString(m_instance->d->m_fileSettings.licenseTemplatePath);
131 }
132 
licenseTemplate()133 QString CppToolsPlugin::licenseTemplate()
134 {
135     return CppFileSettings::licenseTemplate();
136 }
137 
usePragmaOnce()138 bool CppToolsPlugin::usePragmaOnce()
139 {
140     return m_instance->d->m_fileSettings.headerPragmaOnce;
141 }
142 
headerSearchPaths()143 const QStringList &CppToolsPlugin::headerSearchPaths()
144 {
145     return m_instance->d->m_fileSettings.headerSearchPaths;
146 }
147 
sourceSearchPaths()148 const QStringList &CppToolsPlugin::sourceSearchPaths()
149 {
150     return m_instance->d->m_fileSettings.sourceSearchPaths;
151 }
152 
headerPrefixes()153 const QStringList &CppToolsPlugin::headerPrefixes()
154 {
155     return m_instance->d->m_fileSettings.headerPrefixes;
156 }
157 
sourcePrefixes()158 const QStringList &CppToolsPlugin::sourcePrefixes()
159 {
160     return m_instance->d->m_fileSettings.sourcePrefixes;
161 }
162 
initialize(const QStringList & arguments,QString * error)163 bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error)
164 {
165     Q_UNUSED(arguments)
166     Q_UNUSED(error)
167 
168     d = new CppToolsPluginPrivate;
169     d->initialize();
170 
171     CppModelManager::instance()->registerJsExtension();
172     ExtensionSystem::PluginManager::addObject(&d->m_cppProjectUpdaterFactory);
173 
174     // Menus
175     ActionContainer *mtools = ActionManager::actionContainer(Core::Constants::M_TOOLS);
176     ActionContainer *mcpptools = ActionManager::createMenu(CppTools::Constants::M_TOOLS_CPP);
177     QMenu *menu = mcpptools->menu();
178     menu->setTitle(tr("&C++"));
179     menu->setEnabled(true);
180     mtools->addMenu(mcpptools);
181 
182     // Actions
183     Context context(CppEditor::Constants::CPPEDITOR_ID);
184 
185     QAction *switchAction = new QAction(tr("Switch Header/Source"), this);
186     Command *command = ActionManager::registerAction(switchAction, Constants::SWITCH_HEADER_SOURCE, context, true);
187     command->setDefaultKeySequence(QKeySequence(Qt::Key_F4));
188     mcpptools->addAction(command);
189     connect(switchAction, &QAction::triggered,
190             this, &CppToolsPlugin::switchHeaderSource);
191 
192     QAction *openInNextSplitAction = new QAction(tr("Open Corresponding Header/Source in Next Split"), this);
193     command = ActionManager::registerAction(openInNextSplitAction, Constants::OPEN_HEADER_SOURCE_IN_NEXT_SPLIT, context, true);
194     command->setDefaultKeySequence(QKeySequence(Utils::HostOsInfo::isMacHost()
195                                                 ? tr("Meta+E, F4")
196                                                 : tr("Ctrl+E, F4")));
197     mcpptools->addAction(command);
198     connect(openInNextSplitAction, &QAction::triggered,
199             this, &CppToolsPlugin::switchHeaderSourceInNextSplit);
200 
201     Utils::MacroExpander *expander = Utils::globalMacroExpander();
202     expander->registerVariable("Cpp:LicenseTemplate",
203                                tr("The license template."),
204                                []() { return CppToolsPlugin::licenseTemplate(); });
205     expander->registerFileVariables("Cpp:LicenseTemplatePath",
206                                     tr("The configured path to the license template"),
207                                     []() { return CppToolsPlugin::licenseTemplatePath(); });
208 
209     expander->registerVariable(
210                 "Cpp:PragmaOnce",
211                 tr("Insert \"#pragma once\" instead of \"#ifndef\" include guards into header file"),
212                 [] { return usePragmaOnce() ? QString("true") : QString(); });
213 
214     const auto panelFactory = new ProjectExplorer::ProjectPanelFactory;
215     panelFactory->setPriority(100);
216     panelFactory->setDisplayName(tr("Clangd"));
217     panelFactory->setCreateWidgetFunction([](ProjectExplorer::Project *project) {
218         return new ClangdProjectSettingsWidget(project);
219     });
220     ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory);
221 
222     return true;
223 }
224 
extensionsInitialized()225 void CppToolsPlugin::extensionsInitialized()
226 {
227     // The Cpp editor plugin, which is loaded later on, registers the Cpp mime types,
228     // so, apply settings here
229     d->m_fileSettings.fromSettings(ICore::settings());
230     if (!d->m_fileSettings.applySuffixesToMimeDB())
231         qWarning("Unable to apply cpp suffixes to mime database (cpp mime types not found).\n");
232     if (CppModelManager::instance()->isClangCodeModelActive())
233         d->m_clangdSettingsPage = new ClangdSettingsPage;
234 }
235 
codeModelSettings()236 CppCodeModelSettings *CppToolsPlugin::codeModelSettings()
237 {
238     return &d->m_codeModelSettings;
239 }
240 
fileSettings()241 CppFileSettings *CppToolsPlugin::fileSettings()
242 {
243     return &d->m_fileSettings;
244 }
245 
switchHeaderSource()246 void CppToolsPlugin::switchHeaderSource()
247 {
248     CppTools::switchHeaderSource();
249 }
250 
switchHeaderSourceInNextSplit()251 void CppToolsPlugin::switchHeaderSourceInNextSplit()
252 {
253     QString otherFile = correspondingHeaderOrSource(
254                 EditorManager::currentDocument()->filePath().toString());
255     if (!otherFile.isEmpty())
256         EditorManager::openEditor(otherFile, Id(), EditorManager::OpenInOtherSplit);
257 }
258 
findFilesInProject(const QString & name,const ProjectExplorer::Project * project)259 static QStringList findFilesInProject(const QString &name,
260                                    const ProjectExplorer::Project *project)
261 {
262     if (debug)
263         qDebug() << Q_FUNC_INFO << name << project;
264 
265     if (!project)
266         return QStringList();
267 
268     QString pattern = QString(1, QLatin1Char('/'));
269     pattern += name;
270     const QStringList projectFiles
271             = Utils::transform(project->files(ProjectExplorer::Project::AllFiles), &Utils::FilePath::toString);
272     const QStringList::const_iterator pcend = projectFiles.constEnd();
273     QStringList candidateList;
274     for (QStringList::const_iterator it = projectFiles.constBegin(); it != pcend; ++it) {
275         if (it->endsWith(pattern, Utils::HostOsInfo::fileNameCaseSensitivity()))
276             candidateList.append(*it);
277     }
278     return candidateList;
279 }
280 
281 // Return the suffixes that should be checked when trying to find a
282 // source belonging to a header and vice versa
matchingCandidateSuffixes(ProjectFile::Kind kind)283 static QStringList matchingCandidateSuffixes(ProjectFile::Kind kind)
284 {
285     switch (kind) {
286     case ProjectFile::AmbiguousHeader:
287     case ProjectFile::CHeader:
288     case ProjectFile::CXXHeader:
289     case ProjectFile::ObjCHeader:
290     case ProjectFile::ObjCXXHeader:
291         return mimeTypeForName(Constants::C_SOURCE_MIMETYPE).suffixes()
292                 + mimeTypeForName(Constants::CPP_SOURCE_MIMETYPE).suffixes()
293                 + mimeTypeForName(Constants::OBJECTIVE_C_SOURCE_MIMETYPE).suffixes()
294                 + mimeTypeForName(Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE).suffixes()
295                 + mimeTypeForName(Constants::CUDA_SOURCE_MIMETYPE).suffixes();
296     case ProjectFile::CSource:
297     case ProjectFile::ObjCSource:
298         return mimeTypeForName(Constants::C_HEADER_MIMETYPE).suffixes();
299     case ProjectFile::CXXSource:
300     case ProjectFile::ObjCXXSource:
301     case ProjectFile::CudaSource:
302     case ProjectFile::OpenCLSource:
303         return mimeTypeForName(Constants::CPP_HEADER_MIMETYPE).suffixes();
304     default:
305         return QStringList();
306     }
307 }
308 
baseNameWithAllSuffixes(const QString & baseName,const QStringList & suffixes)309 static QStringList baseNameWithAllSuffixes(const QString &baseName, const QStringList &suffixes)
310 {
311     QStringList result;
312     const QChar dot = QLatin1Char('.');
313     foreach (const QString &suffix, suffixes) {
314         QString fileName = baseName;
315         fileName += dot;
316         fileName += suffix;
317         result += fileName;
318     }
319     return result;
320 }
321 
baseNamesWithAllPrefixes(const QStringList & baseNames,bool isHeader)322 static QStringList baseNamesWithAllPrefixes(const QStringList &baseNames, bool isHeader)
323 {
324     QStringList result;
325     const QStringList &sourcePrefixes = m_instance->sourcePrefixes();
326     const QStringList &headerPrefixes = m_instance->headerPrefixes();
327 
328     foreach (const QString &name, baseNames) {
329         foreach (const QString &prefix, isHeader ? headerPrefixes : sourcePrefixes) {
330             if (name.startsWith(prefix)) {
331                 QString nameWithoutPrefix = name.mid(prefix.size());
332                 result += nameWithoutPrefix;
333                 foreach (const QString &prefix, isHeader ? sourcePrefixes : headerPrefixes)
334                     result += prefix + nameWithoutPrefix;
335             }
336         }
337         foreach (const QString &prefix, isHeader ? sourcePrefixes : headerPrefixes)
338             result += prefix + name;
339 
340     }
341     return result;
342 }
343 
baseDirWithAllDirectories(const QDir & baseDir,const QStringList & directories)344 static QStringList baseDirWithAllDirectories(const QDir &baseDir, const QStringList &directories)
345 {
346     QStringList result;
347     foreach (const QString &dir, directories)
348         result << QDir::cleanPath(baseDir.absoluteFilePath(dir));
349     return result;
350 }
351 
commonFilePathLength(const QString & s1,const QString & s2)352 static int commonFilePathLength(const QString &s1, const QString &s2)
353 {
354     int length = qMin(s1.length(), s2.length());
355     for (int i = 0; i < length; ++i)
356         if (Utils::HostOsInfo::fileNameCaseSensitivity() == Qt::CaseSensitive) {
357             if (s1[i] != s2[i])
358                 return i;
359         } else {
360             if (s1[i].toLower() != s2[i].toLower())
361                 return i;
362         }
363     return length;
364 }
365 
correspondingHeaderOrSourceInProject(const QFileInfo & fileInfo,const QStringList & candidateFileNames,const ProjectExplorer::Project * project,CacheUsage cacheUsage)366 static QString correspondingHeaderOrSourceInProject(const QFileInfo &fileInfo,
367                                                     const QStringList &candidateFileNames,
368                                                     const ProjectExplorer::Project *project,
369                                                     CacheUsage cacheUsage)
370 {
371     QString bestFileName;
372     int compareValue = 0;
373     const QString filePath = fileInfo.filePath();
374     foreach (const QString &candidateFileName, candidateFileNames) {
375         const QStringList projectFiles = findFilesInProject(candidateFileName, project);
376         // Find the file having the most common path with fileName
377         foreach (const QString &projectFile, projectFiles) {
378             int value = commonFilePathLength(filePath, projectFile);
379             if (value > compareValue) {
380                 compareValue = value;
381                 bestFileName = projectFile;
382             }
383         }
384     }
385     if (!bestFileName.isEmpty()) {
386         const QFileInfo candidateFi(bestFileName);
387         QTC_ASSERT(candidateFi.isFile(), return QString());
388         if (cacheUsage == CacheUsage::ReadWrite) {
389             m_headerSourceMapping[fileInfo.absoluteFilePath()] = candidateFi.absoluteFilePath();
390             m_headerSourceMapping[candidateFi.absoluteFilePath()] = fileInfo.absoluteFilePath();
391         }
392         return candidateFi.absoluteFilePath();
393     }
394 
395     return QString();
396 }
397 
398 } // namespace Internal
399 
correspondingHeaderOrSource(const QString & fileName,bool * wasHeader,CacheUsage cacheUsage)400 QString correspondingHeaderOrSource(const QString &fileName, bool *wasHeader, CacheUsage cacheUsage)
401 {
402     using namespace Internal;
403 
404     const QFileInfo fi(fileName);
405     ProjectFile::Kind kind = ProjectFile::classify(fileName);
406     const bool isHeader = ProjectFile::isHeader(kind);
407     if (wasHeader)
408         *wasHeader = isHeader;
409     if (m_headerSourceMapping.contains(fi.absoluteFilePath()))
410         return m_headerSourceMapping.value(fi.absoluteFilePath());
411 
412     if (debug)
413         qDebug() << Q_FUNC_INFO << fileName <<  kind;
414 
415     if (kind == ProjectFile::Unsupported)
416         return QString();
417 
418     const QString baseName = fi.completeBaseName();
419     const QString privateHeaderSuffix = QLatin1String("_p");
420     const QStringList suffixes = matchingCandidateSuffixes(kind);
421 
422     QStringList candidateFileNames = baseNameWithAllSuffixes(baseName, suffixes);
423     if (isHeader) {
424         if (baseName.endsWith(privateHeaderSuffix)) {
425             QString sourceBaseName = baseName;
426             sourceBaseName.truncate(sourceBaseName.size() - privateHeaderSuffix.size());
427             candidateFileNames += baseNameWithAllSuffixes(sourceBaseName, suffixes);
428         }
429     } else {
430         QString privateHeaderBaseName = baseName;
431         privateHeaderBaseName.append(privateHeaderSuffix);
432         candidateFileNames += baseNameWithAllSuffixes(privateHeaderBaseName, suffixes);
433     }
434 
435     const QDir absoluteDir = fi.absoluteDir();
436     QStringList candidateDirs(absoluteDir.absolutePath());
437     // If directory is not root, try matching against its siblings
438     const QStringList searchPaths = isHeader ? m_instance->sourceSearchPaths()
439                                              : m_instance->headerSearchPaths();
440     candidateDirs += baseDirWithAllDirectories(absoluteDir, searchPaths);
441 
442     candidateFileNames += baseNamesWithAllPrefixes(candidateFileNames, isHeader);
443 
444     // Try to find a file in the same or sibling directories first
445     foreach (const QString &candidateDir, candidateDirs) {
446         foreach (const QString &candidateFileName, candidateFileNames) {
447             const QString candidateFilePath = candidateDir + QLatin1Char('/') + candidateFileName;
448             const QString normalized = Utils::FileUtils::normalizePathName(candidateFilePath);
449             const QFileInfo candidateFi(normalized);
450             if (candidateFi.isFile()) {
451                 if (cacheUsage == CacheUsage::ReadWrite) {
452                     m_headerSourceMapping[fi.absoluteFilePath()] = candidateFi.absoluteFilePath();
453                     if (!isHeader || !baseName.endsWith(privateHeaderSuffix))
454                         m_headerSourceMapping[candidateFi.absoluteFilePath()] = fi.absoluteFilePath();
455                 }
456                 return candidateFi.absoluteFilePath();
457             }
458         }
459     }
460 
461     // Find files in the current project
462     ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectTree::currentProject();
463     if (currentProject) {
464         const QString path = correspondingHeaderOrSourceInProject(fi, candidateFileNames,
465                                                                   currentProject, cacheUsage);
466         if (!path.isEmpty())
467             return path;
468 
469     // Find files in other projects
470     } else {
471         CppModelManager *modelManager = CppModelManager::instance();
472         QList<ProjectInfo> projectInfos = modelManager->projectInfos();
473         foreach (const ProjectInfo &projectInfo, projectInfos) {
474             const ProjectExplorer::Project *project = projectInfo.project().data();
475             if (project == currentProject)
476                 continue; // We have already checked the current project.
477 
478             const QString path = correspondingHeaderOrSourceInProject(fi, candidateFileNames,
479                                                                       project, cacheUsage);
480             if (!path.isEmpty())
481                 return path;
482         }
483     }
484 
485     return QString();
486 }
487 
488 } // namespace CppTools
489