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