cppfindreferences.cpp 29.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

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

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

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

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

52 53
#include <functional>

54
using namespace Core;
Roberto Raggi's avatar
Roberto Raggi committed
55
using namespace CppTools::Internal;
56
using namespace CppTools;
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
static QByteArray typeId(CPlusPlus::Symbol *symbol)
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
{
    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,");
100
        CPlusPlus::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
        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");
}

135
static QByteArray idForSymbol(CPlusPlus::Symbol *symbol)
136 137
{
    QByteArray uid(typeId(symbol));
138
    if (const CPlusPlus::Identifier *id = symbol->identifier()) {
139 140
        uid.append("|");
        uid.append(QByteArray(id->chars(), id->size()));
141
    } else if (CPlusPlus::Scope *scope = symbol->enclosingScope()) {
142 143 144
        // add the index of this symbol within its enclosing scope
        // (counting symbols without identifier of the same type)
        int count = 0;
145
        CPlusPlus::Scope::iterator it = scope->memberBegin();
146
        while (it != scope->memberEnd() && *it != symbol) {
147
            CPlusPlus::Symbol *val = *it;
148 149 150 151 152 153 154 155 156 157
            ++it;
            if (val->identifier() || typeId(val) != uid)
                continue;
            ++count;
        }
        uid.append(QString::number(count).toLocal8Bit());
    }
    return uid;
}

158
static QList<QByteArray> fullIdForSymbol(CPlusPlus::Symbol *symbol)
159 160
{
    QList<QByteArray> uid;
161
    CPlusPlus::Symbol *current = symbol;
162 163 164 165 166 167 168
    do {
        uid.prepend(idForSymbol(current));
        current = current->enclosingScope();
    } while (current);
    return uid;
}

