cppfindreferences.cpp 27.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Roberto Raggi's avatar
Roberto Raggi committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
Roberto Raggi's avatar
Roberto Raggi committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Roberto Raggi's avatar
Roberto Raggi 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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
Roberto Raggi's avatar
Roberto Raggi committed
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
Roberto Raggi's avatar
Roberto Raggi committed
25 26

#include "cppfindreferences.h"
27

Roberto Raggi's avatar
Roberto Raggi committed
28
#include "cpptoolsconstants.h"
29
#include "cppmodelmanager.h"
30
#include "cppworkingcopy.h"
Roberto Raggi's avatar
Roberto Raggi committed
31

32
#include <coreplugin/editormanager/editormanager.h>
33 34 35
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
36 37 38
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/session.h>
39 40
#include <texteditor/basefilefind.h>

41
#include <utils/algorithm.h>
42
#include <utils/qtcassert.h>
43 44
#include <utils/runextensions.h>
#include <utils/textfileformat.h>
Roberto Raggi's avatar
Roberto Raggi committed
45

46
#include <cplusplus/Overview.h>
47
#include <QtConcurrentMap>
48
#include <QCheckBox>
49
#include <QDir>
Roberto Raggi's avatar
Roberto Raggi committed
50

51 52
#include <functional>

53
using namespace Core;
Roberto Raggi's avatar
Roberto Raggi committed
54
using namespace CppTools::Internal;
55
using namespace CppTools;
Roberto Raggi's avatar
Roberto Raggi committed
56
using namespace CPlusPlus;
57
using namespace ProjectExplorer;
Roberto Raggi's avatar
Roberto Raggi committed
58

59
static QByteArray getSource(const Utils::FileName &fileName,
60
                            const WorkingCopy &workingCopy)
Christian Kamm's avatar
Christian Kamm committed
61 62 63 64
{
    if (workingCopy.contains(fileName)) {
        return workingCopy.source(fileName);
    } else {
65 66 67
        QString fileContents;
        Utils::TextFileFormat format;
        QString error;
68
        QTextCodec *defaultCodec = EditorManager::defaultTextCodec();
69
        Utils::TextFileFormat::ReadResult result = Utils::TextFileFormat::readFile(
70
                    fileName.toString(), defaultCodec, &fileContents, &format, &error);
71 72
        if (result != Utils::TextFileFormat::ReadSuccess)
            qWarning() << "Could not read " << fileName << ". Error: " << error;
Christian Kamm's avatar
Christian Kamm committed
73

74
        return fileContents.toUtf8();
Christian Kamm's avatar
Christian Kamm committed
75 76 77
    }
}

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
static QByteArray typeId(Symbol *symbol)
{
    if (symbol->asEnum()) {
        return QByteArray("e");
    } else if (symbol->asFunction()) {
        return QByteArray("f");
    } else if (symbol->asNamespace()) {
        return QByteArray("n");
    } else if (symbol->asTemplate()) {
        return QByteArray("t");
    } else if (symbol->asNamespaceAlias()) {
        return QByteArray("na");
    } else if (symbol->asClass()) {
        return QByteArray("c");
    } else if (symbol->asBlock()) {
        return QByteArray("b");
    } else if (symbol->asUsingNamespaceDirective()) {
        return QByteArray("u");
    } else if (symbol->asUsingDeclaration()) {
        return QByteArray("ud");
    } else if (symbol->asDeclaration()) {
        QByteArray temp("d,");
        Overview pretty;
101
        temp.append(pretty.prettyType(symbol->type()).toUtf8());
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
        return temp;
    } else if (symbol->asArgument()) {
        return QByteArray("a");
    } else if (symbol->asTypenameArgument()) {
        return QByteArray("ta");
    } else if (symbol->asBaseClass()) {
        return QByteArray("bc");
    } else if (symbol->asForwardClassDeclaration()) {
        return QByteArray("fcd");
    } else if (symbol->asQtPropertyDeclaration()) {
        return QByteArray("qpd");
    } else if (symbol->asQtEnum()) {
        return QByteArray("qe");
    } else if (symbol->asObjCBaseClass()) {
        return QByteArray("ocbc");
    } else if (symbol->asObjCBaseProtocol()) {
        return QByteArray("ocbp");
    } else if (symbol->asObjCClass()) {
        return QByteArray("occ");
    } else if (symbol->asObjCForwardClassDeclaration()) {
        return QByteArray("ocfd");
    } else if (symbol->asObjCProtocol()) {
        return QByteArray("ocp");
    } else if (symbol->asObjCForwardProtocolDeclaration()) {
        return QByteArray("ocfpd");
    } else if (symbol->asObjCMethod()) {
        return QByteArray("ocm");
    } else if (symbol->asObjCPropertyDeclaration()) {
        return QByteArray("ocpd");
    }
    return QByteArray("unknown");
}

