cppmodelmanager.cpp 35.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11
** 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
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
#include "cppmodelmanager.h"
32

33
#include "abstracteditorsupport.h"
34
#include "builtinindexingsupport.h"
35
#include "cppcodemodelinspectordumper.h"
36
#include "cppcodemodelsettings.h"
37
#include "cppfindreferences.h"
38
#include "cppindexingsupport.h"
39
#include "cppmodelmanagersupportinternal.h"
40
#include "cpprefactoringchanges.h"
41
#include "cppsourceprocessor.h"
42
#include "cpptoolsconstants.h"
43
#include "cpptoolsplugin.h"
44
#include "editordocumenthandle.h"
con's avatar
con committed
45

46
#include <coreplugin/documentmanager.h>
47 48
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
49 50
#include <coreplugin/editormanager/editormanager.h>
#include <texteditor/textdocument.h>
51 52
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
53
#include <extensionsystem/pluginmanager.h>
54
#include <utils/fileutils.h>
hjk's avatar
hjk committed
55
#include <utils/qtcassert.h>
con's avatar
con committed
56

57 58 59 60
#include <QCoreApplication>
#include <QDebug>
#include <QMutexLocker>
#include <QTextBlock>
61
#include <QTimer>
62

63 64
#if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU)
#define WITH_AST_DUMP
65 66
#include <iostream>
#include <sstream>
67
#endif
hjk's avatar
hjk committed
68

69 70
Q_DECLARE_METATYPE(QSet<QString>)

71 72
static const bool DumpProjectInfo = qgetenv("QTC_DUMP_PROJECT_INFO") == "1";

73 74
using namespace CppTools;
using namespace CppTools::Internal;
con's avatar
con committed
75 76
using namespace CPlusPlus;

77
#ifdef QTCREATOR_WITH_DUMP_AST
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

#include <cxxabi.h>

class DumpAST: protected ASTVisitor
{
public:
    int depth;

    DumpAST(Control *control)
        : ASTVisitor(control), depth(0)
    { }

    void operator()(AST *ast)
    { accept(ast); }

protected:
    virtual bool preVisit(AST *ast)
    {
        std::ostringstream s;
        PrettyPrinter pp(control(), s);
        pp(ast);
        QString code = QString::fromStdString(s.str());
        code.replace('\n', ' ');
        code.replace(QRegExp("\\s+"), " ");

        const char *name = abi::__cxa_demangle(typeid(*ast).name(), 0, 0, 0) + 11;

        QByteArray ind(depth, ' ');
        ind += name;

        printf("%-40s %s\n", ind.constData(), qPrintable(code));
        ++depth;
        return true;
    }

    virtual void postVisit(AST *)
    { --depth; }
};

Roberto Raggi's avatar
Roberto Raggi committed
117
#endif // QTCREATOR_WITH_DUMP_AST
118

119 120 121 122 123 124 125 126 127 128 129
namespace CppTools {
namespace Internal {

static QMutex m_instanceMutex;
static CppModelManager *m_instance;

class CppModelManagerPrivate
{
public:
    // Snapshot
    mutable QMutex m_snapshotMutex;
130
    Snapshot m_snapshot;
131 132 133 134

    // Project integration
    mutable QMutex m_projectMutex;
    QMap<ProjectExplorer::Project *, ProjectInfo> m_projectToProjectsInfo;
135 136
    QMap<Utils::FileName, QList<ProjectPart::Ptr> > m_fileToProjectParts;
    QMap<QString, ProjectPart::Ptr> m_projectFileToProjectPart;
137 138 139 140 141 142 143
    // The members below are cached/(re)calculated from the projects and/or their project parts
    bool m_dirty;
    QStringList m_projectFiles;
    ProjectPart::HeaderPaths m_headerPaths;
    QByteArray m_definedMacros;

    // Editor integration
144 145
    mutable QMutex m_cppEditorDocumentsMutex;
    QMap<QString, CppEditorDocumentHandle *> m_cppEditorDocuments;
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
    QSet<AbstractEditorSupport *> m_extraEditorSupports;

    // Completion & highlighting
    QHash<QString, ModelManagerSupport *> m_idTocodeModelSupporter;
    QScopedPointer<ModelManagerSupport> m_modelManagerSupportFallback;

    // Indexing
    CppIndexingSupport *m_indexingSupporter;
    CppIndexingSupport *m_internalIndexingSupport;
    bool m_indexerEnabled;