169
namespace {
170

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

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

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

201 202
        if (CPlusPlus::Document::Ptr previousDoc = snapshot.document(fileName)) {
            CPlusPlus::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
        CPlusPlus::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
        CPlusPlus::Control *control = doc->control();
217
        if (control->findIdentifier(symbolId->chars(), symbolId->size()) != 0) {
218 219
            if (doc != symbolDocument)
                doc->check();
220

221
            CPlusPlus::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<CPlusPlus::Usage> &, QList<CPlusPlus::Usage>, void>
234
{
235
    QFutureInterface<CPlusPlus::Usage> *future;
236 237

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

240
    void operator()(QList<CPlusPlus::Usage> &, const QList<CPlusPlus::Usage> &usages)
241
    {
242
        foreach (const CPlusPlus::Usage &u, usages)
243 244 245 246 247 248
            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 262
QList<int> CppFindReferences::references(CPlusPlus::Symbol *symbol,
                                         const CPlusPlus::LookupContext &context) const
263 264 265
{
    QList<int> references;

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

    return references;
}

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

281
    const CPlusPlus::Snapshot snapshot = context.snapshot();
282

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

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

297
            const CPlusPlus::Control *control = i.value()->control();
298 299

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

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

309
    ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &future);
310
    UpdateUI reduce(&future);
311 312 313
    // 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();
314
    QtConcurrent::blockingMappedReduced<QList<CPlusPlus::Usage> > (files, process, reduce);
315
    QThreadPool::globalInstance()->reserveThread();
Roberto Raggi's avatar
Roberto Raggi committed
316 317 318
    future.setProgressValue(files.size());
}

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

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

    if (symbol->isClass() || symbol->isForwardClassDeclaration()) {
348
        CPlusPlus::Overview overview;
349 350 351
        parameters.prettySymbolName = overview.prettyName(context.path(symbol).last());
    }

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

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

367 368
void CppFindReferences::findAll_helper(SearchResult *search, CPlusPlus::Symbol *symbol,
                                       const CPlusPlus::LookupContext &context)
369
{
370
    if (!(symbol && symbol->identifier())) {
371
        search->finishSearch(false);
372
        return;
373
    }
374
    connect(search, &SearchResult::activated,
375 376 377
            [](const SearchResultItem& item) {
                Core::EditorManager::openEditorAtSearchResult(item);
            });
378

379
    SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
380
    const WorkingCopy workingCopy = m_modelManager->workingCopy();
381
    QFuture<CPlusPlus::Usage> result;
382 383
    result = Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper,
                             workingCopy, context, symbol);
384 385
    createWatcher(result, search);

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

389
    connect(progress, &FutureProgress::clicked, search, &SearchResult::popup);
Roberto Raggi's avatar
Roberto Raggi committed
390 391
}

392 393 394 395 396
static bool isAllLowerCase(const QString &text)
{
    return text.toLower() == text;
}

Roberto Raggi's avatar
Roberto Raggi committed
397
void CppFindReferences::onReplaceButtonClicked(const QString &text,
398
                                               const QList<SearchResultItem> &items,
399
                                               bool preserveCase)
Roberto Raggi's avatar
Roberto Raggi committed
400
{
401
    const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items, preserveCase);
402
    if (!fileNames.isEmpty()) {
403
        m_modelManager->updateSourceFiles(fileNames.toSet());
404
        SearchResultWindow::instance()->hide();
Roberto Raggi's avatar
Roberto Raggi committed
405
    }
406 407 408 409 410 411 412 413 414 415 416 417

    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;

418 419 420
    CppFileSettings settings;
    settings.fromSettings(Core::ICore::settings());

421 422
    const QStringList newPaths =
            Utils::transform<QList>(parameters.filesToRename,
423
                                    [&parameters, text, &settings](const Node *node) -> QString {
424
        const QFileInfo fi = node->filePath().toFileInfo();
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
        const QString oldSymbolName = parameters.prettySymbolName;
        const QString oldBaseName = fi.baseName();
        const QString newSymbolName = text;
        QString newBaseName = newSymbolName;

        // 1) new symbol lowercase: new base name lowercase
        if (isAllLowerCase(newSymbolName)) {
            newBaseName = newSymbolName;

        // 2) old base name mixed case: new base name is verbatim symbol name
        } else if (!isAllLowerCase(oldBaseName)) {
            newBaseName = newSymbolName;

        // 3) old base name lowercase, old symbol mixed case: new base name lowercase
        } else if (!isAllLowerCase(oldSymbolName)) {
            newBaseName = newSymbolName.toLower();

        // 4) old base name lowercase, old symbol lowercase, new symbol mixed case:
        //    use the preferences setting for new base name case
        } else if (settings.lowerCaseFiles) {
            newBaseName = newSymbolName.toLower();
446 447
        }

448 449 450 451
        if (newBaseName == oldBaseName)
            return QString();

        return fi.absolutePath() + "/" + newBaseName + '.' + fi.completeSuffix();
452 453 454 455 456 457 458 459
    });

    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
460 461
}

462 463
void CppFindReferences::searchAgain()
{
464
    SearchResult *search = qobject_cast<SearchResult *>(sender());
465
    CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
466
    parameters.filesToRename.clear();
467
    CPlusPlus::Snapshot snapshot = CppModelManager::instance()->snapshot();
Eike Ziller's avatar
Eike Ziller committed
468
    search->restart();
469 470
    CPlusPlus::LookupContext context;
    CPlusPlus::Symbol *symbol = findSymbol(parameters, snapshot, &context);
471
    if (!symbol) {
472
        search->finishSearch(false);
473 474
        return;
    }
475
    findAll_helper(search, symbol, context);
476 477 478
}

namespace {
479
class UidSymbolFinder : public CPlusPlus::SymbolVisitor
480 481
{
public:
482
    UidSymbolFinder(const QList<QByteArray> &uid) : m_uid(uid), m_index(0), m_result(0) { }
483
    CPlusPlus::Symbol *result() const { return m_result; }
484

485
    bool preVisit(CPlusPlus::Symbol *symbol)
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
    {
        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;
    }

504
    void postVisit(CPlusPlus::Symbol *symbol)
505 506 507 508 509 510
    {
        if (symbol->asScope())
            --m_index;
    }

private:
511
    QList<QByteArray> m_uid;
512
    int m_index;
513
    CPlusPlus::Symbol *m_result;
514 515 516
};
}

517 518 519
CPlusPlus::Symbol *CppFindReferences::findSymbol(const CppFindReferencesParameters &parameters,
                                                 const CPlusPlus::Snapshot &snapshot,
                                                 CPlusPlus::LookupContext *context)
520
{
521 522
    QTC_ASSERT(context, return 0);
    QString symbolFile = QLatin1String(parameters.symbolFileName);
523
    if (!snapshot.contains(symbolFile))
524
        return 0;
525

526
    CPlusPlus::Document::Ptr newSymbolDocument = snapshot.document(symbolFile);
527
    // document is not parsed and has no bindings yet, do it
528 529
    QByteArray source = getSource(Utils::FileName::fromString(newSymbolDocument->fileName()),
                                  m_modelManager->workingCopy());
530
    CPlusPlus::Document::Ptr doc =
531
            snapshot.preprocessedDocument(source, newSymbolDocument->fileName());
532 533 534
    doc->check();

    // find matching symbol in new document and return the new parameters
535
    UidSymbolFinder finder(parameters.symbolId);
536 537
    finder.accept(doc->globalNamespace());
    if (finder.result()) {
538
        *context = CPlusPlus::LookupContext(doc, snapshot);
539
        return finder.result();
540
    }
541
    return 0;
542 543
}

544
static void displayResults(SearchResult *search, QFutureWatcher<CPlusPlus::Usage> *watcher,
545
                           int first, int last)
Roberto Raggi's avatar
Roberto Raggi committed
546
{
547 548
    CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();

Roberto Raggi's avatar
Roberto Raggi committed
549
    for (int index = first; index != last; ++index) {
550
        CPlusPlus::Usage result = watcher->future().resultAt(index);
551
        search->addResult(result.path.toString(),
552 553 554 555
                          result.line,
                          result.lineText,
                          result.col,
                          result.len);
556 557 558 559 560 561 562 563 564 565 566 567 568 569

        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
570
    }
571 572 573 574

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

575
static void searchFinished(SearchResult *search, QFutureWatcher<CPlusPlus::Usage> *watcher)
576 577 578 579 580 581 582 583 584 585 586 587
{
    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) {
Jarek Kobus's avatar
Jarek Kobus committed
588
            renameCheckBox->setText(CppFindReferences::tr("Re&name %n files", nullptr, filesToRename.size()));
589 590 591 592 593 594
            renameCheckBox->setToolTip(CppFindReferences::tr("Files:\n%1").arg(filesToRename.join('\n')));
            renameCheckBox->setVisible(true);
        }
    }

