1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "cppfindreferences.h"
27 
28 #include "cppcodemodelsettings.h"
29 #include "cppfilesettingspage.h"
30 #include "cpptoolsconstants.h"
31 #include "cppmodelmanager.h"
32 #include "cpptoolsreuse.h"
33 #include "cppworkingcopy.h"
34 
35 #include <coreplugin/editormanager/editormanager.h>
36 #include <coreplugin/icore.h>
37 #include <coreplugin/progressmanager/futureprogress.h>
38 #include <coreplugin/progressmanager/progressmanager.h>
39 #include <projectexplorer/projectexplorer.h>
40 #include <projectexplorer/projectnodes.h>
41 #include <projectexplorer/projecttree.h>
42 #include <projectexplorer/session.h>
43 #include <texteditor/basefilefind.h>
44 
45 #include <utils/algorithm.h>
46 #include <utils/qtcassert.h>
47 #include <utils/runextensions.h>
48 #include <utils/textfileformat.h>
49 
50 #include <cplusplus/Overview.h>
51 #include <QtConcurrentMap>
52 #include <QCheckBox>
53 #include <QDir>
54 #include <QFutureWatcher>
55 #include <QVBoxLayout>
56 
57 #include <functional>
58 
59 using namespace Core;
60 using namespace ProjectExplorer;
61 
62 namespace CppTools {
63 
isAllLowerCase(const QString & text)64 namespace { static bool isAllLowerCase(const QString &text) { return text.toLower() == text; } }
65 
colorStyleForUsageType(CPlusPlus::Usage::Type type)66 SearchResultColor::Style colorStyleForUsageType(CPlusPlus::Usage::Type type)
67 {
68     switch (type) {
69     case CPlusPlus::Usage::Type::Read:
70         return SearchResultColor::Style::Alt1;
71     case CPlusPlus::Usage::Type::Initialization:
72     case CPlusPlus::Usage::Type::Write:
73     case CPlusPlus::Usage::Type::WritableRef:
74         return SearchResultColor::Style::Alt2;
75     case CPlusPlus::Usage::Type::Declaration:
76     case CPlusPlus::Usage::Type::Other:
77         return SearchResultColor::Style::Default;
78     }
79     return SearchResultColor::Style::Default; // For dumb compilers.
80 }
81 
renameFilesForSymbol(const QString & oldSymbolName,const QString & newSymbolName,const QVector<Node * > & files)82 void renameFilesForSymbol(const QString &oldSymbolName, const QString &newSymbolName,
83                           const QVector<Node *> &files)
84 {
85     Internal::CppFileSettings settings;
86     settings.fromSettings(Core::ICore::settings());
87 
88     const QStringList newPaths =
89             Utils::transform<QList>(files,
90                                     [&oldSymbolName, newSymbolName, &settings](const Node *node) -> QString {
91         const QFileInfo fi = node->filePath().toFileInfo();
92         const QString oldBaseName = fi.baseName();
93         QString newBaseName = newSymbolName;
94 
95         // 1) new symbol lowercase: new base name lowercase
96         if (isAllLowerCase(newSymbolName)) {
97             newBaseName = newSymbolName;
98 
99         // 2) old base name mixed case: new base name is verbatim symbol name
100         } else if (!isAllLowerCase(oldBaseName)) {
101             newBaseName = newSymbolName;
102 
103         // 3) old base name lowercase, old symbol mixed case: new base name lowercase
104         } else if (!isAllLowerCase(oldSymbolName)) {
105             newBaseName = newSymbolName.toLower();
106 
107         // 4) old base name lowercase, old symbol lowercase, new symbol mixed case:
108         //    use the preferences setting for new base name case
109         } else if (settings.lowerCaseFiles) {
110             newBaseName = newSymbolName.toLower();
111         }
112 
113         if (newBaseName == oldBaseName)
114             return QString();
115 
116         return fi.absolutePath() + "/" + newBaseName + '.' + fi.completeSuffix();
117     });
118 
119     for (int i = 0; i < files.size(); ++i) {
120         if (!newPaths.at(i).isEmpty()) {
121             Node *node = files.at(i);
122             ProjectExplorerPlugin::renameFile(node, newPaths.at(i));
123         }
124     }
125 }
126 
createWidget()127 QWidget *CppSearchResultFilter::createWidget()
128 {
129     const auto widget = new QWidget;
130     const auto layout = new QVBoxLayout(widget);
131     layout->setContentsMargins(0, 0, 0, 0);
132     const auto readsCheckBox = new QCheckBox(Internal::CppFindReferences::tr("Reads"));
133     readsCheckBox->setChecked(m_showReads);
134     const auto writesCheckBox = new QCheckBox(Internal::CppFindReferences::tr("Writes"));
135     writesCheckBox->setChecked(m_showWrites);
136     const auto declsCheckBox = new QCheckBox(Internal::CppFindReferences::tr("Declarations"));
137     declsCheckBox->setChecked(m_showDecls);
138     const auto otherCheckBox = new QCheckBox(Internal::CppFindReferences::tr("Other"));
139     otherCheckBox->setChecked(m_showOther);
140     layout->addWidget(readsCheckBox);
141     layout->addWidget(writesCheckBox);
142     layout->addWidget(declsCheckBox);
143     layout->addWidget(otherCheckBox);
144     connect(readsCheckBox, &QCheckBox::toggled,
145             this, [this](bool checked) { setValue(m_showReads, checked); });
146     connect(writesCheckBox, &QCheckBox::toggled,
147             this, [this](bool checked) { setValue(m_showWrites, checked); });
148     connect(declsCheckBox, &QCheckBox::toggled,
149             this, [this](bool checked) { setValue(m_showDecls, checked); });
150     connect(otherCheckBox, &QCheckBox::toggled,
151             this, [this](bool checked) { setValue(m_showOther, checked); });
152     return widget;
153 }
154 
matches(const SearchResultItem & item) const155 bool CppSearchResultFilter::matches(const SearchResultItem &item) const
156 {
157     switch (static_cast<CPlusPlus::Usage::Type>(item.userData().toInt())) {
158     case CPlusPlus::Usage::Type::Read:
159         return m_showReads;
160     case CPlusPlus::Usage::Type::Write:
161     case CPlusPlus::Usage::Type::WritableRef:
162     case CPlusPlus::Usage::Type::Initialization:
163         return m_showWrites;
164     case CPlusPlus::Usage::Type::Declaration:
165         return m_showDecls;
166     case CPlusPlus::Usage::Type::Other:
167         return m_showOther;
168     }
169     return false;
170 }
171 
setValue(bool & member,bool value)172 void CppSearchResultFilter::setValue(bool &member, bool value)
173 {
174     member = value;
175     emit filterChanged();
176 }
177 
178 namespace Internal {
179 
180 
getSource(const Utils::FilePath & fileName,const WorkingCopy & workingCopy)181 static QByteArray getSource(const Utils::FilePath &fileName,
182                             const WorkingCopy &workingCopy)
183 {
184     if (workingCopy.contains(fileName)) {
185         return workingCopy.source(fileName);
186     } else {
187         QString fileContents;
188         Utils::TextFileFormat format;
189         QString error;
190         QTextCodec *defaultCodec = EditorManager::defaultTextCodec();
191         Utils::TextFileFormat::ReadResult result = Utils::TextFileFormat::readFile(
192                     fileName, defaultCodec, &fileContents, &format, &error);
193         if (result != Utils::TextFileFormat::ReadSuccess)
194             qWarning() << "Could not read " << fileName << ". Error: " << error;
195 
196         return fileContents.toUtf8();
197     }
198 }
199 
typeId(CPlusPlus::Symbol * symbol)200 static QByteArray typeId(CPlusPlus::Symbol *symbol)
201 {
202     if (symbol->asEnum()) {
203         return QByteArray("e");
204     } else if (symbol->asFunction()) {
205         return QByteArray("f");
206     } else if (symbol->asNamespace()) {
207         return QByteArray("n");
208     } else if (symbol->asTemplate()) {
209         return QByteArray("t");
210     } else if (symbol->asNamespaceAlias()) {
211         return QByteArray("na");
212     } else if (symbol->asClass()) {
213         return QByteArray("c");
214     } else if (symbol->asBlock()) {
215         return QByteArray("b");
216     } else if (symbol->asUsingNamespaceDirective()) {
217         return QByteArray("u");
218     } else if (symbol->asUsingDeclaration()) {
219         return QByteArray("ud");
220     } else if (symbol->asDeclaration()) {
221         QByteArray temp("d,");
222         CPlusPlus::Overview pretty;
223         temp.append(pretty.prettyType(symbol->type()).toUtf8());
224         return temp;
225     } else if (symbol->asArgument()) {
226         return QByteArray("a");
227     } else if (symbol->asTypenameArgument()) {
228         return QByteArray("ta");
229     } else if (symbol->asBaseClass()) {
230         return QByteArray("bc");
231     } else if (symbol->asForwardClassDeclaration()) {
232         return QByteArray("fcd");
233     } else if (symbol->asQtPropertyDeclaration()) {
234         return QByteArray("qpd");
235     } else if (symbol->asQtEnum()) {
236         return QByteArray("qe");
237     } else if (symbol->asObjCBaseClass()) {
238         return QByteArray("ocbc");
239     } else if (symbol->asObjCBaseProtocol()) {
240         return QByteArray("ocbp");
241     } else if (symbol->asObjCClass()) {
242         return QByteArray("occ");
243     } else if (symbol->asObjCForwardClassDeclaration()) {
244         return QByteArray("ocfd");
245     } else if (symbol->asObjCProtocol()) {
246         return QByteArray("ocp");
247     } else if (symbol->asObjCForwardProtocolDeclaration()) {
248         return QByteArray("ocfpd");
249     } else if (symbol->asObjCMethod()) {
250         return QByteArray("ocm");
251     } else if (symbol->asObjCPropertyDeclaration()) {
252         return QByteArray("ocpd");
253     }
254     return QByteArray("unknown");
255 }
256 
idForSymbol(CPlusPlus::Symbol * symbol)257 static QByteArray idForSymbol(CPlusPlus::Symbol *symbol)
258 {
259     QByteArray uid(typeId(symbol));
260     if (const CPlusPlus::Identifier *id = symbol->identifier()) {
261         uid.append("|");
262         uid.append(QByteArray(id->chars(), id->size()));
263     } else if (CPlusPlus::Scope *scope = symbol->enclosingScope()) {
264         // add the index of this symbol within its enclosing scope
265         // (counting symbols without identifier of the same type)
266         int count = 0;
267         CPlusPlus::Scope::iterator it = scope->memberBegin();
268         while (it != scope->memberEnd() && *it != symbol) {
269             CPlusPlus::Symbol *val = *it;
270             ++it;
271             if (val->identifier() || typeId(val) != uid)
272                 continue;
273             ++count;
274         }
275         uid.append(QString::number(count).toLocal8Bit());
276     }
277     return uid;
278 }
279 
fullIdForSymbol(CPlusPlus::Symbol * symbol)280 static QList<QByteArray> fullIdForSymbol(CPlusPlus::Symbol *symbol)
281 {
282     QList<QByteArray> uid;
283     CPlusPlus::Symbol *current = symbol;
284     do {
285         uid.prepend(idForSymbol(current));
286         current = current->enclosingScope();
287     } while (current);
288     return uid;
289 }
290 
291 namespace {
292 
293 class ProcessFile
294 {
295     const WorkingCopy workingCopy;
296     const CPlusPlus::Snapshot snapshot;
297     CPlusPlus::Document::Ptr symbolDocument;
298     CPlusPlus::Symbol *symbol;
299     QFutureInterface<CPlusPlus::Usage> *future;
300     const bool categorize;
301 
302 public:
303     // needed by QtConcurrent
304     using argument_type = const Utils::FilePath &;
305     using result_type = QList<CPlusPlus::Usage>;
306 
ProcessFile(const WorkingCopy & workingCopy,const CPlusPlus::Snapshot snapshot,CPlusPlus::Document::Ptr symbolDocument,CPlusPlus::Symbol * symbol,QFutureInterface<CPlusPlus::Usage> * future,bool categorize)307     ProcessFile(const WorkingCopy &workingCopy,
308                 const CPlusPlus::Snapshot snapshot,
309                 CPlusPlus::Document::Ptr symbolDocument,
310                 CPlusPlus::Symbol *symbol,
311                 QFutureInterface<CPlusPlus::Usage> *future,
312                 bool categorize)
313         : workingCopy(workingCopy),
314           snapshot(snapshot),
315           symbolDocument(symbolDocument),
316           symbol(symbol),
317           future(future),
318           categorize(categorize)
319     { }
320 
operator ()(const Utils::FilePath & fileName)321     QList<CPlusPlus::Usage> operator()(const Utils::FilePath &fileName)
322     {
323         QList<CPlusPlus::Usage> usages;
324         if (future->isPaused())
325             future->waitForResume();
326         if (future->isCanceled())
327             return usages;
328         const CPlusPlus::Identifier *symbolId = symbol->identifier();
329 
330         if (CPlusPlus::Document::Ptr previousDoc = snapshot.document(fileName)) {
331             CPlusPlus::Control *control = previousDoc->control();
332             if (!control->findIdentifier(symbolId->chars(), symbolId->size()))
333                 return usages; // skip this document, it's not using symbolId.
334         }
335         CPlusPlus::Document::Ptr doc;
336         const QByteArray unpreprocessedSource = getSource(fileName, workingCopy);
337 
338         if (symbolDocument && fileName == Utils::FilePath::fromString(symbolDocument->fileName())) {
339             doc = symbolDocument;
340         } else {
341             doc = snapshot.preprocessedDocument(unpreprocessedSource, fileName);
342             doc->tokenize();
343         }
344 
345         CPlusPlus::Control *control = doc->control();
346         if (control->findIdentifier(symbolId->chars(), symbolId->size()) != nullptr) {
347             if (doc != symbolDocument)
348                 doc->check();
349 
350             CPlusPlus::FindUsages process(unpreprocessedSource, doc, snapshot, categorize);
351             process(symbol);
352 
353             usages = process.usages();
354         }
355 
356         if (future->isPaused())
357             future->waitForResume();
358         return usages;
359     }
360 };
361 
362 class UpdateUI
363 {
364     QFutureInterface<CPlusPlus::Usage> *future;
365 
366 public:
UpdateUI(QFutureInterface<CPlusPlus::Usage> * future)367     explicit UpdateUI(QFutureInterface<CPlusPlus::Usage> *future): future(future) {}
368 
operator ()(QList<CPlusPlus::Usage> &,const QList<CPlusPlus::Usage> & usages)369     void operator()(QList<CPlusPlus::Usage> &, const QList<CPlusPlus::Usage> &usages)
370     {
371         foreach (const CPlusPlus::Usage &u, usages)
372             future->reportResult(u);
373 
374         future->setProgressValue(future->progressValue() + 1);
375     }
376 };
377 
378 } // end of anonymous namespace
379 
CppFindReferences(CppModelManager * modelManager)380 CppFindReferences::CppFindReferences(CppModelManager *modelManager)
381     : QObject(modelManager),
382       m_modelManager(modelManager)
383 {
384 }
385 
386 CppFindReferences::~CppFindReferences() = default;
387 
references(CPlusPlus::Symbol * symbol,const CPlusPlus::LookupContext & context) const388 QList<int> CppFindReferences::references(CPlusPlus::Symbol *symbol,
389                                          const CPlusPlus::LookupContext &context) const
390 {
391     QList<int> references;
392 
393     CPlusPlus::FindUsages findUsages(context);
394     findUsages(symbol);
395     references = findUsages.references();
396 
397     return references;
398 }
399 
find_helper(QFutureInterface<CPlusPlus::Usage> & future,const WorkingCopy workingCopy,const CPlusPlus::LookupContext & context,CPlusPlus::Symbol * symbol,bool categorize)400 static void find_helper(QFutureInterface<CPlusPlus::Usage> &future,
401                         const WorkingCopy workingCopy,
402                         const CPlusPlus::LookupContext &context,
403                         CPlusPlus::Symbol *symbol,
404                         bool categorize)
405 {
406     const CPlusPlus::Identifier *symbolId = symbol->identifier();
407     QTC_ASSERT(symbolId != nullptr, return);
408 
409     const CPlusPlus::Snapshot snapshot = context.snapshot();
410 
411     const Utils::FilePath sourceFile = Utils::FilePath::fromUtf8(symbol->fileName(),
412                                                                  symbol->fileNameLength());
413     Utils::FilePaths files{sourceFile};
414 
415     if (symbol->isClass()
416         || symbol->isForwardClassDeclaration()
417         || (symbol->enclosingScope()
418             && !symbol->isStatic()
419             && symbol->enclosingScope()->isNamespace())) {
420         const CPlusPlus::Snapshot snapshotFromContext = context.snapshot();
421         for (auto i = snapshotFromContext.begin(), ei = snapshotFromContext.end(); i != ei; ++i) {
422             if (i.key() == sourceFile)
423                 continue;
424 
425             const CPlusPlus::Control *control = i.value()->control();
426 
427             if (control->findIdentifier(symbolId->chars(), symbolId->size()))
428                 files.append(i.key());
429         }
430     } else {
431         files += snapshot.filesDependingOn(sourceFile);
432     }
433     files = Utils::filteredUnique(files);
434 
435     future.setProgressRange(0, files.size());
436 
437     ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &future, categorize);
438     UpdateUI reduce(&future);
439     // This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count
440     // so the blockingMappedReduced can use one more thread, and increase it again afterwards.
441     QThreadPool::globalInstance()->releaseThread();
442     QtConcurrent::blockingMappedReduced<QList<CPlusPlus::Usage> > (files, process, reduce);
443     QThreadPool::globalInstance()->reserveThread();
444     future.setProgressValue(files.size());
445 }
446 
findUsages(CPlusPlus::Symbol * symbol,const CPlusPlus::LookupContext & context)447 void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol,
448                                    const CPlusPlus::LookupContext &context)
449 {
450     findUsages(symbol, context, QString(), false);
451 }
452 
findUsages(CPlusPlus::Symbol * symbol,const CPlusPlus::LookupContext & context,const QString & replacement,bool replace)453 void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol,
454                                    const CPlusPlus::LookupContext &context,
455                                    const QString &replacement,
456                                    bool replace)
457 {
458     CPlusPlus::Overview overview;
459     SearchResult *search = SearchResultWindow::instance()->startNewSearch(tr("C++ Usages:"),
460                                                 QString(),
461                                                 overview.prettyName(CPlusPlus::LookupContext::fullyQualifiedName(symbol)),
462                                                 replace ? SearchResultWindow::SearchAndReplace
463                                                         : SearchResultWindow::SearchOnly,
464                                                 SearchResultWindow::PreserveCaseDisabled,
465                                                 QLatin1String("CppEditor"));
466     search->setTextToReplace(replacement);
467     if (codeModelSettings()->categorizeFindReferences())
468         search->setFilter(new CppSearchResultFilter);
469     auto renameFilesCheckBox = new QCheckBox();
470     renameFilesCheckBox->setVisible(false);
471     search->setAdditionalReplaceWidget(renameFilesCheckBox);
472     connect(search, &SearchResult::replaceButtonClicked,
473             this, &CppFindReferences::onReplaceButtonClicked);
474     search->setSearchAgainSupported(true);
475     connect(search, &SearchResult::searchAgainRequested, this, &CppFindReferences::searchAgain);
476     CppFindReferencesParameters parameters;
477     parameters.symbolId = fullIdForSymbol(symbol);
478     parameters.symbolFileName = QByteArray(symbol->fileName());
479     parameters.categorize = codeModelSettings()->categorizeFindReferences();
480 
481     if (symbol->isClass() || symbol->isForwardClassDeclaration()) {
482         CPlusPlus::Overview overview;
483         parameters.prettySymbolName =
484                 overview.prettyName(CPlusPlus::LookupContext::path(symbol).constLast());
485     }
486 
487     search->setUserData(QVariant::fromValue(parameters));
488     findAll_helper(search, symbol, context, codeModelSettings()->categorizeFindReferences());
489 }
490 
renameUsages(CPlusPlus::Symbol * symbol,const CPlusPlus::LookupContext & context,const QString & replacement)491 void CppFindReferences::renameUsages(CPlusPlus::Symbol *symbol,
492                                      const CPlusPlus::LookupContext &context,
493                                      const QString &replacement)
494 {
495     if (const CPlusPlus::Identifier *id = symbol->identifier()) {
496         const QString textToReplace = replacement.isEmpty()
497                 ? QString::fromUtf8(id->chars(), id->size()) : replacement;
498         findUsages(symbol, context, textToReplace, true);
499     }
500 }
501 
findAll_helper(SearchResult * search,CPlusPlus::Symbol * symbol,const CPlusPlus::LookupContext & context,bool categorize)502 void CppFindReferences::findAll_helper(SearchResult *search, CPlusPlus::Symbol *symbol,
503                                        const CPlusPlus::LookupContext &context, bool categorize)
504 {
505     if (!(symbol && symbol->identifier())) {
506         search->finishSearch(false);
507         return;
508     }
509     connect(search, &SearchResult::activated,
510             [](const SearchResultItem& item) {
511                 Core::EditorManager::openEditorAtSearchResult(item);
512             });
513 
514     SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
515     const WorkingCopy workingCopy = m_modelManager->workingCopy();
516     QFuture<CPlusPlus::Usage> result;
517     result = Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper,
518                              workingCopy, context, symbol, categorize);
519     createWatcher(result, search);
520 
521     FutureProgress *progress = ProgressManager::addTask(result, tr("Searching for Usages"),
522                                                               CppTools::Constants::TASK_SEARCH);
523 
524     connect(progress, &FutureProgress::clicked, search, &SearchResult::popup);
525 }
526 
onReplaceButtonClicked(const QString & text,const QList<SearchResultItem> & items,bool preserveCase)527 void CppFindReferences::onReplaceButtonClicked(const QString &text,
528                                                const QList<SearchResultItem> &items,
529                                                bool preserveCase)
530 {
531     const Utils::FilePaths filePaths = TextEditor::BaseFileFind::replaceAll(text, items, preserveCase);
532     if (!filePaths.isEmpty()) {
533         m_modelManager->updateSourceFiles(
534             Utils::transform<QSet>(filePaths, &Utils::FilePath::toString));
535         SearchResultWindow::instance()->hide();
536     }
537 
538     auto search = qobject_cast<SearchResult *>(sender());
539     QTC_ASSERT(search, return);
540 
541     CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
542     if (parameters.filesToRename.isEmpty())
543         return;
544 
545     auto renameFilesCheckBox = qobject_cast<QCheckBox *>(search->additionalReplaceWidget());
546     if (!renameFilesCheckBox || !renameFilesCheckBox->isChecked())
547         return;
548 
549     renameFilesForSymbol(parameters.prettySymbolName, text, parameters.filesToRename);
550 }
551 
searchAgain()552 void CppFindReferences::searchAgain()
553 {
554     auto search = qobject_cast<SearchResult *>(sender());
555     CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
556     parameters.filesToRename.clear();
557     CPlusPlus::Snapshot snapshot = CppModelManager::instance()->snapshot();
558     search->restart();
559     CPlusPlus::LookupContext context;
560     CPlusPlus::Symbol *symbol = findSymbol(parameters, snapshot, &context);
561     if (!symbol) {
562         search->finishSearch(false);
563         return;
564     }
565     findAll_helper(search, symbol, context, parameters.categorize);
566 }
567 
568 namespace {
569 class UidSymbolFinder : public CPlusPlus::SymbolVisitor
570 {
571 public:
UidSymbolFinder(const QList<QByteArray> & uid)572     explicit UidSymbolFinder(const QList<QByteArray> &uid) : m_uid(uid) { }
result() const573     CPlusPlus::Symbol *result() const { return m_result; }
574 
preVisit(CPlusPlus::Symbol * symbol)575     bool preVisit(CPlusPlus::Symbol *symbol) override
576     {
577         if (m_result)
578             return false;
579         int index = m_index;
580         if (symbol->asScope())
581             ++m_index;
582         if (index >= m_uid.size())
583             return false;
584         if (idForSymbol(symbol) != m_uid.at(index))
585             return false;
586         if (index == m_uid.size() - 1) {
587             // symbol found
588             m_result = symbol;
589             return false;
590         }
591         return true;
592     }
593 
postVisit(CPlusPlus::Symbol * symbol)594     void postVisit(CPlusPlus::Symbol *symbol) override
595     {
596         if (symbol->asScope())
597             --m_index;
598     }
599 
600 private:
601     QList<QByteArray> m_uid;
602     int m_index = 0;
603     CPlusPlus::Symbol *m_result = nullptr;
604 };
605 }
606 
findSymbol(const CppFindReferencesParameters & parameters,const CPlusPlus::Snapshot & snapshot,CPlusPlus::LookupContext * context)607 CPlusPlus::Symbol *CppFindReferences::findSymbol(const CppFindReferencesParameters &parameters,
608                                                  const CPlusPlus::Snapshot &snapshot,
609                                                  CPlusPlus::LookupContext *context)
610 {
611     QTC_ASSERT(context, return nullptr);
612     QString symbolFile = QLatin1String(parameters.symbolFileName);
613     if (!snapshot.contains(symbolFile))
614         return nullptr;
615 
616     CPlusPlus::Document::Ptr newSymbolDocument = snapshot.document(symbolFile);
617     // document is not parsed and has no bindings yet, do it
618     QByteArray source = getSource(Utils::FilePath::fromString(newSymbolDocument->fileName()),
619                                   m_modelManager->workingCopy());
620     CPlusPlus::Document::Ptr doc =
621             snapshot.preprocessedDocument(source, newSymbolDocument->fileName());
622     doc->check();
623 
624     // find matching symbol in new document and return the new parameters
625     UidSymbolFinder finder(parameters.symbolId);
626     finder.accept(doc->globalNamespace());
627     if (finder.result()) {
628         *context = CPlusPlus::LookupContext(doc, snapshot);
629         return finder.result();
630     }
631     return nullptr;
632 }
633 
displayResults(SearchResult * search,QFutureWatcher<CPlusPlus::Usage> * watcher,int first,int last)634 static void displayResults(SearchResult *search, QFutureWatcher<CPlusPlus::Usage> *watcher,
635                            int first, int last)
636 {
637     CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
638 
639     for (int index = first; index != last; ++index) {
640         const CPlusPlus::Usage result = watcher->future().resultAt(index);
641         SearchResultItem item;
642         item.setFilePath(result.path);
643         item.setMainRange(result.line, result.col, result.len);
644         item.setLineText(result.lineText);
645         item.setUserData(int(result.type));
646         item.setStyle(colorStyleForUsageType(result.type));
647         item.setUseTextEditorFont(true);
648         if (search->supportsReplace())
649             item.setSelectForReplacement(SessionManager::projectForFile(result.path));
650         search->addResult(item);
651 
652         if (parameters.prettySymbolName.isEmpty())
653             continue;
654 
655         if (Utils::contains(parameters.filesToRename, Utils::equal(&Node::filePath, result.path)))
656             continue;
657 
658         Node *node = ProjectTree::nodeForFile(result.path);
659         if (!node) // Not part of any project
660             continue;
661 
662         const QFileInfo fi = node->filePath().toFileInfo();
663         if (fi.baseName().compare(parameters.prettySymbolName, Qt::CaseInsensitive) == 0)
664             parameters.filesToRename.append(node);
665     }
666 
667     search->setUserData(QVariant::fromValue(parameters));
668 }
669 
searchFinished(SearchResult * search,QFutureWatcher<CPlusPlus::Usage> * watcher)670 static void searchFinished(SearchResult *search, QFutureWatcher<CPlusPlus::Usage> *watcher)
671 {
672     search->finishSearch(watcher->isCanceled());
673 
674     CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
675     if (!parameters.filesToRename.isEmpty()) {
676         const QStringList filesToRename
677                 = Utils::transform<QList>(parameters.filesToRename, [](const Node *node) {
678             return node->filePath().toUserOutput();
679         });
680 
681         auto renameCheckBox = qobject_cast<QCheckBox *>(search->additionalReplaceWidget());
682         if (renameCheckBox) {
683             renameCheckBox->setText(CppFindReferences::tr("Re&name %n files", nullptr, filesToRename.size()));
684             renameCheckBox->setToolTip(CppFindReferences::tr("Files:\n%1").arg(filesToRename.join('\n')));
685             renameCheckBox->setVisible(true);
686         }
687     }
688 
689     watcher->deleteLater();
690 }
691 
692 namespace {
693 
694 class FindMacroUsesInFile
695 {
696     const WorkingCopy workingCopy;
697     const CPlusPlus::Snapshot snapshot;
698     const CPlusPlus::Macro &macro;
699     QFutureInterface<CPlusPlus::Usage> *future;
700 
701 public:
702     // needed by QtConcurrent
703     using argument_type = const Utils::FilePath &;
704     using result_type = QList<CPlusPlus::Usage>;
705 
FindMacroUsesInFile(const WorkingCopy & workingCopy,const CPlusPlus::Snapshot snapshot,const CPlusPlus::Macro & macro,QFutureInterface<CPlusPlus::Usage> * future)706     FindMacroUsesInFile(const WorkingCopy &workingCopy,
707                         const CPlusPlus::Snapshot snapshot,
708                         const CPlusPlus::Macro &macro,
709                         QFutureInterface<CPlusPlus::Usage> *future)
710         : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future)
711     { }
712 
operator ()(const Utils::FilePath & fileName)713     QList<CPlusPlus::Usage> operator()(const Utils::FilePath &fileName)
714     {
715         QList<CPlusPlus::Usage> usages;
716         CPlusPlus::Document::Ptr doc = snapshot.document(fileName);
717         QByteArray source;
718 
719 restart_search:
720         if (future->isPaused())
721             future->waitForResume();
722         if (future->isCanceled())
723             return usages;
724 
725         usages.clear();
726         foreach (const CPlusPlus::Document::MacroUse &use, doc->macroUses()) {
727             const CPlusPlus::Macro &useMacro = use.macro();
728 
729             if (useMacro.fileName() == macro.fileName()) { // Check if this is a match, but possibly against an outdated document.
730                 if (source.isEmpty())
731                     source = getSource(fileName, workingCopy);
732 
733                 if (macro.fileRevision() > useMacro.fileRevision()) {
734                     // yes, it is outdated, so re-preprocess and start from scratch for this file.
735                     doc = snapshot.preprocessedDocument(source, fileName);
736                     usages.clear();
737                     goto restart_search;
738                 }
739 
740                 if (macro.name() == useMacro.name()) {
741                     unsigned column;
742                     const QString &lineSource = matchingLine(use.bytesBegin(), source, &column);
743                     usages.append(CPlusPlus::Usage(fileName, lineSource,
744                                                    CPlusPlus::Usage::Type::Other, use.beginLine(),
745                                                    column, useMacro.nameToQString().size()));
746                 }
747             }
748         }
749 
750         if (future->isPaused())
751             future->waitForResume();
752         return usages;
753     }
754 
matchingLine(unsigned bytesOffsetOfUseStart,const QByteArray & utf8Source,unsigned * columnOfUseStart=nullptr)755     static QString matchingLine(unsigned bytesOffsetOfUseStart, const QByteArray &utf8Source,
756                                 unsigned *columnOfUseStart = nullptr)
757     {
758         int lineBegin = utf8Source.lastIndexOf('\n', bytesOffsetOfUseStart) + 1;
759         int lineEnd = utf8Source.indexOf('\n', bytesOffsetOfUseStart);
760         if (lineEnd == -1)
761             lineEnd = utf8Source.length();
762 
763         if (columnOfUseStart) {
764             *columnOfUseStart = 0;
765             const char *startOfUse = utf8Source.constData() + bytesOffsetOfUseStart;
766             QTC_ASSERT(startOfUse < utf8Source.constData() + lineEnd, return QString());
767             const char *currentSourceByte = utf8Source.constData() + lineBegin;
768             unsigned char yychar = *currentSourceByte;
769             while (currentSourceByte != startOfUse)
770                 CPlusPlus::Lexer::yyinp_utf8(currentSourceByte, yychar, *columnOfUseStart);
771         }
772 
773         const QByteArray matchingLine = utf8Source.mid(lineBegin, lineEnd - lineBegin);
774         return QString::fromUtf8(matchingLine, matchingLine.size());
775     }
776 };
777 
778 } // end of anonymous namespace
779 
findMacroUses_helper(QFutureInterface<CPlusPlus::Usage> & future,const WorkingCopy workingCopy,const CPlusPlus::Snapshot snapshot,const CPlusPlus::Macro macro)780 static void findMacroUses_helper(QFutureInterface<CPlusPlus::Usage> &future,
781                                  const WorkingCopy workingCopy,
782                                  const CPlusPlus::Snapshot snapshot,
783                                  const CPlusPlus::Macro macro)
784 {
785     const Utils::FilePath sourceFile = Utils::FilePath::fromString(macro.fileName());
786     Utils::FilePaths files{sourceFile};
787     files = Utils::filteredUnique(files + snapshot.filesDependingOn(sourceFile));
788 
789     future.setProgressRange(0, files.size());
790     FindMacroUsesInFile process(workingCopy, snapshot, macro, &future);
791     UpdateUI reduce(&future);
792     // This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count
793     // so the blockingMappedReduced can use one more thread, and increase it again afterwards.
794     QThreadPool::globalInstance()->releaseThread();
795     QtConcurrent::blockingMappedReduced<QList<CPlusPlus::Usage> > (files, process, reduce);
796     QThreadPool::globalInstance()->reserveThread();
797     future.setProgressValue(files.size());
798 }
799 
findMacroUses(const CPlusPlus::Macro & macro)800 void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro)
801 {
802     findMacroUses(macro, QString(), false);
803 }
804 
findMacroUses(const CPlusPlus::Macro & macro,const QString & replacement,bool replace)805 void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro, const QString &replacement,
806                                       bool replace)
807 {
808     SearchResult *search = SearchResultWindow::instance()->startNewSearch(
809                 tr("C++ Macro Usages:"),
810                 QString(),
811                 macro.nameToQString(),
812                 replace ? SearchResultWindow::SearchAndReplace
813                         : SearchResultWindow::SearchOnly,
814                 SearchResultWindow::PreserveCaseDisabled,
815                 QLatin1String("CppEditor"));
816 
817     search->setTextToReplace(replacement);
818     auto renameFilesCheckBox = new QCheckBox();
819     renameFilesCheckBox->setVisible(false);
820     search->setAdditionalReplaceWidget(renameFilesCheckBox);
821     connect(search, &SearchResult::replaceButtonClicked,
822             this, &CppFindReferences::onReplaceButtonClicked);
823 
824     SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
825 
826     connect(search, &SearchResult::activated,
827             [](const Core::SearchResultItem& item) {
828                 Core::EditorManager::openEditorAtSearchResult(item);
829             });
830 
831     const CPlusPlus::Snapshot snapshot = m_modelManager->snapshot();
832     const WorkingCopy workingCopy = m_modelManager->workingCopy();
833 
834     // add the macro definition itself
835     {
836         const QByteArray &source = getSource(Utils::FilePath::fromString(macro.fileName()),
837                                              workingCopy);
838         unsigned column;
839         const QString line = FindMacroUsesInFile::matchingLine(macro.bytesOffset(), source,
840                                                                &column);
841         SearchResultItem item;
842         const Utils::FilePath filePath = Utils::FilePath::fromString(macro.fileName());
843         item.setFilePath(filePath);
844         item.setLineText(line);
845         item.setMainRange(macro.line(), column, macro.nameToQString().length());
846         item.setUseTextEditorFont(true);
847         if (search->supportsReplace())
848             item.setSelectForReplacement(SessionManager::projectForFile(filePath));
849         search->addResult(item);
850     }
851 
852     QFuture<CPlusPlus::Usage> result;
853     result = Utils::runAsync(m_modelManager->sharedThreadPool(), findMacroUses_helper,
854                              workingCopy, snapshot, macro);
855     createWatcher(result, search);
856 
857     FutureProgress *progress = ProgressManager::addTask(result, tr("Searching for Usages"),
858                                                               CppTools::Constants::TASK_SEARCH);
859     connect(progress, &FutureProgress::clicked, search, &SearchResult::popup);
860 }
861 
renameMacroUses(const CPlusPlus::Macro & macro,const QString & replacement)862 void CppFindReferences::renameMacroUses(const CPlusPlus::Macro &macro, const QString &replacement)
863 {
864     const QString textToReplace = replacement.isEmpty() ? macro.nameToQString() : replacement;
865     findMacroUses(macro, textToReplace, true);
866 }
867 
createWatcher(const QFuture<CPlusPlus::Usage> & future,SearchResult * search)868 void CppFindReferences::createWatcher(const QFuture<CPlusPlus::Usage> &future, SearchResult *search)
869 {
870     auto watcher = new QFutureWatcher<CPlusPlus::Usage>();
871     // auto-delete:
872     connect(watcher, &QFutureWatcherBase::finished, watcher, [search, watcher]() {
873                 searchFinished(search, watcher);
874             });
875 
876     connect(watcher, &QFutureWatcherBase::resultsReadyAt, search,
877             [search, watcher](int first, int last) {
878                 displayResults(search, watcher, first, last);
879             });
880     connect(watcher, &QFutureWatcherBase::finished, search, [search, watcher]() {
881         search->finishSearch(watcher->isCanceled());
882     });
883     connect(search, &SearchResult::cancelled, watcher, [watcher]() { watcher->cancel(); });
884     connect(search, &SearchResult::paused, watcher, [watcher](bool paused) {
885         if (!paused || watcher->isRunning()) // guard against pausing when the search is finished
886             watcher->setPaused(paused);
887     });
888     watcher->setPendingResultsLimit(1);
889     watcher->setFuture(future);
890 }
891 
892 } // namespace Internal
893 } // namespace CppTools
894