static QByteArray idForSymbol(Symbol *symbol)
{
    QByteArray uid(typeId(symbol));
    if (const Identifier *id = symbol->identifier()) {
        uid.append("|");
        uid.append(QByteArray(id->chars(), id->size()));
    } else if (Scope *scope = symbol->enclosingScope()) {
        // add the index of this symbol within its enclosing scope
        // (counting symbols without identifier of the same type)
        int count = 0;
145 146
        Scope::iterator it = scope->memberBegin();
        while (it != scope->memberEnd() && *it != symbol) {
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
            Symbol *val = *it;
            ++it;
            if (val->identifier() || typeId(val) != uid)
                continue;
            ++count;
        }
        uid.append(QString::number(count).toLocal8Bit());
    }
    return uid;
}

static QList<QByteArray> fullIdForSymbol(Symbol *symbol)
{
    QList<QByteArray> uid;
    Symbol *current = symbol;
    do {
        uid.prepend(idForSymbol(current));
        current = current->enclosingScope();
    } while (current);
    return uid;
}

169
namespace {
170

171
class ProcessFile: public std::unary_function<QString, QList<Usage> >
172
{
173
    const WorkingCopy workingCopy;
174
    const Snapshot snapshot;
Erik Verbruggen's avatar
Erik Verbruggen committed
175
    Document::Ptr symbolDocument;
176
    Symbol *symbol;
177
    QFutureInterface<Usage> *future;
178 179

public:
180
    ProcessFile(const WorkingCopy &workingCopy,
181
                const Snapshot snapshot,
Erik Verbruggen's avatar
Erik Verbruggen committed
182
                Document::Ptr symbolDocument,
183 184 185 186 187 188 189
                Symbol *symbol,
                QFutureInterface<Usage> *future)
        : workingCopy(workingCopy),
          snapshot(snapshot),
          symbolDocument(symbolDocument),
          symbol(symbol),
          future(future)
190 191
    { }

192
    QList<Usage> operator()(const Utils::FileName &fileName)
193 194
    {
        QList<Usage> usages;
195 196
        if (future->isPaused())
            future->waitForResume();
197 198
        if (future->isCanceled())
            return usages;
199 200
        const Identifier *symbolId = symbol->identifier();

201
        if (Document::Ptr previousDoc = snapshot.document(fileName)) {
202
            Control *control = previousDoc->control();
203
            if (!control->findIdentifier(symbolId->chars(), symbolId->size()))
204 205
                return usages; // skip this document, it's not using symbolId.
        }
206
        Document::Ptr doc;
207
        const QByteArray unpreprocessedSource = getSource(fileName, workingCopy);
208

209
        if (symbolDocument && fileName == Utils::FileName::fromString(symbolDocument->fileName())) {
210
            doc = symbolDocument;
211
        } else {
212
            doc = snapshot.preprocessedDocument(unpreprocessedSource, fileName);
213 214
            doc->tokenize();
        }
215 216 217

        Control *control = doc->control();
        if (control->findIdentifier(symbolId->chars(), symbolId->size()) != 0) {
218 219
            if (doc != symbolDocument)
                doc->check();
220

221
            FindUsages process(unpreprocessedSource, doc, snapshot);
222
            process(symbol);
223

224 225 226
            usages = process.usages();
        }

227 228
        if (future->isPaused())
            future->waitForResume();
229 230 231 232
        return usages;
    }
};

233
class UpdateUI: public std::binary_function<QList<Usage> &, QList<Usage>, void>
234 235 236 237
{
    QFutureInterface<Usage> *future;

public:
238
    UpdateUI(QFutureInterface<Usage> *future): future(future) {}
239

240
    void operator()(QList<Usage> &, const QList<Usage> &usages)
241 242 243 244 245 246 247 248
    {
        foreach (const Usage &u, usages)
            future->reportResult(u);

        future->setProgressValue(future->progressValue() + 1);
    }
};

249 250
} // end of anonymous namespace