    CppFindReferences *m_findReferences;

    bool m_enableGC;
    QTimer m_delayedGcTimer;
};

} // namespace Internal

const char pp_configuration[] =
con's avatar
con committed
166
    "# 1 \"<configuration>\"\n"
167
    "#define Q_CREATOR_RUN 1\n"
con's avatar
con committed
168 169 170 171 172 173
    "#define __cplusplus 1\n"
    "#define __extension__\n"
    "#define __context__\n"
    "#define __range__\n"
    "#define   restrict\n"
    "#define __restrict\n"
174
    "#define __restrict__\n"
con's avatar
con committed
175

176 177 178 179
    "#define __complex__\n"
    "#define __imag__\n"
    "#define __real__\n"

180 181
    "#define __builtin_va_arg(a,b) ((b)0)\n"

con's avatar
con committed
182 183
    // ### add macros for win32
    "#define __cdecl\n"
184
    "#define __stdcall\n"
185
    "#define __thiscall\n"
con's avatar
con committed
186 187 188 189 190
    "#define QT_WA(x) x\n"
    "#define CALLBACK\n"
    "#define STDMETHODCALLTYPE\n"
    "#define __RPC_FAR\n"
    "#define __declspec(a)\n"
191 192 193
    "#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method\n"
    "#define __try try\n"
    "#define __except catch\n"
194 195 196
    "#define __finally\n"
    "#define __inline inline\n"
    "#define __forceinline inline\n";
con's avatar
con committed
197

198
QSet<QString> CppModelManager::timeStampModifiedFiles(const QList<Document::Ptr> &documentsToCheck)
199
{
200
    QSet<QString> sourceFiles;
201

202
    foreach (const Document::Ptr doc, documentsToCheck) {
203 204
        const QDateTime lastModified = doc->lastModified();

205
        if (!lastModified.isNull()) {
206 207 208
            QFileInfo fileInfo(doc->fileName());

            if (fileInfo.exists() && fileInfo.lastModified() != lastModified)
209
                sourceFiles.insert(doc->fileName());
210 211 212
        }
    }

213 214 215
    return sourceFiles;
}

216 217 218 219 220 221 222 223 224 225 226 227
/*!
 * \brief createSourceProcessor Create a new source processor, which will signal the
 * model manager when a document has been processed.
 *
 * Indexed file is truncated version of fully parsed document: copy of source
 * code and full AST will be dropped when indexing is done.
 *
 * \return a new source processor object, which the caller needs to delete when finished.
 */
CppSourceProcessor *CppModelManager::createSourceProcessor()
{
    CppModelManager *that = instance();
Orgad Shaneh's avatar
Orgad Shaneh committed
228
    return new CppSourceProcessor(that->snapshot(), [that](const Document::Ptr &doc) {
229 230 231 232 233
        that->emitDocumentUpdated(doc);
        doc->releaseSourceAndAST();
    });
}

234 235 236 237 238 239 240
QString CppModelManager::editorConfigurationFileName()
{
    return QLatin1String("<per-editor-defines>");
}

QString CppModelManager::configurationFileName()
{
241
    return Preprocessor::configurationFileName;
242 243
}

244 245 246 247 248 249 250
void CppModelManager::updateModifiedSourceFiles()
{
    const Snapshot snapshot = this->snapshot();
    QList<Document::Ptr> documentsToCheck;
    foreach (const Document::Ptr document, snapshot)
        documentsToCheck << document;

251
    updateSourceFiles(timeStampModifiedFiles(documentsToCheck));
252 253
}

con's avatar
con committed
254 255
/*!
    \class CppTools::CppModelManager
256 257 258
    \brief The CppModelManager keeps tracks of the source files the code model is aware of.

    The CppModelManager manages the source files in a Snapshot object.
con's avatar
con committed
259

260 261 262 263
    The snapshot is updated in case e.g.
        * New files are opened/edited (Editor integration)
        * A project manager pushes updated project information (Project integration)
        * Files are garbage collected
con's avatar
con committed
264 265
*/

266 267
CppModelManager *CppModelManager::instance()
{
268 269 270 271 272 273 274 275
    if (m_instance)
        return m_instance;

    QMutexLocker locker(&m_instanceMutex);
    if (!m_instance)
        m_instance = new CppModelManager;

    return m_instance;
276 277
}

278
CppModelManager::CppModelManager(QObject *parent)
279
    : CppModelManagerBase(parent), d(new CppModelManagerPrivate)
con's avatar
con committed
280
{
281 282 283
    d->m_indexingSupporter = 0;
    d->m_enableGC = true;

284
    qRegisterMetaType<QSet<QString> >();
285
    connect(this, SIGNAL(sourceFilesRefreshed(QSet<QString>)),
286
            this, SLOT(onSourceFilesRefreshed()));
Erik Verbruggen's avatar
Erik Verbruggen committed
287

288 289
    d->m_findReferences = new CppFindReferences(this);
    d->m_indexerEnabled = qgetenv("QTC_NO_CODE_INDEXER") != "1";
Roberto Raggi's avatar
Roberto Raggi committed
290

291
    d->m_dirty = true;
292

293 294 295
    d->m_delayedGcTimer.setObjectName(QLatin1String("CppModelManager::m_delayedGcTimer"));
    d->m_delayedGcTimer.setSingleShot(true);
    connect(&d->m_delayedGcTimer, SIGNAL(timeout()), this, SLOT(GC()));
296

hjk's avatar
hjk committed
297 298
    QObject *sessionManager = ProjectExplorer::SessionManager::instance();
    connect(sessionManager, SIGNAL(projectAdded(ProjectExplorer::Project*)),
299
            this, SLOT(onProjectAdded(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
300
    connect(sessionManager, SIGNAL(aboutToRemoveProject(ProjectExplorer::Project*)),
Robert Loehning's avatar
Robert Loehning committed
301
            this, SLOT(onAboutToRemoveProject(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
302
    connect(sessionManager, SIGNAL(aboutToLoadSession(QString)),
303
            this, SLOT(onAboutToLoadSession()));
hjk's avatar
hjk committed
304
    connect(sessionManager, SIGNAL(aboutToUnloadSession(QString)),
305
            this, SLOT(onAboutToUnloadSession()));
306 307 308 309

    connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
            this, &CppModelManager::onCurrentEditorChanged);

310 311
    connect(Core::DocumentManager::instance(), &Core::DocumentManager::allDocumentsRenamed,
            this, &CppModelManager::renameIncludes);
con's avatar
con committed
312

313 314 315
    connect(Core::ICore::instance(), SIGNAL(coreAboutToClose()),
            this, SLOT(onCoreAboutToClose()));

con's avatar
con committed
316
    qRegisterMetaType<CPlusPlus::Document::Ptr>("CPlusPlus::Document::Ptr");
317
    qRegisterMetaType<QList<Document::DiagnosticMessage>>(
318
                "QList<CPlusPlus::Document::DiagnosticMessage>");
con's avatar
con committed
319

320
    d->m_modelManagerSupportFallback.reset(new ModelManagerSupportInternal);
321
    CppToolsPlugin::instance()->codeModelSettings()->setDefaultId(
322 323
                d->m_modelManagerSupportFallback->id());
    addModelManagerSupport(d->m_modelManagerSupportFallback.data());
324

325
    d->m_internalIndexingSupport = new BuiltinIndexingSupport;
con's avatar
con committed
326 327 328
}

CppModelManager::~CppModelManager()
329
{
330
    delete d->m_internalIndexingSupport;
331
    delete d;
332
}
con's avatar
con committed
333

334
Snapshot CppModelManager::snapshot() const
335
{
336 337
    QMutexLocker locker(&d->m_snapshotMutex);
    return d->m_snapshot;
338
}
con's avatar
con committed
339

340 341
Document::Ptr CppModelManager::document(const QString &fileName) const
{
342 343
    QMutexLocker locker(&d->m_snapshotMutex);
    return d->m_snapshot.document(fileName);
344 345 346 347 348 349 350
}

/// Replace the document in the snapshot.
///
/// \returns true if successful, false if the new document is out-dated.
bool CppModelManager::replaceDocument(Document::Ptr newDoc)
{
351
    QMutexLocker locker(&d->m_snapshotMutex);
352

353
    Document::Ptr previous = d->m_snapshot.document(newDoc->fileName());
354 355 356 357
    if (previous && (newDoc->revision() != 0 && newDoc->revision() < previous->revision()))
        // the new document is outdated
        return false;

358
    d->m_snapshot.insert(newDoc);
359 360 361
    return true;
}

362 363
void CppModelManager::ensureUpdated()
{
364 365
    QMutexLocker locker(&d->m_projectMutex);
    if (!d->m_dirty)
366 367
        return;

368 369 370 371
    d->m_projectFiles = internalProjectFiles();
    d->m_headerPaths = internalHeaderPaths();
    d->m_definedMacros = internalDefinedMacros();
    d->m_dirty = false;
372 373
}

374
QStringList CppModelManager::internalProjectFiles() const
con's avatar
con committed
375 376
{
    QStringList files;
377
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
378 379
    while (it.hasNext()) {
        it.next();
380
        const ProjectInfo pinfo = it.value();
381
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
382 383
            foreach (const ProjectFile &file, part->files)
                files += file.path;
384
        }
con's avatar
con committed
385
    }
386
    files.removeDuplicates();
con's avatar
con committed
387 388 389
    return files;
}

390
ProjectPart::HeaderPaths CppModelManager::internalHeaderPaths() const
con's avatar
con committed
391
{
392
    ProjectPart::HeaderPaths headerPaths;
393
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
394 395
    while (it.hasNext()) {
        it.next();
396
        const ProjectInfo pinfo = it.value();
397 398
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
            foreach (const ProjectPart::HeaderPath &path, part->headerPaths) {
399
                const ProjectPart::HeaderPath hp(QDir::cleanPath(path.path), path.type);
400 401 402 403
                if (!headerPaths.contains(hp))
                    headerPaths += hp;
            }
        }
con's avatar
con committed
404
    }
405
    return headerPaths;
con's avatar
con committed
406 407
}

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
static void addUnique(const QList<QByteArray> &defs, QByteArray *macros, QSet<QByteArray> *alreadyIn)
{
    Q_ASSERT(macros);
    Q_ASSERT(alreadyIn);

    foreach (const QByteArray &def, defs) {
        if (def.trimmed().isEmpty())
            continue;
        if (!alreadyIn->contains(def)) {
            macros->append(def);
            macros->append('\n');
            alreadyIn->insert(def);
        }
    }
}

424
QByteArray CppModelManager::internalDefinedMacros() const
con's avatar
con committed
425 426
{
    QByteArray macros;
427
    QSet<QByteArray> alreadyIn;
428
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
429 430
    while (it.hasNext()) {
        it.next();
431
        const ProjectInfo pinfo = it.value();
432
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
433 434
            addUnique(part->toolchainDefines.split('\n'), &macros, &alreadyIn);
            addUnique(part->projectDefines.split('\n'), &macros, &alreadyIn);
435
            if (!part->projectConfigFile.isEmpty())
436
                macros += ProjectPart::readProjectConfigFile(part);
437
        }
con's avatar
con committed
438 439 440 441
    }
    return macros;
}

442
/// This function will acquire mutexes!
443
void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId)
444
{
445 446 447 448
    const Snapshot globalSnapshot = snapshot();
    const QString globalSnapshotTitle
        = QString::fromLatin1("Global/Indexing Snapshot (%1 Documents)").arg(globalSnapshot.size());

449
    CppCodeModelInspector::Dumper dumper(globalSnapshot, logFileId);
450 451 452
    dumper.dumpProjectInfos(projectInfos());
    dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true);
    dumper.dumpWorkingCopy(workingCopy());
453
    ensureUpdated();
454
    dumper.dumpMergedEntities(d->m_headerPaths, d->m_definedMacros);
455 456
}

457
void CppModelManager::addExtraEditorSupport(AbstractEditorSupport *editorSupport)
458
{
459
    d->m_extraEditorSupports.insert(editorSupport);
460 461
}

462
void CppModelManager::removeExtraEditorSupport(AbstractEditorSupport *editorSupport)
463
{
464
    d->m_extraEditorSupports.remove(editorSupport);
465 466
}

467
CppEditorDocumentHandle *CppModelManager::cppEditorDocument(const QString &filePath) const
468
{
469 470
    if (filePath.isEmpty())
        return 0;
471

472 473
    QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
    return d->m_cppEditorDocuments.value(filePath, 0);
474 475
}

476
void CppModelManager::registerCppEditorDocument(CppEditorDocumentHandle *editorDocument)
477
{
478 479 480
    QTC_ASSERT(editorDocument, return);
    const QString filePath = editorDocument->filePath();
    QTC_ASSERT(!filePath.isEmpty(), return);
481

482 483 484
    QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
    QTC_ASSERT(d->m_cppEditorDocuments.value(filePath, 0) == 0, return);
    d->m_cppEditorDocuments.insert(filePath, editorDocument);
485
}
486

487
void CppModelManager::unregisterCppEditorDocument(const QString &filePath)
488 489
{
    QTC_ASSERT(!filePath.isEmpty(), return);
490

491 492
    static short closedCppDocuments = 0;
    int openCppDocuments = 0;
493

494
    {
495 496 497 498
        QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
        QTC_ASSERT(d->m_cppEditorDocuments.value(filePath, 0), return);
        QTC_CHECK(d->m_cppEditorDocuments.remove(filePath) == 1);
        openCppDocuments = d->m_cppEditorDocuments.size();
499 500
    }

501 502 503
    ++closedCppDocuments;
    if (openCppDocuments == 0 || closedCppDocuments == 5) {
        closedCppDocuments = 0;
504
        delayedGC();
505 506 507
    }
}

508
QList<int> CppModelManager::references(Symbol *symbol, const LookupContext &context)
509
{
510
    return d->m_findReferences->references(symbol, context);
511 512
}

513
void CppModelManager::findUsages(Symbol *symbol, const LookupContext &context)
Roberto Raggi's avatar
Roberto Raggi committed
514
{
515
    if (symbol->identifier())
516
        d->m_findReferences->findUsages(symbol, context);
517 518
}

519 520
void CppModelManager::renameUsages(Symbol *symbol,
                                   const LookupContext &context,
521
                                   const QString &replacement)
522 523
{
    if (symbol->identifier())
524
        d->m_findReferences->renameUsages(symbol, context, replacement);
Roberto Raggi's avatar
Roberto Raggi committed
525 526
}

527
void CppModelManager::findMacroUsages(const Macro &macro)
Christian Kamm's avatar
Christian Kamm committed
528
{
529
    d->m_findReferences->findMacroUses(macro);
Christian Kamm's avatar
Christian Kamm committed
530 531
}

532
void CppModelManager::renameMacroUsages(const Macro &macro, const QString &replacement)
533
{
534
    d->m_findReferences->renameMacroUses(macro, replacement);
535 536
}

537
void CppModelManager::replaceSnapshot(const Snapshot &newSnapshot)
538
{
539 540
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
    d->m_snapshot = newSnapshot;
541 542
}

543
WorkingCopy CppModelManager::buildWorkingCopyList()
con's avatar
con committed
544
{
545
    WorkingCopy workingCopy;
546

547 548 549 550 551
    foreach (const CppEditorDocumentHandle *cppEditorDocument, cppEditorDocuments()) {
        workingCopy.insert(cppEditorDocument->filePath(),
                           cppEditorDocument->contents(),
                           cppEditorDocument->revision());
    }
con's avatar
con committed
552

553
    QSetIterator<AbstractEditorSupport *> it(d->m_extraEditorSupports);
554 555
    while (it.hasNext()) {
        AbstractEditorSupport *es = it.next();
556
        workingCopy.insert(es->fileName(), es->contents(), es->revision());
557 558
    }

559
    // Add the project configuration file
560
    QByteArray conf = codeModelConfiguration();
con's avatar
con committed
561
    conf += definedMacros();
562
    workingCopy.insert(configurationFileName(), conf);
con's avatar
con committed
563 564 565 566

    return workingCopy;
}

567
WorkingCopy CppModelManager::workingCopy() const
568 569 570 571
{
    return const_cast<CppModelManager *>(this)->buildWorkingCopyList();
}

572 573 574 575 576
QByteArray CppModelManager::codeModelConfiguration() const
{
    return QByteArray::fromRawData(pp_configuration, qstrlen(pp_configuration));
}

577
QFuture<void> CppModelManager::updateSourceFiles(const QSet<QString> &sourceFiles,
578
                                                 ProgressNotificationMode mode)
579
{
580
    if (sourceFiles.isEmpty() || !d->m_indexerEnabled)
581 582
        return QFuture<void>();

583 584 585
    if (d->m_indexingSupporter)
        d->m_indexingSupporter->refreshSourceFiles(sourceFiles, mode);
    return d->m_internalIndexingSupport->refreshSourceFiles(sourceFiles, mode);
586
}
con's avatar
con committed
587

588
QList<ProjectInfo> CppModelManager::projectInfos() const
589
{
590 591
    QMutexLocker locker(&d->m_projectMutex);
    return d->m_projectToProjectsInfo.values();
592 593
}

594
ProjectInfo CppModelManager::projectInfo(ProjectExplorer::Project *project) const
595
{
596
    QMutexLocker locker(&d->m_projectMutex);
597
    return d->m_projectToProjectsInfo.value(project, ProjectInfo());
598 599
}

600 601 602 603 604 605
/// \brief Remove all files and their includes (recursively) of given ProjectInfo from the snapshot.
void CppModelManager::removeProjectInfoFilesAndIncludesFromSnapshot(const ProjectInfo &projectInfo)
{
    if (!projectInfo.isValid())
        return;

606
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
607 608
    foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
        foreach (const ProjectFile &cxxFile, projectPart->files) {
609 610 611
            foreach (const QString &fileName, d->m_snapshot.allIncludesForDocument(cxxFile.path))
                d->m_snapshot.remove(fileName);
            d->m_snapshot.remove(cxxFile.path);
612 613 614 615
        }
    }
}

