cppfindreferences.cpp 18.1 KB
Newer Older
Roberto Raggi's avatar
Roberto Raggi committed
1 2 3 4
/**************************************************************************
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
Roberto Raggi's avatar
Roberto Raggi committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
Roberto Raggi's avatar
Roberto Raggi committed
8 9 10 11
**
**
** GNU Lesser General Public License Usage
**
hjk's avatar
hjk committed
12 13 14 15 16 17
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
Roberto Raggi's avatar
Roberto Raggi committed
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21 22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23 24 25 26 27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
Roberto Raggi's avatar
Roberto Raggi committed
30 31 32 33 34 35 36
**
**************************************************************************/

#include "cppfindreferences.h"
#include "cpptoolsconstants.h"

#include <texteditor/basetexteditor.h>
37
#include <texteditor/basefilefind.h>
Roberto Raggi's avatar
Roberto Raggi committed
38 39 40
#include <find/searchresultwindow.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/filesearch.h>
41
#include <utils/fileutils.h>
42
#include <utils/qtcassert.h>
Roberto Raggi's avatar
Roberto Raggi committed
43
#include <coreplugin/progressmanager/progressmanager.h>
44
#include <coreplugin/progressmanager/futureprogress.h>
45
#include <coreplugin/editormanager/editormanager.h>
Roberto Raggi's avatar
Roberto Raggi committed
46
#include <coreplugin/icore.h>
47
#include <coreplugin/infobar.h>
Roberto Raggi's avatar
Roberto Raggi committed
48 49 50 51 52 53 54

#include <ASTVisitor.h>
#include <AST.h>
#include <Control.h>
#include <Literals.h>
#include <TranslationUnit.h>
#include <Symbols.h>
55 56
#include <Names.h>
#include <Scope.h>
Roberto Raggi's avatar
Roberto Raggi committed
57

58
#include <cplusplus/ModelManagerInterface.h>
Roberto Raggi's avatar
Roberto Raggi committed
59
#include <cplusplus/CppDocument.h>
60
#include <cplusplus/Overview.h>
61
#include <cplusplus/FindUsages.h>
Roberto Raggi's avatar
Roberto Raggi committed
62 63

#include <QtCore/QTime>
64
#include <QtCore/QTimer>
Roberto Raggi's avatar
Roberto Raggi committed
65
#include <QtCore/QtConcurrentRun>
66
#include <QtCore/QtConcurrentMap>
Roberto Raggi's avatar
Roberto Raggi committed
67
#include <QtCore/QDir>
68
#include <QtGui/QApplication>
Roberto Raggi's avatar
Roberto Raggi committed
69 70
#include <qtconcurrent/runextensions.h>

71 72
#include <functional>

Roberto Raggi's avatar
Roberto Raggi committed
73 74 75
using namespace CppTools::Internal;
using namespace CPlusPlus;

Christian Kamm's avatar
Christian Kamm committed
76
static QString getSource(const QString &fileName,
77
                         const CppModelManagerInterface::WorkingCopy &workingCopy)
Christian Kamm's avatar
Christian Kamm committed
78 79 80 81
{
    if (workingCopy.contains(fileName)) {
        return workingCopy.source(fileName);
    } else {
82
        Utils::FileReader reader;
83
        if (!reader.fetch(fileName, QFile::Text)) // ### FIXME error reporting
Christian Kamm's avatar
Christian Kamm committed
84 85
            return QString();

86
        return QString::fromLocal8Bit(reader.data()); // ### FIXME encoding
Christian Kamm's avatar
Christian Kamm committed
87 88 89
    }
}

