ClangCodeModel: Work around clangd cursor issue

If the cursor is right before the "." in a member access expression,
clangd interprets it as belonging to the member instead of the base
expression, which leads to unexpected behavior.
Work around this by sending a cursor position one to the left of the
real one to clangd in such cases.

Change-Id: I429ee9189760ccb02d231acfcb94ab6cfde3cd8d
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2021-12-02 13:18:02 +01:00
parent 0f9aa307a3
commit 825c9ea64f
4 changed files with 75 additions and 14 deletions

View File

@@ -38,6 +38,8 @@
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/find/searchresultitem.h> #include <coreplugin/find/searchresultitem.h>
#include <coreplugin/find/searchresultwindow.h> #include <coreplugin/find/searchresultwindow.h>
#include <cplusplus/AST.h>
#include <cplusplus/ASTPath.h>
#include <cplusplus/FindUsages.h> #include <cplusplus/FindUsages.h>
#include <cplusplus/Icons.h> #include <cplusplus/Icons.h>
#include <cplusplus/MatchingText.h> #include <cplusplus/MatchingText.h>
@@ -1090,7 +1092,9 @@ public:
void handleDeclDefSwitchReplies(); void handleDeclDefSwitchReplies();
static CppEditor::CppEditorWidget *widgetFromDocument(const TextDocument *doc);
QString searchTermFromCursor(const QTextCursor &cursor) const; QString searchTermFromCursor(const QTextCursor &cursor) const;
static QTextCursor adjustedCursor(const QTextCursor &cursor, const TextDocument *doc);
void setHelpItemForTooltip(const MessageId &token, const QString &fqn = {}, void setHelpItemForTooltip(const MessageId &token, const QString &fqn = {},
HelpItem::Category category = HelpItem::Unknown, HelpItem::Category category = HelpItem::Unknown,
@@ -1380,22 +1384,23 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor,
if (searchTerm.isEmpty()) if (searchTerm.isEmpty())
return; return;
const QTextCursor adjustedCursor = Private::adjustedCursor(cursor, document);
const bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences(); const bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences();
// If it's a "normal" symbol, go right ahead. // If it's a "normal" symbol, go right ahead.
if (searchTerm != "operator" && Utils::allOf(searchTerm, [](const QChar &c) { if (searchTerm != "operator" && Utils::allOf(searchTerm, [](const QChar &c) {
return c.isLetterOrNumber() || c == '_'; return c.isLetterOrNumber() || c == '_';
})) { })) {
d->findUsages(document, cursor, searchTerm, replacement, categorize); d->findUsages(document, adjustedCursor, searchTerm, replacement, categorize);
return; return;
} }
// Otherwise get the proper spelling of the search term from clang, so we can put it into the // Otherwise get the proper spelling of the search term from clang, so we can put it into the
// search widget. // search widget.
const TextDocumentIdentifier docId(DocumentUri::fromFilePath(document->filePath())); const TextDocumentIdentifier docId(DocumentUri::fromFilePath(document->filePath()));
const TextDocumentPositionParams params(docId, Range(cursor).start()); const TextDocumentPositionParams params(docId, Range(adjustedCursor).start());
SymbolInfoRequest symReq(params); SymbolInfoRequest symReq(params);
symReq.setResponseCallback([this, doc = QPointer(document), cursor, replacement, categorize] symReq.setResponseCallback([this, doc = QPointer(document), adjustedCursor, replacement, categorize]
(const SymbolInfoRequest::Response &response) { (const SymbolInfoRequest::Response &response) {
if (!doc) if (!doc)
return; return;
@@ -1408,7 +1413,7 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor,
const SymbolDetails &sd = list->first(); const SymbolDetails &sd = list->first();
if (sd.name().isEmpty()) if (sd.name().isEmpty())
return; return;
d->findUsages(doc.data(), cursor, sd.name(), replacement, categorize); d->findUsages(doc.data(), adjustedCursor, sd.name(), replacement, categorize);
}); });
sendContent(symReq); sendContent(symReq);
} }
@@ -1444,6 +1449,12 @@ void ClangdClient::handleDocumentClosed(TextDocument *doc)
d->virtualRanges.remove(doc); d->virtualRanges.remove(doc);
} }
QTextCursor ClangdClient::adjustedCursorForHighlighting(const QTextCursor &cursor,
TextEditor::TextDocument *doc)
{
return Private::adjustedCursor(cursor, doc);
}
const LanguageClient::Client::CustomInspectorTabs ClangdClient::createCustomInspectorTabs() const LanguageClient::Client::CustomInspectorTabs ClangdClient::createCustomInspectorTabs()
{ {
return {std::make_pair(new MemoryUsageWidget(this), tr("Memory Usage"))}; return {std::make_pair(new MemoryUsageWidget(this), tr("Memory Usage"))};
@@ -1782,15 +1793,16 @@ void ClangdClient::followSymbol(TextDocument *document,
) )
{ {
QTC_ASSERT(documentOpen(document), openDocument(document)); QTC_ASSERT(documentOpen(document), openDocument(document));
const QTextCursor adjustedCursor = Private::adjustedCursor(cursor, document);
if (!resolveTarget) { if (!resolveTarget) {
d->followSymbolData.reset(); d->followSymbolData.reset();
symbolSupport().findLinkAt(document, cursor, std::move(callback), false); symbolSupport().findLinkAt(document, adjustedCursor, std::move(callback), false);
return; return;
} }
qCDebug(clangdLog) << "follow symbol requested" << document->filePath() qCDebug(clangdLog) << "follow symbol requested" << document->filePath()
<< cursor.blockNumber() << cursor.positionInBlock(); << adjustedCursor.blockNumber() << adjustedCursor.positionInBlock();
d->followSymbolData.emplace(this, ++d->nextJobId, cursor, editorWidget, d->followSymbolData.emplace(this, ++d->nextJobId, adjustedCursor, editorWidget,
DocumentUri::fromFilePath(document->filePath()), DocumentUri::fromFilePath(document->filePath()),
std::move(callback), openInSplit); std::move(callback), openInSplit);
@@ -1809,7 +1821,7 @@ void ClangdClient::followSymbol(TextDocument *document,
if (d->followSymbolData->cursorNode) if (d->followSymbolData->cursorNode)
d->handleGotoDefinitionResult(); d->handleGotoDefinitionResult();
}; };
symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true); symbolSupport().findLinkAt(document, adjustedCursor, std::move(gotoDefCallback), true);
if (versionNumber() < QVersionNumber(12)) { if (versionNumber() < QVersionNumber(12)) {
d->followSymbolData->cursorNode.emplace(AstNode()); d->followSymbolData->cursorNode.emplace(AstNode());
@@ -1825,7 +1837,8 @@ void ClangdClient::followSymbol(TextDocument *document,
if (d->followSymbolData->defLink.hasValidTarget()) if (d->followSymbolData->defLink.hasValidTarget())
d->handleGotoDefinitionResult(); d->handleGotoDefinitionResult();
}; };
d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::AlwaysAsync, Range(cursor)); d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::AlwaysAsync,
Range(adjustedCursor));
} }
void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &cursor, void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &cursor,
@@ -2347,6 +2360,13 @@ void ClangdClient::Private::handleDeclDefSwitchReplies()
switchDeclDefData.reset(); switchDeclDefData.reset();
} }
CppEditor::CppEditorWidget *ClangdClient::Private::widgetFromDocument(const TextDocument *doc)
{
IEditor * const editor = Utils::findOrDefault(EditorManager::visibleEditors(),
[doc](const IEditor *editor) { return editor->document() == doc; });
return qobject_cast<CppEditor::CppEditorWidget *>(TextEditorWidget::fromEditor(editor));
}
QString ClangdClient::Private::searchTermFromCursor(const QTextCursor &cursor) const QString ClangdClient::Private::searchTermFromCursor(const QTextCursor &cursor) const
{ {
QTextCursor termCursor(cursor); QTextCursor termCursor(cursor);
@@ -2354,6 +2374,36 @@ QString ClangdClient::Private::searchTermFromCursor(const QTextCursor &cursor) c
return termCursor.selectedText(); return termCursor.selectedText();
} }
// https://github.com/clangd/clangd/issues/936
QTextCursor ClangdClient::Private::adjustedCursor(const QTextCursor &cursor,
const TextDocument *doc)
{
CppEditor::CppEditorWidget * const widget = widgetFromDocument(doc);
if (!widget)
return cursor;
const Document::Ptr cppDoc = widget->semanticInfo().doc;
if (!cppDoc)
return cursor;
const QList<AST *> astPath = ASTPath(cppDoc)(cursor);
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
const MemberAccessAST * const memberAccess = (*it)->asMemberAccess();
if (!memberAccess)
continue;
const TranslationUnit * const tu = cppDoc->translationUnit();
if (tu->tokenAt(memberAccess->access_token).kind() != T_DOT)
return cursor;
int dotLine, dotColumn;
tu->getTokenPosition(memberAccess->access_token, &dotLine, &dotColumn);
const int dotPos = Utils::Text::positionInText(doc->document(), dotLine, dotColumn);
if (dotPos != cursor.position())
return cursor;
QTextCursor c = cursor;
c.setPosition(cursor.position() - 1);
return c;
}
return cursor;
}
void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const QString &fqn, void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const QString &fqn,
HelpItem::Category category, HelpItem::Category category,
const QString &type) const QString &type)
@@ -2704,12 +2754,10 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
if (clangdLogAst().isDebugEnabled()) if (clangdLogAst().isDebugEnabled())
ast.print(); ast.print();
IEditor * const editor = Utils::findOrDefault(EditorManager::visibleEditors(),
[doc](const IEditor *editor) { return editor->document() == doc; });
const auto editorWidget = TextEditorWidget::fromEditor(editor);
const auto runner = [tokens, filePath = doc->filePath(), const auto runner = [tokens, filePath = doc->filePath(),
text = doc->document()->toPlainText(), ast, text = doc->document()->toPlainText(), ast,
w = QPointer(editorWidget), rev = doc->document()->revision(), w = QPointer<TextEditorWidget>(widgetFromDocument(doc)),
rev = doc->document()->revision(),
clangdVersion = q->versionNumber()] { clangdVersion = q->versionNumber()] {
return Utils::runAsync(semanticHighlighter, filePath, tokens, text, ast, w, rev, return Utils::runAsync(semanticHighlighter, filePath, tokens, text, ast, w, rev,
clangdVersion); clangdVersion);

View File

@@ -102,6 +102,8 @@ private:
void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams &params) override; void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams &params) override;
void handleDocumentOpened(TextEditor::TextDocument *doc) override; void handleDocumentOpened(TextEditor::TextDocument *doc) override;
void handleDocumentClosed(TextEditor::TextDocument *doc) override; void handleDocumentClosed(TextEditor::TextDocument *doc) override;
QTextCursor adjustedCursorForHighlighting(const QTextCursor &cursor,
TextEditor::TextDocument *doc) override;
const CustomInspectorTabs createCustomInspectorTabs() override; const CustomInspectorTabs createCustomInspectorTabs() override;
class Private; class Private;

