cppfindreferences.cpp 11.9 KB
Newer Older
Roberto Raggi's avatar
Roberto Raggi committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, 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.
**
** If you are unsure which license is appropriate for your use, please
con's avatar
con committed
26
** contact the sales department at http://qt.nokia.com/contact.
Roberto Raggi's avatar
Roberto Raggi committed
27 28 29 30
**
**************************************************************************/

#include "cppfindreferences.h"
31
#include "cppmodelmanagerinterface.h"
Roberto Raggi's avatar
Roberto Raggi committed
32 33 34 35 36 37 38
#include "cpptoolsconstants.h"

#include <texteditor/basetexteditor.h>
#include <find/searchresultwindow.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/filesearch.h>
#include <coreplugin/progressmanager/progressmanager.h>
39
#include <coreplugin/editormanager/editormanager.h>
Roberto Raggi's avatar
Roberto Raggi committed
40 41 42 43 44 45 46 47
#include <coreplugin/icore.h>

#include <ASTVisitor.h>
#include <AST.h>
#include <Control.h>
#include <Literals.h>
#include <TranslationUnit.h>
#include <Symbols.h>
48 49
#include <Names.h>
#include <Scope.h>
Roberto Raggi's avatar
Roberto Raggi committed
50 51

#include <cplusplus/CppDocument.h>
52
#include <cplusplus/CppBindings.h>
53
#include <cplusplus/Overview.h>
Roberto Raggi's avatar
Roberto Raggi committed
54 55 56

#include <QtCore/QTime>
#include <QtCore/QtConcurrentRun>
57
#include <QtCore/QtConcurrentMap>
Roberto Raggi's avatar
Roberto Raggi committed
58
#include <QtCore/QDir>
59
#include <QtGui/QApplication>
Roberto Raggi's avatar
Roberto Raggi committed
60 61
#include <qtconcurrent/runextensions.h>

62 63
#include <functional>

Roberto Raggi's avatar
Roberto Raggi committed
64 65 66
using namespace CppTools::Internal;
using namespace CPlusPlus;

Roberto Raggi's avatar
Roberto Raggi committed
67
namespace {
68

Roberto Raggi's avatar
Roberto Raggi committed
69
class ProcessFile: public std::unary_function<QString, QList<Usage> >
70
{
71
    const QHash<QString, QString> workingList;
72 73 74 75
    const Snapshot snapshot;
    Symbol *symbol;

public:
76
    ProcessFile(const QHash<QString, QString> workingList,
77 78
              const Snapshot snapshot,
              Symbol *symbol)
Roberto Raggi's avatar
Roberto Raggi committed
79
        : workingList(workingList), snapshot(snapshot), symbol(symbol)
80 81 82 83 84 85 86
    { }

    QList<Usage> operator()(const QString &fileName)
    {
        QList<Usage> usages;
        const Identifier *symbolId = symbol->identifier();

87
        if (Document::Ptr previousDoc = snapshot.document(fileName)) {
88 89 90 91 92 93 94
            Control *control = previousDoc->control();
            if (! control->findIdentifier(symbolId->chars(), symbolId->size()))
                return usages; // skip this document, it's not using symbolId.
        }

        QByteArray source;

Roberto Raggi's avatar
Roberto Raggi committed
95 96
        if (workingList.contains(fileName))
            source = snapshot.preprocessedCode(workingList.value(fileName), fileName);
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
        else {
            QFile file(fileName);
            if (! file.open(QFile::ReadOnly))
                return usages;

            const QString contents = QTextStream(&file).readAll(); // ### FIXME
            source = snapshot.preprocessedCode(contents, fileName);
        }

        Document::Ptr doc = snapshot.documentFromSource(source, fileName);
        doc->tokenize();

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

            FindUsages process(doc, snapshot);
            process.setGlobalNamespaceBinding(bind(doc, snapshot));

            process(symbol);
            usages = process.usages();
        }

        return usages;
    }
};