616
QList<CppEditorDocumentHandle *> CppModelManager::cppEditorDocuments() const
617
{
618 619
    QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
    return d->m_cppEditorDocuments.values();
620 621
}

622 623 624
/// \brief Remove all given files from the snapshot.
void CppModelManager::removeFilesFromSnapshot(const QSet<QString> &filesToRemove)
{
625
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
626 627
    QSetIterator<QString> i(filesToRemove);
    while (i.hasNext())
628
        d->m_snapshot.remove(i.next());
629 630 631
}

class ProjectInfoComparer
632
{
633
public:
634 635
    ProjectInfoComparer(const ProjectInfo &oldProjectInfo,
                        const ProjectInfo &newProjectInfo)
636
        : m_old(oldProjectInfo)
637
        , m_oldSourceFiles(oldProjectInfo.sourceFiles())
638
        , m_new(newProjectInfo)
639
        , m_newSourceFiles(newProjectInfo.sourceFiles())
640 641
    {}

642 643 644
    bool definesChanged() const { return m_new.definesChanged(m_old); }
    bool configurationChanged() const { return m_new.configurationChanged(m_old); }
    bool configurationOrFilesChanged() const { return m_new.configurationOrFilesChanged(m_old); }
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673

    QSet<QString> addedFiles() const
    {
        QSet<QString> addedFilesSet = m_newSourceFiles;
        addedFilesSet.subtract(m_oldSourceFiles);
        return addedFilesSet;
    }

    QSet<QString> removedFiles() const
    {
        QSet<QString> removedFilesSet = m_oldSourceFiles;
        removedFilesSet.subtract(m_newSourceFiles);
        return removedFilesSet;
    }

    /// Returns a list of common files that have a changed timestamp.
    QSet<QString> timeStampModifiedFiles(const Snapshot &snapshot) const
    {
        QSet<QString> commonSourceFiles = m_newSourceFiles;
        commonSourceFiles.intersect(m_oldSourceFiles);

        QList<Document::Ptr> documentsToCheck;
        QSetIterator<QString> i(commonSourceFiles);
        while (i.hasNext()) {
            const QString file = i.next();
            if (Document::Ptr document = snapshot.document(file))
                documentsToCheck << document;
        }

674
        return CppModelManager::timeStampModifiedFiles(documentsToCheck);
675 676 677
    }

private:
678
    const ProjectInfo &m_old;
679 680
    const QSet<QString> m_oldSourceFiles;

681
    const ProjectInfo &m_new;
682 683 684
    const QSet<QString> m_newSourceFiles;
};

