cppfindreferences.cpp 28.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;
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 373
    connect(search, &SearchResult::activated,
            this, &CppFindReferences::openEditor);
374

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

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

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

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

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

    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;

414 415 416
    CppFileSettings settings;
    settings.fromSettings(Core::ICore::settings());

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

444 445 446 447
        if (newBaseName == oldBaseName)
            return QString();

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

    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
456 457
}

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

namespace {
475
class UidSymbolFinder : public SymbolVisitor
476 477
{
public:
478
    UidSymbolFinder(const QList<QByteArray> &uid) : m_uid(uid), m_index(0), m_result(0) { }
479 480 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
    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:
507
    QList<QByteArray> m_uid;
508 509 510 511 512
    int m_index;
    Symbol *m_result;
};
}

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

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

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

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

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

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

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

    watcher->deleteLater();
Roberto Raggi's avatar
Roberto Raggi committed
590 591
}

592
void CppFindReferences::openEditor(const SearchResultItem &item)
Roberto Raggi's avatar
Roberto Raggi committed
593
{
594
    if (item.path.size() > 0) {
595
        EditorManager::openEditorAt(QDir::fromNativeSeparators(item.path.first()),
596 597
                                    item.mainRange.begin.line,
                                    item.mainRange.begin.column);
598
    } else {
599
        EditorManager::openEditor(QDir::fromNativeSeparators(item.text));
600
    }
Roberto Raggi's avatar
Roberto Raggi committed
601 602
}

Christian Kamm's avatar
Christian Kamm committed
603 604 605 606 607

namespace {

class FindMacroUsesInFile: public std::unary_function<QString, QList<Usage> >
{
608
    const WorkingCopy workingCopy;
Christian Kamm's avatar
Christian Kamm committed
609 610
    const Snapshot snapshot;
    const Macro &macro;
611
    QFutureInterface<Usage> *future;
Christian Kamm's avatar
Christian Kamm committed
612 613

public:
614
    FindMacroUsesInFile(const WorkingCopy &workingCopy,
Christian Kamm's avatar
Christian Kamm committed
615
                        const Snapshot snapshot,
616 617 618
                        const Macro &macro,
                        QFutureInterface<Usage> *future)
        : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future)
Christian Kamm's avatar
Christian Kamm committed
619 620
    { }

621
    QList<Usage> operator()(const Utils::FileName &fileName)
Christian Kamm's avatar
Christian Kamm committed
622 623
    {
        QList<Usage> usages;
624
        Document::Ptr doc = snapshot.document(fileName);
625
        QByteArray source;
626

627
restart_search:
628 629
        if (future->isPaused())
            future->waitForResume();
630 631
        if (future->isCanceled())
            return usages;
Christian Kamm's avatar
Christian Kamm committed
632

633
        usages.clear();
Christian Kamm's avatar
Christian Kamm committed
634 635
        foreach (const Document::MacroUse &use, doc->macroUses()) {
            const Macro &useMacro = use.macro();
636 637

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

641 642 643
                if (macro.fileRevision() > useMacro.fileRevision()) {
                    // yes, it is outdated, so re-preprocess and start from scratch for this file.
                    doc = snapshot.preprocessedDocument(source, fileName);
644 645
                    usages.clear();
                    goto restart_search;
646 647
                }

648
                if (macro.name() == useMacro.name()) {
649 650
                    unsigned column;
                    const QString &lineSource = matchingLine(use.bytesBegin(), source, &column);
651
                    usages.append(Usage(fileName, lineSource, use.beginLine(), column,
652
                                        useMacro.nameToQString().size()));
653
                }
Christian Kamm's avatar
Christian Kamm committed
654 655 656
            }
        }

657 658
        if (future->isPaused())
            future->waitForResume();
Christian Kamm's avatar
Christian Kamm committed
659 660 661
        return usages;
    }

662 663
    static QString matchingLine(unsigned bytesOffsetOfUseStart, const QByteArray &utf8Source,
                                unsigned *columnOfUseStart = 0)