Roberto Raggi's avatar
Roberto Raggi committed
90
namespace {
91

Roberto Raggi's avatar
Roberto Raggi committed
92
class ProcessFile: public std::unary_function<QString, QList<Usage> >
93
{
94
    const CppModelManagerInterface::WorkingCopy workingCopy;
95
    const Snapshot snapshot;
Erik Verbruggen's avatar
Erik Verbruggen committed
96
    Document::Ptr symbolDocument;
97
    Symbol *symbol;
98
    QFutureInterface<Usage> *future;
99 100

public:
101
    ProcessFile(const CppModelManagerInterface::WorkingCopy &workingCopy,
102
                const Snapshot snapshot,
Erik Verbruggen's avatar
Erik Verbruggen committed
103
                Document::Ptr symbolDocument,
104 105 106 107 108 109 110
                Symbol *symbol,
                QFutureInterface<Usage> *future)
        : workingCopy(workingCopy),
          snapshot(snapshot),
          symbolDocument(symbolDocument),
          symbol(symbol),
          future(future)
111 112 113 114 115
    { }

    QList<Usage> operator()(const QString &fileName)
    {
        QList<Usage> usages;
116 117
        if (future->isCanceled())
            return usages;
118 119
        const Identifier *symbolId = symbol->identifier();

120
        if (Document::Ptr previousDoc = snapshot.document(fileName)) {
121 122 123 124 125
            Control *control = previousDoc->control();
            if (! control->findIdentifier(symbolId->chars(), symbolId->size()))
                return usages; // skip this document, it's not using symbolId.
        }

126 127
        Document::Ptr doc;
        QByteArray source;
128
        const QString unpreprocessedSource = getSource(fileName, workingCopy);
129

130 131 132
        if (symbolDocument && fileName == symbolDocument->fileName())
            doc = symbolDocument;
        else {
133
            source = snapshot.preprocessedCode(unpreprocessedSource, fileName);
134 135 136
            doc = snapshot.documentFromSource(source, fileName);
            doc->tokenize();
        }
137 138 139

        Control *control = doc->control();
        if (control->findIdentifier(symbolId->chars(), symbolId->size()) != 0) {
140 141
            if (doc != symbolDocument)
                doc->check();
142

143
            FindUsages process(unpreprocessedSource.toUtf8(), doc, snapshot);
144
            process(symbol);
145

146 147 148 149 150 151 152
            usages = process.usages();
        }

        return usages;
    }
};

Roberto Raggi's avatar
Roberto Raggi committed
153
class UpdateUI: public std::binary_function<QList<Usage> &, QList<Usage>, void>
154 155 156 157
{
    QFutureInterface<Usage> *future;

public:
Roberto Raggi's avatar
Roberto Raggi committed
158
    UpdateUI(QFutureInterface<Usage> *future): future(future) {}
159

160
    void operator()(QList<Usage> &, const QList<Usage> &usages)
161 162 163 164 165 166 167 168
    {
        foreach (const Usage &u, usages)
            future->reportResult(u);

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

Roberto Raggi's avatar
Roberto Raggi committed
169 170
} // end of anonymous namespace

171
CppFindReferences::CppFindReferences(CppModelManagerInterface *modelManager)
Roberto Raggi's avatar
Roberto Raggi committed
172
    : QObject(modelManager),
173
      _modelManager(modelManager)
Roberto Raggi's avatar
Roberto Raggi committed
174 175 176 177 178 179 180
{
}

CppFindReferences::~CppFindReferences()
{
}

181
QList<int> CppFindReferences::references(Symbol *symbol, const LookupContext &context) const
Roberto Raggi's avatar
Roberto Raggi committed
182 183 184
{
    QList<int> references;

185
    FindUsages findUsages(context);
Roberto Raggi's avatar
Roberto Raggi committed
186 187 188 189 190 191
    findUsages(symbol);
    references = findUsages.references();

    return references;
}

192
static void find_helper(QFutureInterface<Usage> &future,
193
                        const CppModelManagerInterface::WorkingCopy workingCopy,
194
                        const LookupContext context,
195
                        CppFindReferences *findRefs,
196
                        Symbol *symbol)
Roberto Raggi's avatar
Roberto Raggi committed
197
{
Roberto Raggi's avatar
Roberto Raggi committed
198
    const Identifier *symbolId = symbol->identifier();
199 200
    Q_ASSERT(symbolId != 0);

201 202
    const Snapshot snapshot = context.snapshot();

Roberto Raggi's avatar
Cleanup  
Roberto Raggi committed
203 204
    const QString sourceFile = QString::fromUtf8(symbol->fileName(), symbol->fileNameLength());
    QStringList files(sourceFile);
205

206 207
    if (symbol->isClass() || symbol->isForwardClassDeclaration() || (symbol->enclosingScope() && ! symbol->isStatic() &&
                                                                     symbol->enclosingScope()->isNamespace())) {
208
        foreach (const Document::Ptr &doc, context.snapshot()) {
209 210 211 212 213 214 215 216 217
            if (doc->fileName() == sourceFile)
                continue;

            Control *control = doc->control();

            if (control->findIdentifier(symbolId->chars(), symbolId->size()))
                files.append(doc->fileName());
        }
    } else {
218
        DependencyTable dependencyTable = findRefs->updateDependencyTable(snapshot);
219
        files += dependencyTable.filesDependingOn(sourceFile);
220
    }
221
    files.removeDuplicates();
Roberto Raggi's avatar
Roberto Raggi committed
222 223 224

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

225
    ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &future);
Roberto Raggi's avatar
Roberto Raggi committed
226
    UpdateUI reduce(&future);
227 228 229
    // 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();
230
    QtConcurrent::blockingMappedReduced<QList<Usage> > (files, process, reduce);
231
    QThreadPool::globalInstance()->reserveThread();
Roberto Raggi's avatar
Roberto Raggi committed
232 233 234
    future.setProgressValue(files.size());
}

235
void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context)
236
{
237
    Overview overview;
238
    Find::SearchResult *search = Find::SearchResultWindow::instance()->startNewSearch(tr("C++ Usages:"),
239 240 241
                                                QString(),
                                                overview(context.fullyQualifiedName(symbol)),
                                                Find::SearchResultWindow::SearchOnly);
242
    findAll_helper(search, symbol, context);
243 244
}