View File

@@ -523,8 +523,10 @@ void Client::requestDocumentHighlights(TextEditor::TextEditorWidget *widget)
if (m_highlightRequests.contains(widget)) if (m_highlightRequests.contains(widget))
cancelRequest(m_highlightRequests.take(widget)); cancelRequest(m_highlightRequests.take(widget));
const QTextCursor adjustedCursor = adjustedCursorForHighlighting(widget->textCursor(),
widget->textDocument());
DocumentHighlightsRequest request( DocumentHighlightsRequest request(
TextDocumentPositionParams(TextDocumentIdentifier(uri), Position(widget->textCursor()))); TextDocumentPositionParams(TextDocumentIdentifier(uri), Position{adjustedCursor}));
auto connection = connect(widget, &QObject::destroyed, this, [this, widget]() { auto connection = connect(widget, &QObject::destroyed, this, [this, widget]() {
if (m_highlightRequests.contains(widget)) if (m_highlightRequests.contains(widget))
cancelRequest(m_highlightRequests.take(widget)); cancelRequest(m_highlightRequests.take(widget));
@@ -1570,4 +1572,11 @@ bool Client::sendWorkspceFolderChanges() const
return false; return false;
} }
QTextCursor Client::adjustedCursorForHighlighting(const QTextCursor &cursor,
TextEditor::TextDocument *doc)
{
Q_UNUSED(doc)
return cursor;
}
} // namespace LanguageClient } // namespace LanguageClient

View File

@@ -248,6 +248,8 @@ private:
virtual void handleDocumentClosed(TextEditor::TextDocument *) {} virtual void handleDocumentClosed(TextEditor::TextDocument *) {}
virtual void handleDocumentOpened(TextEditor::TextDocument *) {} virtual void handleDocumentOpened(TextEditor::TextDocument *) {}
virtual QTextCursor adjustedCursorForHighlighting(const QTextCursor &cursor,
TextEditor::TextDocument *doc);
using ContentHandler = std::function<void(const QByteArray &, QTextCodec *, QString &, using ContentHandler = std::function<void(const QByteArray &, QTextCodec *, QString &,
LanguageServerProtocol::ResponseHandlers, LanguageServerProtocol::ResponseHandlers,