251
CppFindReferences::CppFindReferences(CppModelManager *modelManager)
252
    : QObject(modelManager),
253
      m_modelManager(modelManager)
254 255 256 257 258 259 260
{
}

CppFindReferences::~CppFindReferences()
{
}

261
QList<int> CppFindReferences::references(Symbol *symbol, const LookupContext &context) const
262 263 264
{
    QList<int> references;

265
    FindUsages findUsages(context);
266 267 268 269 270 271
    findUsages(symbol);
    references = findUsages.references();

    return references;
}

272
static void find_helper(QFutureInterface<Usage> &future,
273
                        const WorkingCopy workingCopy,
274
                        const LookupContext context,
275
                        Symbol *symbol)
Roberto Raggi's avatar
Roberto Raggi committed
276
{
Roberto Raggi's avatar
Roberto Raggi committed
277
    const Identifier *symbolId = symbol->identifier();
278
    QTC_ASSERT(symbolId != 0, return);
279

280 281
    const Snapshot snapshot = context.snapshot();

282 283
    const Utils::FileName sourceFile = Utils::FileName::fromUtf8(symbol->fileName(),
                                                                 symbol->fileNameLength());
284
    Utils::FileNameList files{sourceFile};
285

286 287 288 289 290
    if (symbol->isClass()
        || symbol->isForwardClassDeclaration()
        || (symbol->enclosingScope()
            && !symbol->isStatic()
            && symbol->enclosingScope()->isNamespace())) {
291 292 293
        const Snapshot snapshotFromContext = context.snapshot();
        for (auto i = snapshotFromContext.begin(), ei = snapshotFromContext.end(); i != ei; ++i) {
            if (i.key() == sourceFile)
294 295
                continue;

296
            const Control *control = i.value()->control();
297 298

            if (control->findIdentifier(symbolId->chars(), symbolId->size()))
299
                files.append(i.key());
300 301
        }
    } else {
302
        files += snapshot.filesDependingOn(sourceFile);
303
    }
304
    files = Utils::filteredUnique(files);
Roberto Raggi's avatar
Roberto Raggi committed
305 306 307

    future.setProgressRange(0, files.size());

308
    ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &future);
309
    UpdateUI reduce(&future);
310 311 312
    // This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count
    // so the blockingMappedReduced can use one more thread, and increase it again afterwards.
    QThreadPool::globalInstance()->releaseThread();
313
    QtConcurrent::blockingMappedReduced<QList<Usage> > (files, process, reduce);
314
    QThreadPool::globalInstance()->reserveThread();
Roberto Raggi's avatar
Roberto Raggi committed
315 316 317
    future.setProgressValue(files.size());
}

318
void CppFindReferences::findUsages(Symbol *symbol, const LookupContext &context)
319 320 321 322
{
    findUsages(symbol, context, QString(), false);
}

323 324
void CppFindReferences::findUsages(Symbol *symbol,
                                   const LookupContext &context,
325 326
                                   const QString &replacement,
                                   bool replace)