245 246
void CppFindReferences::renameUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context,
                                     const QString &replacement)
Roberto Raggi's avatar
Roberto Raggi committed
247
{
Roberto Raggi's avatar
Roberto Raggi committed
248
    if (const Identifier *id = symbol->identifier()) {
249 250
        const QString textToReplace = replacement.isEmpty()
                ? QString::fromUtf8(id->chars(), id->size()) : replacement;
Roberto Raggi's avatar
Roberto Raggi committed
251

252
        Overview overview;
253
        Find::SearchResult *search = Find::SearchResultWindow::instance()->startNewSearch(
254 255 256 257
                    tr("C++ Usages:"),
                    QString(),
                    overview(context.fullyQualifiedName(symbol)),
                    Find::SearchResultWindow::SearchAndReplace, QLatin1String("CppEditor"));
258
        search->setTextToReplace(textToReplace);
Roberto Raggi's avatar
Roberto Raggi committed
259

260
        connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
261 262
                SLOT(onReplaceButtonClicked(QString,QList<Find::SearchResultItem>)));

263
        findAll_helper(search, symbol, context);
264
    }
265 266
}

267 268
void CppFindReferences::findAll_helper(Find::SearchResult *search,
                                       Symbol *symbol, const LookupContext &context)
269
{
270 271
    if (! (symbol && symbol->identifier())) {
        search->finishSearch();
272
        return;
273 274 275 276
    }
    connect(search, SIGNAL(cancelled()), this, SLOT(cancel()));
    connect(search, SIGNAL(activated(Find::SearchResultItem)),
            this, SLOT(openEditor(Find::SearchResultItem)));
277

278
    Find::SearchResultWindow::instance()->popup(true);
279
    const CppModelManagerInterface::WorkingCopy workingCopy = _modelManager->workingCopy();
Roberto Raggi's avatar
Roberto Raggi committed
280
    QFuture<Usage> result;
281
    result = QtConcurrent::run(&find_helper, workingCopy, context, this, symbol);
282 283 284
    createWatcher(result, search);

    Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
285
    Core::FutureProgress *progress = progressManager->addTask(result, tr("Searching"),
286
                                                              CppTools::Constants::TASK_SEARCH);
Roberto Raggi's avatar
Roberto Raggi committed
287

288
    connect(progress, SIGNAL(clicked()), Find::SearchResultWindow::instance(), SLOT(popup()));
Roberto Raggi's avatar
Roberto Raggi committed
289 290
}

Roberto Raggi's avatar
Roberto Raggi committed
291 292 293
void CppFindReferences::onReplaceButtonClicked(const QString &text,
                                               const QList<Find::SearchResultItem> &items)
{
294 295 296
    const QStringList fileNames = TextEditor::BaseFileFind::replaceAll(text, items);
    if (!fileNames.isEmpty()) {
        _modelManager->updateSourceFiles(fileNames);
297
        Find::SearchResultWindow::instance()->hide();
Roberto Raggi's avatar
Roberto Raggi committed
298 299 300
    }
}

Roberto Raggi's avatar
Roberto Raggi committed
301
void CppFindReferences::displayResults(int first, int last)
Roberto Raggi's avatar
Roberto Raggi committed
302
{
303 304 305 306 307
    QFutureWatcher<Usage> *watcher = static_cast<QFutureWatcher<Usage> *>(sender());
    Find::SearchResult *search = m_watchers.value(watcher);
    if (!search) {
        // search was deleted while it was running
        watcher->cancel();
308 309
        return;
    }
Roberto Raggi's avatar
Roberto Raggi committed
310
    for (int index = first; index != last; ++index) {
311 312 313 314 315 316
        Usage result = watcher->future().resultAt(index);
        search->addResult(result.path,
                          result.line,
                          result.lineText,
                          result.col,
                          result.len);
Roberto Raggi's avatar
Roberto Raggi committed
317
    }
Roberto Raggi's avatar
Roberto Raggi committed
318 319 320 321
}