    watcher->deleteLater();
Roberto Raggi's avatar
Roberto Raggi committed
595 596
}

Christian Kamm's avatar
Christian Kamm committed
597 598
namespace {

599
class FindMacroUsesInFile: public std::unary_function<QString, QList<CPlusPlus::Usage> >
Christian Kamm's avatar
Christian Kamm committed
600
{
601
    const WorkingCopy workingCopy;
602
    const CPlusPlus::Snapshot snapshot;
603
    const CPlusPlus::Macro &macro;
604
    QFutureInterface<CPlusPlus::Usage> *future;
Christian Kamm's avatar
Christian Kamm committed
605 606

public:
607
    FindMacroUsesInFile(const WorkingCopy &workingCopy,
608
                        const CPlusPlus::Snapshot snapshot,
609
                        const CPlusPlus::Macro &macro,
610
                        QFutureInterface<CPlusPlus::Usage> *future)
611
        : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future)
Christian Kamm's avatar
Christian Kamm committed
612 613
    { }

614
    QList<CPlusPlus::Usage> operator()(const Utils::FileName &fileName)
Christian Kamm's avatar
Christian Kamm committed
615
    {
616 617
        QList<CPlusPlus::Usage> usages;
        CPlusPlus::Document::Ptr doc = snapshot.document(fileName);
618
        QByteArray source;
619

620
restart_search:
621 622
        if (future->isPaused())
            future->waitForResume();
623 624
        if (future->isCanceled())
            return usages;
Christian Kamm's avatar
Christian Kamm committed
625

626
        usages.clear();
627
        foreach (const CPlusPlus::Document::MacroUse &use, doc->macroUses()) {
628
            const CPlusPlus::Macro &useMacro = use.macro();
629 630

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

634 635 636
                if (macro.fileRevision() > useMacro.fileRevision()) {
                    // yes, it is outdated, so re-preprocess and start from scratch for this file.
                    doc = snapshot.preprocessedDocument(source, fileName);
637 638
                    usages.clear();
                    goto restart_search;
639 640
                }

641
                if (macro.name() == useMacro.name()) {
642 643
                    unsigned column;
                    const QString &lineSource = matchingLine(use.bytesBegin(), source, &column);
644
                    usages.append(CPlusPlus::Usage(fileName, lineSource, use.beginLine(), column,
645
                                        useMacro.nameToQString().size()));
646
                }
Christian Kamm's avatar
Christian Kamm committed
647 648 649
            }
        }

650 651
        if (future->isPaused())
            future->waitForResume();
Christian Kamm's avatar
Christian Kamm committed
652 653 654
        return usages;
    }

655 656
    static QString matchingLine(unsigned bytesOffsetOfUseStart, const QByteArray &utf8Source,
                                unsigned *columnOfUseStart = 0)
Christian Kamm's avatar
Christian Kamm committed
657
    {
658 659
        int lineBegin = utf8Source.lastIndexOf('\n', bytesOffsetOfUseStart) + 1;
        int lineEnd = utf8Source.indexOf('\n', bytesOffsetOfUseStart);
660
        if (lineEnd == -1)
661 662 663 664 665 666 667 668 669
            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)
670
                CPlusPlus::Lexer::yyinp_utf8(currentSourceByte, yychar, *columnOfUseStart);
671
        }
Christian Kamm's avatar
Christian Kamm committed
672

