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 ¶meters,
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 ¯o;
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 ¯o,
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 ¯o)
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 ¯o, 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 ¯o, 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