685 686 687
/// Make sure that m_projectMutex is locked when calling this.
void CppModelManager::recalculateFileToProjectParts()
{
688 689 690
    d->m_projectFileToProjectPart.clear();
    d->m_fileToProjectParts.clear();
    foreach (const ProjectInfo &projectInfo, d->m_projectToProjectsInfo) {
691
        foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
692
            d->m_projectFileToProjectPart[projectPart->projectFile] = projectPart;
693
            foreach (const ProjectFile &cxxFile, projectPart->files)
694 695
                d->m_fileToProjectParts[Utils::FileName::fromString(cxxFile.path)].append(
                            projectPart);
696

697 698 699 700
        }
    }
}

701
void CppModelManager::updateCppEditorDocuments() const
702
{
703 704
    // Refresh visible documents
    QSet<Core::IDocument *> visibleCppEditorDocuments;
705
    foreach (Core::IEditor *editor, Core::EditorManager::visibleEditors()) {
706
        if (Core::IDocument *document = editor->document()) {
707 708
            const QString filePath = document->filePath().toString();
            if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) {
709
                visibleCppEditorDocuments.insert(document);
710
                theCppEditorDocument->processor()->run();
711
            }
712 713 714
        }
    }

715 716 717 718 719
    // Mark invisible documents dirty
    QSet<Core::IDocument *> invisibleCppEditorDocuments
        = Core::DocumentModel::openedDocuments().toSet();
    invisibleCppEditorDocuments.subtract(visibleCppEditorDocuments);
    foreach (Core::IDocument *document, invisibleCppEditorDocuments) {
720 721 722
        const QString filePath = document->filePath().toString();
        if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath))
            theCppEditorDocument->setNeedsRefresh(true);