327
{
328
    Overview overview;
329
    SearchResult *search = SearchResultWindow::instance()->startNewSearch(tr("C++ Usages:"),
330
                                                QString(),
331
                                                overview.prettyName(context.fullyQualifiedName(symbol)),
332 333 334
                                                replace ? SearchResultWindow::SearchAndReplace
                                                        : SearchResultWindow::SearchOnly,
                                                SearchResultWindow::PreserveCaseDisabled,
335 336
                                                QLatin1String("CppEditor"));
    search->setTextToReplace(replacement);
337 338
    connect(search, &SearchResult::replaceButtonClicked,
            this, &CppFindReferences::onReplaceButtonClicked);
339
    search->setSearchAgainSupported(true);
340
    connect(search, &SearchResult::searchAgainRequested, this, &CppFindReferences::searchAgain);
341
    CppFindReferencesParameters parameters;
342 343
    parameters.symbolId = fullIdForSymbol(symbol);
    parameters.symbolFileName = QByteArray(symbol->fileName());
344 345 346 347 348 349

    if (symbol->isClass() || symbol->isForwardClassDeclaration()) {
        Overview overview;
        parameters.prettySymbolName = overview.prettyName(context.path(symbol).last());
    }

350
    search->setUserData(qVariantFromValue(parameters));
351
    findAll_helper(search, symbol, context);
352 353
}

354
void CppFindReferences::renameUsages(Symbol *symbol, const LookupContext &context,
355
                                     const QString &replacement)
Roberto Raggi's avatar
Roberto Raggi committed
356
{
Roberto Raggi's avatar
Roberto Raggi committed
357
    if (const Identifier *id = symbol->identifier()) {
358 359
        const QString textToReplace = replacement.isEmpty()
                ? QString::fromUtf8(id->chars(), id->size()) : replacement;
360
        findUsages(symbol, context, textToReplace, true);
361
    }
362 363
}

364 365
void CppFindReferences::findAll_helper(SearchResult *search, Symbol *symbol,
                                       const LookupContext &context)
366
{
367
    if (!(symbol && symbol->identifier())) {
368
        search->finishSearch(false);
369
        return;
370
    }
371 372
    connect(search, &SearchResult::activated,
            this, &CppFindReferences::openEditor);
373

374
    SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
375
    const WorkingCopy workingCopy = m_modelManager->workingCopy();
Roberto Raggi's avatar
Roberto Raggi committed
376
    QFuture<Usage> result;
377 378
    result = Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper,
                             workingCopy, context, symbol);
379 380
    createWatcher(result, search);

381
    FutureProgress *progress = ProgressManager::addTask(result, tr("Searching for Usages"),
382
                                                              CppTools::Constants::TASK_SEARCH);
Roberto Raggi's avatar
Roberto Raggi committed
383

384
    connect(progress, &FutureProgress::clicked, search, &SearchResult::popup);
Roberto Raggi's avatar
Roberto Raggi committed
385 386
}

Roberto Raggi's avatar
Roberto Raggi committed
387
void CppFindReferences::onReplaceButtonClicked(const QString &text,
388
                                               const QList<SearchResultItem> &items,
389
                                               bool preserveCase)
Roberto Raggi's avatar
Roberto Raggi committed
390
{
391
    const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items, preserveCase);
