CppEditor: Consider symbol occurrences in comments

... when renaming.
For local renaming, we consider only function parameters.

Task-number: QTCREATORBUG-12051
Change-Id: I7948d69f11b97663c9bd747ae6241a82dd9bdd82
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2023-08-23 17:26:02 +02:00
parent 164cb389dc
commit 0a058bb657
28 changed files with 525 additions and 86 deletions

View File

@@ -8,58 +8,35 @@
#include <cplusplus/Overview.h>
#include <utils/algorithm.h>
#include <utils/textutils.h>
#include <QRegularExpression>
#include <QStringList>
#include <QTextBlock>
#include <QTextDocument>
namespace CPlusPlus {
QList<Token> 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<AST *> 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<Token> 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<Token> allTokens = tu->allTokens();
QTC_ASSERT(!allTokens.empty(), return {});
int tokenPos = -1;
@@ -86,6 +63,7 @@ QList<Token> 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<Token> 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<Token> commentsForDeclaration(const Symbol *symbol, const Snapshot &snapsh
return {};
}
QList<Token> 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<Token> 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<AST *> 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<Token> 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

View File

@@ -3,6 +3,7 @@
#pragma once
#include <cplusplus/CppDocument.h>
#include <cplusplus/Token.h>
#include <QList>
@@ -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<Token> CPLUSPLUS_EXPORT commentsForDeclaration(const Symbol *symbol,
const Snapshot &snapshot,
const QTextDocument &textDoc);
const QTextDocument &textDoc,
const Document::Ptr &cppDoc);
QList<Token> CPLUSPLUS_EXPORT commentsForDeclaration(const Symbol *symbol,
const AST *decl,
const QTextDocument &textDoc,
const Document::Ptr &cppDoc);
QList<Token> CPLUSPLUS_EXPORT commentsForDeclaration(const QString &symbolName,
const Utils::Text::Position &pos,
const QTextDocument &textDoc,
const Document::Ptr &cppDoc);
} // namespace CPlusPlus

View File

@@ -96,6 +96,11 @@ private:
using SearchResultItems = QList<SearchResultItem>;
inline size_t qHash(const SearchResultItem &item)
{
return item.mainRange().begin.line << 16 | item.mainRange().begin.column;
}
} // namespace Utils
Q_DECLARE_METATYPE(Utils::SearchResultItem)

View File

