cppfindreferences.cpp 28.4 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;
Roberto Raggi's avatar
Roberto Raggi committed
57
using namespace CPlusPlus;
58
using namespace ProjectExplorer;
Roberto Raggi's avatar
Roberto Raggi committed
59

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

75
        return fileContents.toUtf8();
Christian Kamm's avatar
Christian Kamm committed
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 101
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;
102
        temp.append(pretty.prettyType(symbol->type()).toUtf8());
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 145
        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;
146 147
        Scope::iterator it = scope->memberBegin();
        while (it != scope->memberEnd() && *it != symbol) {
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
            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;
}

170
namespace {
171

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

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

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

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

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

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

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

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

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

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

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

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

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

250 251
} // end of anonymous namespace

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

CppFindReferences::~CppFindReferences()
{
}

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

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

    return references;
}

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

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

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 293 294
        const Snapshot snapshotFromContext = context.snapshot();
        for (auto i = snapshotFromContext.begin(), ei = snapshotFromContext.end(); i != ei; ++i) {
            if (i.key() == sourceFile)
295 296
                continue;

297
            const 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<Usage> > (files, process, reduce);
315
    QThreadPool::globalInstance()->reserveThread();
Roberto Raggi's avatar
Roberto Raggi committed
316 317 318
    future.setProgressValue(files.size());
}

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

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

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

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

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

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

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

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

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

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

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

    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;

416 417 418
    CppFileSettings settings;
    settings.fromSettings(Core::ICore::settings());

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

446 447 448 449
        if (newBaseName == oldBaseName)
            return QString();

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

    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
458 459
}

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

namespace {
477
class UidSymbolFinder : public SymbolVisitor
478 479
{
public:
480
    UidSymbolFinder(const QList<QByteArray> &uid) : m_uid(uid), m_index(0), m_result(0) { }
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
    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:
509
    QList<QByteArray> m_uid;
510 511 512 513 514
    int m_index;
    Symbol *m_result;
};
}

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

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

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

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

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

        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
567
    }
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584

    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) {
Jarek Kobus's avatar
Jarek Kobus committed
585
            renameCheckBox->setText(CppFindReferences::tr("Re&name %n files", nullptr, filesToRename.size()));
586 587 588 589 590 591
            renameCheckBox->setToolTip(CppFindReferences::tr("Files:\n%1").arg(filesToRename.join('\n')));
            renameCheckBox->setVisible(true);
        }
    }

    watcher->deleteLater();
Roberto Raggi's avatar
Roberto Raggi committed
592 593
}

Christian Kamm's avatar
Christian Kamm committed
594 595 596 597
namespace {

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

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

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

617
restart_search:
618 619
        if (future->isPaused())
            future->waitForResume();
620 621
        if (future->isCanceled())
            return usages;
Christian Kamm's avatar
Christian Kamm committed
622

623
        usages.clear();
Christian Kamm's avatar
Christian Kamm committed
624
        foreach (const Document::MacroUse &use, doc->macroUses()) {
625
            const CPlusPlus::Macro &useMacro = use.macro();
626 627

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

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

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

647 648
        if (future->isPaused())
            future->waitForResume();
Christian Kamm's avatar
Christian Kamm committed
649 650 651
        return usages;
    }

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

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

} // end of anonymous namespace

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

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

697
void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro)
698 699 700 701
{
    findMacroUses(macro, QString(), false);
}

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

    search->setTextToReplace(replacement);
715 716
    connect(search, &SearchResult::replaceButtonClicked,
            this, &CppFindReferences::onReplaceButtonClicked);
Christian Kamm's avatar
Christian Kamm committed
717

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

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

725
    const Snapshot snapshot = m_modelManager->snapshot();
726
    const WorkingCopy workingCopy = m_modelManager->workingCopy();
Christian Kamm's avatar
Christian Kamm committed
727 728 729

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

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

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

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

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

    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);
    });
775
    watcher->setPendingResultsLimit(1);
Eike Ziller's avatar
Eike Ziller committed
776
    watcher->setFuture(future);
777
}