392
    if (!fileNames.isEmpty()) {
393
        m_modelManager->updateSourceFiles(fileNames.toSet());
394
        SearchResultWindow::instance()->hide();
Roberto Raggi's avatar
Roberto Raggi committed
395
    }
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430

    auto search = qobject_cast<SearchResult *>(sender());
    QTC_ASSERT(search, return);

    CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
    if (parameters.filesToRename.isEmpty())
        return;

    auto renameFilesCheckBox = qobject_cast<QCheckBox *>(search->additionalReplaceWidget());
    if (!renameFilesCheckBox || !renameFilesCheckBox->isChecked())
        return;

    const QStringList newPaths =
            Utils::transform<QList>(parameters.filesToRename,
                                    [&parameters, text](const Node *node) -> QString {
        const QFileInfo fi = node->filePath().toFileInfo();
        const QString fileName = fi.fileName();
        QString newName = fileName;
        newName.replace(parameters.prettySymbolName, text, Qt::CaseInsensitive);

        if (newName != fileName) {
            newName = Utils::matchCaseReplacement(fileName, newName);

            return fi.absolutePath() + "/" + newName;
        }

        return QString();
    });

    for (int i = 0; i < parameters.filesToRename.size(); ++i) {
        if (!newPaths.at(i).isEmpty()) {
            Node *node = parameters.filesToRename.at(i);
            ProjectExplorerPlugin::renameFile(node, newPaths.at(i));
        }
    }
Roberto Raggi's avatar
Roberto Raggi committed
431 432
}

433 434
void CppFindReferences::searchAgain()
{
435
    SearchResult *search = qobject_cast<SearchResult *>(sender());
436
    CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
437
    parameters.filesToRename.clear();
438
    Snapshot snapshot = CppModelManager::instance()->snapshot();
Eike Ziller's avatar
Eike Ziller committed
439
    search->restart();
440 441 442
    LookupContext context;
    Symbol *symbol = findSymbol(parameters, snapshot, &context);
    if (!symbol) {
443
        search->finishSearch(false);
444 445
        return;
    }
446
    findAll_helper(search, symbol, context);
447 448 449
}

namespace {
450
class UidSymbolFinder : public SymbolVisitor
451 452
{
public:
453
    UidSymbolFinder(const QList<QByteArray> &uid) : m_uid(uid), m_index(0), m_result(0) { }
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
    Symbol *result() const { return m_result; }

    bool preVisit(Symbol *symbol)
    {
        if (m_result)
            return false;
        int index = m_index;
        if (symbol->asScope())
            ++m_index;
        if (index >= m_uid.size())
            return false;
        if (idForSymbol(symbol) != m_uid.at(index))
            return false;
        if (index == m_uid.size() - 1) {
            // symbol found
            m_result = symbol;
            return false;
        }
        return true;
    }

    void postVisit(Symbol *symbol)
    {
        if (symbol->asScope())
            --m_index;
    }

private:
482
    QList<QByteArray> m_uid;
483 484 485 486 487
    int m_index;
    Symbol *m_result;
};
}

488
Symbol *CppFindReferences::findSymbol(const CppFindReferencesParameters &parameters,
489
                                      const Snapshot &snapshot, LookupContext *context)
490
{
491 492
    QTC_ASSERT(context, return 0);
    QString symbolFile = QLatin1String(parameters.symbolFileName);
493
    if (!snapshot.contains(symbolFile))
494
        return 0;
495 496 497

    Document::Ptr newSymbolDocument = snapshot.document(symbolFile);
    // document is not parsed and has no bindings yet, do it
498 499
    QByteArray source = getSource(Utils::FileName::fromString(newSymbolDocument->fileName()),
                                  m_modelManager->workingCopy());
500
    Document::Ptr doc =
501
            snapshot.preprocessedDocument(source, newSymbolDocument->fileName());
502 503 504
    doc->check();

    // find matching symbol in new document and return the new parameters
505
    UidSymbolFinder finder(parameters.symbolId);
506 507
    finder.accept(doc->globalNamespace());
    if (finder.result()) {
508 509
        *context = LookupContext(doc, snapshot);
        return finder.result();
510
    }
511
    return 0;
512 513
}

514 515
static void displayResults(SearchResult *search, QFutureWatcher<Usage> *watcher,
                           int first, int last)