673 674
        const QByteArray matchingLine = utf8Source.mid(lineBegin, lineEnd - lineBegin);
        return QString::fromUtf8(matchingLine, matchingLine.size());
Christian Kamm's avatar
Christian Kamm committed
675 676 677 678 679
    }
};

} // end of anonymous namespace

680
static void findMacroUses_helper(QFutureInterface<CPlusPlus::Usage> &future,
681
                                 const WorkingCopy workingCopy,
682
                                 const CPlusPlus::Snapshot snapshot,
683
                                 const CPlusPlus::Macro macro)
Christian Kamm's avatar
Christian Kamm committed
684
{
685
    const Utils::FileName sourceFile = Utils::FileName::fromString(macro.fileName());
686
    Utils::FileNameList files{sourceFile};
687
    files = Utils::filteredUnique(files + snapshot.filesDependingOn(sourceFile));
Christian Kamm's avatar
Christian Kamm committed
688 689

    future.setProgressRange(0, files.size());
690
    FindMacroUsesInFile process(workingCopy, snapshot, macro, &future);
Christian Kamm's avatar
Christian Kamm committed
691
    UpdateUI reduce(&future);
692 693 694
    // 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();
695
    QtConcurrent::blockingMappedReduced<QList<CPlusPlus::Usage> > (files, process, reduce);
696
    QThreadPool::globalInstance()->reserveThread();
Christian Kamm's avatar
Christian Kamm committed
697 698 699
    future.setProgressValue(files.size());
}

700
void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro)
701 702 703 704
{
    findMacroUses(macro, QString(), false);
}

705 706
void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro, const QString &replacement,
                                      bool replace)
Christian Kamm's avatar
Christian Kamm committed
707
{
708
    SearchResult *search = SearchResultWindow::instance()->startNewSearch(
709 710
                tr("C++ Macro Usages:"),
                QString(),
711
                macro.nameToQString(),
712 713 714
                replace ? SearchResultWindow::SearchAndReplace
                        : SearchResultWindow::SearchOnly,
                SearchResultWindow::PreserveCaseDisabled,
715 716 717
                QLatin1String("CppEditor"));

    search->setTextToReplace(replacement);
718 719
    connect(search, &SearchResult::replaceButtonClicked,
            this, &CppFindReferences::onReplaceButtonClicked);
Christian Kamm's avatar
Christian Kamm committed
720

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

723
    connect(search, &SearchResult::activated,
724 725 726
            [](const Core::SearchResultItem& item) {
                Core::EditorManager::openEditorAtSearchResult(item);
            });
Christian Kamm's avatar
Christian Kamm committed
727

728
    const CPlusPlus::Snapshot snapshot = m_modelManager->snapshot();
729
    const WorkingCopy workingCopy = m_modelManager->workingCopy();
Christian Kamm's avatar
Christian Kamm committed
730 731 732

    // add the macro definition itself
    {
733 734
        const QByteArray &source = getSource(Utils::FileName::fromString(macro.fileName()),
                                             workingCopy);
735 736 737 738 739
        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
740 741
    }

742
    QFuture<CPlusPlus::Usage> result;
743 744
    result = Utils::runAsync(m_modelManager->sharedThreadPool(), findMacroUses_helper,
                             workingCopy, snapshot, macro);
745
    createWatcher(result, search);
Christian Kamm's avatar
Christian Kamm committed
746

747
    FutureProgress *progress = ProgressManager::addTask(result, tr("Searching for Usages"),
748
                                                              CppTools::Constants::TASK_SEARCH);
749
    connect(progress, &FutureProgress::clicked, search, &SearchResult::popup);
Christian Kamm's avatar
Christian Kamm committed
750 751
}

752
void CppFindReferences::renameMacroUses(const CPlusPlus::Macro &macro, const QString &replacement)
753
{
754
    const QString textToReplace = replacement.isEmpty() ? macro.nameToQString() : replacement;
755 756 757
    findMacroUses(macro, textToReplace, true);
}

758
void CppFindReferences::createWatcher(const QFuture<CPlusPlus::Usage> &future, SearchResult *search)
759
{
760
    QFutureWatcher<CPlusPlus::Usage> *watcher = new QFutureWatcher<CPlusPlus::Usage>();
761
    // auto-delete:
762 763 764
    connect(watcher, &QFutureWatcherBase::finished, watcher, [search, watcher]() {
                searchFinished(search, watcher);
            });
765 766 767 768 769 770 771 772 773 774 775 776 777

    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);
    });
778
    watcher->setPendingResultsLimit(1);
Eike Ziller's avatar
Eike Ziller committed
779
    watcher->setFuture(future);
780
}