forked from qt-creator/qt-creator
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:
@@ -8,58 +8,35 @@
|
|||||||
#include <cplusplus/Overview.h>
|
#include <cplusplus/Overview.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/textutils.h>
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <QStringList>
|
||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
|
||||||
namespace CPlusPlus {
|
namespace CPlusPlus {
|
||||||
|
|
||||||
QList<Token> commentsForDeclaration(const Symbol *symbol, const Snapshot &snapshot,
|
static QString nameFromSymbol(const Symbol *symbol)
|
||||||
const QTextDocument &textDoc)
|
|
||||||
{
|
{
|
||||||
// Set up cpp document.
|
const QStringList symbolParts = Overview().prettyName(symbol->name())
|
||||||
const Document::Ptr cppDoc = snapshot.preprocessedDocument(textDoc.toPlainText().toUtf8(),
|
.split("::", Qt::SkipEmptyParts);
|
||||||
symbol->filePath());
|
if (symbolParts.isEmpty())
|
||||||
cppDoc->parse();
|
|
||||||
TranslationUnit * const tu = cppDoc->translationUnit();
|
|
||||||
if (!tu || !tu->isParsed())
|
|
||||||
return {};
|
return {};
|
||||||
|
return symbolParts.last();
|
||||||
|
}
|
||||||
|
|
||||||
// Find the symbol declaration's AST node.
|
static QList<Token> commentsForDeclaration(
|
||||||
// We stop at the last declaration node that precedes the symbol, except:
|
const AST *decl, const QString &symbolName, const QTextDocument &textDoc,
|
||||||
// - For parameter declarations, we just continue, because we are interested in the function.
|
const Document::Ptr &cppDoc, bool isParameter)
|
||||||
// - 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).
|
if (symbolName.isEmpty())
|
||||||
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)
|
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
// Get the list of all tokens (including comments) and find the declaration start token there.
|
// 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();
|
std::vector<Token> allTokens = tu->allTokens();
|
||||||
QTC_ASSERT(!allTokens.empty(), return {});
|
QTC_ASSERT(!allTokens.empty(), return {});
|
||||||
int tokenPos = -1;
|
int tokenPos = -1;
|
||||||
@@ -86,6 +63,7 @@ QList<Token> commentsForDeclaration(const Symbol *symbol, const Snapshot &snapsh
|
|||||||
const auto blockForTokenEnd = [&](const Token &tok) {
|
const auto blockForTokenEnd = [&](const Token &tok) {
|
||||||
return textDoc.findBlock(tu->getTokenEndPositionInDocument(tok, &textDoc));
|
return textDoc.findBlock(tu->getTokenEndPositionInDocument(tok, &textDoc));
|
||||||
};
|
};
|
||||||
|
bool needsSymbolReference = isParameter;
|
||||||
for (int i = tokenPos - 1; i >= 0; --i) {
|
for (int i = tokenPos - 1; i >= 0; --i) {
|
||||||
const Token &tok = allTokens.at(i);
|
const Token &tok = allTokens.at(i);
|
||||||
if (!tok.isComment())
|
if (!tok.isComment())
|
||||||
@@ -127,7 +105,6 @@ QList<Token> commentsForDeclaration(const Symbol *symbol, const Snapshot &snapsh
|
|||||||
return tokenList();
|
return tokenList();
|
||||||
|
|
||||||
// b)
|
// b)
|
||||||
const QString symbolName = Overview().prettyName(symbol->name());
|
|
||||||
const Kind tokenKind = comments.first().token.kind();
|
const Kind tokenKind = comments.first().token.kind();
|
||||||
const bool isDoxygenComment = tokenKind == T_DOXY_COMMENT || tokenKind == T_CPP_DOXY_COMMENT;
|
const bool isDoxygenComment = tokenKind == T_DOXY_COMMENT || tokenKind == T_CPP_DOXY_COMMENT;
|
||||||
const QRegularExpression symbolRegExp(QString("%1\\b%2\\b").arg(
|
const QRegularExpression symbolRegExp(QString("%1\\b%2\\b").arg(
|
||||||
@@ -142,4 +119,57 @@ QList<Token> commentsForDeclaration(const Symbol *symbol, const Snapshot &snapsh
|
|||||||
return {};
|
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
|
} // namespace CPlusPlus
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cplusplus/CppDocument.h>
|
||||||
#include <cplusplus/Token.h>
|
#include <cplusplus/Token.h>
|
||||||
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
@@ -11,11 +12,24 @@ QT_BEGIN_NAMESPACE
|
|||||||
class QTextDocument;
|
class QTextDocument;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace Utils { namespace Text { class Position; } }
|
||||||
|
|
||||||
namespace CPlusPlus {
|
namespace CPlusPlus {
|
||||||
class Snapshot;
|
class AST;
|
||||||
|
class Symbol;
|
||||||
|
|
||||||
QList<Token> CPLUSPLUS_EXPORT commentsForDeclaration(const Symbol *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
|
} // namespace CPlusPlus
|
||||||
|
@@ -96,6 +96,11 @@ private:
|
|||||||
|
|
||||||
using SearchResultItems = QList<SearchResultItem>;
|
using SearchResultItems = QList<SearchResultItem>;
|
||||||
|
|
||||||
|
inline size_t qHash(const SearchResultItem &item)
|
||||||
|
{
|
||||||
|
return item.mainRange().begin.line << 16 | item.mainRange().begin.column;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Utils::SearchResultItem)
|
Q_DECLARE_METATYPE(Utils::SearchResultItem)
|
||||||
|
@@ -402,6 +402,9 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir, c
|
|||||||
setCompletionAssistProvider(new ClangdCompletionAssistProvider(this));
|
setCompletionAssistProvider(new ClangdCompletionAssistProvider(this));
|
||||||
setQuickFixAssistProvider(new ClangdQuickFixProvider(this));
|
setQuickFixAssistProvider(new ClangdQuickFixProvider(this));
|
||||||
symbolSupport().setLimitRenamingToProjects(true);
|
symbolSupport().setLimitRenamingToProjects(true);
|
||||||
|
symbolSupport().setRenameResultsEnhancer([](const SearchResultItems &symbolOccurrencesInCode) {
|
||||||
|
return CppEditor::symbolOccurrencesInDeclarationComments(symbolOccurrencesInCode);
|
||||||
|
});
|
||||||
if (!project) {
|
if (!project) {
|
||||||
QJsonObject initOptions;
|
QJsonObject initOptions;
|
||||||
const Utils::FilePath includeDir
|
const Utils::FilePath includeDir
|
||||||
@@ -752,6 +755,13 @@ bool ClangdClient::fileBelongsToProject(const Utils::FilePath &filePath) const
|
|||||||
return Client::fileBelongsToProject(filePath);
|
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
|
RefactoringChangesData *ClangdClient::createRefactoringChangesBackend() const
|
||||||
{
|
{
|
||||||
return new CppEditor::CppRefactoringChangesData(
|
return new CppEditor::CppRefactoringChangesData(
|
||||||
@@ -1056,9 +1066,11 @@ void ClangdClient::switchHeaderSource(const Utils::FilePath &filePath, bool inNe
|
|||||||
sendMessage(req);
|
sendMessage(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cursor,
|
void ClangdClient::findLocalUsages(CppEditor::CppEditorWidget *editorWidget,
|
||||||
CppEditor::RenameCallback &&callback)
|
const QTextCursor &cursor, CppEditor::RenameCallback &&callback)
|
||||||
{
|
{
|
||||||
|
QTC_ASSERT(editorWidget, return);
|
||||||
|
TextDocument * const document = editorWidget->textDocument();
|
||||||
QTC_ASSERT(documentOpen(document), openDocument(document));
|
QTC_ASSERT(documentOpen(document), openDocument(document));
|
||||||
|
|
||||||
qCDebug(clangdLog) << "local references requested" << document->filePath()
|
qCDebug(clangdLog) << "local references requested" << document->filePath()
|
||||||
@@ -1076,7 +1088,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->findLocalRefs = new ClangdFindLocalReferences(this, document, cursor, callback);
|
d->findLocalRefs = new ClangdFindLocalReferences(this, editorWidget, cursor, callback);
|
||||||
connect(d->findLocalRefs, &ClangdFindLocalReferences::done, this, [this] {
|
connect(d->findLocalRefs, &ClangdFindLocalReferences::done, this, [this] {
|
||||||
d->findLocalRefs->deleteLater();
|
d->findLocalRefs->deleteLater();
|
||||||
d->findLocalRefs = nullptr;
|
d->findLocalRefs = nullptr;
|
||||||
|
@@ -74,7 +74,7 @@ public:
|
|||||||
const Utils::LinkHandler &callback);
|
const Utils::LinkHandler &callback);
|
||||||
void switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit);
|
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);
|
CppEditor::RenameCallback &&callback);
|
||||||
|
|
||||||
void gatherHelpItemForTooltip(
|
void gatherHelpItemForTooltip(
|
||||||
@@ -148,6 +148,9 @@ private:
|
|||||||
bool referencesShadowFile(const TextEditor::TextDocument *doc,
|
bool referencesShadowFile(const TextEditor::TextDocument *doc,
|
||||||
const Utils::FilePath &candidate) override;
|
const Utils::FilePath &candidate) override;
|
||||||
bool fileBelongsToProject(const Utils::FilePath &filePath) const override;
|
bool fileBelongsToProject(const Utils::FilePath &filePath) const override;
|
||||||
|
QList<Utils::Text::Range> additionalDocumentHighlights(
|
||||||
|
TextEditor::TextEditorWidget *editorWidget, const QTextCursor &cursor) override;
|
||||||
|
|
||||||
|
|
||||||
class Private;
|
class Private;
|
||||||
class VirtualFunctionAssistProcessor;
|
class VirtualFunctionAssistProcessor;
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
#include <cplusplus/FindUsages.h>
|
#include <cplusplus/FindUsages.h>
|
||||||
|
|
||||||
#include <cppeditor/cppcodemodelsettings.h>
|
#include <cppeditor/cppcodemodelsettings.h>
|
||||||
|
#include <cppeditor/cppeditorwidget.h>
|
||||||
#include <cppeditor/cppfindreferences.h>
|
#include <cppeditor/cppfindreferences.h>
|
||||||
#include <cppeditor/cpptoolsreuse.h>
|
#include <cppeditor/cpptoolsreuse.h>
|
||||||
|
|
||||||
@@ -670,10 +671,10 @@ ClangdFindReferences::CheckUnusedData::~CheckUnusedData()
|
|||||||
class ClangdFindLocalReferences::Private
|
class ClangdFindLocalReferences::Private
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Private(ClangdFindLocalReferences *q, TextDocument *document, const QTextCursor &cursor,
|
Private(ClangdFindLocalReferences *q, CppEditorWidget *editorWidget, const QTextCursor &cursor,
|
||||||
const RenameCallback &callback)
|
const RenameCallback &callback)
|
||||||
: q(q), document(document), cursor(cursor), callback(callback),
|
: q(q), editorWidget(editorWidget), document(editorWidget->textDocument()), cursor(cursor),
|
||||||
uri(client()->hostPathToServerUri(document->filePath())),
|
callback(callback), uri(client()->hostPathToServerUri(document->filePath())),
|
||||||
revision(document->document()->revision())
|
revision(document->document()->revision())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -685,6 +686,7 @@ public:
|
|||||||
void finish();
|
void finish();
|
||||||
|
|
||||||
ClangdFindLocalReferences * const q;
|
ClangdFindLocalReferences * const q;
|
||||||
|
const QPointer<CppEditorWidget> editorWidget;
|
||||||
const QPointer<TextDocument> document;
|
const QPointer<TextDocument> document;
|
||||||
const QTextCursor cursor;
|
const QTextCursor cursor;
|
||||||
RenameCallback callback;
|
RenameCallback callback;
|
||||||
@@ -694,9 +696,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
ClangdFindLocalReferences::ClangdFindLocalReferences(
|
ClangdFindLocalReferences::ClangdFindLocalReferences(
|
||||||
ClangdClient *client, TextDocument *document, const QTextCursor &cursor,
|
ClangdClient *client, CppEditorWidget *editorWidget, const QTextCursor &cursor,
|
||||||
const RenameCallback &callback)
|
const RenameCallback &callback)
|
||||||
: QObject(client), d(new Private(this, document, cursor, callback))
|
: QObject(client), d(new Private(this, editorWidget, cursor, callback))
|
||||||
{
|
{
|
||||||
d->findDefinition();
|
d->findDefinition();
|
||||||
}
|
}
|
||||||
@@ -780,7 +782,7 @@ void ClangdFindLocalReferences::Private::handleReferences(const QList<Location>
|
|||||||
return loc.toLink(mapper);
|
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.
|
// The callback only uses the symbol length, so we just create a dummy.
|
||||||
// Note that the calculation will be wrong for identifiers with
|
// Note that the calculation will be wrong for identifiers with
|
||||||
@@ -788,7 +790,27 @@ void ClangdFindLocalReferences::Private::handleReferences(const QList<Location>
|
|||||||
QString symbol;
|
QString symbol;
|
||||||
if (!references.isEmpty()) {
|
if (!references.isEmpty()) {
|
||||||
const Range r = references.first().range();
|
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(symbol, links, revision);
|
||||||
callback = {};
|
callback = {};
|
||||||
|
@@ -48,8 +48,8 @@ class ClangdFindLocalReferences : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ClangdFindLocalReferences(ClangdClient *client, TextEditor::TextDocument *document,
|
explicit ClangdFindLocalReferences(
|
||||||
const QTextCursor &cursor,
|
ClangdClient *client, CppEditor::CppEditorWidget *editorWidget, const QTextCursor &cursor,
|
||||||
const CppEditor::RenameCallback &callback);
|
const CppEditor::RenameCallback &callback);
|
||||||
~ClangdFindLocalReferences();
|
~ClangdFindLocalReferences();
|
||||||
|
|
||||||
|
@@ -309,7 +309,7 @@ void ClangModelManagerSupport::startLocalRenaming(const CursorInEditor &data,
|
|||||||
{
|
{
|
||||||
if (ClangdClient * const client = clientForFile(data.filePath());
|
if (ClangdClient * const client = clientForFile(data.filePath());
|
||||||
client && client->reachable()) {
|
client && client->reachable()) {
|
||||||
client->findLocalUsages(data.textDocument(), data.cursor(),
|
client->findLocalUsages(data.editorWidget(), data.cursor(),
|
||||||
std::move(renameSymbolsCallback));
|
std::move(renameSymbolsCallback));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <cplusplus/FindUsages.h>
|
#include <cplusplus/FindUsages.h>
|
||||||
#include <cppeditor/cppcodemodelsettings.h>
|
#include <cppeditor/cppcodemodelsettings.h>
|
||||||
|
#include <cppeditor/cppeditorwidget.h>
|
||||||
#include <cppeditor/cpptoolsreuse.h>
|
#include <cppeditor/cpptoolsreuse.h>
|
||||||
#include <cppeditor/cpptoolstestcase.h>
|
#include <cppeditor/cpptoolstestcase.h>
|
||||||
#include <cppeditor/semantichighlighter.h>
|
#include <cppeditor/semantichighlighter.h>
|
||||||
@@ -536,6 +537,8 @@ void ClangdTestLocalReferences::test_data()
|
|||||||
QTest::newRow("overloaded operators arguments from outside") << 171 << 7
|
QTest::newRow("overloaded operators arguments from outside") << 171 << 7
|
||||||
<< QList<Range>{{171, 6, 1}, {172, 6, 1}, {172, 11, 1},
|
<< QList<Range>{{171, 6, 1}, {172, 6, 1}, {172, 11, 1},
|
||||||
{173, 6, 1}, {173, 9, 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()
|
void ClangdTestLocalReferences::test()
|
||||||
@@ -546,6 +549,11 @@ void ClangdTestLocalReferences::test()
|
|||||||
|
|
||||||
TextEditor::TextDocument * const doc = document("references.cpp");
|
TextEditor::TextDocument * const doc = document("references.cpp");
|
||||||
QVERIFY(doc);
|
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;
|
QTimer timer;
|
||||||
timer.setSingleShot(true);
|
timer.setSingleShot(true);
|
||||||
@@ -561,7 +569,7 @@ void ClangdTestLocalReferences::test()
|
|||||||
QTextCursor cursor(doc->document());
|
QTextCursor cursor(doc->document());
|
||||||
const int pos = Text::positionInText(doc->document(), sourceLine, sourceColumn);
|
const int pos = Text::positionInText(doc->document(), sourceLine, sourceColumn);
|
||||||
cursor.setPosition(pos);
|
cursor.setPosition(pos);
|
||||||
client()->findLocalUsages(doc, cursor, std::move(handler));
|
client()->findLocalUsages(editorWidget, cursor, std::move(handler));
|
||||||
timer.start(10000);
|
timer.start(10000);
|
||||||
loop.exec();
|
loop.exec();
|
||||||
QVERIFY(timer.isActive());
|
QVERIFY(timer.isActive());
|
||||||
|
@@ -172,3 +172,14 @@ int testOperator() {
|
|||||||
vec[n] = n * 100;
|
vec[n] = n * 100;
|
||||||
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;
|
||||||
|
}
|
||||||
|
@@ -82,9 +82,26 @@ int SearchResultTreeItem::insertionIndex(const QString &text, SearchResultTreeIt
|
|||||||
}
|
}
|
||||||
|
|
||||||
int SearchResultTreeItem::insertionIndex(const Utils::SearchResultItem &item,
|
int SearchResultTreeItem::insertionIndex(const Utils::SearchResultItem &item,
|
||||||
SearchResultTreeItem **existingItem) const
|
SearchResultTreeItem **existingItem,
|
||||||
|
SearchResult::AddMode mode) const
|
||||||
{
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case SearchResult::AddSortedByContent:
|
||||||
return insertionIndex(item.lineText(), existingItem);
|
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)
|
void SearchResultTreeItem::insertChild(int index, SearchResultTreeItem *child)
|
||||||
|
@@ -21,7 +21,8 @@ public:
|
|||||||
SearchResultTreeItem *parent() const;
|
SearchResultTreeItem *parent() const;
|
||||||
SearchResultTreeItem *childAt(int index) const;
|
SearchResultTreeItem *childAt(int index) const;
|
||||||
int insertionIndex(const QString &text, SearchResultTreeItem **existingItem) 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, SearchResultTreeItem *child);
|
||||||
void insertChild(int index, const Utils::SearchResultItem &item);
|
void insertChild(int index, const Utils::SearchResultItem &item);
|
||||||
void appendChild(const Utils::SearchResultItem &item);
|
void appendChild(const Utils::SearchResultItem &item);
|
||||||
|
@@ -402,10 +402,10 @@ void SearchResultTreeModel::addResultsToCurrentParent(const SearchResultItems &i
|
|||||||
m_currentParent->appendChild(item);
|
m_currentParent->appendChild(item);
|
||||||
}
|
}
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
} else if (mode == SearchResult::AddSorted) {
|
} else {
|
||||||
for (const SearchResultItem &item : items) {
|
for (const SearchResultItem &item : items) {
|
||||||
SearchResultTreeItem *existingItem;
|
SearchResultTreeItem *existingItem;
|
||||||
const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem);
|
const int insertionIndex = m_currentParent->insertionIndex(item, &existingItem, mode);
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
existingItem->setGenerated(false);
|
existingItem->setGenerated(false);
|
||||||
existingItem->item = item;
|
existingItem->item = item;
|
||||||
|
@@ -478,7 +478,7 @@ void SearchResultWidget::doReplace()
|
|||||||
{
|
{
|
||||||
m_infoBar.clear();
|
m_infoBar.clear();
|
||||||
setShowReplaceUI(false);
|
setShowReplaceUI(false);
|
||||||
emit replaceButtonClicked(m_replaceTextEdit->text(), checkedItems(),
|
emit replaceButtonClicked(m_replaceTextEdit->text(), items(true),
|
||||||
m_preserveCaseSupported && m_preserveCaseCheck->isChecked());
|
m_preserveCaseSupported && m_preserveCaseCheck->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +496,7 @@ void SearchResultWidget::searchAgain()
|
|||||||
emit searchAgainRequested();
|
emit searchAgainRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchResultItems SearchResultWidget::checkedItems() const
|
SearchResultItems SearchResultWidget::items(bool checkedOnly) const
|
||||||
{
|
{
|
||||||
SearchResultItems result;
|
SearchResultItems result;
|
||||||
SearchResultFilterModel *model = m_searchResultTreeView->model();
|
SearchResultFilterModel *model = m_searchResultTreeView->model();
|
||||||
@@ -508,7 +508,7 @@ SearchResultItems SearchResultWidget::checkedItems() const
|
|||||||
const QModelIndex textIndex = model->index(rowIndex, 0, fileIndex);
|
const QModelIndex textIndex = model->index(rowIndex, 0, fileIndex);
|
||||||
const SearchResultTreeItem * const rowItem = model->itemForIndex(textIndex);
|
const SearchResultTreeItem * const rowItem = model->itemForIndex(textIndex);
|
||||||
QTC_ASSERT(rowItem != nullptr, continue);
|
QTC_ASSERT(rowItem != nullptr, continue);
|
||||||
if (rowItem->checkState())
|
if (!checkedOnly || rowItem->checkState())
|
||||||
result << rowItem->item;
|
result << rowItem->item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -71,6 +71,7 @@ public:
|
|||||||
bool hasFilter() const;
|
bool hasFilter() const;
|
||||||
void showFilterWidget(QWidget *parent);
|
void showFilterWidget(QWidget *parent);
|
||||||
void setReplaceEnabled(bool enabled);
|
void setReplaceEnabled(bool enabled);
|
||||||
|
Utils::SearchResultItems items(bool checkedOnly) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void finishSearch(bool canceled, const QString &reason);
|
void finishSearch(bool canceled, const QString &reason);
|
||||||
@@ -103,7 +104,6 @@ private:
|
|||||||
void continueAfterSizeWarning();
|
void continueAfterSizeWarning();
|
||||||
void cancelAfterSizeWarning();
|
void cancelAfterSizeWarning();
|
||||||
|
|
||||||
Utils::SearchResultItems checkedItems() const;
|
|
||||||
void updateMatchesFoundLabel();
|
void updateMatchesFoundLabel();
|
||||||
|
|
||||||
SearchResultTreeView *m_searchResultTreeView = nullptr;
|
SearchResultTreeView *m_searchResultTreeView = nullptr;
|
||||||
|
@@ -900,6 +900,11 @@ void Core::SearchResult::makeNonInteractive(const std::function<void ()> &callba
|
|||||||
m_finishedHandler = callback;
|
m_finishedHandler = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Utils::SearchResultItems SearchResult::allItems() const
|
||||||
|
{
|
||||||
|
return m_widget->items(false);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
||||||
#include "searchresultwindow.moc"
|
#include "searchresultwindow.moc"
|
||||||
|
@@ -43,7 +43,8 @@ class CORE_EXPORT SearchResult : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
enum AddMode {
|
enum AddMode {
|
||||||
AddSorted,
|
AddSortedByContent,
|
||||||
|
AddSortedByPosition,
|
||||||
AddOrdered
|
AddOrdered
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ public:
|
|||||||
void setAdditionalReplaceWidget(QWidget *widget);
|
void setAdditionalReplaceWidget(QWidget *widget);
|
||||||
void makeNonInteractive(const std::function<void()> &callback);
|
void makeNonInteractive(const std::function<void()> &callback);
|
||||||
bool isInteractive() const { return !m_finishedHandler; }
|
bool isInteractive() const { return !m_finishedHandler; }
|
||||||
|
Utils::SearchResultItems allItems() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void addResult(const Utils::SearchResultItem &item);
|
void addResult(const Utils::SearchResultItem &item);
|
||||||
|
@@ -600,6 +600,10 @@ static void displayResults(SearchResult *search,
|
|||||||
|
|
||||||
static void searchFinished(SearchResult *search, QFutureWatcher<CPlusPlus::Usage> *watcher)
|
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());
|
search->finishSearch(watcher->isCanceled());
|
||||||
|
|
||||||
CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
|
CppFindReferencesParameters parameters = search->userData().value<CppFindReferencesParameters>();
|
||||||
|
@@ -4,8 +4,14 @@
|
|||||||
#include "cpplocalsymbols.h"
|
#include "cpplocalsymbols.h"
|
||||||
|
|
||||||
#include "cppsemanticinfo.h"
|
#include "cppsemanticinfo.h"
|
||||||
|
#include "cpptoolsreuse.h"
|
||||||
#include "semantichighlighter.h"
|
#include "semantichighlighter.h"
|
||||||
|
|
||||||
|
#include <cplusplus/declarationcomments.h>
|
||||||
|
#include <cplusplus/Overview.h>
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
#include <utils/textutils.h>
|
||||||
|
|
||||||
using namespace CPlusPlus;
|
using namespace CPlusPlus;
|
||||||
|
|
||||||
namespace CppEditor::Internal {
|
namespace CppEditor::Internal {
|
||||||
@@ -16,7 +22,7 @@ class FindLocalSymbols: protected ASTVisitor
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit FindLocalSymbols(Document::Ptr doc)
|
explicit FindLocalSymbols(Document::Ptr doc)
|
||||||
: ASTVisitor(doc->translationUnit())
|
: ASTVisitor(doc->translationUnit()), _doc(doc)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
// local and external uses.
|
// local and external uses.
|
||||||
@@ -38,6 +44,42 @@ public:
|
|||||||
accept(ast);
|
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:
|
protected:
|
||||||
@@ -275,6 +317,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QList<Scope *> _scopeStack;
|
QList<Scope *> _scopeStack;
|
||||||
|
Document::Ptr _doc;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end of anonymous namespace
|
} // end of anonymous namespace
|
||||||
|
@@ -82,19 +82,19 @@ void MyClass::run() {}
|
|||||||
origHeaderClassName.insert(classOffset + 6, '@');
|
origHeaderClassName.insert(classOffset + 6, '@');
|
||||||
const QByteArray newHeaderClassName = R"cpp(
|
const QByteArray newHeaderClassName = R"cpp(
|
||||||
/**
|
/**
|
||||||
* \brief MyClass
|
* \brief MyNewClass
|
||||||
*/
|
*/
|
||||||
class MyNewClass {
|
class MyNewClass {
|
||||||
/** \brief MyClass::MyClass */
|
/** \brief MyNewClass::MyNewClass */
|
||||||
MyNewClass() {}
|
MyNewClass() {}
|
||||||
~MyNewClass();
|
~MyNewClass();
|
||||||
/** \brief MyClass::run */
|
/** \brief MyNewClass::run */
|
||||||
void run();
|
void run();
|
||||||
};
|
};
|
||||||
)cpp";
|
)cpp";
|
||||||
const QByteArray newSourceClassName = R"cpp(
|
const QByteArray newSourceClassName = R"cpp(
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
/** \brief MyClass::~MyClass */
|
/** \brief MyNewClass::~MyNewClass */
|
||||||
MyNewClass::~MyNewClass() {}
|
MyNewClass::~MyNewClass() {}
|
||||||
|
|
||||||
void MyNewClass::run() {}
|
void MyNewClass::run() {}
|
||||||
@@ -115,7 +115,7 @@ class MyClass {
|
|||||||
/** \brief MyClass::MyClass */
|
/** \brief MyClass::MyClass */
|
||||||
MyClass() {}
|
MyClass() {}
|
||||||
~MyClass();
|
~MyClass();
|
||||||
/** \brief MyClass::run */
|
/** \brief MyClass::runAgain */
|
||||||
void runAgain();
|
void runAgain();
|
||||||
};
|
};
|
||||||
)cpp";
|
)cpp";
|
||||||
|
@@ -5,10 +5,12 @@
|
|||||||
|
|
||||||
#include "clangdiagnosticconfigsmodel.h"
|
#include "clangdiagnosticconfigsmodel.h"
|
||||||
#include "cppautocompleter.h"
|
#include "cppautocompleter.h"
|
||||||
|
#include "cppcanonicalsymbol.h"
|
||||||
#include "cppcodemodelsettings.h"
|
#include "cppcodemodelsettings.h"
|
||||||
#include "cppcompletionassist.h"
|
#include "cppcompletionassist.h"
|
||||||
#include "cppeditorconstants.h"
|
#include "cppeditorconstants.h"
|
||||||
#include "cppeditorplugin.h"
|
#include "cppeditorplugin.h"
|
||||||
|
#include "cppeditorwidget.h"
|
||||||
#include "cppeditortr.h"
|
#include "cppeditortr.h"
|
||||||
#include "cppfilesettingspage.h"
|
#include "cppfilesettingspage.h"
|
||||||
#include "cpphighlighter.h"
|
#include "cpphighlighter.h"
|
||||||
@@ -28,20 +30,27 @@
|
|||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
#include <cplusplus/BackwardsScanner.h>
|
#include <cplusplus/BackwardsScanner.h>
|
||||||
|
#include <cplusplus/declarationcomments.h>
|
||||||
#include <cplusplus/LookupContext.h>
|
#include <cplusplus/LookupContext.h>
|
||||||
#include <cplusplus/Overview.h>
|
#include <cplusplus/Overview.h>
|
||||||
#include <cplusplus/SimpleLexer.h>
|
#include <cplusplus/SimpleLexer.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/textutils.h>
|
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/textfileformat.h>
|
||||||
|
#include <utils/textutils.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QHash>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
#include <QStringView>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace CPlusPlus;
|
using namespace CPlusPlus;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
@@ -626,6 +635,200 @@ QString preferredCxxSourceSuffix(ProjectExplorer::Project *project)
|
|||||||
return Internal::CppEditorPlugin::fileSettings(project).sourceSuffix;
|
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 {
|
namespace Internal {
|
||||||
|
|
||||||
void decorateCppEditor(TextEditor::TextEditorWidget *editor)
|
void decorateCppEditor(TextEditor::TextEditorWidget *editor)
|
||||||
|
@@ -12,6 +12,8 @@
|
|||||||
#include <texteditor/quickfix.h>
|
#include <texteditor/quickfix.h>
|
||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
|
|
||||||
|
#include <utils/searchresultitem.h>
|
||||||
|
|
||||||
#include <cplusplus/ASTVisitor.h>
|
#include <cplusplus/ASTVisitor.h>
|
||||||
#include <cplusplus/CppDocument.h>
|
#include <cplusplus/CppDocument.h>
|
||||||
#include <cplusplus/Token.h>
|
#include <cplusplus/Token.h>
|
||||||
@@ -23,9 +25,10 @@ class LookupContext;
|
|||||||
} // namespace CPlusPlus
|
} // namespace CPlusPlus
|
||||||
|
|
||||||
namespace TextEditor { class AssistInterface; }
|
namespace TextEditor { class AssistInterface; }
|
||||||
|
namespace Utils { namespace Text { class Range; } }
|
||||||
|
|
||||||
namespace CppEditor {
|
namespace CppEditor {
|
||||||
|
class CppEditorWidget;
|
||||||
class CppRefactoringFile;
|
class CppRefactoringFile;
|
||||||
class ProjectInfo;
|
class ProjectInfo;
|
||||||
class CppCompletionAssistProcessor;
|
class CppCompletionAssistProcessor;
|
||||||
@@ -71,6 +74,14 @@ QString CPPEDITOR_EXPORT preferredCxxHeaderSuffix(ProjectExplorer::Project *proj
|
|||||||
QString CPPEDITOR_EXPORT preferredCxxSourceSuffix(ProjectExplorer::Project *project);
|
QString CPPEDITOR_EXPORT preferredCxxSourceSuffix(ProjectExplorer::Project *project);
|
||||||
bool CPPEDITOR_EXPORT preferLowerCaseFileNames(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();
|
UsePrecompiledHeaders CPPEDITOR_EXPORT getPchUsage();
|
||||||
|
|
||||||
int indexerFileSizeLimitInMb();
|
int indexerFileSizeLimitInMb();
|
||||||
|
@@ -138,7 +138,7 @@ void SymbolsFindFilter::addResults(QFutureWatcher<SearchResultItem> *watcher, in
|
|||||||
SearchResultItems items;
|
SearchResultItems items;
|
||||||
for (int i = begin; i < end; ++i)
|
for (int i = begin; i < end; ++i)
|
||||||
items << watcher->resultAt(i);
|
items << watcher->resultAt(i);
|
||||||
search->addResults(items, SearchResult::AddSorted);
|
search->addResults(items, SearchResult::AddSortedByContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymbolsFindFilter::finish(QFutureWatcher<SearchResultItem> *watcher)
|
void SymbolsFindFilter::finish(QFutureWatcher<SearchResultItem> *watcher)
|
||||||
|
@@ -848,7 +848,7 @@ void ClientPrivate::requestDocumentHighlightsNow(TextEditor::TextEditorWidget *w
|
|||||||
q->cancelRequest(m_highlightRequests.take(widget));
|
q->cancelRequest(m_highlightRequests.take(widget));
|
||||||
});
|
});
|
||||||
request.setResponseCallback(
|
request.setResponseCallback(
|
||||||
[widget, this, uri, connection]
|
[widget, this, uri, connection, adjustedCursor]
|
||||||
(const DocumentHighlightsRequest::Response &response)
|
(const DocumentHighlightsRequest::Response &response)
|
||||||
{
|
{
|
||||||
m_highlightRequests.remove(widget);
|
m_highlightRequests.remove(widget);
|
||||||
@@ -874,6 +874,30 @@ void ClientPrivate::requestDocumentHighlightsNow(TextEditor::TextEditorWidget *w
|
|||||||
selection.cursor.setPosition(end, QTextCursor::KeepAnchor);
|
selection.cursor.setPosition(end, QTextCursor::KeepAnchor);
|
||||||
selections << selection;
|
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);
|
widget->setExtraSelections(id, selections);
|
||||||
});
|
});
|
||||||
m_highlightRequests[widget] = request.id();
|
m_highlightRequests[widget] = request.id();
|
||||||
|
@@ -16,6 +16,8 @@ class TextDocument;
|
|||||||
class TextEditorWidget;
|
class TextEditorWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Utils { namespace Text { class Range; } }
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QWidget;
|
class QWidget;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
@@ -226,6 +228,8 @@ private:
|
|||||||
TextEditor::TextDocument *doc);
|
TextEditor::TextDocument *doc);
|
||||||
virtual bool referencesShadowFile(const TextEditor::TextDocument *doc,
|
virtual bool referencesShadowFile(const TextEditor::TextDocument *doc,
|
||||||
const Utils::FilePath &candidate);
|
const Utils::FilePath &candidate);
|
||||||
|
virtual QList<Utils::Text::Range> additionalDocumentHighlights(
|
||||||
|
TextEditor::TextEditorWidget *, const QTextCursor &) { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace LanguageClient
|
} // namespace LanguageClient
|
||||||
|
@@ -563,11 +563,22 @@ void SymbolSupport::handleRenameResponse(Core::SearchResult *search,
|
|||||||
|
|
||||||
const std::optional<WorkspaceEdit> &edits = response.result();
|
const std::optional<WorkspaceEdit> &edits = response.result();
|
||||||
if (edits.has_value()) {
|
if (edits.has_value()) {
|
||||||
search->addResults(generateReplaceItems(*edits,
|
const Utils::SearchResultItems items = generateReplaceItems(
|
||||||
search,
|
*edits, search, m_limitRenamingToProjects, m_client->hostPathMapper());
|
||||||
m_limitRenamingToProjects,
|
search->addResults(items, Core::SearchResult::AddOrdered);
|
||||||
m_client->hostPathMapper()),
|
if (m_renameResultsEnhancer) {
|
||||||
Core::SearchResult::AddOrdered);
|
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);
|
qobject_cast<ReplaceWidget *>(search->additionalReplaceWidget())->showLabel(false);
|
||||||
search->setReplaceEnabled(true);
|
search->setReplaceEnabled(true);
|
||||||
search->finishSearch(false);
|
search->finishSearch(false);
|
||||||
@@ -634,6 +645,11 @@ void SymbolSupport::setDefaultRenamingSymbolMapper(const SymbolMapper &mapper)
|
|||||||
m_defaultSymbolMapper = mapper;
|
m_defaultSymbolMapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SymbolSupport::setRenameResultsEnhancer(const RenameResultsEnhancer &enhancer)
|
||||||
|
{
|
||||||
|
m_renameResultsEnhancer = enhancer;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace LanguageClient
|
} // namespace LanguageClient
|
||||||
|
|
||||||
#include <languageclientsymbolsupport.moc>
|
#include <languageclientsymbolsupport.moc>
|
||||||
|
@@ -51,6 +51,9 @@ public:
|
|||||||
|
|
||||||
void setLimitRenamingToProjects(bool limit) { m_limitRenamingToProjects = limit; }
|
void setLimitRenamingToProjects(bool limit) { m_limitRenamingToProjects = limit; }
|
||||||
|
|
||||||
|
using RenameResultsEnhancer = std::function<Utils::SearchResultItems(const Utils::SearchResultItems &)>;
|
||||||
|
void setRenameResultsEnhancer(const RenameResultsEnhancer &enhancer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleFindReferencesResponse(
|
void handleFindReferencesResponse(
|
||||||
const LanguageServerProtocol::FindReferencesRequest::Response &response,
|
const LanguageServerProtocol::FindReferencesRequest::Response &response,
|
||||||
@@ -78,6 +81,7 @@ private:
|
|||||||
|
|
||||||
Client *m_client = nullptr;
|
Client *m_client = nullptr;
|
||||||
SymbolMapper m_defaultSymbolMapper;
|
SymbolMapper m_defaultSymbolMapper;
|
||||||
|
RenameResultsEnhancer m_renameResultsEnhancer;
|
||||||
QHash<Core::SearchResult *, LanguageServerProtocol::MessageId> m_renameRequestIds;
|
QHash<Core::SearchResult *, LanguageServerProtocol::MessageId> m_renameRequestIds;
|
||||||
bool m_limitRenamingToProjects = false;
|
bool m_limitRenamingToProjects = false;
|
||||||
};
|
};
|
||||||
|
@@ -155,7 +155,7 @@ void TestDeclarationComments::commentsForDecl()
|
|||||||
const Symbol * const symbol = finder.find();
|
const Symbol * const symbol = finder.find();
|
||||||
QVERIFY(symbol);
|
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()) {
|
if (expectedCommentPrefix.isEmpty()) {
|
||||||
QVERIFY(commentTokens.isEmpty());
|
QVERIFY(commentTokens.isEmpty());
|
||||||
return;
|
return;
|
||||||
|
Reference in New Issue
Block a user