Roberto Raggi's avatar
Roberto Raggi committed
516
{
517 518
    CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();

Roberto Raggi's avatar
Roberto Raggi committed
519
    for (int index = first; index != last; ++index) {
520
        Usage result = watcher->future().resultAt(index);
521
        search->addResult(result.path.toString(),
522 523 524 525
                          result.line,
                          result.lineText,
                          result.col,
                          result.len);
526 527 528 529 530 531 532 533 534 535 536 537 538 539

        if (parameters.prettySymbolName.isEmpty())
            continue;

        if (Utils::contains(parameters.filesToRename, Utils::equal(&Node::filePath, result.path)))
            continue;

        Node *node = SessionManager::nodeForFile(result.path);
        if (!node) // Not part of any project
            continue;

        const QFileInfo fi = node->filePath().toFileInfo();
        if (fi.baseName().compare(parameters.prettySymbolName, Qt::CaseInsensitive) == 0)
            parameters.filesToRename.append(node);
Roberto Raggi's avatar
Roberto Raggi committed
540
    }
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557

    search->setUserData(qVariantFromValue(parameters));
}

static void searchFinished(SearchResult *search, QFutureWatcher<Usage> *watcher)
{
    search->finishSearch(watcher->isCanceled());

    CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
    if (!parameters.filesToRename.isEmpty()) {
        const QStringList filesToRename
                = Utils::transform<QList>(parameters.filesToRename, [](const Node *node) {
            return node->filePath().toUserOutput();
        });

        auto renameCheckBox = qobject_cast<QCheckBox *>(search->additionalReplaceWidget());
        if (renameCheckBox) {
558
            renameCheckBox->setText(CppFindReferences::tr("Re&name %1 files").arg(filesToRename.size()));
559 560 561 562 563 564
            renameCheckBox->setToolTip(CppFindReferences::tr("Files:\n%1").arg(filesToRename.join('\n')));
            renameCheckBox->setVisible(true);
        }
    }

    watcher->deleteLater();
Roberto Raggi's avatar
Roberto Raggi committed
565 566
}

567
void CppFindReferences::openEditor(const SearchResultItem &item)
Roberto Raggi's avatar
Roberto Raggi committed
568
{
569
    if (item.path.size() > 0) {
570
        EditorManager::openEditorAt(QDir::fromNativeSeparators(item.path.first()),
571 572
                                    item.mainRange.begin.line,
                                    item.mainRange.begin.column);
573
    } else {
574
        EditorManager::openEditor(QDir::fromNativeSeparators(item.text));
575
    }
Roberto Raggi's avatar
Roberto Raggi committed
576 577
}

Christian Kamm's avatar
Christian Kamm committed
578 579 580 581 582

