diff --git a/src/libs/cplusplus/declarationcomments.cpp b/src/libs/cplusplus/declarationcomments.cpp index 2283fa847ff..f67ac1be9d5 100644 --- a/src/libs/cplusplus/declarationcomments.cpp +++ b/src/libs/cplusplus/declarationcomments.cpp @@ -8,58 +8,35 @@ #include #include +#include #include +#include #include #include namespace CPlusPlus { -QList commentsForDeclaration(const Symbol *symbol, const Snapshot &snapshot, - const QTextDocument &textDoc) +static QString nameFromSymbol(const Symbol *symbol) { - // Set up cpp document. - const Document::Ptr cppDoc = snapshot.preprocessedDocument(textDoc.toPlainText().toUtf8(), - symbol->filePath()); - cppDoc->parse(); - TranslationUnit * const tu = cppDoc->translationUnit(); - if (!tu || !tu->isParsed()) + const QStringList symbolParts = Overview().prettyName(symbol->name()) + .split("::", Qt::SkipEmptyParts); + if (symbolParts.isEmpty()) return {}; + return symbolParts.last(); +} - // Find the symbol declaration's AST node. - // We stop at the last declaration node that precedes the symbol, except: - // - For parameter declarations, we just continue, because we are interested in the function. - // - If the declaration node is preceded directly by another one, we choose that one instead, - // because with nested declarations we want the outer one (e.g. templates). - int line, column; - tu->getTokenPosition(symbol->sourceLocation(), &line, &column); - const QList astPath = ASTPath(cppDoc)(line, column); - if (astPath.isEmpty()) - return {}; - if (astPath.last()->firstToken() != symbol->sourceLocation()) - return {}; - const AST *declAst = nullptr; - bool needsSymbolReference = false; - bool isParameter = false; - for (auto it = std::next(std::rbegin(astPath)); it != std::rend(astPath); ++it) { - AST * const node = *it; - if (node->asParameterDeclaration()) { - needsSymbolReference = true; - isParameter = true; - continue; - } - if (node->asDeclaration()) { - declAst = node; - continue; - } - if (declAst) - break; - } - if (!declAst) +static QList commentsForDeclaration( + const AST *decl, const QString &symbolName, const QTextDocument &textDoc, + const Document::Ptr &cppDoc, bool isParameter) +{ + if (symbolName.isEmpty()) return {}; // Get the list of all tokens (including comments) and find the declaration start token there. - const Token &declToken = tu->tokenAt(declAst->firstToken()); + TranslationUnit * const tu = cppDoc->translationUnit(); + QTC_ASSERT(tu && tu->isParsed(), return {}); + const Token &declToken = tu->tokenAt(decl->firstToken()); std::vector allTokens = tu->allTokens(); QTC_ASSERT(!allTokens.empty(), return {}); int tokenPos = -1; @@ -86,6 +63,7 @@ QList commentsForDeclaration(const Symbol *symbol, const Snapshot &snapsh const auto blockForTokenEnd = [&](const Token &tok) { return textDoc.findBlock(tu->getTokenEndPositionInDocument(tok, &textDoc)); }; + bool needsSymbolReference = isParameter; for (int i = tokenPos - 1; i >= 0; --i) { const Token &tok = allTokens.at(i); if (!tok.isComment()) @@ -127,7 +105,6 @@ QList commentsForDeclaration(const Symbol *symbol, const Snapshot &snapsh return tokenList(); // b) - const QString symbolName = Overview().prettyName(symbol->name()); const Kind tokenKind = comments.first().token.kind(); const bool isDoxygenComment = tokenKind == T_DOXY_COMMENT || tokenKind == T_CPP_DOXY_COMMENT; const QRegularExpression symbolRegExp(QString("%1\\b%2\\b").arg( @@ -142,4 +119,57 @@ QList commentsForDeclaration(const Symbol *symbol, const Snapshot &snapsh return {}; } + +QList commentsForDeclaration(const Symbol *symbol, const QTextDocument &textDoc, + const Document::Ptr &cppDoc) +{ + QTC_ASSERT(cppDoc->translationUnit() && cppDoc->translationUnit()->isParsed(), return {}); + Utils::Text::Position pos; + cppDoc->translationUnit()->getTokenPosition(symbol->sourceLocation(), &pos.line, &pos.column); + --pos.column; + return commentsForDeclaration(nameFromSymbol(symbol), pos, textDoc, cppDoc); +} + +QList commentsForDeclaration(const QString &symbolName, const Utils::Text::Position &pos, + const QTextDocument &textDoc, const Document::Ptr &cppDoc) +{ + if (symbolName.isEmpty()) + return {}; + + // Find the symbol declaration's AST node. + // We stop at the last declaration node that precedes the symbol, except: + // - For parameter declarations, we just continue, because we are interested in the function. + // - If the declaration node is preceded directly by another one, we choose that one instead, + // because with nested declarations we want the outer one (e.g. templates). + const QList astPath = ASTPath(cppDoc)(pos.line, pos.column + 1); + if (astPath.isEmpty()) + return {}; + const AST *declAst = nullptr; + bool isParameter = false; + for (auto it = std::next(std::rbegin(astPath)); it != std::rend(astPath); ++it) { + AST * const node = *it; + if (node->asParameterDeclaration()) { + isParameter = true; + continue; + } + if (node->asDeclaration()) { + declAst = node; + continue; + } + if (declAst) + break; + } + if (!declAst) + return {}; + + return commentsForDeclaration(declAst, symbolName, textDoc, cppDoc, isParameter); +} + +QList commentsForDeclaration(const Symbol *symbol, const AST *decl, + const QTextDocument &textDoc, const Document::Ptr &cppDoc) +{ + return commentsForDeclaration(decl, nameFromSymbol(symbol), textDoc, cppDoc, + symbol->asArgument()); +} + } // namespace CPlusPlus diff --git a/src/libs/cplusplus/declarationcomments.h b/src/libs/cplusplus/declarationcomments.h index 490290450ce..7b775ad7549 100644 --- a/src/libs/cplusplus/declarationcomments.h +++ b/src/libs/cplusplus/declarationcomments.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -11,11 +12,24 @@ QT_BEGIN_NAMESPACE class QTextDocument; QT_END_NAMESPACE +namespace Utils { namespace Text { class Position; } } + namespace CPlusPlus { -class Snapshot; +class AST; +class Symbol; QList CPLUSPLUS_EXPORT commentsForDeclaration(const Symbol *symbol, - const Snapshot &snapshot, - const QTextDocument &textDoc); + const QTextDocument &textDoc, + const Document::Ptr &cppDoc); + +QList CPLUSPLUS_EXPORT commentsForDeclaration(const Symbol *symbol, + const AST *decl, + const QTextDocument &textDoc, + const Document::Ptr &cppDoc); + +QList CPLUSPLUS_EXPORT commentsForDeclaration(const QString &symbolName, + const Utils::Text::Position &pos, + const QTextDocument &textDoc, + const Document::Ptr &cppDoc); } // namespace CPlusPlus diff --git a/src/libs/utils/searchresultitem.h b/src/libs/utils/searchresultitem.h index ea9d0332d5a..bbaaa1caf5e 100644 --- a/src/libs/utils/searchresultitem.h +++ b/src/libs/utils/searchresultitem.h @@ -96,6 +96,11 @@ private: using SearchResultItems = QList; +inline size_t qHash(const SearchResultItem &item) +{ + return item.mainRange().begin.line << 16 | item.mainRange().begin.column; +} + } // namespace Utils Q_DECLARE_METATYPE(Utils::SearchResultItem) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 43db68c24b7..bb9ec7fc1be 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -402,6 +402,9 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir, c setCompletionAssistProvider(new ClangdCompletionAssistProvider(this)); setQuickFixAssistProvider(new ClangdQuickFixProvider(this)); symbolSupport().setLimitRenamingToProjects(true); + symbolSupport().setRenameResultsEnhancer([](const SearchResultItems &symbolOccurrencesInCode) { + return CppEditor::symbolOccurrencesInDeclarationComments(symbolOccurrencesInCode); + }); if (!project) { QJsonObject initOptions; const Utils::FilePath includeDir @@ -752,6 +755,13 @@ bool ClangdClient::fileBelongsToProject(const Utils::FilePath &filePath) const return Client::fileBelongsToProject(filePath); } +QList ClangdClient::additionalDocumentHighlights( + TextEditorWidget *editorWidget, const QTextCursor &cursor) +{ + return CppEditor::symbolOccurrencesInDeclarationComments( + qobject_cast(editorWidget), cursor); +} + RefactoringChangesData *ClangdClient::createRefactoringChangesBackend() const { return new CppEditor::CppRefactoringChangesData( @@ -1056,9 +1066,11 @@ void ClangdClient::switchHeaderSource(const Utils::FilePath &filePath, bool inNe sendMessage(req); } -void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cursor, - CppEditor::RenameCallback &&callback) +void ClangdClient::findLocalUsages(CppEditor::CppEditorWidget *editorWidget, + const QTextCursor &cursor, CppEditor::RenameCallback &&callback) { + QTC_ASSERT(editorWidget, return); + TextDocument * const document = editorWidget->textDocument(); QTC_ASSERT(documentOpen(document), openDocument(document)); qCDebug(clangdLog) << "local references requested" << document->filePath() @@ -1076,7 +1088,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu return; } - d->findLocalRefs = new ClangdFindLocalReferences(this, document, cursor, callback); + d->findLocalRefs = new ClangdFindLocalReferences(this, editorWidget, cursor, callback); connect(d->findLocalRefs, &ClangdFindLocalReferences::done, this, [this] { d->findLocalRefs->deleteLater(); d->findLocalRefs = nullptr; diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 2b354eaa2cc..4ac861127cc 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -74,7 +74,7 @@ public: const Utils::LinkHandler &callback); void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit); - void findLocalUsages(TextEditor::TextDocument *document, const QTextCursor &cursor, + void findLocalUsages(CppEditor::CppEditorWidget *editorWidget, const QTextCursor &cursor, CppEditor::RenameCallback &&callback); void gatherHelpItemForTooltip( @@ -148,6 +148,9 @@ private: bool referencesShadowFile(const TextEditor::TextDocument *doc, const Utils::FilePath &candidate) override; bool fileBelongsToProject(const Utils::FilePath &filePath) const override; + QList additionalDocumentHighlights( + TextEditor::TextEditorWidget *editorWidget, const QTextCursor &cursor) override; + class Private; class VirtualFunctionAssistProcessor; diff --git a/src/plugins/clangcodemodel/clangdfindreferences.cpp b/src/plugins/clangcodemodel/clangdfindreferences.cpp index d6a91aa334b..24175a82504 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.cpp +++ b/src/plugins/clangcodemodel/clangdfindreferences.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -670,10 +671,10 @@ ClangdFindReferences::CheckUnusedData::~CheckUnusedData() class ClangdFindLocalReferences::Private { public: - Private(ClangdFindLocalReferences *q, TextDocument *document, const QTextCursor &cursor, + Private(ClangdFindLocalReferences *q, CppEditorWidget *editorWidget, const QTextCursor &cursor, const RenameCallback &callback) - : q(q), document(document), cursor(cursor), callback(callback), - uri(client()->hostPathToServerUri(document->filePath())), + : q(q), editorWidget(editorWidget), document(editorWidget->textDocument()), cursor(cursor), + callback(callback), uri(client()->hostPathToServerUri(document->filePath())), revision(document->document()->revision()) {} @@ -685,6 +686,7 @@ public: void finish(); ClangdFindLocalReferences * const q; + const QPointer editorWidget; const QPointer document; const QTextCursor cursor; RenameCallback callback; @@ -694,9 +696,9 @@ public: }; ClangdFindLocalReferences::ClangdFindLocalReferences( - ClangdClient *client, TextDocument *document, const QTextCursor &cursor, - const RenameCallback &callback) - : QObject(client), d(new Private(this, document, cursor, callback)) + ClangdClient *client, CppEditorWidget *editorWidget, const QTextCursor &cursor, + const RenameCallback &callback) + : QObject(client), d(new Private(this, editorWidget, cursor, callback)) { d->findDefinition(); } @@ -780,7 +782,7 @@ void ClangdFindLocalReferences::Private::handleReferences(const QList return loc.toLink(mapper); }; - const Utils::Links links = Utils::transform(references, transformLocation); + Utils::Links links = Utils::transform(references, transformLocation); // The callback only uses the symbol length, so we just create a dummy. // Note that the calculation will be wrong for identifiers with @@ -788,7 +790,27 @@ void ClangdFindLocalReferences::Private::handleReferences(const QList QString symbol; if (!references.isEmpty()) { const Range r = references.first().range(); - symbol = QString(r.end().character() - r.start().character(), 'x'); + const Position pos = r.start(); + symbol = QString(r.end().character() - pos.character(), 'x'); + if (editorWidget && document) { + QTextCursor cursor(document->document()); + cursor.setPosition(Text::positionInText(document->document(), pos.line() + 1, + pos.character() + 1)); + const QList occurrencesInComments + = symbolOccurrencesInDeclarationComments(editorWidget, cursor); + for (const Text::Range &range : occurrencesInComments) { + static const auto cmp = [](const Link &l, const Text::Range &r) { + if (l.targetLine < r.begin.line) + return true; + if (l.targetLine > r.begin.line) + return false; + return l.targetColumn < r.begin.column; + }; + const auto it = std::lower_bound(links.begin(), links.end(), range, cmp); + links.emplace(it, links.first().targetFilePath, range.begin.line, + range.begin.column); + } + } } callback(symbol, links, revision); callback = {}; diff --git a/src/plugins/clangcodemodel/clangdfindreferences.h b/src/plugins/clangcodemodel/clangdfindreferences.h index e110b355434..c61afe17a1f 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.h +++ b/src/plugins/clangcodemodel/clangdfindreferences.h @@ -48,9 +48,9 @@ class ClangdFindLocalReferences : public QObject { Q_OBJECT public: - explicit ClangdFindLocalReferences(ClangdClient *client, TextEditor::TextDocument *document, - const QTextCursor &cursor, - const CppEditor::RenameCallback &callback); + explicit ClangdFindLocalReferences( + ClangdClient *client, CppEditor::CppEditorWidget *editorWidget, const QTextCursor &cursor, + const CppEditor::RenameCallback &callback); ~ClangdFindLocalReferences(); signals: diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index e9f37abb4e5..e1bd9114717 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -309,7 +309,7 @@ void ClangModelManagerSupport::startLocalRenaming(const CursorInEditor &data, { if (ClangdClient * const client = clientForFile(data.filePath()); client && client->reachable()) { - client->findLocalUsages(data.textDocument(), data.cursor(), + client->findLocalUsages(data.editorWidget(), data.cursor(), std::move(renameSymbolsCallback)); return; } diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index 54c9bfa7c1d..bfc6d1d4f8d 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -536,6 +537,8 @@ void ClangdTestLocalReferences::test_data() QTest::newRow("overloaded operators arguments from outside") << 171 << 7 << QList{{171, 6, 1}, {172, 6, 1}, {172, 11, 1}, {173, 6, 1}, {173, 9, 1}}; + QTest::newRow("documented function parameter") << 181 << 32 + << QList{{177, 10, 6}, {179, 9, 6}, {181, 31, 6}, {183, 6, 6}, {184, 17, 6}}; } void ClangdTestLocalReferences::test() @@ -546,6 +549,11 @@ void ClangdTestLocalReferences::test() TextEditor::TextDocument * const doc = document("references.cpp"); QVERIFY(doc); + const QList editors = BaseTextEditor::textEditorsForDocument(doc); + QCOMPARE(editors.size(), 1); + const auto editorWidget = qobject_cast( + editors.first()->editorWidget()); + QVERIFY(editorWidget); QTimer timer; timer.setSingleShot(true); @@ -561,7 +569,7 @@ void ClangdTestLocalReferences::test() QTextCursor cursor(doc->document()); const int pos = Text::positionInText(doc->document(), sourceLine, sourceColumn); cursor.setPosition(pos); - client()->findLocalUsages(doc, cursor, std::move(handler)); + client()->findLocalUsages(editorWidget, cursor, std::move(handler)); timer.start(10000); loop.exec(); QVERIFY(timer.isActive()); diff --git a/src/plugins/clangcodemodel/test/data/local-references/references.cpp b/src/plugins/clangcodemodel/test/data/local-references/references.cpp index 32ff1b90753..1c8556581d9 100644 --- a/src/plugins/clangcodemodel/test/data/local-references/references.cpp +++ b/src/plugins/clangcodemodel/test/data/local-references/references.cpp @@ -172,3 +172,14 @@ int testOperator() { vec[n] = n * 100; vec(n, n) = 100; } + +/* + * @param param1 + * @param param2 + * @note param1 and param2 should be the same. + */ +void funcWithParamComments(int param1, int param2) +{ + if (param1 != param2) + param2 = param1; +} diff --git a/src/plugins/coreplugin/find/searchresulttreeitems.cpp b/src/plugins/coreplugin/find/searchresulttreeitems.cpp index 9aa8a765db9..6eafd49615d 100644 --- a/src/plugins/coreplugin/find/searchresulttreeitems.cpp +++ b/src/plugins/coreplugin/find/searchresulttreeitems.cpp @@ -82,9 +82,26 @@ int SearchResultTreeItem::insertionIndex(const QString &text, SearchResultTreeIt } int SearchResultTreeItem::insertionIndex(const Utils::SearchResultItem &item, - SearchResultTreeItem **existingItem) const + SearchResultTreeItem **existingItem, + SearchResult::AddMode mode) const { - return insertionIndex(item.lineText(), existingItem); + switch (mode) { + case SearchResult::AddSortedByContent: + return insertionIndex(item.lineText(), existingItem); + case SearchResult::AddSortedByPosition: + break; + case Core::SearchResult::AddOrdered: + QTC_ASSERT(false, return 0); + } + + static const auto cmp = [](const SearchResultTreeItem *a, const Utils::Text::Position b) { + return a->item.mainRange().begin < b; + }; + const auto insertionPosition = + std::lower_bound(m_children.begin(), m_children.end(), item.mainRange().begin, cmp); + if (existingItem) + *existingItem = nullptr; + return insertionPosition - m_children.begin(); } void SearchResultTreeItem::insertChild(int index, SearchResultTreeItem *child) diff --git a/src/plugins/coreplugin/find/searchresulttreeitems.h b/src/plugins/coreplugin/find/searchresulttreeitems.h index dbbc27d8861..e3ec8aa1eae 100644 --- a/src/plugins/coreplugin/find/searchresulttreeitems.h +++ b/src/plugins/coreplugin/find/searchresulttreeitems.h @@ -21,7 +21,8 @@ public: SearchResultTreeItem *parent() const; SearchResultTreeItem *childAt(int index) const; int insertionIndex(const QString &text, SearchResultTreeItem **existingItem) const; - int insertionIndex(const Utils::SearchResultItem &item, SearchResultTreeItem **existingItem) const; + int insertionIndex(const Utils::SearchResultItem &item, SearchResultTreeItem **existingItem, + SearchResult::AddMode mode) const; void insertChild(int index, SearchResultTreeItem *child); void insertChild(int index, const Utils::SearchResultItem &item); void appendChild(const Utils::SearchResultItem &item); diff --git a/src/plugins/coreplugin/find/searchresulttreemodel.cpp b/src/plugins/coreplugin/find/searchresulttreemodel.cpp index b6d620b80b5..09f16f0453b 100644 --- a/src/plugins/coreplugin/find/searchresulttreemodel.cpp +++ b/src/plugins/coreplugin/find/searchresulttreemodel.cpp @@ -402,10 +402,10 @@ void SearchResultTreeModel::addResultsToCurrentParent(const SearchResultItems &i m_currentParent->appendChild(item); } endInsertRows(); - } else if (mode == SearchResult::AddSorted) { + } else { for (const SearchResultItem &item : items) { SearchResultTreeItem *existingItem; - const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem); + const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem, mode); if (existingItem) { existingItem->setGenerated(false); existingItem->item = item; diff --git a/src/plugins/coreplugin/find/searchresultwidget.cpp b/src/plugins/coreplugin/find/searchresultwidget.cpp index ec2ab2c0f64..06c59d95ecc 100644 --- a/src/plugins/coreplugin/find/searchresultwidget.cpp +++ b/src/plugins/coreplugin/find/searchresultwidget.cpp @@ -478,7 +478,7 @@ void SearchResultWidget::doReplace() { m_infoBar.clear(); setShowReplaceUI(false); - emit replaceButtonClicked(m_replaceTextEdit->text(), checkedItems(), + emit replaceButtonClicked(m_replaceTextEdit->text(), items(true), m_preserveCaseSupported && m_preserveCaseCheck->isChecked()); } @@ -496,7 +496,7 @@ void SearchResultWidget::searchAgain() emit searchAgainRequested(); } -SearchResultItems SearchResultWidget::checkedItems() const +SearchResultItems SearchResultWidget::items(bool checkedOnly) const { SearchResultItems result; SearchResultFilterModel *model = m_searchResultTreeView->model(); @@ -508,7 +508,7 @@ SearchResultItems SearchResultWidget::checkedItems() const const QModelIndex textIndex = model->index(rowIndex, 0, fileIndex); const SearchResultTreeItem * const rowItem = model->itemForIndex(textIndex); QTC_ASSERT(rowItem != nullptr, continue); - if (rowItem->checkState()) + if (!checkedOnly || rowItem->checkState()) result << rowItem->item; } } diff --git a/src/plugins/coreplugin/find/searchresultwidget.h b/src/plugins/coreplugin/find/searchresultwidget.h index 6722af579eb..a9f87c467f1 100644 --- a/src/plugins/coreplugin/find/searchresultwidget.h +++ b/src/plugins/coreplugin/find/searchresultwidget.h @@ -71,6 +71,7 @@ public: bool hasFilter() const; void showFilterWidget(QWidget *parent); void setReplaceEnabled(bool enabled); + Utils::SearchResultItems items(bool checkedOnly) const; public slots: void finishSearch(bool canceled, const QString &reason); @@ -103,7 +104,6 @@ private: void continueAfterSizeWarning(); void cancelAfterSizeWarning(); - Utils::SearchResultItems checkedItems() const; void updateMatchesFoundLabel(); SearchResultTreeView *m_searchResultTreeView = nullptr; diff --git a/src/plugins/coreplugin/find/searchresultwindow.cpp b/src/plugins/coreplugin/find/searchresultwindow.cpp index b591d9aa131..2f048f93f34 100644 --- a/src/plugins/coreplugin/find/searchresultwindow.cpp +++ b/src/plugins/coreplugin/find/searchresultwindow.cpp @@ -900,6 +900,11 @@ void Core::SearchResult::makeNonInteractive(const std::function &callba m_finishedHandler = callback; } +Utils::SearchResultItems SearchResult::allItems() const +{ + return m_widget->items(false); +} + } // namespace Core #include "searchresultwindow.moc" diff --git a/src/plugins/coreplugin/find/searchresultwindow.h b/src/plugins/coreplugin/find/searchresultwindow.h index eb621ac43f9..0b99269c964 100644 --- a/src/plugins/coreplugin/find/searchresultwindow.h +++ b/src/plugins/coreplugin/find/searchresultwindow.h @@ -43,7 +43,8 @@ class CORE_EXPORT SearchResult : public QObject public: enum AddMode { - AddSorted, + AddSortedByContent, + AddSortedByPosition, AddOrdered }; @@ -57,6 +58,7 @@ public: void setAdditionalReplaceWidget(QWidget *widget); void makeNonInteractive(const std::function &callback); bool isInteractive() const { return !m_finishedHandler; } + Utils::SearchResultItems allItems() const; public slots: void addResult(const Utils::SearchResultItem &item); diff --git a/src/plugins/cppeditor/cppfindreferences.cpp b/src/plugins/cppeditor/cppfindreferences.cpp index b933a0cde84..a894c3ffe06 100644 --- a/src/plugins/cppeditor/cppfindreferences.cpp +++ b/src/plugins/cppeditor/cppfindreferences.cpp @@ -600,6 +600,10 @@ static void displayResults(SearchResult *search, static void searchFinished(SearchResult *search, QFutureWatcher *watcher) { + if (!watcher->isCanceled() && search->supportsReplace()) { + search->addResults(symbolOccurrencesInDeclarationComments(search->allItems()), + SearchResult::AddSortedByPosition); + } search->finishSearch(watcher->isCanceled()); CppFindReferencesParameters parameters = search->userData().value(); diff --git a/src/plugins/cppeditor/cpplocalsymbols.cpp b/src/plugins/cppeditor/cpplocalsymbols.cpp index 66200b3f3dc..3dc93245184 100644 --- a/src/plugins/cppeditor/cpplocalsymbols.cpp +++ b/src/plugins/cppeditor/cpplocalsymbols.cpp @@ -4,8 +4,14 @@ #include "cpplocalsymbols.h" #include "cppsemanticinfo.h" +#include "cpptoolsreuse.h" #include "semantichighlighter.h" +#include +#include +#include +#include + using namespace CPlusPlus; namespace CppEditor::Internal { @@ -16,7 +22,7 @@ class FindLocalSymbols: protected ASTVisitor { public: explicit FindLocalSymbols(Document::Ptr doc) - : ASTVisitor(doc->translationUnit()) + : ASTVisitor(doc->translationUnit()), _doc(doc) { } // local and external uses. @@ -38,6 +44,42 @@ public: accept(ast); } } + + if (localUses.isEmpty()) + return; + + // Look for parameter occurrences in function comments. + const TextEditor::TextDocument * const editorDoc + = TextEditor::TextDocument::textDocumentForFilePath(_doc->filePath()); + if (!editorDoc) + return; + QTextDocument * const textDoc = editorDoc->document(); + if (!textDoc) + return; + const QString &content = textDoc->toPlainText(); + const QStringView docView(content); + for (auto it = localUses.begin(); it != localUses.end(); ++it) { + Symbol * const symbol = it.key(); + if (!symbol->asArgument()) + continue; + const QList commentTokens = commentsForDeclaration(symbol, ast, *textDoc, _doc); + if (commentTokens.isEmpty()) + continue; + const QString symbolName = Overview().prettyName(symbol->name()); + for (const Token &tok : commentTokens) { + const int commentPos = translationUnit()->getTokenPositionInDocument(tok, textDoc); + const int commentEndPos = translationUnit()->getTokenEndPositionInDocument( + tok, textDoc); + const QStringView commentView = docView.mid(commentPos, commentEndPos - commentPos); + const QList ranges = symbolOccurrencesInText( + *textDoc, commentView, commentPos, symbolName); + for (const Utils::Text::Range &range : ranges) { + it.value().append(HighlightingResult(range.begin.line, range.begin.column + 1, + symbolName.size(), + SemanticHighlighter::LocalUse)); + } + } + } } protected: @@ -275,6 +317,7 @@ protected: private: QList _scopeStack; + Document::Ptr _doc; }; } // end of anonymous namespace diff --git a/src/plugins/cppeditor/cpprenaming_test.cpp b/src/plugins/cppeditor/cpprenaming_test.cpp index d1b9a9891df..2827434be55 100644 --- a/src/plugins/cppeditor/cpprenaming_test.cpp +++ b/src/plugins/cppeditor/cpprenaming_test.cpp @@ -82,19 +82,19 @@ void MyClass::run() {} origHeaderClassName.insert(classOffset + 6, '@'); const QByteArray newHeaderClassName = R"cpp( /** - * \brief MyClass + * \brief MyNewClass */ class MyNewClass { - /** \brief MyClass::MyClass */ + /** \brief MyNewClass::MyNewClass */ MyNewClass() {} ~MyNewClass(); - /** \brief MyClass::run */ + /** \brief MyNewClass::run */ void run(); }; )cpp"; const QByteArray newSourceClassName = R"cpp( #include "file.h" -/** \brief MyClass::~MyClass */ +/** \brief MyNewClass::~MyNewClass */ MyNewClass::~MyNewClass() {} void MyNewClass::run() {} @@ -115,7 +115,7 @@ class MyClass { /** \brief MyClass::MyClass */ MyClass() {} ~MyClass(); - /** \brief MyClass::run */ + /** \brief MyClass::runAgain */ void runAgain(); }; )cpp"; diff --git a/src/plugins/cppeditor/cpptoolsreuse.cpp b/src/plugins/cppeditor/cpptoolsreuse.cpp index 325aa8bb249..c098aca99c8 100644 --- a/src/plugins/cppeditor/cpptoolsreuse.cpp +++ b/src/plugins/cppeditor/cpptoolsreuse.cpp @@ -5,10 +5,12 @@ #include "clangdiagnosticconfigsmodel.h" #include "cppautocompleter.h" +#include "cppcanonicalsymbol.h" #include "cppcodemodelsettings.h" #include "cppcompletionassist.h" #include "cppeditorconstants.h" #include "cppeditorplugin.h" +#include "cppeditorwidget.h" #include "cppeditortr.h" #include "cppfilesettingspage.h" #include "cpphighlighter.h" @@ -28,20 +30,27 @@ #include #include +#include #include #include #include #include -#include #include +#include +#include #include +#include +#include #include #include +#include #include #include +#include + using namespace CPlusPlus; using namespace Utils; @@ -626,6 +635,200 @@ QString preferredCxxSourceSuffix(ProjectExplorer::Project *project) return Internal::CppEditorPlugin::fileSettings(project).sourceSuffix; } +SearchResultItems symbolOccurrencesInDeclarationComments( + const Utils::SearchResultItems &symbolOccurrencesInCode) +{ + if (symbolOccurrencesInCode.isEmpty()) + return {}; + + // When using clangd, this function gets called every time the replacement string changes, + // so cache the results. + static QHash resultCache; + if (const auto it = resultCache.constFind(symbolOccurrencesInCode); + it != resultCache.constEnd()) { + return it.value(); + } + if (resultCache.size() > 5) + resultCache.clear(); + + QElapsedTimer timer; + timer.start(); + Snapshot snapshot = CppModelManager::snapshot(); + std::vector> docPool; + using FileData = std::tuple>; + QHash dataPerFile; + QString symbolName; + const auto fileData = [&](const FilePath &filePath) -> FileData & { + auto &data = dataPerFile[filePath]; + auto &[doc, content, cppDoc, allCommentTokens] = data; + if (!doc) { + if (TextEditor::TextDocument * const textDoc + = TextEditor::TextDocument::textDocumentForFilePath(filePath)) { + doc = textDoc->document(); + } else { + std::unique_ptr newDoc = std::make_unique(); + if (const auto content = TextFileFormat::readFile( + filePath, Core::EditorManager::defaultTextCodec())) { + newDoc->setPlainText(content.value()); + } + doc = newDoc.get(); + docPool.push_back(std::move(newDoc)); + } + content = doc->toPlainText(); + cppDoc = snapshot.preprocessedDocument(content.toUtf8(), filePath); + cppDoc->check(); + } + return data; + }; + static const auto addToken = [](QList &tokens, const Token &tok) { + if (!Utils::contains(tokens, [&tok](const Token &t) { + return t.byteOffset == tok.byteOffset; })) { + tokens << tok; + } + }; + + // Collect comment blocks associated with replace locations. + Symbol *canonicalSymbol = nullptr; + for (const SearchResultItem &item : symbolOccurrencesInCode) { + const FilePath filePath = FilePath::fromUserInput(item.path().last()); + auto &[doc, content, cppDoc, allCommentTokens] = fileData(filePath); + const Text::Range &range = item.mainRange(); + if (symbolName.isEmpty()) { + const int symbolStartPos = Utils::Text::positionInText(doc, range.begin.line, + range.begin.column + 1); + const int symbolEndPos = Utils::Text::positionInText(doc, range.end.line, + range.end.column + 1); + symbolName = content.mid(symbolStartPos, symbolEndPos - symbolStartPos); + } + const QList commentTokens = commentsForDeclaration(symbolName, range.begin, + *doc, cppDoc); + for (const Token &tok : commentTokens) + addToken(allCommentTokens, tok); + + if (!canonicalSymbol) { + QTextCursor cursor(doc); + cursor.setPosition(Text::positionInText(doc, range.begin.line, range.begin.column + 1)); + canonicalSymbol = Internal::CanonicalSymbol(cppDoc, snapshot)(cursor); + } + + // We hook in between the end of the "regular" search and (possibly non-interactive) + // actions on it, so we must run synchronously in the UI thread and therefore be fast. + // If we notice we are lagging, just abort, as renaming the comments is not + // required for code correctness. + if (timer.elapsed() > 1000) { + resultCache.insert(symbolOccurrencesInCode, {}); + return {}; + } + } + + // If the symbol is a class, collect all comment blocks in the class body. + if (Class * const klass = canonicalSymbol ? canonicalSymbol->asClass() : nullptr) { + auto &[_1, _2, symbolCppDoc, commentTokens] = fileData(canonicalSymbol->filePath()); + TranslationUnit * const tu = symbolCppDoc->translationUnit(); + for (int i = 0; i < tu->commentCount(); ++i) { + const Token &tok = tu->commentAt(i); + if (tok.bytesBegin() < klass->startOffset()) + continue; + if (tok.bytesBegin() >= klass->endOffset()) + break; + addToken(commentTokens, tok); + } + } + + // Create new replace items for occurrences of the symbol name in collected comment blocks. + SearchResultItems commentItems; + for (auto it = dataPerFile.cbegin(); it != dataPerFile.cend(); ++it) { + const auto &[doc, content, cppDoc, commentTokens] = it.value(); + const QStringView docView(content); + for (const Token &tok : commentTokens) { + const int tokenStartPos = cppDoc->translationUnit()->getTokenPositionInDocument( + tok, doc); + const int tokenEndPos = cppDoc->translationUnit()->getTokenEndPositionInDocument( + tok, doc); + const QStringView tokenView = docView.mid(tokenStartPos, tokenEndPos - tokenStartPos); + const QList ranges = symbolOccurrencesInText( + *doc, tokenView, tokenStartPos, symbolName); + for (const Text::Range &range : ranges) { + SearchResultItem item; + item.setUseTextEditorFont(true); + item.setFilePath(it.key()); + item.setMainRange(range); + item.setLineText(doc->findBlockByNumber(range.begin.line - 1).text()); + commentItems << item; + } + } + } + + resultCache.insert(symbolOccurrencesInCode, commentItems); + return commentItems; +} + +QList symbolOccurrencesInText(const QTextDocument &doc, QStringView text, int offset, + const QString &symbolName) +{ + QList ranges; + int index = 0; + while (true) { + index = text.indexOf(symbolName, index); + if (index == -1) + break; + + // Prevent substring matching. + const auto checkAdjacent = [&](int i) { + if (i == -1 || i == text.size()) + return true; + const QChar c = text.at(i); + if (c.isLetterOrNumber() || c == '_') { + index += symbolName.length(); + return false; + } + return true; + }; + if (!checkAdjacent(index - 1)) + continue; + if (!checkAdjacent(index + symbolName.length())) + continue; + + const Text::Position startPos = Text::Position::fromPositionInDocument(&doc, offset + index); + index += symbolName.length(); + const Text::Position endPos = Text::Position::fromPositionInDocument(&doc, offset + index); + ranges << Text::Range{startPos, endPos}; + } + return ranges; +} + +QList symbolOccurrencesInDeclarationComments(CppEditorWidget *editorWidget, + const QTextCursor &cursor) +{ + if (!editorWidget) + return {}; + const SemanticInfo &semanticInfo = editorWidget->semanticInfo(); + const Document::Ptr &cppDoc = semanticInfo.doc; + if (!cppDoc) + return {}; + const Symbol * const symbol = Internal::CanonicalSymbol(cppDoc, semanticInfo.snapshot)(cursor); + if (!symbol || !symbol->asArgument()) + return {}; + const QTextDocument * const textDoc = editorWidget->textDocument()->document(); + QTC_ASSERT(textDoc, return {}); + const QList comments = commentsForDeclaration(symbol, *textDoc, cppDoc); + if (comments.isEmpty()) + return {}; + QList ranges; + const QString &content = textDoc->toPlainText(); + const QStringView docView = QStringView(content); + const QString symbolName = Overview().prettyName(symbol->name()); + for (const Token &tok : comments) { + const int tokenStartPos = cppDoc->translationUnit()->getTokenPositionInDocument( + tok, textDoc); + const int tokenEndPos = cppDoc->translationUnit()->getTokenEndPositionInDocument( + tok, textDoc); + const QStringView tokenView = docView.mid(tokenStartPos, tokenEndPos - tokenStartPos); + ranges << symbolOccurrencesInText(*textDoc, tokenView, tokenStartPos, symbolName); + } + return ranges; +} + namespace Internal { void decorateCppEditor(TextEditor::TextEditorWidget *editor) diff --git a/src/plugins/cppeditor/cpptoolsreuse.h b/src/plugins/cppeditor/cpptoolsreuse.h index 78d158b5764..fc90b900a24 100644 --- a/src/plugins/cppeditor/cpptoolsreuse.h +++ b/src/plugins/cppeditor/cpptoolsreuse.h @@ -12,6 +12,8 @@ #include #include +#include + #include #include #include @@ -23,9 +25,10 @@ class LookupContext; } // namespace CPlusPlus namespace TextEditor { class AssistInterface; } +namespace Utils { namespace Text { class Range; } } namespace CppEditor { - +class CppEditorWidget; class CppRefactoringFile; class ProjectInfo; class CppCompletionAssistProcessor; @@ -71,6 +74,14 @@ QString CPPEDITOR_EXPORT preferredCxxHeaderSuffix(ProjectExplorer::Project *proj QString CPPEDITOR_EXPORT preferredCxxSourceSuffix(ProjectExplorer::Project *project); bool CPPEDITOR_EXPORT preferLowerCaseFileNames(ProjectExplorer::Project *project); + +QList CPPEDITOR_EXPORT symbolOccurrencesInText( + const QTextDocument &doc, QStringView text, int offset, const QString &symbolName); +Utils::SearchResultItems CPPEDITOR_EXPORT +symbolOccurrencesInDeclarationComments(const Utils::SearchResultItems &symbolOccurrencesInCode); +QList CPPEDITOR_EXPORT symbolOccurrencesInDeclarationComments( + CppEditorWidget *editorWidget, const QTextCursor &cursor); + UsePrecompiledHeaders CPPEDITOR_EXPORT getPchUsage(); int indexerFileSizeLimitInMb(); diff --git a/src/plugins/cppeditor/symbolsfindfilter.cpp b/src/plugins/cppeditor/symbolsfindfilter.cpp index bf1158c414f..ef067876825 100644 --- a/src/plugins/cppeditor/symbolsfindfilter.cpp +++ b/src/plugins/cppeditor/symbolsfindfilter.cpp @@ -138,7 +138,7 @@ void SymbolsFindFilter::addResults(QFutureWatcher *watcher, in SearchResultItems items; for (int i = begin; i < end; ++i) items << watcher->resultAt(i); - search->addResults(items, SearchResult::AddSorted); + search->addResults(items, SearchResult::AddSortedByContent); } void SymbolsFindFilter::finish(QFutureWatcher *watcher) diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 444d0d7589b..13f51d2cde9 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -848,7 +848,7 @@ void ClientPrivate::requestDocumentHighlightsNow(TextEditor::TextEditorWidget *w q->cancelRequest(m_highlightRequests.take(widget)); }); request.setResponseCallback( - [widget, this, uri, connection] + [widget, this, uri, connection, adjustedCursor] (const DocumentHighlightsRequest::Response &response) { m_highlightRequests.remove(widget); @@ -874,6 +874,30 @@ void ClientPrivate::requestDocumentHighlightsNow(TextEditor::TextEditorWidget *w selection.cursor.setPosition(end, QTextCursor::KeepAnchor); selections << selection; } + if (!selections.isEmpty()) { + const QList extraRanges = q->additionalDocumentHighlights( + widget, adjustedCursor); + for (const Text::Range &range : extraRanges) { + QTextEdit::ExtraSelection selection{widget->textCursor(), format}; + const Text::Position &startPos = range.begin; + const Text::Position &endPos = range.end; + const int start = Text::positionInText(document, startPos.line, + startPos.column + 1); + const int end = Text::positionInText(document, endPos.line, + endPos.column + 1); + if (start < 0 || end < 0 || start >= end) + continue; + selection.cursor.setPosition(start); + selection.cursor.setPosition(end, QTextCursor::KeepAnchor); + static const auto cmp = [](const QTextEdit::ExtraSelection &s1, + const QTextEdit::ExtraSelection &s2) { + return s1.cursor.position() < s2.cursor.position(); + }; + const auto it = std::lower_bound(selections.begin(), selections.end(), + selection, cmp); + selections.insert(it, selection); + } + } widget->setExtraSelections(id, selections); }); m_highlightRequests[widget] = request.id(); diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index f0c071c2a1f..8aede4b249d 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -16,6 +16,8 @@ class TextDocument; class TextEditorWidget; } +namespace Utils { namespace Text { class Range; } } + QT_BEGIN_NAMESPACE class QWidget; QT_END_NAMESPACE @@ -226,6 +228,8 @@ private: TextEditor::TextDocument *doc); virtual bool referencesShadowFile(const TextEditor::TextDocument *doc, const Utils::FilePath &candidate); + virtual QList additionalDocumentHighlights( + TextEditor::TextEditorWidget *, const QTextCursor &) { return {}; } }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp index 4ef534d7f3e..b6ac1bdb307 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.cpp +++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp @@ -563,11 +563,22 @@ void SymbolSupport::handleRenameResponse(Core::SearchResult *search, const std::optional &edits = response.result(); if (edits.has_value()) { - search->addResults(generateReplaceItems(*edits, - search, - m_limitRenamingToProjects, - m_client->hostPathMapper()), - Core::SearchResult::AddOrdered); + const Utils::SearchResultItems items = generateReplaceItems( + *edits, search, m_limitRenamingToProjects, m_client->hostPathMapper()); + search->addResults(items, Core::SearchResult::AddOrdered); + if (m_renameResultsEnhancer) { + Utils::SearchResultItems additionalItems = m_renameResultsEnhancer(items); + for (Utils::SearchResultItem &item : additionalItems) { + TextEdit edit; + const Utils::Text::Position startPos = item.mainRange().begin; + const Utils::Text::Position endPos = item.mainRange().end; + edit.setRange({{startPos.line - 1, startPos.column}, + {endPos.line - 1, endPos.column}}); + edit.setNewText(search->textToReplace()); + item.setUserData(QVariant(edit)); + } + search->addResults(additionalItems, Core::SearchResult::AddSortedByPosition); + } qobject_cast(search->additionalReplaceWidget())->showLabel(false); search->setReplaceEnabled(true); search->finishSearch(false); @@ -634,6 +645,11 @@ void SymbolSupport::setDefaultRenamingSymbolMapper(const SymbolMapper &mapper) m_defaultSymbolMapper = mapper; } +void SymbolSupport::setRenameResultsEnhancer(const RenameResultsEnhancer &enhancer) +{ + m_renameResultsEnhancer = enhancer; +} + } // namespace LanguageClient #include diff --git a/src/plugins/languageclient/languageclientsymbolsupport.h b/src/plugins/languageclient/languageclientsymbolsupport.h index a26c36e7ede..54666e7b1f9 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.h +++ b/src/plugins/languageclient/languageclientsymbolsupport.h @@ -51,6 +51,9 @@ public: void setLimitRenamingToProjects(bool limit) { m_limitRenamingToProjects = limit; } + using RenameResultsEnhancer = std::function; + void setRenameResultsEnhancer(const RenameResultsEnhancer &enhancer); + private: void handleFindReferencesResponse( const LanguageServerProtocol::FindReferencesRequest::Response &response, @@ -78,6 +81,7 @@ private: Client *m_client = nullptr; SymbolMapper m_defaultSymbolMapper; + RenameResultsEnhancer m_renameResultsEnhancer; QHash m_renameRequestIds; bool m_limitRenamingToProjects = false; }; diff --git a/tests/auto/cplusplus/declarationcomments/tst_declarationcomments.cpp b/tests/auto/cplusplus/declarationcomments/tst_declarationcomments.cpp index 0a92cfbbd61..27caff6c451 100644 --- a/tests/auto/cplusplus/declarationcomments/tst_declarationcomments.cpp +++ b/tests/auto/cplusplus/declarationcomments/tst_declarationcomments.cpp @@ -155,7 +155,7 @@ void TestDeclarationComments::commentsForDecl() const Symbol * const symbol = finder.find(); QVERIFY(symbol); - const QList commentTokens = commentsForDeclaration(symbol, m_snapshot, m_textDoc); + const QList commentTokens = commentsForDeclaration(symbol, m_textDoc, m_cppDoc); if (expectedCommentPrefix.isEmpty()) { QVERIFY(commentTokens.isEmpty()); return;