Christian Kamm's avatar
Christian Kamm committed
664
    {
665 666
        int lineBegin = utf8Source.lastIndexOf('\n', bytesOffsetOfUseStart) + 1;
        int lineEnd = utf8Source.indexOf('\n', bytesOffsetOfUseStart);
667
        if (lineEnd == -1)
668 669 670 671 672 673 674 675 676 677 678
            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
679

680 681
        const QByteArray matchingLine = utf8Source.mid(lineBegin, lineEnd - lineBegin);
        return QString::fromUtf8(matchingLine, matchingLine.size());
Christian Kamm's avatar
Christian Kamm committed
682 683 684 685 686 687
    }
};

} // end of anonymous namespace

static void findMacroUses_helper(QFutureInterface<Usage> &future,
688
                                 const WorkingCopy workingCopy,
689 690
                                 const Snapshot snapshot,
                                 const Macro macro)
Christian Kamm's avatar
Christian Kamm committed
691
{
692
    const Utils::FileName sourceFile = Utils::FileName::fromString(macro.fileName());
693
    Utils::FileNameList files{sourceFile};
694
    files = Utils::filteredUnique(files + snapshot.filesDependingOn(sourceFile));
Christian Kamm's avatar
Christian Kamm committed
695 696

    future.setProgressRange(0, files.size());
697
    FindMacroUsesInFile process(workingCopy, snapshot, macro, &future);
Christian Kamm's avatar
Christian Kamm committed
698
    UpdateUI reduce(&future);
699 700 701
    // 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
702
    QtConcurrent::blockingMappedReduced<QList<Usage> > (files, process, reduce);
703
    QThreadPool::globalInstance()->reserveThread();
Christian Kamm's avatar
Christian Kamm committed
704 705 706 707
    future.setProgressValue(files.size());
}

void CppFindReferences::findMacroUses(const Macro &macro)
708 709 710 711 712
{
    findMacroUses(macro, QString(), false);
}

void CppFindReferences::findMacroUses(const Macro &macro, const QString &replacement, bool replace)
Christian Kamm's avatar
Christian Kamm committed
713
{
714
    SearchResult *search = SearchResultWindow::instance()->startNewSearch(
715 716
                tr("C++ Macro Usages:"),
                QString(),
717
                macro.nameToQString(),
718 719 720
                replace ? SearchResultWindow::SearchAndReplace
                        : SearchResultWindow::SearchOnly,
                SearchResultWindow::PreserveCaseDisabled,
721 722 723
                QLatin1String("CppEditor"));

    search->setTextToReplace(replacement);
724 725
    connect(search, &SearchResult::replaceButtonClicked,
            this, &CppFindReferences::onReplaceButtonClicked);
Christian Kamm's avatar
Christian Kamm committed
726

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

729 730
    connect(search, &SearchResult::activated,
            this, &CppFindReferences::openEditor);
Christian Kamm's avatar
Christian Kamm committed
731

732
    const Snapshot snapshot = m_modelManager->snapshot();
733
    const WorkingCopy workingCopy = m_modelManager->workingCopy();
Christian Kamm's avatar
Christian Kamm committed
734 735 736

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

    QFuture<Usage> result;
747 748
    result = Utils::runAsync(m_modelManager->sharedThreadPool(), findMacroUses_helper,
                             workingCopy, snapshot, macro);
749
    createWatcher(result, search);
Christian Kamm's avatar
Christian Kamm committed
750

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

756 757
void CppFindReferences::renameMacroUses(const Macro &macro, const QString &replacement)
{
758
    const QString textToReplace = replacement.isEmpty() ? macro.nameToQString() : replacement;
759 760 761
    findMacroUses(macro, textToReplace, true);
}

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

    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);
    });
782
    watcher->setPendingResultsLimit(1);
Eike Ziller's avatar
Eike Ziller committed
783
    watcher->setFuture(future);
784
}