namespace {

class FindMacroUsesInFile: public std::unary_function<QString, QList<Usage> >
{
583
    const WorkingCopy workingCopy;
Christian Kamm's avatar
Christian Kamm committed
584 585
    const Snapshot snapshot;
    const Macro &macro;
586
    QFutureInterface<Usage> *future;
Christian Kamm's avatar
Christian Kamm committed
587 588

public:
589
    FindMacroUsesInFile(const WorkingCopy &workingCopy,
Christian Kamm's avatar
Christian Kamm committed
590
                        const Snapshot snapshot,
591 592 593
                        const Macro &macro,
                        QFutureInterface<Usage> *future)
        : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future)
Christian Kamm's avatar
Christian Kamm committed
594 595
    { }

596
    QList<Usage> operator()(const Utils::FileName &fileName)
Christian Kamm's avatar
Christian Kamm committed
597 598
    {
        QList<Usage> usages;
599
        Document::Ptr doc = snapshot.document(fileName);
600
        QByteArray source;
601

602
restart_search:
603 604
        if (future->isPaused())
            future->waitForResume();
605 606
        if (future->isCanceled())
            return usages;
Christian Kamm's avatar
Christian Kamm committed
607

608
        usages.clear();
Christian Kamm's avatar
Christian Kamm committed
609 610
        foreach (const Document::MacroUse &use, doc->macroUses()) {
            const Macro &useMacro = use.macro();
611 612

            if (useMacro.fileName() == macro.fileName()) { // Check if this is a match, but possibly against an outdated document.
613 614 615
                if (source.isEmpty())
                    source = getSource(fileName, workingCopy);

616 617 618
                if (macro.fileRevision() > useMacro.fileRevision()) {
                    // yes, it is outdated, so re-preprocess and start from scratch for this file.
                    doc = snapshot.preprocessedDocument(source, fileName);
619 620
                    usages.clear();
                    goto restart_search;
621 622
                }

623
                if (macro.name() == useMacro.name()) {
624 625
                    unsigned column;
                    const QString &lineSource = matchingLine(use.bytesBegin(), source, &column);
626
                    usages.append(Usage(fileName, lineSource, use.beginLine(), column,
627
                                        useMacro.nameToQString().size()));
628
                }
Christian Kamm's avatar
Christian Kamm committed
629 630 631
            }
        }

632 633
        if (future->isPaused())
            future->waitForResume();
Christian Kamm's avatar
Christian Kamm committed
634 635 636
        return usages;
    }

637 638
    static QString matchingLine(unsigned bytesOffsetOfUseStart, const QByteArray &utf8Source,
                                unsigned *columnOfUseStart = 0)
Christian Kamm's avatar
Christian Kamm committed
639
    {
640 641
        int lineBegin = utf8Source.lastIndexOf('\n', bytesOffsetOfUseStart) + 1;
        int lineEnd = utf8Source.indexOf('\n', bytesOffsetOfUseStart);
642
        if (lineEnd == -1)
643 644 645 646 647 648 649 650 651 652 653
            lineEnd = utf8Source.length();

        if (columnOfUseStart) {
            *columnOfUseStart = 0;
            const char *startOfUse = utf8Source.constData() + bytesOffsetOfUseStart;
            QTC_ASSERT(startOfUse < utf8Source.constData() + lineEnd, return QString());
            const char *currentSourceByte = utf8Source.constData() + lineBegin;
            unsigned char yychar = *currentSourceByte;
            while (currentSourceByte != startOfUse)
                Lexer::yyinp_utf8(currentSourceByte, yychar, *columnOfUseStart);
        }
Christian Kamm's avatar
Christian Kamm committed
654

655 656
        const QByteArray matchingLine = utf8Source.mid(lineBegin, lineEnd - lineBegin);
        return QString::fromUtf8(matchingLine, matchingLine.size());
Christian Kamm's avatar
Christian Kamm committed
657 658 659 660 661 662
    }
};

} // end of anonymous namespace

static void findMacroUses_helper(QFutureInterface<Usage> &future,
663
                                 const WorkingCopy workingCopy,
664 665
                                 const Snapshot snapshot,
                                 const Macro macro)
Christian Kamm's avatar
Christian Kamm committed
666
{
667
    const Utils::FileName sourceFile = Utils::FileName::fromString(macro.fileName());
668
    Utils::FileNameList files{sourceFile};
669
    files = Utils::filteredUnique(files + snapshot.filesDependingOn(sourceFile));
Christian Kamm's avatar
Christian Kamm committed
670 671

    future.setProgressRange(0, files.size());
672
    FindMacroUsesInFile process(workingCopy, snapshot, macro, &future);
Christian Kamm's avatar
Christian Kamm committed
673
    UpdateUI reduce(&future);
674 675 676
    // This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count
    // so the blockingMappedReduced can use one more thread, and increase it again afterwards.
    QThreadPool::globalInstance()->releaseThread();
Christian Kamm's avatar
Christian Kamm committed
677
    QtConcurrent::blockingMappedReduced<QList<Usage> > (files, process, reduce);
678
    QThreadPool::globalInstance()->reserveThread();
Christian Kamm's avatar
Christian Kamm committed
679 680 681 682
    future.setProgressValue(files.size());
}

void CppFindReferences::findMacroUses(const Macro &macro)
683 684 685 686 687
{
    findMacroUses(macro, QString(), false);
}