@@ -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<Text::Range> ClangdClient::additionalDocumentHighlights(
TextEditorWidget *editorWidget, const QTextCursor &cursor)
{
return CppEditor::symbolOccurrencesInDeclarationComments(
qobject_cast<CppEditor::CppEditorWidget *>(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;

View File

@@ -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<Utils::Text::Range> additionalDocumentHighlights(
TextEditor::TextEditorWidget *editorWidget, const QTextCursor &cursor) override;
class Private;
class VirtualFunctionAssistProcessor;

View File

@@ -14,6 +14,7 @@
#include <cplusplus/FindUsages.h>
#include <cppeditor/cppcodemodelsettings.h>
#include <cppeditor/cppeditorwidget.h>
#include <cppeditor/cppfindreferences.h>
#include <cppeditor/cpptoolsreuse.h>
@@ -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<CppEditorWidget> editorWidget;
const QPointer<TextDocument> 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<Location>
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<Location>
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<Text::Range> 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 = {};

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -10,6 +10,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <cplusplus/FindUsages.h>
#include <cppeditor/cppcodemodelsettings.h>
#include <cppeditor/cppeditorwidget.h>
#include <cppeditor/cpptoolsreuse.h>
#include <cppeditor/cpptoolstestcase.h>
#include <cppeditor/semantichighlighter.h>
@@ -536,6 +537,8 @@ void ClangdTestLocalReferences::test_data()
QTest::newRow("overloaded operators arguments from outside") << 171 << 7
<< QList<Range>{{171, 6, 1}, {172, 6, 1}, {172, 11, 1},
{173, 6, 1}, {173, 9, 1}};
QTest::newRow("documented function parameter") << 181 << 32
<< QList<Range>{{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<BaseTextEditor *> editors = BaseTextEditor::textEditorsForDocument(doc);
QCOMPARE(editors.size(), 1);
const auto editorWidget = qobject_cast<CppEditor::CppEditorWidget *>(
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());

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -900,6 +900,11 @@ void Core::SearchResult::makeNonInteractive(const std::function<void ()> &callba
m_finishedHandler = callback;
}
Utils::SearchResultItems SearchResult::allItems() const
{
return m_widget->items(false);
}
} // namespace Core
#include "searchresultwindow.moc"

View File

@@ -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<void()> &callback);
bool isInteractive() const { return !m_finishedHandler; }
Utils::SearchResultItems allItems() const;
public slots:
void addResult(const Utils::SearchResultItem &item);

View File

@@ -600,6 +600,10 @@ static void displayResults(SearchResult *search,
static void searchFinished(SearchResult *search, QFutureWatcher<CPlusPlus::Usage> *watcher)
{
if (!watcher->isCanceled() && search->supportsReplace()) {
search->addResults(symbolOccurrencesInDeclarationComments(search->allItems()),
SearchResult::AddSortedByPosition);
}
search->finishSearch(watcher->isCanceled());
CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();

View File

@@ -4,8 +4,14 @@
#include "cpplocalsymbols.h"
#include "cppsemanticinfo.h"
#include "cpptoolsreuse.h"
#include "semantichighlighter.h"
#include <cplusplus/declarationcomments.h>
#include <cplusplus/Overview.h>
#include <texteditor/textdocument.h>
#include <utils/textutils.h>
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<Token> 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<Utils::Text::Range> 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<Scope *> _scopeStack;
Document::Ptr _doc;
};
} // end of anonymous namespace

View File

@@ -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";

View File

@@ -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 <texteditor/textdocument.h>
#include <cplusplus/BackwardsScanner.h>
#include <cplusplus/declarationcomments.h>
#include <cplusplus/LookupContext.h>
#include <cplusplus/Overview.h>
#include <cplusplus/SimpleLexer.h>
#include <utils/algorithm.h>
#include <utils/textutils.h>
#include <utils/qtcassert.h>
#include <utils/textfileformat.h>
#include <utils/textutils.h>
#include <QDebug>
#include <QElapsedTimer>
#include <QHash>
#include <QRegularExpression>
#include <QSet>
#include <QStringView>
#include <QTextCursor>
#include <QTextDocument>
#include <vector>
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<SearchResultItems, SearchResultItems> 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<std::unique_ptr<QTextDocument>> docPool;
using FileData = std::tuple<QTextDocument *, QString, Document::Ptr, QList<Token>>;
QHash<FilePath, FileData> 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<QTextDocument> newDoc = std::make_unique<QTextDocument>();
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<Token> &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<Token> 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<Text::Range> 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<Text::Range> symbolOccurrencesInText(const QTextDocument &doc, QStringView text, int offset,
const QString &symbolName)
{
QList<Text::Range> 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<Text::Range> 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<Token> comments = commentsForDeclaration(symbol, *textDoc, cppDoc);
if (comments.isEmpty())
return {};
QList<Text::Range> 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)

View File

@@ -12,6 +12,8 @@
#include <texteditor/quickfix.h>
#include <texteditor/texteditor.h>
#include <utils/searchresultitem.h>
#include <cplusplus/ASTVisitor.h>
#include <cplusplus/CppDocument.h>
#include <cplusplus/Token.h>
@@ -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<Utils::Text::Range> CPPEDITOR_EXPORT symbolOccurrencesInText(
const QTextDocument &doc, QStringView text, int offset, const QString &symbolName);
Utils::SearchResultItems CPPEDITOR_EXPORT
symbolOccurrencesInDeclarationComments(const Utils::SearchResultItems &symbolOccurrencesInCode);
QList<Utils::Text::Range> CPPEDITOR_EXPORT symbolOccurrencesInDeclarationComments(
CppEditorWidget *editorWidget, const QTextCursor &cursor);
UsePrecompiledHeaders CPPEDITOR_EXPORT getPchUsage();
int indexerFileSizeLimitInMb();

View File

@@ -138,7 +138,7 @@ void SymbolsFindFilter::addResults(QFutureWatcher<SearchResultItem> *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<SearchResultItem> *watcher)

View File

@@ -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<Text::Range> 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();

View File

@@ -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<Utils::Text::Range> additionalDocumentHighlights(
TextEditor::TextEditorWidget *, const QTextCursor &) { return {}; }
};
} // namespace LanguageClient

View File

@@ -563,11 +563,22 @@ void SymbolSupport::handleRenameResponse(Core::SearchResult *search,
const std::optional<WorkspaceEdit> &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<ReplaceWidget *>(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 <languageclientsymbolsupport.moc>

View File

@@ -51,6 +51,9 @@ public:
void setLimitRenamingToProjects(bool limit) { m_limitRenamingToProjects = limit; }
using RenameResultsEnhancer = std::function<Utils::SearchResultItems(const Utils::SearchResultItems &)>;
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<Core::SearchResult *, LanguageServerProtocol::MessageId> m_renameRequestIds;
bool m_limitRenamingToProjects = false;
};

View File

@@ -155,7 +155,7 @@ void TestDeclarationComments::commentsForDecl()
const Symbol * const symbol = finder.find();
QVERIFY(symbol);
const QList<Token> commentTokens = commentsForDeclaration(symbol, m_snapshot, m_textDoc);
const QList<Token> commentTokens = commentsForDeclaration(symbol, m_textDoc, m_cppDoc);
if (expectedCommentPrefix.isEmpty()) {
QVERIFY(commentTokens.isEmpty());
return;