Roberto Raggi's avatar
Roberto Raggi committed
124
class UpdateUI: public std::binary_function<QList<Usage> &, QList<Usage>, void>
125 126 127 128
{
    QFutureInterface<Usage> *future;

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

131
    void operator()(QList<Usage> &, const QList<Usage> &usages)
132 133 134 135 136 137 138 139
    {
        foreach (const Usage &u, usages)
            future->reportResult(u);

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

Roberto Raggi's avatar
Roberto Raggi committed
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
} // end of anonymous namespace

CppFindReferences::CppFindReferences(CppTools::CppModelManagerInterface *modelManager)
    : QObject(modelManager),
      _modelManager(modelManager),
      _resultWindow(ExtensionSystem::PluginManager::instance()->getObject<Find::SearchResultWindow>())
{
    m_watcher.setPendingResultsLimit(1);
    connect(&m_watcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(displayResults(int,int)));
    connect(&m_watcher, SIGNAL(finished()), this, SLOT(searchFinished()));
}

CppFindReferences::~CppFindReferences()
{
}

QList<int> CppFindReferences::references(Symbol *symbol,
                                         Document::Ptr doc,
                                         const Snapshot& snapshot) const
{
    QList<int> references;

    FindUsages findUsages(doc, snapshot);
    findUsages.setGlobalNamespaceBinding(bind(doc, snapshot));
    findUsages(symbol);
    references = findUsages.references();

    return references;
}

170
static void find_helper(QFutureInterface<Usage> &future,
171
                        const QHash<QString, QString> workingList,
172 173
                        Snapshot snapshot,
                        Symbol *symbol)
Roberto Raggi's avatar
Roberto Raggi committed
174 175 176
{
    QTime tm;
    tm.start();
177

Roberto Raggi's avatar
Roberto Raggi committed
178
    const Identifier *symbolId = symbol->identifier();
179 180
    Q_ASSERT(symbolId != 0);

Roberto Raggi's avatar
Cleanup  
Roberto Raggi committed
181 182
    const QString sourceFile = QString::fromUtf8(symbol->fileName(), symbol->fileNameLength());
    QStringList files(sourceFile);
183 184 185 186 187 188 189 190 191 192 193 194

    if (symbol->isClass() || symbol->isForwardClassDeclaration()) {
        foreach (const Document::Ptr &doc, snapshot) {
            if (doc->fileName() == sourceFile)
                continue;

            Control *control = doc->control();

            if (control->findIdentifier(symbolId->chars(), symbolId->size()))
                files.append(doc->fileName());
        }
    } else {
195
        files += snapshot.filesDependingOn(sourceFile);
196
    }
197
    files.removeDuplicates();
198
    //qDebug() << "done in:" << tm.elapsed() << "number of files to parse:" << files.size();
Roberto Raggi's avatar
Roberto Raggi committed
199 200 201

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

202
    ProcessFile process(workingList, snapshot, symbol);
Roberto Raggi's avatar
Roberto Raggi committed
203
    UpdateUI reduce(&future);
204

205
    QtConcurrent::blockingMappedReduced<QList<Usage> > (files, process, reduce);
206

Roberto Raggi's avatar
Roberto Raggi committed
207 208 209
    future.setProgressValue(files.size());
}

210 211
void CppFindReferences::findUsages(Symbol *symbol)
{
Roberto Raggi's avatar
Roberto Raggi committed
212 213 214 215 216
    Find::SearchResult *search = _resultWindow->startNewSearch(Find::SearchResultWindow::SearchOnly);

    connect(search, SIGNAL(activated(Find::SearchResultItem)),
            this, SLOT(openEditor(Find::SearchResultItem)));

217 218 219 220
    findAll_helper(symbol);
}

void CppFindReferences::renameUsages(Symbol *symbol)
Roberto Raggi's avatar
Roberto Raggi committed
221
{
Roberto Raggi's avatar
Roberto Raggi committed
222
    if (const Identifier *id = symbol->identifier()) {
223
        const QString textToReplace = QString::fromUtf8(id->chars(), id->size());
Roberto Raggi's avatar
Roberto Raggi committed
224

225 226
        Find::SearchResult *search = _resultWindow->startNewSearch(Find::SearchResultWindow::SearchAndReplace);
        _resultWindow->setTextToReplace(textToReplace);
227

228 229
        connect(search, SIGNAL(activated(Find::SearchResultItem)),
                this, SLOT(openEditor(Find::SearchResultItem)));
Roberto Raggi's avatar
Roberto Raggi committed
230

231 232 233 234 235
        connect(search, SIGNAL(replaceButtonClicked(QString,QList<Find::SearchResultItem>)),
                SLOT(onReplaceButtonClicked(QString,QList<Find::SearchResultItem>)));

        findAll_helper(symbol);
    }
236 237 238 239
}

void CppFindReferences::findAll_helper(Symbol *symbol)
{
240 241 242
    if (! (symbol && symbol->identifier()))
        return;

Roberto Raggi's avatar
Roberto Raggi committed
243 244
    _resultWindow->popup(true);

245
    const Snapshot snapshot = _modelManager->snapshot();
246
    const QHash<QString, QString> wl = _modelManager->workingCopy();
247

Roberto Raggi's avatar
Roberto Raggi committed
248
    Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
Roberto Raggi's avatar
Roberto Raggi committed
249 250 251 252

    QFuture<Usage> result;

    result = QtConcurrent::run(&find_helper, wl, snapshot, symbol);
Roberto Raggi's avatar
Roberto Raggi committed
253 254 255
    m_watcher.setFuture(result);

    Core::FutureProgress *progress = progressManager->addTask(result, tr("Searching..."),
256
                                                              CppTools::Constants::TASK_SEARCH);
Roberto Raggi's avatar
Roberto Raggi committed
257 258 259 260

    connect(progress, SIGNAL(clicked()), _resultWindow, SLOT(popup()));
}

261 262 263 264 265 266 267
static void applyChanges(QTextDocument *doc, const QString &text, const QList<Find::SearchResultItem> &items)
{
    QList<QTextCursor> cursors;

    foreach (const Find::SearchResultItem &item, items) {
        const int blockNumber = item.lineNumber - 1;
        QTextCursor tc(doc->findBlockByNumber(blockNumber));
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282

        const int cursorPosition = tc.position() + item.searchTermStart;

        int cursorIndex = 0;
        for (; cursorIndex < cursors.size(); ++cursorIndex) {
            const QTextCursor &tc = cursors.at(cursorIndex);

            if (tc.position() == cursorPosition)
                break;
        }

        if (cursorIndex != cursors.size())
            continue; // skip this change.

        tc.setPosition(cursorPosition);
283 284 285 286 287 288 289 290 291
        tc.setPosition(tc.position() + item.searchTermLength,
                       QTextCursor::KeepAnchor);
        cursors.append(tc);
    }

    foreach (QTextCursor tc, cursors)
        tc.insertText(text);
}

Roberto Raggi's avatar
Roberto Raggi committed
292 293 294
void CppFindReferences::onReplaceButtonClicked(const QString &text,
                                               const QList<Find::SearchResultItem> &items)
{
295 296
    Core::EditorManager::instance()->hideEditorInfoBar(QLatin1String("CppEditor.Rename"));

Roberto Raggi's avatar
Roberto Raggi committed
297 298 299 300 301 302 303 304
    if (text.isEmpty())
        return;

    QHash<QString, QList<Find::SearchResultItem> > changes;

    foreach (const Find::SearchResultItem &item, items)
        changes[item.fileName].append(item);

305 306
    Core::EditorManager *editorManager = Core::EditorManager::instance();

Roberto Raggi's avatar
Roberto Raggi committed
307 308 309 310 311
    QHashIterator<QString, QList<Find::SearchResultItem> > it(changes);
    while (it.hasNext()) {
        it.next();

        const QString fileName = it.key();
312
        const QList<Find::SearchResultItem> items = it.value();
Roberto Raggi's avatar
Roberto Raggi committed
313

314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
        const QList<Core::IEditor *> editors = editorManager->editorsForFileName(fileName);
        TextEditor::BaseTextEditor *textEditor = 0;
        foreach (Core::IEditor *editor, editors) {
            textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor->widget());
            if (textEditor != 0)
                break;
        }

        if (textEditor != 0) {
            QTextCursor tc = textEditor->textCursor();
            tc.beginEditBlock();
            applyChanges(textEditor->document(), text, items);
            tc.endEditBlock();
        } else {
            QFile file(fileName);
Roberto Raggi's avatar
Roberto Raggi committed
329

330 331
            if (file.open(QFile::ReadOnly)) {
                QTextStream stream(&file);
Roberto Raggi's avatar
Roberto Raggi committed
332
                // ### set the encoding
333 334 335 336 337 338 339 340 341 342 343 344 345 346
                const QString plainText = stream.readAll();
                file.close();

                QTextDocument doc;
                doc.setPlainText(plainText);

                applyChanges(&doc, text, items);

                QFile newFile(fileName);
                if (newFile.open(QFile::WriteOnly)) {
                    QTextStream stream(&newFile);
                    // ### set the encoding
                    stream << doc.toPlainText();
                }
Roberto Raggi's avatar
Roberto Raggi committed
347 348 349 350 351 352
            }
        }
    }

    const QStringList fileNames = changes.keys();
    _modelManager->updateSourceFiles(fileNames);
353
    _resultWindow->hide();
Roberto Raggi's avatar
Roberto Raggi committed
354 355
}

Roberto Raggi's avatar
Roberto Raggi committed
356
void CppFindReferences::displayResults(int first, int last)
Roberto Raggi's avatar
Roberto Raggi committed
357
{
Roberto Raggi's avatar
Roberto Raggi committed
358 359 360 361 362 363 364 365
    for (int index = first; index != last; ++index) {
        Usage result = m_watcher.future().resultAt(index);
        _resultWindow->addResult(result.path,
                                 result.line,
                                 result.lineText,
                                 result.col,
                                 result.len);
    }
Roberto Raggi's avatar
Roberto Raggi committed
366 367 368 369
}

void CppFindReferences::searchFinished()
{
370
    _resultWindow->finishSearch();
Roberto Raggi's avatar
Roberto Raggi committed
371 372 373
    emit changed();
}

374
void CppFindReferences::openEditor(const Find::SearchResultItem &item)
Roberto Raggi's avatar
Roberto Raggi committed
375
{
376
    TextEditor::BaseTextEditor::openEditorAt(item.fileName, item.lineNumber, item.searchTermStart);
Roberto Raggi's avatar
Roberto Raggi committed
377 378
}