void CppFindReferences::findMacroUses(const Macro &macro, const QString &replacement, bool replace)
Christian Kamm's avatar
Christian Kamm committed
688
{
689
    SearchResult *search = SearchResultWindow::instance()->startNewSearch(
690 691
                tr("C++ Macro Usages:"),
                QString(),
692
                macro.nameToQString(),
693 694 695
                replace ? SearchResultWindow::SearchAndReplace
                        : SearchResultWindow::SearchOnly,
                SearchResultWindow::PreserveCaseDisabled,
696 697 698
                QLatin1String("CppEditor"));

    search->setTextToReplace(replacement);
699 700
    connect(search, &SearchResult::replaceButtonClicked,
            this, &CppFindReferences::onReplaceButtonClicked);
Christian Kamm's avatar
Christian Kamm committed
701

702
    SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
Christian Kamm's avatar
Christian Kamm committed
703

704 705
    connect(search, &SearchResult::activated,
            this, &CppFindReferences::openEditor);
Christian Kamm's avatar
Christian Kamm committed
706

707
    const Snapshot snapshot = m_modelManager->snapshot();
708
    const WorkingCopy workingCopy = m_modelManager->workingCopy();
Christian Kamm's avatar
Christian Kamm committed
709 710 711

    // add the macro definition itself
    {
712 713
        const QByteArray &source = getSource(Utils::FileName::fromString(macro.fileName()),
                                             workingCopy);
714 715 716 717 718
        unsigned column;
        const QString line = FindMacroUsesInFile::matchingLine(macro.bytesOffset(), source,
                                                               &column);
        search->addResult(macro.fileName(), macro.line(), line, column,
                          macro.nameToQString().length());
Christian Kamm's avatar
Christian Kamm committed
719 720 721
    }

    QFuture<Usage> result;
722 723
    result = Utils::runAsync(m_modelManager->sharedThreadPool(), findMacroUses_helper,
                             workingCopy, snapshot, macro);
724
    createWatcher(result, search);
Christian Kamm's avatar
Christian Kamm committed
725

726
    FutureProgress *progress = ProgressManager::addTask(result, tr("Searching for Usages"),
727
                                                              CppTools::Constants::TASK_SEARCH);
728
    connect(progress, &FutureProgress::clicked, search, &SearchResult::popup);
Christian Kamm's avatar
Christian Kamm committed
729 730
}

731 732
void CppFindReferences::renameMacroUses(const Macro &macro, const QString &replacement)
{
733
    const QString textToReplace = replacement.isEmpty() ? macro.nameToQString() : replacement;
734 735 736
    findMacroUses(macro, textToReplace, true);
}

737
void CppFindReferences::createWatcher(const QFuture<Usage> &future, SearchResult *search)
738 739
{
    QFutureWatcher<Usage> *watcher = new QFutureWatcher<Usage>();
740
    // auto-delete:
741 742 743
    connect(watcher, &QFutureWatcherBase::finished, watcher, [search, watcher]() {
                searchFinished(search, watcher);
            });
744 745 746 747 748 749 750 751 752 753 754 755 756

    connect(watcher, &QFutureWatcherBase::resultsReadyAt, search,
            [search, watcher](int first, int last) {
                displayResults(search, watcher, first, last);
            });
    connect(watcher, &QFutureWatcherBase::finished, search, [search, watcher]() {
        search->finishSearch(watcher->isCanceled());
    });
    connect(search, &SearchResult::cancelled, watcher, [watcher]() { watcher->cancel(); });
    connect(search, &SearchResult::paused, watcher, [watcher](bool paused) {
        if (!paused || watcher->isRunning()) // guard against pausing when the search is finished
            watcher->setPaused(paused);
    });
757
    watcher->setPendingResultsLimit(1);
Eike Ziller's avatar
Eike Ziller committed
758
    watcher->setFuture(future);
759
}