void CppFindReferences::searchFinished()
{
322 323 324 325 326
    QFutureWatcher<Usage> *watcher = static_cast<QFutureWatcher<Usage> *>(sender());
    Find::SearchResult *search = m_watchers.value(watcher);
    if (search)
        search->finishSearch();
    m_watchers.remove(watcher);
Roberto Raggi's avatar
Roberto Raggi committed
327 328
}

329 330
void CppFindReferences::cancel()
{
331 332 333 334 335
    Find::SearchResult *search = qobject_cast<Find::SearchResult *>(sender());
    QTC_ASSERT(search, return);
    QFutureWatcher<Usage> *watcher = m_watchers.key(search);
    QTC_ASSERT(watcher, return);
    watcher->cancel();
336 337
}

338
void CppFindReferences::openEditor(const Find::SearchResultItem &item)
Roberto Raggi's avatar
Roberto Raggi committed
339
{
340
    if (item.path.size() > 0) {
341
        TextEditor::BaseTextEditorWidget::openEditorAt(item.path.first(), item.lineNumber, item.textMarkPos,
hjk's avatar
hjk committed
342
                                                 Core::Id(),
343
                                                 Core::EditorManager::ModeSwitch);
344
    } else {
hjk's avatar
hjk committed
345
        Core::EditorManager::instance()->openEditor(item.text, Core::Id(), Core::EditorManager::ModeSwitch);
346
    }
Roberto Raggi's avatar
Roberto Raggi committed
347 348
}

Christian Kamm's avatar
Christian Kamm committed
349 350 351 352 353

namespace {

class FindMacroUsesInFile: public std::unary_function<QString, QList<Usage> >
{
354
    const CppModelManagerInterface::WorkingCopy workingCopy;
Christian Kamm's avatar
Christian Kamm committed
355 356
    const Snapshot snapshot;
    const Macro &macro;
357
    QFutureInterface<Usage> *future;
Christian Kamm's avatar
Christian Kamm committed
358 359

public:
360
    FindMacroUsesInFile(const CppModelManagerInterface::WorkingCopy &workingCopy,
Christian Kamm's avatar
Christian Kamm committed
361
                        const Snapshot snapshot,
362 363 364
                        const Macro &macro,
                        QFutureInterface<Usage> *future)
        : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future)
Christian Kamm's avatar
Christian Kamm committed
365 366 367 368 369
    { }

    QList<Usage> operator()(const QString &fileName)
    {
        QList<Usage> usages;
370 371
        if (future->isCanceled())
            return usages;
Christian Kamm's avatar
Christian Kamm committed
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424

        const Document::Ptr &doc = snapshot.document(fileName);
        QByteArray source;

        foreach (const Document::MacroUse &use, doc->macroUses()) {
            const Macro &useMacro = use.macro();
            if (useMacro.line() == macro.line()
                && useMacro.fileName() == macro.fileName())
                {
                if (source.isEmpty())
                    source = getSource(fileName, workingCopy).toLatin1(); // ### FIXME: Encoding?

                unsigned lineStart;
                const QString &lineSource = matchingLine(use.begin(), source, &lineStart);
                usages.append(Usage(fileName, lineSource, use.beginLine(),
                                    use.begin() - lineStart, use.length()));
            }
        }

        return usages;
    }

    // ### FIXME: Pretty close to FindUsages::matchingLine.
    static QString matchingLine(unsigned position, const QByteArray &source,
                                unsigned *lineStart = 0)
    {
        const char *beg = source.constData();
        const char *start = beg + position;
        for (; start != beg - 1; --start) {
            if (*start == '\n')
                break;
        }

        ++start;

        const char *end = start + 1;
        for (; *end; ++end) {
            if (*end == '\n')
                break;
        }

        if (lineStart)
            *lineStart = start - beg;

        // ### FIXME: Encoding?
        const QString matchingLine = QString::fromUtf8(start, end - start);
        return matchingLine;
    }
};

} // end of anonymous namespace