723 724 725
    }
}

726 727 728
QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo &newProjectInfo)
{
    if (!newProjectInfo.isValid())
729
        return QFuture<void>();
730

731
    QSet<QString> filesToReindex;
732 733
    bool filesRemoved = false;

734
    { // Only hold the mutex for a limited scope, so the dumping afterwards does not deadlock.
735
        QMutexLocker projectLocker(&d->m_projectMutex);
736 737

        ProjectExplorer::Project *project = newProjectInfo.project().data();
738
        const QSet<QString> newSourceFiles = newProjectInfo.sourceFiles();
739

740
        // Check if we can avoid a full reindexing
741
        ProjectInfo oldProjectInfo = d->m_projectToProjectsInfo.value(project);
742
        if (oldProjectInfo.isValid()) {
743
            ProjectInfoComparer comparer(oldProjectInfo, newProjectInfo);
744 745 746 747

            if (!comparer.configurationOrFilesChanged()) {
                // Some other attached data might have changed
                d->m_projectToProjectsInfo.insert(project, newProjectInfo);
748
                return QFuture<void>();
749
            }
750

751 752 753
            // If the project configuration changed, do a full reindexing
            if (comparer.configurationChanged()) {
                removeProjectInfoFilesAndIncludesFromSnapshot(oldProjectInfo);
754
                filesToReindex.unite(newSourceFiles);
755 756 757

                // The "configuration file" includes all defines and therefore should be updated
                if (comparer.definesChanged()) {
758 759
                    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
                    d->m_snapshot.remove(configurationFileName());
760
                }
761 762 763 764

            // Otherwise check for added and modified files
            } else {
                const QSet<QString> addedFiles = comparer.addedFiles();
765
                filesToReindex.unite(addedFiles);
766 767

                const QSet<QString> modifiedFiles = comparer.timeStampModifiedFiles(snapshot());
768
                filesToReindex.unite(modifiedFiles);
769
            }
770 771 772 773 774 775 776 777 778 779 780

            // Announce and purge the removed files from the snapshot
            const QSet<QString> removedFiles = comparer.removedFiles();
            if (!removedFiles.isEmpty()) {
                filesRemoved = true;
                emit aboutToRemoveFiles(removedFiles.toList());
                removeFilesFromSnapshot(removedFiles);
            }

        // A new project was opened/created, do a full indexing
        } else {
781
            filesToReindex.unite(newSourceFiles);
782 783
        }

784
        // Update Project/ProjectInfo and File/ProjectPart table
785 786
        d->m_dirty = true;
        d->m_projectToProjectsInfo.insert(project, newProjectInfo);
787
        recalculateFileToProjectParts();
788

789 790 791
    } // Mutex scope

    // If requested, dump everything we got
792
    if (DumpProjectInfo)
793
        dumpModelManagerConfiguration(QLatin1String("updateProjectInfo"));
794

795 796 797 798 799
    // Remove files from snapshot that are not reachable any more
    if (filesRemoved)
        GC();

    emit projectPartsUpdated(newProjectInfo.project().data());
800

801 802 803 804
    // Ideally, we would update all the editor documents that depend on the 'filesToReindex'.
    // However, on e.g. a session restore first the editor documents are created and then the
    // project updates come in. That is, there are no reasonable dependency tables based on
    // resolved includes that we could rely on.
805
    updateCppEditorDocuments();
806

807 808
    // Trigger reindexing
    return updateSourceFiles(filesToReindex, ForcedProgressNotification);
809 810
}

811 812
ProjectPart::Ptr CppModelManager::projectPartForProjectFile(const QString &projectFile) const
{
813
    return d->m_projectFileToProjectPart.value(projectFile);
814 815
}