static void findMacroUses_helper(QFutureInterface<Usage> &future,
425
                                 const CppModelManagerInterface::WorkingCopy workingCopy,
426 427 428
                                 const Snapshot snapshot,
                                 CppFindReferences *findRefs,
                                 const Macro macro)
Christian Kamm's avatar
Christian Kamm committed
429
{
430 431 432
    // ensure the dependency table is updated
    DependencyTable dependencies = findRefs->updateDependencyTable(snapshot);

Christian Kamm's avatar
Christian Kamm committed
433 434
    const QString& sourceFile = macro.fileName();
    QStringList files(sourceFile);
435
    files += dependencies.filesDependingOn(sourceFile);
Christian Kamm's avatar
Christian Kamm committed
436 437 438 439
    files.removeDuplicates();

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

440
    FindMacroUsesInFile process(workingCopy, snapshot, macro, &future);
Christian Kamm's avatar
Christian Kamm committed
441
    UpdateUI reduce(&future);
442 443 444
    // 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
445
    QtConcurrent::blockingMappedReduced<QList<Usage> > (files, process, reduce);
446
    QThreadPool::globalInstance()->reserveThread();
Christian Kamm's avatar
Christian Kamm committed
447 448 449 450 451
    future.setProgressValue(files.size());
}

void CppFindReferences::findMacroUses(const Macro &macro)
{
452
    Find::SearchResult *search = Find::SearchResultWindow::instance()->startNewSearch(
453 454 455 456
                tr("C++ Macro Usages:"),
                QString(),
                macro.name(),
                Find::SearchResultWindow::SearchOnly);
Christian Kamm's avatar
Christian Kamm committed
457

458
    Find::SearchResultWindow::instance()->popup(true);
Christian Kamm's avatar
Christian Kamm committed
459

460
    connect(search, SIGNAL(activated(Find::SearchResultItem)),
Christian Kamm's avatar
Christian Kamm committed
461
            this, SLOT(openEditor(Find::SearchResultItem)));
462
    connect(search, SIGNAL(cancelled()), this, SLOT(cancel()));
Christian Kamm's avatar
Christian Kamm committed
463 464

    const Snapshot snapshot = _modelManager->snapshot();
465
    const CppModelManagerInterface::WorkingCopy workingCopy = _modelManager->workingCopy();
Christian Kamm's avatar
Christian Kamm committed
466 467 468 469 470

    // add the macro definition itself
    {
        // ### FIXME: Encoding?
        const QByteArray &source = getSource(macro.fileName(), workingCopy).toLatin1();
471 472
        search->addResult(macro.fileName(), macro.line(),
                          source.mid(macro.offset(), macro.length()), 0, macro.length());
Christian Kamm's avatar
Christian Kamm committed
473 474 475
    }

    QFuture<Usage> result;
476
    result = QtConcurrent::run(&findMacroUses_helper, workingCopy, snapshot, this, macro);
477
    createWatcher(result, search);
Christian Kamm's avatar
Christian Kamm committed
478 479

    Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
480
    Core::FutureProgress *progress = progressManager->addTask(result, tr("Searching"),
Christian Kamm's avatar
Christian Kamm committed
481
                                                              CppTools::Constants::TASK_SEARCH);
482
    connect(progress, SIGNAL(clicked()), Find::SearchResultWindow::instance(), SLOT(popup()));
Christian Kamm's avatar
Christian Kamm committed
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 509
DependencyTable CppFindReferences::updateDependencyTable(CPlusPlus::Snapshot snapshot)
{
    DependencyTable oldDeps = dependencyTable();
    if (oldDeps.isValidFor(snapshot))
        return oldDeps;

    DependencyTable newDeps;
    newDeps.build(snapshot);
    setDependencyTable(newDeps);
    return newDeps;
}

DependencyTable CppFindReferences::dependencyTable() const
{
    QMutexLocker locker(&m_depsLock);
    Q_UNUSED(locker);
    return m_deps;
}

void CppFindReferences::setDependencyTable(const CPlusPlus::DependencyTable &newTable)
{
    QMutexLocker locker(&m_depsLock);
    Q_UNUSED(locker);
    m_deps = newTable;
}
510 511 512 513 514 515 516 517 518 519

void CppFindReferences::createWatcher(const QFuture<Usage> &future, Find::SearchResult *search)
{
    QFutureWatcher<Usage> *watcher = new QFutureWatcher<Usage>();
    watcher->setFuture(future);
    watcher->setPendingResultsLimit(1);
    connect(watcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(displayResults(int,int)));
    connect(watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
    m_watchers.insert(watcher, search);
}