2021-04-20 14:42:29 +02:00
|
|
|
|
/****************************************************************************
|
|
|
|
|
|
**
|
|
|
|
|
|
** Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
|
**
|
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
|
**
|
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
|
**
|
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
|
**
|
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
#include "clangdclient.h"
|
|
|
|
|
|
|
2021-11-10 12:35:06 +01:00
|
|
|
|
#include "clangcompletionassistprocessor.h"
|
2021-06-18 16:30:03 +02:00
|
|
|
|
#include "clangcompletioncontextanalyzer.h"
|
2021-06-02 17:51:31 +02:00
|
|
|
|
#include "clangdiagnosticmanager.h"
|
2021-11-26 12:54:50 +01:00
|
|
|
|
#include "clangdqpropertyhighlighter.h"
|
2021-10-28 11:59:48 +02:00
|
|
|
|
#include "clangmodelmanagersupport.h"
|
2021-06-18 16:30:03 +02:00
|
|
|
|
#include "clangpreprocessorassistproposalitem.h"
|
2021-06-02 17:51:31 +02:00
|
|
|
|
#include "clangtextmark.h"
|
2021-07-14 17:07:34 +02:00
|
|
|
|
#include "clangutils.h"
|
2021-06-02 17:51:31 +02:00
|
|
|
|
|
2021-06-01 18:14:12 +02:00
|
|
|
|
#include <clangsupport/sourcelocationscontainer.h>
|
2021-05-19 15:51:25 +02:00
|
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
2021-04-21 14:29:49 +02:00
|
|
|
|
#include <coreplugin/find/searchresultitem.h>
|
|
|
|
|
|
#include <coreplugin/find/searchresultwindow.h>
|
2021-12-02 13:18:02 +01:00
|
|
|
|
#include <cplusplus/AST.h>
|
|
|
|
|
|
#include <cplusplus/ASTPath.h>
|
2021-04-21 14:29:49 +02:00
|
|
|
|
#include <cplusplus/FindUsages.h>
|
2021-06-18 16:30:03 +02:00
|
|
|
|
#include <cplusplus/Icons.h>
|
|
|
|
|
|
#include <cplusplus/MatchingText.h>
|
|
|
|
|
|
#include <cppeditor/cppeditorconstants.h>
|
2021-08-30 10:58:08 +02:00
|
|
|
|
#include <cppeditor/cppcodemodelsettings.h>
|
2021-09-28 15:10:47 +02:00
|
|
|
|
#include <cppeditor/cppcompletionassistprocessor.h>
|
2021-09-13 16:09:04 +02:00
|
|
|
|
#include <cppeditor/cppcompletionassistprovider.h>
|
2021-08-30 10:58:08 +02:00
|
|
|
|
#include <cppeditor/cppdoxygen.h>
|
2021-09-01 18:08:54 +02:00
|
|
|
|
#include <cppeditor/cppeditorwidget.h>
|
2021-08-30 10:58:08 +02:00
|
|
|
|
#include <cppeditor/cppfindreferences.h>
|
|
|
|
|
|
#include <cppeditor/cppmodelmanager.h>
|
2022-02-03 10:36:40 +01:00
|
|
|
|
#include <cppeditor/cpprefactoringchanges.h>
|
2021-08-30 10:58:08 +02:00
|
|
|
|
#include <cppeditor/cpptoolsreuse.h>
|
|
|
|
|
|
#include <cppeditor/cppvirtualfunctionassistprovider.h>
|
|
|
|
|
|
#include <cppeditor/cppvirtualfunctionproposalitem.h>
|
|
|
|
|
|
#include <cppeditor/semantichighlighter.h>
|
2021-04-20 14:42:29 +02:00
|
|
|
|
#include <languageclient/languageclientinterface.h>
|
2021-11-01 07:25:07 +01:00
|
|
|
|
#include <languageclient/languageclientmanager.h>
|
2021-06-02 17:51:31 +02:00
|
|
|
|
#include <languageclient/languageclientutils.h>
|
2021-05-19 13:22:49 +02:00
|
|
|
|
#include <projectexplorer/project.h>
|
2021-05-18 12:59:15 +02:00
|
|
|
|
#include <projectexplorer/projecttree.h>
|
|
|
|
|
|
#include <projectexplorer/session.h>
|
|
|
|
|
|
#include <texteditor/basefilefind.h>
|
2021-05-19 15:51:25 +02:00
|
|
|
|
#include <texteditor/codeassist/assistinterface.h>
|
|
|
|
|
|
#include <texteditor/codeassist/iassistprocessor.h>
|
|
|
|
|
|
#include <texteditor/codeassist/iassistprovider.h>
|
2021-06-18 16:30:03 +02:00
|
|
|
|
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
|
|
|
|
|
|
#include <texteditor/texteditorsettings.h>
|
2021-05-19 15:51:25 +02:00
|
|
|
|
#include <texteditor/texteditor.h>
|
2021-05-18 12:59:15 +02:00
|
|
|
|
#include <utils/algorithm.h>
|
2022-02-01 17:39:00 +01:00
|
|
|
|
#include <utils/fileutils.h>
|
2021-11-16 14:12:41 +01:00
|
|
|
|
#include <utils/itemviews.h>
|
2021-06-09 09:47:26 +02:00
|
|
|
|
#include <utils/runextensions.h>
|
2021-11-16 14:12:41 +01:00
|
|
|
|
#include <utils/treemodel.h>
|
2021-11-10 14:44:41 +01:00
|
|
|
|
#include <utils/utilsicons.h>
|
2021-04-20 14:42:29 +02:00
|
|
|
|
|
2021-11-16 14:12:41 +01:00
|
|
|
|
#include <QAction>
|
2021-05-18 12:59:15 +02:00
|
|
|
|
#include <QCheckBox>
|
2021-10-01 12:23:05 +02:00
|
|
|
|
#include <QDateTime>
|
|
|
|
|
|
#include <QElapsedTimer>
|
2021-04-21 14:29:49 +02:00
|
|
|
|
#include <QFile>
|
|
|
|
|
|
#include <QHash>
|
2021-11-16 14:12:41 +01:00
|
|
|
|
#include <QHeaderView>
|
|
|
|
|
|
#include <QMenu>
|
2021-05-19 15:51:25 +02:00
|
|
|
|
#include <QPair>
|
2021-04-21 14:29:49 +02:00
|
|
|
|
#include <QPointer>
|
|
|
|
|
|
#include <QRegularExpression>
|
2022-02-01 17:39:00 +01:00
|
|
|
|
#include <QStandardPaths>
|
2021-11-16 14:12:41 +01:00
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
|
#include <QWidget>
|
2021-10-05 15:22:09 +02:00
|
|
|
|
#include <QtConcurrent>
|
2021-04-21 14:29:49 +02:00
|
|
|
|
|
2021-11-16 14:12:41 +01:00
|
|
|
|
#include <cmath>
|
2021-05-19 15:51:25 +02:00
|
|
|
|
#include <set>
|
2021-06-09 09:47:26 +02:00
|
|
|
|
#include <unordered_map>
|
2021-10-06 13:27:23 +02:00
|
|
|
|
#include <utility>
|
2021-05-19 15:51:25 +02:00
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
using namespace CPlusPlus;
|
|
|
|
|
|
using namespace Core;
|
2021-04-20 14:42:29 +02:00
|
|
|
|
using namespace LanguageClient;
|
2021-04-20 15:46:35 +02:00
|
|
|
|
using namespace LanguageServerProtocol;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
using namespace ProjectExplorer;
|
2021-09-28 13:21:59 +02:00
|
|
|
|
using namespace TextEditor;
|
2021-04-20 14:42:29 +02:00
|
|
|
|
|
|
|
|
|
|
namespace ClangCodeModel {
|
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
|
|
static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg);
|
2021-07-15 11:39:56 +02:00
|
|
|
|
static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", QtWarningMsg);
|
|
|
|
|
|
static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg);
|
|
|
|
|
|
static Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
|
2021-10-01 12:23:05 +02:00
|
|
|
|
static Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg);
|
2021-10-15 11:31:13 +02:00
|
|
|
|
static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.completion",
|
|
|
|
|
|
QtWarningMsg);
|
2021-04-20 15:46:35 +02:00
|
|
|
|
static QString indexingToken() { return "backgroundIndexProgress"; }
|
|
|
|
|
|
|
2021-10-13 14:24:25 +02:00
|
|
|
|
static QStringView subViewLen(const QString &s, qsizetype start, qsizetype length)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (start < 0 || length < 0 || start + length > s.length())
|
|
|
|
|
|
return {};
|
|
|
|
|
|
return QStringView(s).mid(start, length);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end)
|
|
|
|
|
|
{
|
|
|
|
|
|
return subViewLen(s, start, end - start);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
class AstNode : public JsonObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using JsonObject::JsonObject;
|
|
|
|
|
|
|
|
|
|
|
|
static constexpr char roleKey[] = "role";
|
|
|
|
|
|
static constexpr char arcanaKey[] = "arcana";
|
|
|
|
|
|
|
|
|
|
|
|
// The general kind of node, such as “expression”. Corresponds to clang’s base AST node type,
|
|
|
|
|
|
// such as Expr. The most common are “expression”, “statement”, “type” and “declaration”.
|
|
|
|
|
|
QString role() const { return typedValue<QString>(roleKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// The specific kind of node, such as “BinaryOperator”. Corresponds to clang’s concrete
|
|
|
|
|
|
// node class, with Expr etc suffix dropped.
|
|
|
|
|
|
QString kind() const { return typedValue<QString>(kindKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// Brief additional details, such as ‘||’. Information present here depends on the node kind.
|
|
|
|
|
|
Utils::optional<QString> detail() const { return optionalValue<QString>(detailKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// One line dump of information, similar to that printed by clang -Xclang -ast-dump.
|
|
|
|
|
|
// Only available for certain types of nodes.
|
|
|
|
|
|
Utils::optional<QString> arcana() const { return optionalValue<QString>(arcanaKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// The part of the code that produced this node. Missing for implicit nodes, nodes produced
|
|
|
|
|
|
// by macro expansion, etc.
|
|
|
|
|
|
Range range() const { return typedValue<Range>(rangeKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// Descendants describing the internal structure. The tree of nodes is similar to that printed
|
|
|
|
|
|
// by clang -Xclang -ast-dump, or that traversed by clang::RecursiveASTVisitor.
|
|
|
|
|
|
Utils::optional<QList<AstNode>> children() const { return optionalArray<AstNode>(childrenKey); }
|
|
|
|
|
|
|
|
|
|
|
|
bool hasRange() const { return contains(rangeKey); }
|
|
|
|
|
|
|
|
|
|
|
|
bool arcanaContains(const QString &s) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const Utils::optional<QString> arcanaString = arcana();
|
|
|
|
|
|
return arcanaString && arcanaString->contains(s);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool detailIs(const QString &s) const
|
|
|
|
|
|
{
|
2022-02-24 09:38:59 +01:00
|
|
|
|
return detail() && *detail() == s;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
bool isMemberFunctionCall() const
|
|
|
|
|
|
{
|
2021-05-28 14:30:49 +02:00
|
|
|
|
return role() == "expression" && (kind() == "CXXMemberCall"
|
|
|
|
|
|
|| (kind() == "Member" && arcanaContains("member function")));
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isPureVirtualDeclaration() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("virtual pure");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-28 09:40:53 +02:00
|
|
|
|
bool isPureVirtualDefinition() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("' pure");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-27 16:32:24 +02:00
|
|
|
|
bool mightBeAmbiguousVirtualCall() const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isMemberFunctionCall())
|
|
|
|
|
|
return false;
|
2021-05-28 16:06:07 +02:00
|
|
|
|
bool hasBaseCast = false;
|
|
|
|
|
|
bool hasRecordType = false;
|
|
|
|
|
|
const QList<AstNode> childList = children().value_or(QList<AstNode>());
|
|
|
|
|
|
for (const AstNode &c : childList) {
|
|
|
|
|
|
if (!hasBaseCast && c.detailIs("UncheckedDerivedToBase"))
|
|
|
|
|
|
hasBaseCast = true;
|
|
|
|
|
|
if (!hasRecordType && c.role() == "specifier" && c.kind() == "TypeSpec")
|
|
|
|
|
|
hasRecordType = true;
|
|
|
|
|
|
if (hasBaseCast && hasRecordType)
|
2021-05-27 16:32:24 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-04 12:40:26 +02:00
|
|
|
|
bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; }
|
|
|
|
|
|
|
2021-10-04 14:25:35 +02:00
|
|
|
|
bool isTemplateParameterDeclaration() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return role() == "declaration" && (kind() == "TemplateTypeParm"
|
|
|
|
|
|
|| kind() == "NonTypeTemplateParm");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
QString type() const
|
|
|
|
|
|
{
|
|
|
|
|
|
const Utils::optional<QString> arcanaString = arcana();
|
|
|
|
|
|
if (!arcanaString)
|
|
|
|
|
|
return {};
|
2021-06-04 12:40:26 +02:00
|
|
|
|
return typeFromPos(*arcanaString, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString typeFromPos(const QString &s, int pos) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const int quote1Offset = s.indexOf('\'', pos);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
if (quote1Offset == -1)
|
|
|
|
|
|
return {};
|
2021-06-04 12:40:26 +02:00
|
|
|
|
const int quote2Offset = s.indexOf('\'', quote1Offset + 1);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
if (quote2Offset == -1)
|
|
|
|
|
|
return {};
|
2021-06-04 12:40:26 +02:00
|
|
|
|
if (s.mid(quote2Offset + 1, 2) == ":'")
|
|
|
|
|
|
return typeFromPos(s, quote2Offset + 2);
|
|
|
|
|
|
return s.mid(quote1Offset + 1, quote2Offset - quote1Offset - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HelpItem::Category qdocCategoryForDeclaration(HelpItem::Category fallback)
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto childList = children();
|
|
|
|
|
|
if (!childList || childList->size() < 2)
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
const AstNode c1 = childList->first();
|
|
|
|
|
|
if (c1.role() != "type" || c1.kind() != "Auto")
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
QList<AstNode> typeCandidates = {childList->at(1)};
|
|
|
|
|
|
while (!typeCandidates.isEmpty()) {
|
|
|
|
|
|
const AstNode n = typeCandidates.takeFirst();
|
|
|
|
|
|
if (n.role() == "type") {
|
|
|
|
|
|
if (n.kind() == "Enum")
|
|
|
|
|
|
return HelpItem::Enum;
|
|
|
|
|
|
if (n.kind() == "Record")
|
|
|
|
|
|
return HelpItem::ClassOrNamespace;
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
typeCandidates << n.children().value_or(QList<AstNode>());
|
|
|
|
|
|
}
|
|
|
|
|
|
return fallback;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Returns true <=> the type is "recursively const".
|
|
|
|
|
|
// E.g. returns true for "const int &", "const int *" and "const int * const *",
|
|
|
|
|
|
// and false for "int &" and "const int **".
|
|
|
|
|
|
// For non-pointer types such as "int", we check whether they are uses as lvalues
|
|
|
|
|
|
// or rvalues.
|
|
|
|
|
|
bool hasConstType() const
|
|
|
|
|
|
{
|
|
|
|
|
|
QString theType = type();
|
|
|
|
|
|
if (theType.endsWith("const"))
|
|
|
|
|
|
theType.chop(5);
|
2021-11-09 11:54:44 +01:00
|
|
|
|
|
|
|
|
|
|
// We don't care about the "inner" type of templates.
|
|
|
|
|
|
const int openAngleBracketPos = theType.indexOf('<');
|
|
|
|
|
|
if (openAngleBracketPos != -1) {
|
|
|
|
|
|
const int closingAngleBracketPos = theType.lastIndexOf('>');
|
|
|
|
|
|
if (closingAngleBracketPos > openAngleBracketPos) {
|
|
|
|
|
|
theType = theType.left(openAngleBracketPos)
|
|
|
|
|
|
+ theType.mid(closingAngleBracketPos + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-06-09 09:47:26 +02:00
|
|
|
|
const int xrefCount = theType.count("&&");
|
|
|
|
|
|
const int refCount = theType.count('&') - 2 * xrefCount;
|
|
|
|
|
|
const int ptrRefCount = theType.count('*') + refCount;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
const int constCount = theType.count("const");
|
|
|
|
|
|
if (ptrRefCount == 0)
|
2021-06-09 09:47:26 +02:00
|
|
|
|
return constCount > 0 || detailIs("LValueToRValue") || arcanaContains("xvalue");
|
2021-04-21 14:29:49 +02:00
|
|
|
|
return ptrRefCount <= constCount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool childContainsRange(int index, const Range &range) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const Utils::optional<QList<AstNode>> childList = children();
|
|
|
|
|
|
return childList && childList->size() > index
|
|
|
|
|
|
&& childList->at(index).range().contains(range);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-09 09:47:26 +02:00
|
|
|
|
bool hasChildWithRole(const QString &role) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return Utils::contains(children().value_or(QList<AstNode>()), [&role](const AstNode &c) {
|
|
|
|
|
|
return c.role() == role;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
QString operatorString() const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (kind() == "BinaryOperator")
|
|
|
|
|
|
return detail().value_or(QString());
|
|
|
|
|
|
QTC_ASSERT(kind() == "CXXOperatorCall", return {});
|
|
|
|
|
|
const Utils::optional<QString> arcanaString = arcana();
|
|
|
|
|
|
if (!arcanaString)
|
|
|
|
|
|
return {};
|
|
|
|
|
|
const int closingQuoteOffset = arcanaString->lastIndexOf('\'');
|
|
|
|
|
|
if (closingQuoteOffset <= 0)
|
|
|
|
|
|
return {};
|
|
|
|
|
|
const int openingQuoteOffset = arcanaString->lastIndexOf('\'', closingQuoteOffset - 1);
|
|
|
|
|
|
if (openingQuoteOffset == -1)
|
|
|
|
|
|
return {};
|
|
|
|
|
|
return arcanaString->mid(openingQuoteOffset + 1, closingQuoteOffset
|
|
|
|
|
|
- openingQuoteOffset - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-14 12:47:24 +02:00
|
|
|
|
enum class FileStatus { Ours, Foreign, Mixed, Unknown };
|
|
|
|
|
|
FileStatus fileStatus(const Utils::FilePath &thisFile) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const Utils::optional<QString> arcanaString = arcana();
|
|
|
|
|
|
if (!arcanaString)
|
|
|
|
|
|
return FileStatus::Unknown;
|
|
|
|
|
|
|
|
|
|
|
|
// Example arcanas:
|
|
|
|
|
|
// "FunctionDecl 0x7fffb5d0dbd0 </tmp/test.cpp:1:1, line:5:1> line:1:6 func 'void ()'"
|
|
|
|
|
|
// "VarDecl 0x7fffb5d0dcf0 </tmp/test.cpp:2:5, /tmp/test.h:1:1> /tmp/test.cpp:2:10 b 'bool' cinit"
|
|
|
|
|
|
// The second one is for a particularly silly construction where the RHS of an
|
|
|
|
|
|
// initialization comes from an included header.
|
|
|
|
|
|
const int openPos = arcanaString->indexOf('<');
|
|
|
|
|
|
if (openPos == -1)
|
|
|
|
|
|
return FileStatus::Unknown;
|
|
|
|
|
|
const int closePos = arcanaString->indexOf('>', openPos + 1);
|
|
|
|
|
|
if (closePos == -1)
|
|
|
|
|
|
return FileStatus::Unknown;
|
|
|
|
|
|
bool hasOurs = false;
|
|
|
|
|
|
bool hasOther = false;
|
|
|
|
|
|
for (int startPos = openPos + 1; startPos < closePos;) {
|
|
|
|
|
|
int colon1Pos = arcanaString->indexOf(':', startPos);
|
|
|
|
|
|
if (colon1Pos == -1 || colon1Pos > closePos)
|
|
|
|
|
|
break;
|
|
|
|
|
|
if (Utils::HostOsInfo::isWindowsHost())
|
|
|
|
|
|
colon1Pos = arcanaString->indexOf(':', colon1Pos + 1);
|
|
|
|
|
|
if (colon1Pos == -1 || colon1Pos > closePos)
|
|
|
|
|
|
break;
|
|
|
|
|
|
const int colon2Pos = arcanaString->indexOf(':', colon1Pos + 2);
|
|
|
|
|
|
if (colon2Pos == -1 || colon2Pos > closePos)
|
|
|
|
|
|
break;
|
|
|
|
|
|
const int line = subViewEnd(*arcanaString, colon1Pos + 1, colon2Pos).toString().toInt(); // TODO: Drop toString() once we require >= Qt 5.15
|
|
|
|
|
|
if (line == 0)
|
|
|
|
|
|
break;
|
|
|
|
|
|
const QStringView fileOrLineString = subViewEnd(*arcanaString, startPos, colon1Pos);
|
|
|
|
|
|
if (fileOrLineString != QLatin1String("line")) {
|
|
|
|
|
|
if (Utils::FilePath::fromUserInput(fileOrLineString.toString()) == thisFile)
|
|
|
|
|
|
hasOurs = true;
|
|
|
|
|
|
else
|
|
|
|
|
|
hasOther = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
const int commaPos = arcanaString->indexOf(',', colon2Pos + 2);
|
|
|
|
|
|
if (commaPos != -1)
|
|
|
|
|
|
startPos = commaPos + 2;
|
|
|
|
|
|
else
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hasOurs)
|
|
|
|
|
|
return hasOther ? FileStatus::Mixed : FileStatus::Ours;
|
|
|
|
|
|
return hasOther ? FileStatus::Foreign : FileStatus::Unknown;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
// For debugging.
|
|
|
|
|
|
void print(int indent = 0) const
|
|
|
|
|
|
{
|
|
|
|
|
|
(qDebug().noquote() << QByteArray(indent, ' ')).quote() << role() << kind()
|
2021-05-31 15:57:44 +02:00
|
|
|
|
<< detail().value_or(QString()) << arcana().value_or(QString())
|
|
|
|
|
|
<< range();
|
2021-05-19 15:51:25 +02:00
|
|
|
|
for (const AstNode &c : children().value_or(QList<AstNode>()))
|
|
|
|
|
|
c.print(indent + 2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
bool isValid() const override
|
|
|
|
|
|
{
|
|
|
|
|
|
return contains(roleKey) && contains(kindKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-10-25 18:02:38 +02:00
|
|
|
|
class AstPathCollector
|
2021-04-21 14:29:49 +02:00
|
|
|
|
{
|
2021-10-25 18:02:38 +02:00
|
|
|
|
public:
|
|
|
|
|
|
AstPathCollector(const AstNode &root, const Range &range) : m_root(root), m_range(range) {}
|
2021-10-05 12:27:36 +02:00
|
|
|
|
|
2021-10-25 18:02:38 +02:00
|
|
|
|
QList<AstNode> collectPath()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_root.isValid())
|
|
|
|
|
|
return {};
|
|
|
|
|
|
visitNode(m_root, true);
|
|
|
|
|
|
return m_done ? m_path : m_longestSubPath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
void visitNode(const AstNode &node, bool isRoot = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isRoot && (!node.hasRange() || !node.range().contains(m_range)))
|
|
|
|
|
|
return;
|
|
|
|
|
|
m_path << node;
|
|
|
|
|
|
|
|
|
|
|
|
class PathDropper {
|
|
|
|
|
|
public:
|
|
|
|
|
|
PathDropper(AstPathCollector &collector) : m_collector(collector) {};
|
|
|
|
|
|
~PathDropper() {
|
|
|
|
|
|
if (m_collector.m_done)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (m_collector.m_path.size() > m_collector.m_longestSubPath.size())
|
|
|
|
|
|
m_collector.m_longestSubPath = m_collector.m_path;
|
|
|
|
|
|
m_collector.m_path.removeLast();
|
|
|
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
|
|
AstPathCollector &m_collector;
|
|
|
|
|
|
} pathDropper(*this);
|
|
|
|
|
|
|
|
|
|
|
|
// Still traverse the children, because they could have the same range.
|
|
|
|
|
|
if (node.range() == m_range)
|
|
|
|
|
|
m_done = true;
|
|
|
|
|
|
|
|
|
|
|
|
const auto children = node.children();
|
|
|
|
|
|
if (!children)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
QList<AstNode> childrenToCheck;
|
|
|
|
|
|
if (node.kind() == "Function" || node.role() == "expression") {
|
|
|
|
|
|
// Functions and expressions can contain implicit nodes that make the list unsorted.
|
|
|
|
|
|
// They cannot be ignored, as we need to consider them in certain contexts.
|
|
|
|
|
|
// Therefore, the binary search cannot be used here.
|
|
|
|
|
|
childrenToCheck = *children;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for (auto it = std::lower_bound(children->cbegin(), children->cend(), m_range,
|
|
|
|
|
|
leftOfRange);
|
|
|
|
|
|
it != children->cend() && !m_range.isLeftOf(it->range()); ++it) {
|
|
|
|
|
|
childrenToCheck << *it;
|
2021-10-05 12:27:36 +02:00
|
|
|
|
}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
2021-10-25 18:02:38 +02:00
|
|
|
|
|
|
|
|
|
|
const bool wasDone = m_done;
|
|
|
|
|
|
for (const AstNode &child : qAsConst(childrenToCheck)) {
|
|
|
|
|
|
visitNode(child);
|
|
|
|
|
|
if (m_done && !wasDone)
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
2021-10-25 18:02:38 +02:00
|
|
|
|
|
|
|
|
|
|
static bool leftOfRange(const AstNode &node, const Range &range)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Class and struct nodes can contain implicit constructors, destructors and
|
|
|
|
|
|
// operators, which appear at the end of the list, but whose range is the same
|
|
|
|
|
|
// as the class name. Therefore, we must force them not to compare less to
|
|
|
|
|
|
// anything else.
|
|
|
|
|
|
return node.range().isLeftOf(range) && !node.arcanaContains(" implicit ");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const AstNode &m_root;
|
|
|
|
|
|
const Range &m_range;
|
|
|
|
|
|
QList<AstNode> m_path;
|
|
|
|
|
|
QList<AstNode> m_longestSubPath;
|
|
|
|
|
|
bool m_done = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static QList<AstNode> getAstPath(const AstNode &root, const Range &range)
|
|
|
|
|
|
{
|
|
|
|
|
|
return AstPathCollector(root, range).collectPath();
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-05 12:27:36 +02:00
|
|
|
|
static QList<AstNode> getAstPath(const AstNode &root, const Position &pos)
|
|
|
|
|
|
{
|
|
|
|
|
|
return getAstPath(root, Range(pos, pos));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
static Usage::Type getUsageType(const QList<AstNode> &path)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool potentialWrite = false;
|
2021-12-09 17:59:10 +01:00
|
|
|
|
bool isFunction = false;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
const bool symbolIsDataType = path.last().role() == "type" && path.last().kind() == "Record";
|
2021-12-09 17:59:10 +01:00
|
|
|
|
const auto isPotentialWrite = [&] { return potentialWrite && !isFunction; };
|
2021-04-21 14:29:49 +02:00
|
|
|
|
for (auto pathIt = path.rbegin(); pathIt != path.rend(); ++pathIt) {
|
|
|
|
|
|
if (pathIt->arcanaContains("non_odr_use_unevaluated"))
|
|
|
|
|
|
return Usage::Type::Other;
|
|
|
|
|
|
if (pathIt->kind() == "CXXDelete")
|
|
|
|
|
|
return Usage::Type::Write;
|
|
|
|
|
|
if (pathIt->kind() == "CXXNew")
|
|
|
|
|
|
return Usage::Type::Other;
|
|
|
|
|
|
if (pathIt->kind() == "Switch" || pathIt->kind() == "If")
|
|
|
|
|
|
return Usage::Type::Read;
|
2021-12-09 17:59:10 +01:00
|
|
|
|
if (pathIt->kind() == "Call")
|
|
|
|
|
|
return isFunction ? Usage::Type::Other
|
|
|
|
|
|
: potentialWrite ? Usage::Type::WritableRef : Usage::Type::Read;
|
|
|
|
|
|
if (pathIt->kind() == "CXXMemberCall") {
|
|
|
|
|
|
const auto children = pathIt->children();
|
|
|
|
|
|
if (children && children->size() == 1
|
|
|
|
|
|
&& children->first() == path.last()
|
|
|
|
|
|
&& children->first().arcanaContains("bound member function")) {
|
|
|
|
|
|
return Usage::Type::Other;
|
|
|
|
|
|
}
|
|
|
|
|
|
return isPotentialWrite() ? Usage::Type::WritableRef : Usage::Type::Read;
|
|
|
|
|
|
}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
if ((pathIt->kind() == "DeclRef" || pathIt->kind() == "Member")
|
|
|
|
|
|
&& pathIt->arcanaContains("lvalue")) {
|
2021-12-09 17:59:10 +01:00
|
|
|
|
if (pathIt->arcanaContains(" Function "))
|
|
|
|
|
|
isFunction = true;
|
|
|
|
|
|
else
|
|
|
|
|
|
potentialWrite = true;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
if (pathIt->role() == "declaration") {
|
|
|
|
|
|
if (symbolIsDataType)
|
|
|
|
|
|
return Usage::Type::Other;
|
|
|
|
|
|
if (pathIt->arcanaContains("cinit")) {
|
|
|
|
|
|
if (pathIt == path.rbegin())
|
|
|
|
|
|
return Usage::Type::Initialization;
|
|
|
|
|
|
if (pathIt->childContainsRange(0, path.last().range()))
|
|
|
|
|
|
return Usage::Type::Initialization;
|
2021-12-09 17:59:10 +01:00
|
|
|
|
if (isFunction)
|
|
|
|
|
|
return Usage::Type::Read;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
if (!pathIt->hasConstType())
|
|
|
|
|
|
return Usage::Type::WritableRef;
|
|
|
|
|
|
return Usage::Type::Read;
|
|
|
|
|
|
}
|
|
|
|
|
|
return Usage::Type::Declaration;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pathIt->kind() == "MemberInitializer")
|
|
|
|
|
|
return pathIt == path.rbegin() ? Usage::Type::Write : Usage::Type::Read;
|
|
|
|
|
|
if (pathIt->kind() == "UnaryOperator"
|
|
|
|
|
|
&& (pathIt->detailIs("++") || pathIt->detailIs("--"))) {
|
|
|
|
|
|
return Usage::Type::Write;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LLVM uses BinaryOperator only for built-in types; for classes, CXXOperatorCall
|
|
|
|
|
|
// is used. The latter has an additional node at index 0, so the left-hand side
|
|
|
|
|
|
// of an assignment is at index 1.
|
|
|
|
|
|
const bool isBinaryOp = pathIt->kind() == "BinaryOperator";
|
|
|
|
|
|
const bool isOpCall = pathIt->kind() == "CXXOperatorCall";
|
|
|
|
|
|
if (isBinaryOp || isOpCall) {
|
|
|
|
|
|
if (isOpCall && symbolIsDataType) // Constructor invocation.
|
|
|
|
|
|
return Usage::Type::Other;
|
|
|
|
|
|
|
|
|
|
|
|
const QString op = pathIt->operatorString();
|
|
|
|
|
|
if (op.endsWith("=") && op != "==") { // Assignment.
|
|
|
|
|
|
const int lhsIndex = isBinaryOp ? 0 : 1;
|
|
|
|
|
|
if (pathIt->childContainsRange(lhsIndex, path.last().range()))
|
|
|
|
|
|
return Usage::Type::Write;
|
2021-12-09 17:59:10 +01:00
|
|
|
|
return isPotentialWrite() ? Usage::Type::WritableRef : Usage::Type::Read;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
return Usage::Type::Read;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (pathIt->kind() == "ImplicitCast") {
|
|
|
|
|
|
if (pathIt->detailIs("FunctionToPointerDecay"))
|
|
|
|
|
|
return Usage::Type::Other;
|
|
|
|
|
|
if (pathIt->hasConstType())
|
|
|
|
|
|
return Usage::Type::Read;
|
|
|
|
|
|
potentialWrite = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Usage::Type::Other;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
class SymbolDetails : public JsonObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using JsonObject::JsonObject;
|
|
|
|
|
|
|
|
|
|
|
|
static constexpr char usrKey[] = "usr";
|
|
|
|
|
|
|
|
|
|
|
|
// the unqualified name of the symbol
|
|
|
|
|
|
QString name() const { return typedValue<QString>(nameKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// the enclosing namespace, class etc (without trailing ::)
|
|
|
|
|
|
// [NOTE: This is not true, the trailing colons are included]
|
|
|
|
|
|
QString containerName() const { return typedValue<QString>(containerNameKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// the clang-specific “unified symbol resolution” identifier
|
|
|
|
|
|
QString usr() const { return typedValue<QString>(usrKey); }
|
|
|
|
|
|
|
|
|
|
|
|
// the clangd-specific opaque symbol ID
|
|
|
|
|
|
Utils::optional<QString> id() const { return optionalValue<QString>(idKey); }
|
|
|
|
|
|
|
|
|
|
|
|
bool isValid() const override
|
|
|
|
|
|
{
|
|
|
|
|
|
return contains(nameKey) && contains(containerNameKey) && contains(usrKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class SymbolInfoRequest : public Request<LanguageClientArray<SymbolDetails>, std::nullptr_t, TextDocumentPositionParams>
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using Request::Request;
|
|
|
|
|
|
explicit SymbolInfoRequest(const TextDocumentPositionParams ¶ms)
|
|
|
|
|
|
: Request("textDocument/symbolInfo", params) {}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2022-02-01 17:39:00 +01:00
|
|
|
|
void setupClangdConfigFile()
|
|
|
|
|
|
{
|
|
|
|
|
|
const Utils::FilePath baseDir = Utils::FilePath::fromString(
|
|
|
|
|
|
QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)) / "clangd";
|
|
|
|
|
|
baseDir.ensureWritableDir();
|
|
|
|
|
|
const Utils::FilePath targetConfigFile = baseDir / "config.yaml";
|
|
|
|
|
|
Utils::FileReader configReader;
|
|
|
|
|
|
const QByteArray firstLine = "# This file was generated by Qt Creator and will be overwritten "
|
|
|
|
|
|
"unless you remove this line.";
|
|
|
|
|
|
if (!configReader.fetch(targetConfigFile) || configReader.data().startsWith(firstLine)) {
|
|
|
|
|
|
Utils::FileSaver saver(targetConfigFile);
|
|
|
|
|
|
saver.write(firstLine + '\n');
|
|
|
|
|
|
saver.write("Hover:\n");
|
|
|
|
|
|
saver.write(" ShowAKA: Yes\n");
|
|
|
|
|
|
QTC_CHECK(saver.finalize());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-28 14:55:54 +02:00
|
|
|
|
static BaseClientInterface *clientInterface(Project *project, const Utils::FilePath &jsonDbDir)
|
2021-04-20 14:42:29 +02:00
|
|
|
|
{
|
2021-06-25 17:40:20 +02:00
|
|
|
|
QString indexingOption = "--background-index";
|
2021-08-30 10:58:08 +02:00
|
|
|
|
const CppEditor::ClangdSettings settings(CppEditor::ClangdProjectSettings(project).settings());
|
2021-10-29 16:46:34 +02:00
|
|
|
|
if (!settings.indexingEnabled() || jsonDbDir.isEmpty())
|
2021-06-25 17:40:20 +02:00
|
|
|
|
indexingOption += "=0";
|
2021-10-27 11:03:42 +02:00
|
|
|
|
const QString headerInsertionOption = QString("--header-insertion=")
|
|
|
|
|
|
+ (settings.autoIncludeHeaders() ? "iwyu" : "never");
|
|
|
|
|
|
Utils::CommandLine cmd{settings.clangdFilePath(), {indexingOption, headerInsertionOption,
|
2021-11-01 12:17:37 +01:00
|
|
|
|
"--limit-results=0", "--limit-references=0", "--clang-tidy=0"}};
|
2021-06-28 14:55:54 +02:00
|
|
|
|
if (settings.workerThreadLimit() != 0)
|
|
|
|
|
|
cmd.addArg("-j=" + QString::number(settings.workerThreadLimit()));
|
2021-04-20 14:42:29 +02:00
|
|
|
|
if (!jsonDbDir.isEmpty())
|
2021-04-30 08:25:10 +02:00
|
|
|
|
cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString());
|
2021-07-15 11:39:56 +02:00
|
|
|
|
if (clangdLogServer().isDebugEnabled())
|
2021-04-30 08:25:10 +02:00
|
|
|
|
cmd.addArgs({"--log=verbose", "--pretty"});
|
2022-01-18 12:29:12 +01:00
|
|
|
|
if (settings.clangdVersion() >= QVersionNumber(14))
|
|
|
|
|
|
cmd.addArg("--use-dirty-headers");
|
2021-04-20 14:42:29 +02:00
|
|
|
|
const auto interface = new StdIOClientInterface;
|
2021-04-30 08:25:10 +02:00
|
|
|
|
interface->setCommandLine(cmd);
|
2021-04-20 14:42:29 +02:00
|
|
|
|
return interface;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
class ReferencesFileData {
|
|
|
|
|
|
public:
|
|
|
|
|
|
QList<QPair<Range, QString>> rangesAndLineText;
|
|
|
|
|
|
QString fileContent;
|
|
|
|
|
|
AstNode ast;
|
|
|
|
|
|
};
|
2021-05-18 12:59:15 +02:00
|
|
|
|
class ReplacementData {
|
|
|
|
|
|
public:
|
|
|
|
|
|
QString oldSymbolName;
|
|
|
|
|
|
QString newSymbolName;
|
|
|
|
|
|
QSet<Utils::FilePath> fileRenameCandidates;
|
|
|
|
|
|
};
|
2021-04-21 14:29:49 +02:00
|
|
|
|
class ReferencesData {
|
|
|
|
|
|
public:
|
|
|
|
|
|
QMap<DocumentUri, ReferencesFileData> fileData;
|
|
|
|
|
|
QList<MessageId> pendingAstRequests;
|
|
|
|
|
|
QPointer<SearchResult> search;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
Utils::optional<ReplacementData> replacementData;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
quint64 key;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
bool canceled = false;
|
2021-08-30 10:58:08 +02:00
|
|
|
|
bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences();
|
2021-04-21 14:29:49 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
using SymbolData = QPair<QString, Utils::Link>;
|
|
|
|
|
|
using SymbolDataList = QList<SymbolData>;
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
class ClangdClient::VirtualFunctionAssistProcessor : public IAssistProcessor
|
2021-05-19 15:51:25 +02:00
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
VirtualFunctionAssistProcessor(ClangdClient::Private *data) : m_data(data) {}
|
|
|
|
|
|
|
|
|
|
|
|
void cancel() override;
|
|
|
|
|
|
bool running() override { return m_data; }
|
|
|
|
|
|
|
2021-05-31 11:21:30 +02:00
|
|
|
|
void update();
|
2021-05-19 15:51:25 +02:00
|
|
|
|
void finalize();
|
2022-04-11 13:18:05 +02:00
|
|
|
|
void resetData(bool resetFollowSymbolData);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
|
|
|
|
|
|
private:
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProposal *perform(const AssistInterface *) override
|
2021-05-19 15:51:25 +02:00
|
|
|
|
{
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProposal *immediateProposal(const AssistInterface *) override
|
2021-05-31 11:21:30 +02:00
|
|
|
|
{
|
|
|
|
|
|
return createProposal(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProposal *immediateProposalImpl() const;
|
|
|
|
|
|
IAssistProposal *createProposal(bool final) const;
|
2021-08-30 10:58:08 +02:00
|
|
|
|
CppEditor::VirtualFunctionProposalItem *createEntry(const QString &name,
|
2021-05-31 11:21:30 +02:00
|
|
|
|
const Utils::Link &link) const;
|
2021-05-27 16:32:24 +02:00
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
ClangdClient::Private *m_data = nullptr;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
class ClangdClient::VirtualFunctionAssistProvider : public IAssistProvider
|
2021-05-19 15:51:25 +02:00
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
VirtualFunctionAssistProvider(ClangdClient::Private *data) : m_data(data) {}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
RunType runType() const override { return Asynchronous; }
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProcessor *createProcessor(const AssistInterface *) const override;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
|
|
|
|
|
|
ClangdClient::Private * const m_data;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class ClangdClient::FollowSymbolData {
|
|
|
|
|
|
public:
|
|
|
|
|
|
FollowSymbolData(ClangdClient *q, quint64 id, const QTextCursor &cursor,
|
2021-09-01 18:08:54 +02:00
|
|
|
|
CppEditor::CppEditorWidget *editorWidget,
|
2021-05-19 15:51:25 +02:00
|
|
|
|
const DocumentUri &uri, Utils::ProcessLinkCallback &&callback,
|
|
|
|
|
|
bool openInSplit)
|
|
|
|
|
|
: q(q), id(id), cursor(cursor), editorWidget(editorWidget), uri(uri),
|
|
|
|
|
|
callback(std::move(callback)), virtualFuncAssistProvider(q->d),
|
2021-10-28 11:59:48 +02:00
|
|
|
|
docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1),
|
2021-05-19 15:51:25 +02:00
|
|
|
|
openInSplit(openInSplit) {}
|
|
|
|
|
|
|
|
|
|
|
|
~FollowSymbolData()
|
|
|
|
|
|
{
|
|
|
|
|
|
closeTempDocuments();
|
|
|
|
|
|
if (virtualFuncAssistProcessor)
|
2022-04-11 13:18:05 +02:00
|
|
|
|
virtualFuncAssistProcessor->resetData(false);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
for (const MessageId &id : qAsConst(pendingSymbolInfoRequests))
|
|
|
|
|
|
q->cancelRequest(id);
|
2021-05-28 09:40:53 +02:00
|
|
|
|
for (const MessageId &id : qAsConst(pendingGotoImplRequests))
|
|
|
|
|
|
q->cancelRequest(id);
|
2021-05-28 13:12:00 +02:00
|
|
|
|
for (const MessageId &id : qAsConst(pendingGotoDefRequests))
|
|
|
|
|
|
q->cancelRequest(id);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void closeTempDocuments()
|
|
|
|
|
|
{
|
2021-07-12 11:24:19 +02:00
|
|
|
|
for (const Utils::FilePath &fp : qAsConst(openedFiles)) {
|
|
|
|
|
|
if (!q->documentForFilePath(fp))
|
|
|
|
|
|
q->closeExtraFile(fp);
|
|
|
|
|
|
}
|
2021-05-19 15:51:25 +02:00
|
|
|
|
openedFiles.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-28 11:59:48 +02:00
|
|
|
|
bool defLinkIsAmbiguous() const;
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
ClangdClient * const q;
|
|
|
|
|
|
const quint64 id;
|
|
|
|
|
|
const QTextCursor cursor;
|
2021-09-01 18:08:54 +02:00
|
|
|
|
const QPointer<CppEditor::CppEditorWidget> editorWidget;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
const DocumentUri uri;
|
|
|
|
|
|
const Utils::ProcessLinkCallback callback;
|
|
|
|
|
|
VirtualFunctionAssistProvider virtualFuncAssistProvider;
|
|
|
|
|
|
QList<MessageId> pendingSymbolInfoRequests;
|
2021-05-28 09:40:53 +02:00
|
|
|
|
QList<MessageId> pendingGotoImplRequests;
|
2021-05-28 13:12:00 +02:00
|
|
|
|
QList<MessageId> pendingGotoDefRequests;
|
2021-10-28 11:59:48 +02:00
|
|
|
|
const int docRevision;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
const bool openInSplit;
|
|
|
|
|
|
|
|
|
|
|
|
Utils::Link defLink;
|
2021-05-28 09:40:53 +02:00
|
|
|
|
QList<Utils::Link> allLinks;
|
2021-05-28 13:12:00 +02:00
|
|
|
|
QHash<Utils::Link, Utils::Link> declDefMap;
|
2021-06-29 11:01:50 +02:00
|
|
|
|
Utils::optional<AstNode> cursorNode;
|
2021-05-27 16:32:24 +02:00
|
|
|
|
AstNode defLinkNode;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
SymbolDataList symbolsToDisplay;
|
|
|
|
|
|
std::set<Utils::FilePath> openedFiles;
|
|
|
|
|
|
VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr;
|
2021-05-31 11:21:30 +02:00
|
|
|
|
bool finished = false;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-05-31 15:57:44 +02:00
|
|
|
|
class SwitchDeclDefData {
|
|
|
|
|
|
public:
|
2021-09-28 13:21:59 +02:00
|
|
|
|
SwitchDeclDefData(quint64 id, TextDocument *doc, const QTextCursor &cursor,
|
2021-09-01 18:08:54 +02:00
|
|
|
|
CppEditor::CppEditorWidget *editorWidget,
|
2021-05-31 15:57:44 +02:00
|
|
|
|
Utils::ProcessLinkCallback &&callback)
|
|
|
|
|
|
: id(id), document(doc), uri(DocumentUri::fromFilePath(doc->filePath())),
|
|
|
|
|
|
cursor(cursor), editorWidget(editorWidget), callback(std::move(callback)) {}
|
|
|
|
|
|
|
|
|
|
|
|
Utils::optional<AstNode> getFunctionNode() const
|
|
|
|
|
|
{
|
|
|
|
|
|
QTC_ASSERT(ast, return {});
|
|
|
|
|
|
|
|
|
|
|
|
const QList<AstNode> path = getAstPath(*ast, Range(cursor));
|
|
|
|
|
|
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
|
|
|
|
|
if (it->role() == "declaration"
|
|
|
|
|
|
&& (it->kind() == "CXXMethod" || it->kind() == "CXXConversion"
|
|
|
|
|
|
|| it->kind() == "CXXConstructor" || it->kind() == "CXXDestructor")) {
|
|
|
|
|
|
return *it;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QTextCursor cursorForFunctionName(const AstNode &functionNode) const
|
|
|
|
|
|
{
|
|
|
|
|
|
QTC_ASSERT(docSymbols, return {});
|
|
|
|
|
|
|
|
|
|
|
|
const auto symbolList = Utils::get_if<QList<DocumentSymbol>>(&*docSymbols);
|
|
|
|
|
|
if (!symbolList)
|
|
|
|
|
|
return {};
|
|
|
|
|
|
const Range &astRange = functionNode.range();
|
|
|
|
|
|
QList symbolsToCheck = *symbolList;
|
|
|
|
|
|
while (!symbolsToCheck.isEmpty()) {
|
|
|
|
|
|
const DocumentSymbol symbol = symbolsToCheck.takeFirst();
|
|
|
|
|
|
if (symbol.range() == astRange)
|
|
|
|
|
|
return symbol.selectionRange().start().toTextCursor(document->document());
|
|
|
|
|
|
if (symbol.range().contains(astRange))
|
|
|
|
|
|
symbolsToCheck << symbol.children().value_or(QList<DocumentSymbol>());
|
|
|
|
|
|
}
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const quint64 id;
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const QPointer<TextDocument> document;
|
2021-05-31 15:57:44 +02:00
|
|
|
|
const DocumentUri uri;
|
|
|
|
|
|
const QTextCursor cursor;
|
2021-09-01 18:08:54 +02:00
|
|
|
|
const QPointer<CppEditor::CppEditorWidget> editorWidget;
|
2021-05-31 15:57:44 +02:00
|
|
|
|
Utils::ProcessLinkCallback callback;
|
|
|
|
|
|
Utils::optional<DocumentSymbolsResult> docSymbols;
|
|
|
|
|
|
Utils::optional<AstNode> ast;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-01 18:14:12 +02:00
|
|
|
|
class LocalRefsData {
|
|
|
|
|
|
public:
|
2021-09-28 13:21:59 +02:00
|
|
|
|
LocalRefsData(quint64 id, TextDocument *doc, const QTextCursor &cursor,
|
|
|
|
|
|
CppEditor::RefactoringEngineInterface::RenameCallback &&callback)
|
2021-06-01 18:14:12 +02:00
|
|
|
|
: id(id), document(doc), cursor(cursor), callback(std::move(callback)),
|
|
|
|
|
|
uri(DocumentUri::fromFilePath(doc->filePath())), revision(doc->document()->revision())
|
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
~LocalRefsData()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (callback)
|
|
|
|
|
|
callback({}, {}, revision);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const quint64 id;
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const QPointer<TextDocument> document;
|
2021-06-01 18:14:12 +02:00
|
|
|
|
const QTextCursor cursor;
|
2021-08-30 10:58:08 +02:00
|
|
|
|
CppEditor::RefactoringEngineInterface::RenameCallback callback;
|
2021-06-01 18:14:12 +02:00
|
|
|
|
const DocumentUri uri;
|
|
|
|
|
|
const int revision;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-02 17:51:31 +02:00
|
|
|
|
class DiagnosticsCapabilities : public JsonObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using JsonObject::JsonObject;
|
|
|
|
|
|
void enableCategorySupport() { insert("categorySupport", true); }
|
|
|
|
|
|
void enableCodeActionsInline() {insert("codeActionsInline", true);}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class ClangdTextDocumentClientCapabilities : public TextDocumentClientCapabilities
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using TextDocumentClientCapabilities::TextDocumentClientCapabilities;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setPublishDiagnostics(const DiagnosticsCapabilities &caps)
|
|
|
|
|
|
{ insert("publishDiagnostics", caps); }
|
|
|
|
|
|
};
|
2021-05-19 15:51:25 +02:00
|
|
|
|
|
2021-09-28 15:10:47 +02:00
|
|
|
|
|
2021-11-10 12:35:06 +01:00
|
|
|
|
enum class CustomAssistMode { Doxygen, Preprocessor, IncludePath };
|
2021-09-28 15:10:47 +02:00
|
|
|
|
class CustomAssistProcessor : public IAssistProcessor
|
2021-06-18 16:30:03 +02:00
|
|
|
|
{
|
|
|
|
|
|
public:
|
2021-11-10 12:35:06 +01:00
|
|
|
|
CustomAssistProcessor(ClangdClient *client, int position, int endPos,
|
|
|
|
|
|
unsigned completionOperator, CustomAssistMode mode)
|
2021-09-15 11:35:19 +02:00
|
|
|
|
: m_client(client)
|
|
|
|
|
|
, m_position(position)
|
2021-11-10 12:35:06 +01:00
|
|
|
|
, m_endPos(endPos)
|
2021-09-15 11:35:19 +02:00
|
|
|
|
, m_completionOperator(completionOperator)
|
2021-09-28 15:10:47 +02:00
|
|
|
|
, m_mode(mode)
|
2021-09-15 11:35:19 +02:00
|
|
|
|
{}
|
2021-06-18 16:30:03 +02:00
|
|
|
|
|
|
|
|
|
|
private:
|
2021-09-28 15:10:47 +02:00
|
|
|
|
IAssistProposal *perform(const AssistInterface *interface) override
|
2021-06-18 16:30:03 +02:00
|
|
|
|
{
|
2021-09-28 13:21:59 +02:00
|
|
|
|
QList<AssistProposalItemInterface *> completions;
|
2021-09-28 15:10:47 +02:00
|
|
|
|
switch (m_mode) {
|
|
|
|
|
|
case CustomAssistMode::Doxygen:
|
|
|
|
|
|
for (int i = 1; i < CppEditor::T_DOXY_LAST_TAG; ++i) {
|
|
|
|
|
|
completions << createItem(QLatin1String(CppEditor::doxygenTagSpell(i)),
|
|
|
|
|
|
CPlusPlus::Icons::keywordIcon());
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2021-11-10 12:35:06 +01:00
|
|
|
|
case CustomAssistMode::Preprocessor: {
|
2021-09-28 15:10:47 +02:00
|
|
|
|
static QIcon macroIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro);
|
|
|
|
|
|
for (const QString &completion
|
2021-10-08 10:15:51 +02:00
|
|
|
|
: CppEditor::CppCompletionAssistProcessor::preprocessorCompletions()) {
|
2021-09-28 15:10:47 +02:00
|
|
|
|
completions << createItem(completion, macroIcon);
|
|
|
|
|
|
}
|
2021-10-08 10:15:51 +02:00
|
|
|
|
if (CppEditor::ProjectFile::isObjC(interface->filePath().toString()))
|
|
|
|
|
|
completions << createItem("import", macroIcon);
|
|
|
|
|
|
break;
|
2021-06-18 16:30:03 +02:00
|
|
|
|
}
|
2021-11-10 12:35:06 +01:00
|
|
|
|
case ClangCodeModel::Internal::CustomAssistMode::IncludePath: {
|
|
|
|
|
|
HeaderPaths headerPaths;
|
|
|
|
|
|
const CppEditor::ProjectPart::ConstPtr projectPart
|
|
|
|
|
|
= projectPartForFile(interface->filePath().toString());
|
|
|
|
|
|
if (projectPart)
|
|
|
|
|
|
headerPaths = projectPart->headerPaths;
|
|
|
|
|
|
completions = ClangCompletionAssistProcessor::completeInclude(
|
|
|
|
|
|
m_endPos, m_completionOperator, interface, headerPaths);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-09-28 13:21:59 +02:00
|
|
|
|
GenericProposalModelPtr model(new GenericProposalModel);
|
2021-06-18 16:30:03 +02:00
|
|
|
|
model->loadContent(completions);
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const auto proposal = new GenericProposal(m_position, model);
|
2021-09-15 11:35:19 +02:00
|
|
|
|
if (m_client->testingEnabled()) {
|
|
|
|
|
|
emit m_client->proposalReady(proposal);
|
2021-06-18 16:30:03 +02:00
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
return proposal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 15:10:47 +02:00
|
|
|
|
AssistProposalItemInterface *createItem(const QString &text, const QIcon &icon) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto item = new ClangPreprocessorAssistProposalItem;
|
|
|
|
|
|
item->setText(text);
|
|
|
|
|
|
item->setIcon(icon);
|
|
|
|
|
|
item->setCompletionOperator(m_completionOperator);
|
|
|
|
|
|
return item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-15 11:35:19 +02:00
|
|
|
|
ClangdClient * const m_client;
|
2021-06-18 16:30:03 +02:00
|
|
|
|
const int m_position;
|
2021-11-10 12:35:06 +01:00
|
|
|
|
const int m_endPos;
|
2021-06-18 16:30:03 +02:00
|
|
|
|
const unsigned m_completionOperator;
|
2021-09-28 15:10:47 +02:00
|
|
|
|
const CustomAssistMode m_mode;
|
2021-06-18 16:30:03 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
static qint64 getRevision(const TextDocument *doc)
|
2021-09-27 16:24:37 +02:00
|
|
|
|
{
|
|
|
|
|
|
return doc->document()->revision();
|
|
|
|
|
|
}
|
|
|
|
|
|
static qint64 getRevision(const Utils::FilePath &fp)
|
|
|
|
|
|
{
|
|
|
|
|
|
return fp.lastModified().toMSecsSinceEpoch();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename DocType, typename DataType> class VersionedDocData
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
VersionedDocData(const DocType &doc, const DataType &data) :
|
|
|
|
|
|
revision(getRevision(doc)), data(data) {}
|
|
|
|
|
|
|
|
|
|
|
|
const qint64 revision;
|
|
|
|
|
|
const DataType data;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template<typename DocType, typename DataType> class VersionedDataCache
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
void insert(const DocType &doc, const DataType &data)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_data.emplace(std::make_pair(doc, VersionedDocData(doc, data)));
|
|
|
|
|
|
}
|
|
|
|
|
|
void remove(const DocType &doc) { m_data.erase(doc); }
|
|
|
|
|
|
Utils::optional<VersionedDocData<DocType, DataType>> take(const DocType &doc)
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto it = m_data.find(doc);
|
|
|
|
|
|
if (it == m_data.end())
|
|
|
|
|
|
return {};
|
|
|
|
|
|
const auto data = it->second;
|
|
|
|
|
|
m_data.erase(it);
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
Utils::optional<DataType> get(const DocType &doc)
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto it = m_data.find(doc);
|
|
|
|
|
|
if (it == m_data.end())
|
|
|
|
|
|
return {};
|
|
|
|
|
|
if (it->second.revision != getRevision(doc)) {
|
|
|
|
|
|
m_data.erase(it);
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
return it->second.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
|
|
std::unordered_map<DocType, VersionedDocData<DocType, DataType>> m_data;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-10-01 12:23:05 +02:00
|
|
|
|
class TaskTimer
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
TaskTimer(const QString &task) : m_task(task) {}
|
|
|
|
|
|
|
|
|
|
|
|
void stopTask()
|
|
|
|
|
|
{
|
|
|
|
|
|
// This can happen due to the RAII mechanism employed with SubtaskTimer.
|
|
|
|
|
|
// The subtask timers will expire immediately after, so this does not distort
|
|
|
|
|
|
// the timing data.
|
|
|
|
|
|
if (m_subtasks > 0) {
|
|
|
|
|
|
QTC_CHECK(m_timer.isValid());
|
|
|
|
|
|
m_elapsedMs += m_timer.elapsed();
|
|
|
|
|
|
m_timer.invalidate();
|
|
|
|
|
|
m_subtasks = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
m_started = false;
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_elapsedMs
|
|
|
|
|
|
<< " ms in UI thread";
|
2021-11-02 17:41:36 +01:00
|
|
|
|
m_elapsedMs = 0;
|
2021-10-01 12:23:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
void startSubtask()
|
|
|
|
|
|
{
|
|
|
|
|
|
// We have some callbacks that are either synchronous or asynchronous, depending on
|
|
|
|
|
|
// dynamic conditions. In the sync case, we will have nested subtasks, in which case
|
|
|
|
|
|
// the inner ones must not collect timing data, as their code blocks are already covered.
|
|
|
|
|
|
if (++m_subtasks > 1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (!m_started) {
|
|
|
|
|
|
QTC_ASSERT(m_elapsedMs == 0, m_elapsedMs = 0);
|
|
|
|
|
|
m_started = true;
|
|
|
|
|
|
m_finalized = false;
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting";
|
2022-03-04 19:42:46 +01:00
|
|
|
|
|
|
|
|
|
|
// Used by ThreadedSubtaskTimer to mark the end of the whole highlighting operation
|
|
|
|
|
|
m_startTimer.restart();
|
2021-10-01 12:23:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask started at "
|
2022-03-04 19:42:46 +01:00
|
|
|
|
<< QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz");
|
2021-10-01 12:23:05 +02:00
|
|
|
|
QTC_CHECK(!m_timer.isValid());
|
|
|
|
|
|
m_timer.start();
|
|
|
|
|
|
}
|
|
|
|
|
|
void stopSubtask(bool isFinalizing)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_subtasks == 0) // See stopTask().
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (isFinalizing)
|
|
|
|
|
|
m_finalized = true;
|
|
|
|
|
|
if (--m_subtasks > 0) // See startSubtask().
|
|
|
|
|
|
return;
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask stopped at "
|
2022-03-04 19:42:46 +01:00
|
|
|
|
<< QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz");
|
2021-10-01 12:23:05 +02:00
|
|
|
|
QTC_CHECK(m_timer.isValid());
|
|
|
|
|
|
m_elapsedMs += m_timer.elapsed();
|
|
|
|
|
|
m_timer.invalidate();
|
|
|
|
|
|
if (m_finalized)
|
|
|
|
|
|
stopTask();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-04 19:42:46 +01:00
|
|
|
|
QElapsedTimer startTimer() const { return m_startTimer; }
|
|
|
|
|
|
|
2021-10-01 12:23:05 +02:00
|
|
|
|
private:
|
|
|
|
|
|
const QString m_task;
|
|
|
|
|
|
QElapsedTimer m_timer;
|
2022-03-04 19:42:46 +01:00
|
|
|
|
QElapsedTimer m_startTimer;
|
2021-10-01 12:23:05 +02:00
|
|
|
|
qint64 m_elapsedMs = 0;
|
|
|
|
|
|
int m_subtasks = 0;
|
|
|
|
|
|
bool m_started = false;
|
|
|
|
|
|
bool m_finalized = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class SubtaskTimer
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
SubtaskTimer(TaskTimer &timer) : m_timer(timer) { m_timer.startSubtask(); }
|
|
|
|
|
|
~SubtaskTimer() { m_timer.stopSubtask(m_isFinalizing); }
|
|
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
void makeFinalizing() { m_isFinalizing = true; }
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
TaskTimer &m_timer;
|
|
|
|
|
|
bool m_isFinalizing = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class FinalizingSubtaskTimer : public SubtaskTimer
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
FinalizingSubtaskTimer(TaskTimer &timer) : SubtaskTimer(timer) { makeFinalizing(); }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadedSubtaskTimer
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
2022-03-04 19:42:46 +01:00
|
|
|
|
ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer) : m_task(task), m_taskTimer(taskTimer)
|
2021-10-01 12:23:05 +02:00
|
|
|
|
{
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting thread";
|
|
|
|
|
|
m_timer.start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
~ThreadedSubtaskTimer()
|
|
|
|
|
|
{
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_timer.elapsed()
|
|
|
|
|
|
<< " ms in dedicated thread";
|
2022-03-04 19:42:46 +01:00
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": Start to end: "
|
|
|
|
|
|
<< m_taskTimer.startTimer().elapsed() << " ms";
|
2021-10-01 12:23:05 +02:00
|
|
|
|
}
|
2022-03-04 19:42:46 +01:00
|
|
|
|
|
2021-10-01 12:23:05 +02:00
|
|
|
|
private:
|
|
|
|
|
|
const QString m_task;
|
|
|
|
|
|
QElapsedTimer m_timer;
|
2022-03-04 19:42:46 +01:00
|
|
|
|
const TaskTimer &m_taskTimer;
|
2021-10-01 12:23:05 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-11-16 14:12:41 +01:00
|
|
|
|
class MemoryTreeModel;
|
|
|
|
|
|
class MemoryUsageWidget : public QWidget
|
|
|
|
|
|
{
|
|
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(MemoryUsageWidget)
|
|
|
|
|
|
public:
|
|
|
|
|
|
MemoryUsageWidget(ClangdClient *client);
|
2022-02-09 09:58:48 +01:00
|
|
|
|
~MemoryUsageWidget();
|
2021-11-16 14:12:41 +01:00
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
void setupUi();
|
|
|
|
|
|
void getMemoryTree();
|
|
|
|
|
|
|
|
|
|
|
|
ClangdClient * const m_client;
|
|
|
|
|
|
MemoryTreeModel * const m_model;
|
|
|
|
|
|
Utils::TreeView m_view;
|
2022-02-09 09:58:48 +01:00
|
|
|
|
Utils::optional<MessageId> m_currentRequest;
|
2021-11-16 14:12:41 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2022-04-21 13:02:09 +02:00
|
|
|
|
class HighlightingData
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
// For all QPairs, the int member is the corresponding document version.
|
|
|
|
|
|
QPair<QList<ExpandedSemanticToken>, int> previousTokens;
|
|
|
|
|
|
|
|
|
|
|
|
// The ranges of symbols referring to virtual functions,
|
|
|
|
|
|
// as extracted by the highlighting procedure.
|
|
|
|
|
|
QPair<QList<Range>, int> virtualRanges;
|
|
|
|
|
|
|
|
|
|
|
|
// The highlighter is owned by its document.
|
|
|
|
|
|
CppEditor::SemanticHighlighter *highlighter = nullptr;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
class ClangdClient::Private
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
2021-07-01 04:17:54 +02:00
|
|
|
|
Private(ClangdClient *q, Project *project)
|
2021-08-30 10:58:08 +02:00
|
|
|
|
: q(q), settings(CppEditor::ClangdProjectSettings(project).settings()) {}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void findUsages(TextDocument *document, const QTextCursor &cursor,
|
2021-09-03 12:48:29 +02:00
|
|
|
|
const QString &searchTerm, const Utils::optional<QString> &replacement,
|
|
|
|
|
|
bool categorize);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
void handleFindUsagesResult(quint64 key, const QList<Location> &locations);
|
2021-05-18 12:59:15 +02:00
|
|
|
|
static void handleRenameRequest(const SearchResult *search,
|
|
|
|
|
|
const ReplacementData &replacementData,
|
|
|
|
|
|
const QString &newSymbolName,
|
|
|
|
|
|
const QList<Core::SearchResultItem> &checkedItems,
|
|
|
|
|
|
bool preserveCase);
|
|
|
|
|
|
void addSearchResultsForFile(ReferencesData &refData, const Utils::FilePath &file,
|
2021-04-21 14:29:49 +02:00
|
|
|
|
const ReferencesFileData &fileData);
|
2021-05-18 12:59:15 +02:00
|
|
|
|
void reportAllSearchResultsAndFinish(ReferencesData &data);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
void finishSearch(const ReferencesData &refData, bool canceled);
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
void handleGotoDefinitionResult();
|
2021-05-28 09:40:53 +02:00
|
|
|
|
void sendGotoImplementationRequest(const Utils::Link &link);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
void handleGotoImplementationResult(const GotoImplementationRequest::Response &response);
|
|
|
|
|
|
void handleDocumentInfoResults();
|
|
|
|
|
|
|
2021-05-31 15:57:44 +02:00
|
|
|
|
void handleDeclDefSwitchReplies();
|
|
|
|
|
|
|
2021-12-02 13:18:02 +01:00
|
|
|
|
static CppEditor::CppEditorWidget *widgetFromDocument(const TextDocument *doc);
|
2021-06-01 18:14:12 +02:00
|
|
|
|
QString searchTermFromCursor(const QTextCursor &cursor) const;
|
2021-12-03 12:54:21 +01:00
|
|
|
|
QTextCursor adjustedCursor(const QTextCursor &cursor, const TextDocument *doc);
|
2021-06-01 18:14:12 +02:00
|
|
|
|
|
2021-06-04 12:40:26 +02:00
|
|
|
|
void setHelpItemForTooltip(const MessageId &token, const QString &fqn = {},
|
|
|
|
|
|
HelpItem::Category category = HelpItem::Unknown,
|
|
|
|
|
|
const QString &type = {});
|
|
|
|
|
|
|
2021-10-22 15:06:46 +02:00
|
|
|
|
void handleSemanticTokens(TextDocument *doc, const QList<ExpandedSemanticToken> &tokens,
|
2021-11-15 14:58:40 +01:00
|
|
|
|
int version, bool force);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
|
2021-09-27 16:24:37 +02:00
|
|
|
|
enum class AstCallbackMode { SyncIfPossible, AlwaysAsync };
|
2021-09-28 13:21:59 +02:00
|
|
|
|
using TextDocOrFile = const Utils::variant<const TextDocument *, Utils::FilePath>;
|
2021-09-27 16:24:37 +02:00
|
|
|
|
using AstHandler = const std::function<void(const AstNode &ast, const MessageId &)>;
|
|
|
|
|
|
MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler,
|
2021-10-07 10:58:54 +02:00
|
|
|
|
AstCallbackMode callbackMode, const Range &range = {});
|
2021-09-27 16:24:37 +02:00
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
ClangdClient * const q;
|
2021-08-30 10:58:08 +02:00
|
|
|
|
const CppEditor::ClangdSettings::Data settings;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
QHash<quint64, ReferencesData> runningFindUsages;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
Utils::optional<FollowSymbolData> followSymbolData;
|
2021-05-31 15:57:44 +02:00
|
|
|
|
Utils::optional<SwitchDeclDefData> switchDeclDefData;
|
2021-06-01 18:14:12 +02:00
|
|
|
|
Utils::optional<LocalRefsData> localRefsData;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
Utils::optional<QVersionNumber> versionNumber;
|
2021-10-06 13:27:23 +02:00
|
|
|
|
|
2022-04-21 13:02:09 +02:00
|
|
|
|
QHash<TextDocument *, HighlightingData> highlightingData;
|
2022-02-08 11:49:37 +01:00
|
|
|
|
QHash<Utils::FilePath, CppEditor::BaseEditorDocumentParser::Configuration> parserConfigs;
|
2021-11-03 15:30:57 +01:00
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
VersionedDataCache<const TextDocument *, AstNode> astCache;
|
2021-09-27 16:24:37 +02:00
|
|
|
|
VersionedDataCache<Utils::FilePath, AstNode> externalAstCache;
|
2021-10-01 12:23:05 +02:00
|
|
|
|
TaskTimer highlightingTimer{"highlighting"};
|
2021-06-02 16:32:54 +02:00
|
|
|
|
quint64 nextJobId = 0;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
bool isFullyIndexed = false;
|
|
|
|
|
|
bool isTesting = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-06-18 16:30:03 +02:00
|
|
|
|
class ClangdCompletionCapabilities : public TextDocumentClientCapabilities::CompletionCapabilities
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
explicit ClangdCompletionCapabilities(const JsonObject &object)
|
|
|
|
|
|
: TextDocumentClientCapabilities::CompletionCapabilities(object)
|
|
|
|
|
|
{
|
|
|
|
|
|
insert("editsNearCursor", true); // For dot-to-arrow correction.
|
2021-11-03 15:08:38 +01:00
|
|
|
|
if (Utils::optional<CompletionItemCapbilities> completionItemCaps = completionItem()) {
|
|
|
|
|
|
completionItemCaps->setSnippetSupport(false);
|
|
|
|
|
|
setCompletionItem(*completionItemCaps);
|
|
|
|
|
|
}
|
2021-06-18 16:30:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-09-27 11:08:59 +02:00
|
|
|
|
class ClangdCompletionItem : public LanguageClientCompletionItem
|
2021-09-15 11:35:19 +02:00
|
|
|
|
{
|
2021-09-27 11:08:59 +02:00
|
|
|
|
public:
|
|
|
|
|
|
using LanguageClientCompletionItem::LanguageClientCompletionItem;
|
|
|
|
|
|
void apply(TextDocumentManipulatorInterface &manipulator,
|
|
|
|
|
|
int basePosition) const override;
|
2021-11-10 14:44:41 +01:00
|
|
|
|
|
|
|
|
|
|
enum class SpecialQtType { Signal, Slot, None };
|
|
|
|
|
|
static SpecialQtType getQtType(const CompletionItem &item);
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
QIcon icon() const override;
|
2021-09-15 11:35:19 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class ClangdClient::ClangdCompletionAssistProcessor : public LanguageClientCompletionAssistProcessor
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
ClangdCompletionAssistProcessor(ClangdClient *client, const QString &snippetsGroup)
|
2021-09-27 11:08:59 +02:00
|
|
|
|
: LanguageClientCompletionAssistProcessor(client, snippetsGroup)
|
2021-09-15 11:35:19 +02:00
|
|
|
|
, m_client(client)
|
|
|
|
|
|
{
|
2022-03-04 19:42:46 +01:00
|
|
|
|
m_timer.start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
~ClangdCompletionAssistProcessor()
|
|
|
|
|
|
{
|
|
|
|
|
|
qCDebug(clangdLogTiming).noquote().nospace()
|
|
|
|
|
|
<< "ClangdCompletionAssistProcessor took: " << m_timer.elapsed() << " ms";
|
2021-09-15 11:35:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProposal *perform(const AssistInterface *interface) override
|
2021-09-15 11:35:19 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (m_client->d->isTesting) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) {
|
2021-09-15 11:35:19 +02:00
|
|
|
|
emit m_client->proposalReady(proposal);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return LanguageClientCompletionAssistProcessor::perform(interface);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-27 11:08:59 +02:00
|
|
|
|
QList<AssistProposalItemInterface *> generateCompletionItems(
|
|
|
|
|
|
const QList<LanguageServerProtocol::CompletionItem> &items) const override;
|
2021-09-15 11:35:19 +02:00
|
|
|
|
|
|
|
|
|
|
ClangdClient * const m_client;
|
2022-03-04 19:42:46 +01:00
|
|
|
|
QElapsedTimer m_timer;
|
2021-09-15 11:35:19 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-09-27 11:08:59 +02:00
|
|
|
|
QList<AssistProposalItemInterface *>
|
|
|
|
|
|
ClangdClient::ClangdCompletionAssistProcessor::generateCompletionItems(
|
|
|
|
|
|
const QList<LanguageServerProtocol::CompletionItem> &items) const
|
|
|
|
|
|
{
|
|
|
|
|
|
qCDebug(clangdLog) << "received" << items.count() << "completions";
|
|
|
|
|
|
|
|
|
|
|
|
auto itemGenerator = [](const QList<LanguageServerProtocol::CompletionItem> &items) {
|
|
|
|
|
|
return Utils::transform<QList<AssistProposalItemInterface *>>(
|
|
|
|
|
|
items, [](const LanguageServerProtocol::CompletionItem &item) {
|
|
|
|
|
|
return new ClangdCompletionItem(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// If there are signals among the candidates, we employ the built-in code model to find out
|
|
|
|
|
|
// whether the cursor was on the second argument of a (dis)connect() call.
|
|
|
|
|
|
// If so, we offer only signals, as nothing else makes sense in that context.
|
|
|
|
|
|
static const auto criterion = [](const CompletionItem &ci) {
|
2021-11-10 14:44:41 +01:00
|
|
|
|
return ClangdCompletionItem::getQtType(ci) == ClangdCompletionItem::SpecialQtType::Signal;
|
2021-09-27 11:08:59 +02:00
|
|
|
|
};
|
|
|
|
|
|
const QTextDocument *doc = document();
|
|
|
|
|
|
const int pos = basePos();
|
|
|
|
|
|
if (!doc || pos < 0 || !Utils::anyOf(items, criterion))
|
|
|
|
|
|
return itemGenerator(items);
|
|
|
|
|
|
const QString content = doc->toPlainText();
|
|
|
|
|
|
const bool requiresSignal = CppEditor::CppModelManager::instance()
|
|
|
|
|
|
->positionRequiresSignal(filePath().toString(),
|
|
|
|
|
|
content.toUtf8(),
|
|
|
|
|
|
pos);
|
|
|
|
|
|
if (requiresSignal)
|
|
|
|
|
|
return itemGenerator(Utils::filtered(items, criterion));
|
|
|
|
|
|
return itemGenerator(items);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-15 08:10:30 +02:00
|
|
|
|
class ClangdClient::ClangdCompletionAssistProvider : public LanguageClientCompletionAssistProvider
|
2021-09-13 16:09:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
ClangdCompletionAssistProvider(ClangdClient *client);
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
2021-10-08 10:15:51 +02:00
|
|
|
|
IAssistProcessor *createProcessor(const AssistInterface *interface) const override;
|
2021-09-15 08:10:30 +02:00
|
|
|
|
|
2021-09-13 16:09:04 +02:00
|
|
|
|
int activationCharSequenceLength() const override { return 3; }
|
|
|
|
|
|
bool isActivationCharSequence(const QString &sequence) const override;
|
|
|
|
|
|
bool isContinuationChar(const QChar &c) const override;
|
|
|
|
|
|
|
2021-10-08 10:15:51 +02:00
|
|
|
|
bool isInCommentOrString(const AssistInterface *interface) const;
|
|
|
|
|
|
|
2021-09-15 11:35:19 +02:00
|
|
|
|
ClangdClient * const m_client;
|
2021-09-13 16:09:04 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2022-01-31 13:10:28 +01:00
|
|
|
|
class ClangdQuickFixProcessor : public LanguageClientQuickFixAssistProcessor
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
ClangdQuickFixProcessor(LanguageClient::Client *client)
|
|
|
|
|
|
: LanguageClientQuickFixAssistProcessor(client)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
IAssistProposal *perform(const AssistInterface *interface) override
|
|
|
|
|
|
{
|
|
|
|
|
|
m_interface = interface;
|
|
|
|
|
|
|
|
|
|
|
|
// Step 1: Collect clangd code actions asynchronously
|
|
|
|
|
|
LanguageClientQuickFixAssistProcessor::perform(interface);
|
|
|
|
|
|
|
|
|
|
|
|
// Step 2: Collect built-in quickfixes synchronously
|
|
|
|
|
|
m_builtinOps = CppEditor::quickFixOperations(interface);
|
|
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void handleProposalReady(const QuickFixOperations &ops) override
|
|
|
|
|
|
{
|
|
|
|
|
|
// Step 3: Merge the results upon callback from clangd.
|
|
|
|
|
|
for (const auto &op : ops)
|
|
|
|
|
|
op->setDescription("clangd: " + op->description());
|
|
|
|
|
|
setAsyncProposalAvailable(GenericProposal::createProposal(m_interface, ops + m_builtinOps));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QuickFixOperations m_builtinOps;
|
|
|
|
|
|
const AssistInterface *m_interface = nullptr;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class ClangdQuickFixProvider : public LanguageClientQuickFixProvider
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
ClangdQuickFixProvider(ClangdClient *client) : LanguageClientQuickFixProvider(client) {};
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const override
|
|
|
|
|
|
{
|
|
|
|
|
|
return new ClangdQuickFixProcessor(client());
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2022-02-16 12:26:15 +01:00
|
|
|
|
static void addToCompilationDb(QJsonObject &cdb,
|
|
|
|
|
|
const CppEditor::ClangDiagnosticConfig &projectWarnings,
|
|
|
|
|
|
const QStringList &projectOptions,
|
|
|
|
|
|
const CppEditor::ProjectPart::ConstPtr &projectPart,
|
|
|
|
|
|
const Utils::FilePath &workingDir,
|
|
|
|
|
|
const Utils::FilePath &sourceFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: Do we really need to re-calculate the project part options per source file?
|
|
|
|
|
|
QStringList args = createClangOptions(*projectPart, sourceFile.toString(),
|
|
|
|
|
|
projectWarnings, projectOptions);
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: clangd seems to apply some heuristics depending on what we put here.
|
|
|
|
|
|
// Should we make use of them or keep using our own?
|
|
|
|
|
|
args.prepend("clang");
|
|
|
|
|
|
|
|
|
|
|
|
args.append(sourceFile.toUserOutput());
|
|
|
|
|
|
QJsonObject value;
|
|
|
|
|
|
value.insert("workingDirectory", workingDir.toString());
|
|
|
|
|
|
value.insert("compilationCommand", QJsonArray::fromStringList(args));
|
|
|
|
|
|
cdb.insert(sourceFile.toUserOutput(), value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void addCompilationDb(QJsonObject &parentObject, const QJsonObject &cdb)
|
|
|
|
|
|
{
|
|
|
|
|
|
parentObject.insert("compilationDatabaseChanges", cdb);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-18 12:59:15 +02:00
|
|
|
|
ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
|
2021-07-01 04:17:54 +02:00
|
|
|
|
: Client(clientInterface(project, jsonDbDir)), d(new Private(this, project))
|
2021-04-20 14:42:29 +02:00
|
|
|
|
{
|
|
|
|
|
|
setName(tr("clangd"));
|
|
|
|
|
|
LanguageFilter langFilter;
|
2021-06-04 09:43:22 +02:00
|
|
|
|
langFilter.mimeTypes = QStringList{"text/x-chdr", "text/x-csrc",
|
|
|
|
|
|
"text/x-c++hdr", "text/x-c++src", "text/x-objc++src", "text/x-objcsrc"};
|
2021-04-20 14:42:29 +02:00
|
|
|
|
setSupportedLanguage(langFilter);
|
2021-06-02 17:51:31 +02:00
|
|
|
|
setActivateDocumentAutomatically(true);
|
2021-10-15 10:40:02 +02:00
|
|
|
|
setLogTarget(LogTarget::Console);
|
2021-09-13 16:09:04 +02:00
|
|
|
|
setCompletionAssistProvider(new ClangdCompletionAssistProvider(this));
|
2022-01-31 13:10:28 +01:00
|
|
|
|
setQuickFixAssistProvider(new ClangdQuickFixProvider(this));
|
2021-07-14 17:07:34 +02:00
|
|
|
|
if (!project) {
|
|
|
|
|
|
QJsonObject initOptions;
|
2021-05-07 16:10:07 +02:00
|
|
|
|
const QStringList clangOptions = createClangOptions(
|
2021-08-30 10:58:08 +02:00
|
|
|
|
*CppEditor::CppModelManager::instance()->fallbackProjectPart(), {},
|
2021-05-07 16:10:07 +02:00
|
|
|
|
warningsConfigForProject(nullptr), optionsForProject(nullptr));
|
|
|
|
|
|
initOptions.insert("fallbackFlags", QJsonArray::fromStringList(clangOptions));
|
2021-07-14 17:07:34 +02:00
|
|
|
|
setInitializationOptions(initOptions);
|
|
|
|
|
|
}
|
2021-11-01 07:25:07 +01:00
|
|
|
|
auto isRunningClangdClient = [](const LanguageClient::Client *c) {
|
|
|
|
|
|
return qobject_cast<const ClangdClient *>(c) && c->state() != Client::ShutdownRequested
|
|
|
|
|
|
&& c->state() != Client::Shutdown;
|
|
|
|
|
|
};
|
|
|
|
|
|
const QList<Client *> clients =
|
|
|
|
|
|
Utils::filtered(LanguageClientManager::clientsForProject(project), isRunningClangdClient);
|
|
|
|
|
|
QTC_CHECK(clients.isEmpty());
|
|
|
|
|
|
for (const Client *client : clients)
|
|
|
|
|
|
qCWarning(clangdLog) << client->name() << client->stateString();
|
2021-06-02 17:51:31 +02:00
|
|
|
|
ClientCapabilities caps = Client::defaultClientCapabilities();
|
|
|
|
|
|
Utils::optional<TextDocumentClientCapabilities> textCaps = caps.textDocument();
|
|
|
|
|
|
if (textCaps) {
|
|
|
|
|
|
ClangdTextDocumentClientCapabilities clangdTextCaps(*textCaps);
|
|
|
|
|
|
clangdTextCaps.clearDocumentHighlight();
|
|
|
|
|
|
DiagnosticsCapabilities diagnostics;
|
|
|
|
|
|
diagnostics.enableCategorySupport();
|
|
|
|
|
|
diagnostics.enableCodeActionsInline();
|
|
|
|
|
|
clangdTextCaps.setPublishDiagnostics(diagnostics);
|
2021-06-18 16:30:03 +02:00
|
|
|
|
Utils::optional<TextDocumentClientCapabilities::CompletionCapabilities> completionCaps
|
|
|
|
|
|
= textCaps->completion();
|
|
|
|
|
|
if (completionCaps)
|
|
|
|
|
|
clangdTextCaps.setCompletion(ClangdCompletionCapabilities(*completionCaps));
|
2021-06-02 17:51:31 +02:00
|
|
|
|
caps.setTextDocument(clangdTextCaps);
|
|
|
|
|
|
}
|
2021-04-20 14:42:29 +02:00
|
|
|
|
caps.clearExperimental();
|
|
|
|
|
|
setClientCapabilities(caps);
|
|
|
|
|
|
setLocatorsEnabled(false);
|
2021-08-11 11:20:42 +02:00
|
|
|
|
setAutoRequestCodeActions(false); // clangd sends code actions inside diagnostics
|
2022-02-15 15:34:41 +01:00
|
|
|
|
if (project) {
|
|
|
|
|
|
setProgressTitleForToken(indexingToken(),
|
|
|
|
|
|
tr("Indexing %1 with clangd").arg(project->displayName()));
|
|
|
|
|
|
}
|
2021-04-20 14:42:29 +02:00
|
|
|
|
setCurrentProject(project);
|
2021-08-12 12:00:58 +02:00
|
|
|
|
setDocumentChangeUpdateThreshold(d->settings.documentUpdateThreshold);
|
2021-09-30 13:19:50 +02:00
|
|
|
|
setSymbolStringifier(displayNameFromDocumentSymbol);
|
2021-10-22 15:06:46 +02:00
|
|
|
|
setSemanticTokensHandler([this](TextDocument *doc, const QList<ExpandedSemanticToken> &tokens,
|
2021-11-15 14:58:40 +01:00
|
|
|
|
int version, bool force) {
|
|
|
|
|
|
d->handleSemanticTokens(doc, tokens, version, force);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
});
|
2021-06-04 12:40:26 +02:00
|
|
|
|
hoverHandler()->setHelpItemProvider([this](const HoverRequest::Response &response,
|
|
|
|
|
|
const DocumentUri &uri) {
|
|
|
|
|
|
gatherHelpItemForTooltip(response, uri);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2021-07-19 14:49:08 +02:00
|
|
|
|
connect(this, &Client::workDone, this,
|
|
|
|
|
|
[this, p = QPointer(project)](const ProgressToken &token) {
|
2021-04-20 15:46:35 +02:00
|
|
|
|
const QString * const val = Utils::get_if<QString>(&token);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
if (val && *val == indexingToken()) {
|
|
|
|
|
|
d->isFullyIndexed = true;
|
|
|
|
|
|
emit indexingFinished();
|
2021-05-19 13:22:49 +02:00
|
|
|
|
#ifdef WITH_TESTS
|
2021-07-19 14:49:08 +02:00
|
|
|
|
if (p)
|
|
|
|
|
|
emit p->indexingFinished("Indexer.Clangd");
|
2021-05-19 13:22:49 +02:00
|
|
|
|
#endif
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
2021-04-20 15:46:35 +02:00
|
|
|
|
});
|
2021-04-21 14:29:49 +02:00
|
|
|
|
|
|
|
|
|
|
connect(this, &Client::initialized, this, [this] {
|
|
|
|
|
|
// If we get this signal while there are pending searches, it means that
|
|
|
|
|
|
// the client was re-initialized, i.e. clangd crashed.
|
|
|
|
|
|
|
|
|
|
|
|
// Report all search results found so far.
|
|
|
|
|
|
for (quint64 key : d->runningFindUsages.keys())
|
2021-05-18 12:59:15 +02:00
|
|
|
|
d->reportAllSearchResultsAndFinish(d->runningFindUsages[key]);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
QTC_CHECK(d->runningFindUsages.isEmpty());
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2021-05-31 15:57:44 +02:00
|
|
|
|
connect(documentSymbolCache(), &DocumentSymbolCache::gotSymbols, this,
|
|
|
|
|
|
[this](const DocumentUri &uri, const DocumentSymbolsResult &symbols) {
|
|
|
|
|
|
if (!d->switchDeclDefData || d->switchDeclDefData->uri != uri)
|
|
|
|
|
|
return;
|
|
|
|
|
|
d->switchDeclDefData->docSymbols = symbols;
|
|
|
|
|
|
if (d->switchDeclDefData->ast)
|
|
|
|
|
|
d->handleDeclDefSwitchReplies();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2021-04-20 14:42:29 +02:00
|
|
|
|
start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
ClangdClient::~ClangdClient()
|
|
|
|
|
|
{
|
2021-05-19 15:51:25 +02:00
|
|
|
|
if (d->followSymbolData) {
|
|
|
|
|
|
d->followSymbolData->openedFiles.clear();
|
|
|
|
|
|
d->followSymbolData->pendingSymbolInfoRequests.clear();
|
2021-05-28 09:40:53 +02:00
|
|
|
|
d->followSymbolData->pendingGotoImplRequests.clear();
|
2021-05-28 13:12:00 +02:00
|
|
|
|
d->followSymbolData->pendingGotoDefRequests.clear();
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
delete d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ClangdClient::isFullyIndexed() const { return d->isFullyIndexed; }
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::openExtraFile(const Utils::FilePath &filePath, const QString &content)
|
|
|
|
|
|
{
|
|
|
|
|
|
QFile cxxFile(filePath.toString());
|
|
|
|
|
|
if (content.isEmpty() && !cxxFile.open(QIODevice::ReadOnly))
|
|
|
|
|
|
return;
|
|
|
|
|
|
TextDocumentItem item;
|
|
|
|
|
|
item.setLanguageId("cpp");
|
|
|
|
|
|
item.setUri(DocumentUri::fromFilePath(filePath));
|
|
|
|
|
|
item.setText(!content.isEmpty() ? content : QString::fromUtf8(cxxFile.readAll()));
|
|
|
|
|
|
item.setVersion(0);
|
2021-07-08 12:23:12 +02:00
|
|
|
|
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)),
|
|
|
|
|
|
SendDocUpdates::Ignore);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::closeExtraFile(const Utils::FilePath &filePath)
|
|
|
|
|
|
{
|
|
|
|
|
|
sendContent(DidCloseTextDocumentNotification(DidCloseTextDocumentParams(
|
2021-07-08 12:23:12 +02:00
|
|
|
|
TextDocumentIdentifier{DocumentUri::fromFilePath(filePath)})),
|
|
|
|
|
|
SendDocUpdates::Ignore);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor,
|
2021-05-18 12:59:15 +02:00
|
|
|
|
const Utils::optional<QString> &replacement)
|
2021-04-21 14:29:49 +02:00
|
|
|
|
{
|
2021-08-02 18:03:34 +02:00
|
|
|
|
// Quick check: Are we even on anything searchable?
|
2021-10-28 10:23:55 +02:00
|
|
|
|
const QString searchTerm = d->searchTermFromCursor(cursor);
|
|
|
|
|
|
if (searchTerm.isEmpty())
|
2021-04-21 14:29:49 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
2021-12-03 12:54:21 +01:00
|
|
|
|
const QTextCursor adjustedCursor = d->adjustedCursor(cursor, document);
|
2021-10-28 10:23:55 +02:00
|
|
|
|
const bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences();
|
|
|
|
|
|
|
|
|
|
|
|
// If it's a "normal" symbol, go right ahead.
|
|
|
|
|
|
if (searchTerm != "operator" && Utils::allOf(searchTerm, [](const QChar &c) {
|
|
|
|
|
|
return c.isLetterOrNumber() || c == '_';
|
|
|
|
|
|
})) {
|
2021-12-02 13:18:02 +01:00
|
|
|
|
d->findUsages(document, adjustedCursor, searchTerm, replacement, categorize);
|
2021-10-28 10:23:55 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Otherwise get the proper spelling of the search term from clang, so we can put it into the
|
2021-08-02 18:03:34 +02:00
|
|
|
|
// search widget.
|
|
|
|
|
|
const TextDocumentIdentifier docId(DocumentUri::fromFilePath(document->filePath()));
|
2021-12-02 13:18:02 +01:00
|
|
|
|
const TextDocumentPositionParams params(docId, Range(adjustedCursor).start());
|
2021-08-02 18:03:34 +02:00
|
|
|
|
SymbolInfoRequest symReq(params);
|
2021-12-02 13:18:02 +01:00
|
|
|
|
symReq.setResponseCallback([this, doc = QPointer(document), adjustedCursor, replacement, categorize]
|
2021-08-02 18:03:34 +02:00
|
|
|
|
(const SymbolInfoRequest::Response &response) {
|
|
|
|
|
|
if (!doc)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const auto result = response.result();
|
|
|
|
|
|
if (!result)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const auto list = Utils::get_if<QList<SymbolDetails>>(&result.value());
|
|
|
|
|
|
if (!list || list->isEmpty())
|
|
|
|
|
|
return;
|
|
|
|
|
|
const SymbolDetails &sd = list->first();
|
|
|
|
|
|
if (sd.name().isEmpty())
|
|
|
|
|
|
return;
|
2021-12-02 13:18:02 +01:00
|
|
|
|
d->findUsages(doc.data(), adjustedCursor, sd.name(), replacement, categorize);
|
2021-08-02 18:03:34 +02:00
|
|
|
|
});
|
|
|
|
|
|
sendContent(symReq);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
|
|
|
|
|
{
|
|
|
|
|
|
const DocumentUri &uri = params.uri();
|
|
|
|
|
|
Client::handleDiagnostics(params);
|
2021-09-08 13:24:45 +02:00
|
|
|
|
const int docVersion = documentVersion(uri.toFilePath());
|
|
|
|
|
|
if (params.version().value_or(docVersion) != docVersion)
|
|
|
|
|
|
return;
|
2021-08-02 18:03:34 +02:00
|
|
|
|
for (const Diagnostic &diagnostic : params.diagnostics()) {
|
|
|
|
|
|
const ClangdDiagnostic clangdDiagnostic(diagnostic);
|
2022-02-04 10:59:11 +01:00
|
|
|
|
const auto codeActions = clangdDiagnostic.codeActions();
|
|
|
|
|
|
if (codeActions && !codeActions->isEmpty()) {
|
|
|
|
|
|
for (const CodeAction &action : *codeActions)
|
|
|
|
|
|
LanguageClient::updateCodeActionRefactoringMarker(this, action, uri);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// We know that there's only one kind of diagnostic for which clangd has
|
|
|
|
|
|
// a quickfix tweak, so let's not be wasteful.
|
|
|
|
|
|
const Diagnostic::Code code = diagnostic.code().value_or(Diagnostic::Code());
|
|
|
|
|
|
const QString * const codeString = Utils::get_if<QString>(&code);
|
|
|
|
|
|
if (codeString && *codeString == "-Wswitch")
|
|
|
|
|
|
requestCodeActions(uri, diagnostic);
|
|
|
|
|
|
}
|
2021-08-02 18:03:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::handleDocumentOpened(TextDocument *doc)
|
2021-09-27 16:24:37 +02:00
|
|
|
|
{
|
|
|
|
|
|
const auto data = d->externalAstCache.take(doc->filePath());
|
|
|
|
|
|
if (!data)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (data->revision == getRevision(doc->filePath()))
|
|
|
|
|
|
d->astCache.insert(doc, data->data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::handleDocumentClosed(TextDocument *doc)
|
2021-08-02 18:03:34 +02:00
|
|
|
|
{
|
2022-04-21 13:02:09 +02:00
|
|
|
|
d->highlightingData.remove(doc);
|
2021-09-27 16:24:37 +02:00
|
|
|
|
d->astCache.remove(doc);
|
2022-02-08 11:49:37 +01:00
|
|
|
|
d->parserConfigs.remove(doc->filePath());
|
2021-08-02 18:03:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-12-02 13:18:02 +01:00
|
|
|
|
QTextCursor ClangdClient::adjustedCursorForHighlighting(const QTextCursor &cursor,
|
|
|
|
|
|
TextEditor::TextDocument *doc)
|
|
|
|
|
|
{
|
2021-12-03 12:54:21 +01:00
|
|
|
|
return d->adjustedCursor(cursor, doc);
|
2021-12-02 13:18:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-16 14:12:41 +01:00
|
|
|
|
const LanguageClient::Client::CustomInspectorTabs ClangdClient::createCustomInspectorTabs()
|
|
|
|
|
|
{
|
|
|
|
|
|
return {std::make_pair(new MemoryUsageWidget(this), tr("Memory Usage"))};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-01 07:34:49 +02:00
|
|
|
|
class ClangdDiagnosticManager : public LanguageClient::DiagnosticManager
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using LanguageClient::DiagnosticManager::DiagnosticManager;
|
|
|
|
|
|
|
|
|
|
|
|
void hideDiagnostics(const Utils::FilePath &filePath) override
|
|
|
|
|
|
{
|
|
|
|
|
|
DiagnosticManager::hideDiagnostics(filePath);
|
|
|
|
|
|
ClangDiagnosticManager::clearTaskHubIssues();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QList<Diagnostic> filteredDiagnostics(const QList<Diagnostic> &diagnostics) const override
|
|
|
|
|
|
{
|
|
|
|
|
|
return Utils::filtered(diagnostics, [](const Diagnostic &diag){
|
|
|
|
|
|
const Diagnostic::Code code = diag.code().value_or(Diagnostic::Code());
|
|
|
|
|
|
const QString * const codeString = Utils::get_if<QString>(&code);
|
|
|
|
|
|
return !codeString || *codeString != "drv_unknown_argument";
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TextMark *createTextMark(const Utils::FilePath &filePath,
|
|
|
|
|
|
const Diagnostic &diagnostic,
|
|
|
|
|
|
bool isProjectFile) const override
|
|
|
|
|
|
{
|
|
|
|
|
|
return new ClangdTextMark(filePath, diagnostic, isProjectFile, client());
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
DiagnosticManager *ClangdClient::createDiagnosticManager()
|
|
|
|
|
|
{
|
|
|
|
|
|
auto diagnosticManager = new ClangdDiagnosticManager(this);
|
|
|
|
|
|
if (d->isTesting) {
|
|
|
|
|
|
connect(diagnosticManager, &DiagnosticManager::textMarkCreated,
|
|
|
|
|
|
this, &ClangdClient::textMarkCreated);
|
|
|
|
|
|
}
|
|
|
|
|
|
return diagnosticManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-03 10:36:40 +01:00
|
|
|
|
RefactoringChangesData *ClangdClient::createRefactoringChangesBackend() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return new CppEditor::CppRefactoringChangesData(
|
|
|
|
|
|
CppEditor::CppModelManager::instance()->snapshot());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-08-02 18:03:34 +02:00
|
|
|
|
QVersionNumber ClangdClient::versionNumber() const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (d->versionNumber)
|
|
|
|
|
|
return d->versionNumber.value();
|
|
|
|
|
|
|
|
|
|
|
|
const QRegularExpression versionPattern("^clangd version (\\d+)\\.(\\d+)\\.(\\d+).*$");
|
|
|
|
|
|
QTC_CHECK(versionPattern.isValid());
|
|
|
|
|
|
const QRegularExpressionMatch match = versionPattern.match(serverVersion());
|
|
|
|
|
|
if (match.isValid()) {
|
|
|
|
|
|
d->versionNumber.emplace({match.captured(1).toInt(), match.captured(2).toInt(),
|
|
|
|
|
|
match.captured(3).toInt()});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
qCWarning(clangdLog) << "Failed to parse clangd server string" << serverVersion();
|
|
|
|
|
|
d->versionNumber.emplace({0});
|
|
|
|
|
|
}
|
|
|
|
|
|
return d->versionNumber.value();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-08-30 10:58:08 +02:00
|
|
|
|
CppEditor::ClangdSettings::Data ClangdClient::settingsData() const { return d->settings; }
|
2021-08-02 18:03:34 +02:00
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::Private::findUsages(TextDocument *document,
|
2021-08-02 18:03:34 +02:00
|
|
|
|
const QTextCursor &cursor, const QString &searchTerm,
|
2021-09-03 12:48:29 +02:00
|
|
|
|
const Utils::optional<QString> &replacement, bool categorize)
|
2021-08-02 18:03:34 +02:00
|
|
|
|
{
|
2021-04-21 14:29:49 +02:00
|
|
|
|
ReferencesData refData;
|
2021-08-02 18:03:34 +02:00
|
|
|
|
refData.key = nextJobId++;
|
2021-09-03 12:48:29 +02:00
|
|
|
|
refData.categorize = categorize;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
if (replacement) {
|
|
|
|
|
|
ReplacementData replacementData;
|
|
|
|
|
|
replacementData.oldSymbolName = searchTerm;
|
|
|
|
|
|
replacementData.newSymbolName = *replacement;
|
|
|
|
|
|
if (replacementData.newSymbolName.isEmpty())
|
|
|
|
|
|
replacementData.newSymbolName = replacementData.oldSymbolName;
|
|
|
|
|
|
refData.replacementData = replacementData;
|
|
|
|
|
|
}
|
2021-08-02 18:03:34 +02:00
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
refData.search = SearchResultWindow::instance()->startNewSearch(
|
|
|
|
|
|
tr("C++ Usages:"),
|
|
|
|
|
|
{},
|
|
|
|
|
|
searchTerm,
|
2021-05-18 12:59:15 +02:00
|
|
|
|
replacement ? SearchResultWindow::SearchAndReplace : SearchResultWindow::SearchOnly,
|
2021-04-21 14:29:49 +02:00
|
|
|
|
SearchResultWindow::PreserveCaseDisabled,
|
|
|
|
|
|
"CppEditor");
|
2021-05-26 17:24:18 +02:00
|
|
|
|
if (refData.categorize)
|
2021-08-30 10:58:08 +02:00
|
|
|
|
refData.search->setFilter(new CppEditor::CppSearchResultFilter);
|
2021-05-18 12:59:15 +02:00
|
|
|
|
if (refData.replacementData) {
|
|
|
|
|
|
refData.search->setTextToReplace(refData.replacementData->newSymbolName);
|
|
|
|
|
|
const auto renameFilesCheckBox = new QCheckBox;
|
|
|
|
|
|
renameFilesCheckBox->setVisible(false);
|
|
|
|
|
|
refData.search->setAdditionalReplaceWidget(renameFilesCheckBox);
|
|
|
|
|
|
const auto renameHandler =
|
|
|
|
|
|
[search = refData.search](const QString &newSymbolName,
|
|
|
|
|
|
const QList<SearchResultItem> &checkedItems,
|
|
|
|
|
|
bool preserveCase) {
|
|
|
|
|
|
const auto replacementData = search->userData().value<ReplacementData>();
|
|
|
|
|
|
Private::handleRenameRequest(search, replacementData, newSymbolName, checkedItems,
|
|
|
|
|
|
preserveCase);
|
|
|
|
|
|
};
|
|
|
|
|
|
connect(refData.search, &SearchResult::replaceButtonClicked, renameHandler);
|
|
|
|
|
|
}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
connect(refData.search, &SearchResult::activated, [](const SearchResultItem& item) {
|
|
|
|
|
|
Core::EditorManager::openEditorAtSearchResult(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
|
2021-08-02 18:03:34 +02:00
|
|
|
|
runningFindUsages.insert(refData.key, refData);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
|
2021-08-02 18:03:34 +02:00
|
|
|
|
const Utils::optional<MessageId> requestId = q->symbolSupport().findUsages(
|
2021-04-21 14:29:49 +02:00
|
|
|
|
document, cursor, [this, key = refData.key](const QList<Location> &locations) {
|
2021-08-02 18:03:34 +02:00
|
|
|
|
handleFindUsagesResult(key, locations);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!requestId) {
|
2021-08-02 18:03:34 +02:00
|
|
|
|
finishSearch(refData, false);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-08-02 18:03:34 +02:00
|
|
|
|
QObject::connect(refData.search, &SearchResult::cancelled, q, [this, requestId, key = refData.key] {
|
|
|
|
|
|
const auto refData = runningFindUsages.find(key);
|
|
|
|
|
|
if (refData == runningFindUsages.end())
|
2021-04-21 14:29:49 +02:00
|
|
|
|
return;
|
2021-08-02 18:03:34 +02:00
|
|
|
|
q->cancelRequest(*requestId);
|
2021-05-18 12:59:15 +02:00
|
|
|
|
refData->canceled = true;
|
2021-08-02 18:03:34 +02:00
|
|
|
|
refData->search->disconnect(q);
|
|
|
|
|
|
finishSearch(*refData, true);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-18 16:30:03 +02:00
|
|
|
|
void ClangdClient::enableTesting()
|
|
|
|
|
|
{
|
|
|
|
|
|
d->isTesting = true;
|
2021-09-15 11:35:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ClangdClient::testingEnabled() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return d->isTesting;
|
2021-06-18 16:30:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-30 13:19:50 +02:00
|
|
|
|
QString ClangdClient::displayNameFromDocumentSymbol(SymbolKind kind, const QString &name,
|
|
|
|
|
|
const QString &detail)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (kind) {
|
|
|
|
|
|
case SymbolKind::Constructor:
|
|
|
|
|
|
return name + detail;
|
|
|
|
|
|
case SymbolKind::Method:
|
|
|
|
|
|
case LanguageServerProtocol::SymbolKind::Function: {
|
|
|
|
|
|
const int parenOffset = detail.indexOf(" (");
|
|
|
|
|
|
if (parenOffset == -1)
|
|
|
|
|
|
return name;
|
|
|
|
|
|
return name + detail.mid(parenOffset + 1) + " -> " + detail.mid(0, parenOffset);
|
|
|
|
|
|
}
|
|
|
|
|
|
case SymbolKind::Variable:
|
|
|
|
|
|
case SymbolKind::Field:
|
|
|
|
|
|
case SymbolKind::Constant:
|
|
|
|
|
|
if (detail.isEmpty())
|
|
|
|
|
|
return name;
|
|
|
|
|
|
return name + " -> " + detail;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return name;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-12 11:33:07 +01:00
|
|
|
|
// Force re-parse of all open files that include the changed ui header.
|
|
|
|
|
|
// Otherwise, we potentially have stale diagnostics.
|
|
|
|
|
|
void ClangdClient::handleUiHeaderChange(const QString &fileName)
|
|
|
|
|
|
{
|
|
|
|
|
|
const QRegularExpression includeRex("#include.*" + fileName + R"([>"])");
|
2022-03-14 08:36:37 +01:00
|
|
|
|
const QList<Client *> &allClients = LanguageClientManager::clients();
|
2021-11-12 11:33:07 +01:00
|
|
|
|
for (Client * const client : allClients) {
|
|
|
|
|
|
if (!client->reachable() || !qobject_cast<ClangdClient *>(client))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
for (IDocument * const doc : DocumentModel::openedDocuments()) {
|
|
|
|
|
|
const auto textDoc = qobject_cast<TextDocument *>(doc);
|
|
|
|
|
|
if (!textDoc || !client->documentOpen(textDoc))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
const QTextCursor includePos = textDoc->document()->find(includeRex);
|
|
|
|
|
|
if (includePos.isNull())
|
|
|
|
|
|
continue;
|
|
|
|
|
|
qCDebug(clangdLog) << "updating" << textDoc->filePath() << "due to change in UI header"
|
|
|
|
|
|
<< fileName;
|
|
|
|
|
|
client->documentContentsChanged(textDoc, 0, 0, 0);
|
|
|
|
|
|
break; // No sane project includes the same UI header twice.
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-08 11:49:37 +01:00
|
|
|
|
void ClangdClient::updateParserConfig(const Utils::FilePath &filePath,
|
|
|
|
|
|
const CppEditor::BaseEditorDocumentParser::Configuration &config)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (config.preferredProjectPartId.isEmpty())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
CppEditor::BaseEditorDocumentParser::Configuration &cachedConfig = d->parserConfigs[filePath];
|
|
|
|
|
|
if (cachedConfig == config)
|
|
|
|
|
|
return;
|
|
|
|
|
|
cachedConfig = config;
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Also handle editorDefines (and usePrecompiledHeaders?)
|
|
|
|
|
|
const auto projectPart = CppEditor::CppModelManager::instance()
|
|
|
|
|
|
->projectPartForId(config.preferredProjectPartId);
|
|
|
|
|
|
if (!projectPart)
|
|
|
|
|
|
return;
|
|
|
|
|
|
QJsonObject cdbChanges;
|
2022-02-16 12:26:15 +01:00
|
|
|
|
addToCompilationDb(cdbChanges, warningsConfigForProject(project()),
|
|
|
|
|
|
optionsForProject(project()), projectPart, filePath.parentDir(), filePath);
|
|
|
|
|
|
QJsonObject settings;
|
|
|
|
|
|
addCompilationDb(settings, cdbChanges);
|
2022-02-08 11:49:37 +01:00
|
|
|
|
DidChangeConfigurationParams configChangeParams;
|
|
|
|
|
|
configChangeParams.setSettings(settings);
|
|
|
|
|
|
sendContent(DidChangeConfigurationNotification(configChangeParams));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 14:29:49 +02:00
|
|
|
|
void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList<Location> &locations)
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto refData = runningFindUsages.find(key);
|
|
|
|
|
|
if (refData == runningFindUsages.end())
|
|
|
|
|
|
return;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
if (!refData->search || refData->canceled) {
|
2021-04-21 14:29:49 +02:00
|
|
|
|
finishSearch(*refData, true);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
refData->search->disconnect(q);
|
|
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLog) << "found" << locations.size() << "locations";
|
|
|
|
|
|
if (locations.isEmpty()) {
|
|
|
|
|
|
finishSearch(*refData, false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QObject::connect(refData->search, &SearchResult::cancelled, q, [this, key] {
|
|
|
|
|
|
const auto refData = runningFindUsages.find(key);
|
|
|
|
|
|
if (refData == runningFindUsages.end())
|
|
|
|
|
|
return;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
refData->canceled = true;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
refData->search->disconnect(q);
|
|
|
|
|
|
for (const MessageId &id : qAsConst(refData->pendingAstRequests))
|
|
|
|
|
|
q->cancelRequest(id);
|
|
|
|
|
|
refData->pendingAstRequests.clear();
|
|
|
|
|
|
finishSearch(*refData, true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2021-09-23 17:04:33 +02:00
|
|
|
|
for (const Location &loc : locations)
|
2021-09-24 12:41:30 +02:00
|
|
|
|
refData->fileData[loc.uri()].rangesAndLineText << qMakePair(loc.range(), QString());
|
2021-11-19 16:19:02 +01:00
|
|
|
|
for (auto it = refData->fileData.begin(); it != refData->fileData.end();) {
|
|
|
|
|
|
const Utils::FilePath filePath = it.key().toFilePath();
|
|
|
|
|
|
if (!filePath.exists()) { // https://github.com/clangd/clangd/issues/935
|
|
|
|
|
|
it = refData->fileData.erase(it);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
const QStringList lines = SymbolSupport::getFileContents(filePath);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
it->fileContent = lines.join('\n');
|
|
|
|
|
|
for (auto &rangeWithText : it.value().rangesAndLineText) {
|
|
|
|
|
|
const int lineNo = rangeWithText.first.start().line();
|
|
|
|
|
|
if (lineNo >= 0 && lineNo < lines.size())
|
|
|
|
|
|
rangeWithText.second = lines.at(lineNo);
|
|
|
|
|
|
}
|
2021-11-19 16:19:02 +01:00
|
|
|
|
++it;
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLog) << "document count is" << refData->fileData.size();
|
2022-02-14 09:25:29 +01:00
|
|
|
|
if (refData->replacementData || !refData->categorize) {
|
2021-04-21 14:29:49 +02:00
|
|
|
|
qCDebug(clangdLog) << "skipping AST retrieval";
|
|
|
|
|
|
reportAllSearchResultsAndFinish(*refData);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (auto it = refData->fileData.begin(); it != refData->fileData.end(); ++it) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const TextDocument * const doc = q->documentForFilePath(it.key().toFilePath());
|
2021-09-27 16:24:37 +02:00
|
|
|
|
if (!doc)
|
2021-04-21 14:29:49 +02:00
|
|
|
|
q->openExtraFile(it.key().toFilePath(), it->fileContent);
|
|
|
|
|
|
it->fileContent.clear();
|
2021-09-27 16:24:37 +02:00
|
|
|
|
const auto docVariant = doc ? TextDocOrFile(doc) : TextDocOrFile(it.key().toFilePath());
|
|
|
|
|
|
const auto astHandler = [this, key, loc = it.key()](const AstNode &ast,
|
|
|
|
|
|
const MessageId &reqId) {
|
|
|
|
|
|
qCDebug(clangdLog) << "AST for" << loc.toFilePath();
|
2021-04-21 14:29:49 +02:00
|
|
|
|
const auto refData = runningFindUsages.find(key);
|
|
|
|
|
|
if (refData == runningFindUsages.end())
|
|
|
|
|
|
return;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
if (!refData->search || refData->canceled)
|
2021-04-21 14:29:49 +02:00
|
|
|
|
return;
|
|
|
|
|
|
ReferencesFileData &data = refData->fileData[loc];
|
2021-09-27 16:24:37 +02:00
|
|
|
|
data.ast = ast;
|
|
|
|
|
|
refData->pendingAstRequests.removeOne(reqId);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
qCDebug(clangdLog) << refData->pendingAstRequests.size()
|
|
|
|
|
|
<< "AST requests still pending";
|
|
|
|
|
|
addSearchResultsForFile(*refData, loc.toFilePath(), data);
|
|
|
|
|
|
refData->fileData.remove(loc);
|
|
|
|
|
|
if (refData->pendingAstRequests.isEmpty()) {
|
|
|
|
|
|
qDebug(clangdLog) << "retrieved all ASTs";
|
|
|
|
|
|
finishSearch(*refData, false);
|
|
|
|
|
|
}
|
2021-09-27 16:24:37 +02:00
|
|
|
|
};
|
|
|
|
|
|
const MessageId reqId = getAndHandleAst(docVariant, astHandler,
|
|
|
|
|
|
AstCallbackMode::AlwaysAsync);
|
|
|
|
|
|
refData->pendingAstRequests << reqId;
|
|
|
|
|
|
if (!doc)
|
2021-04-21 14:29:49 +02:00
|
|
|
|
q->closeExtraFile(it.key().toFilePath());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-18 12:59:15 +02:00
|
|
|
|
void ClangdClient::Private::handleRenameRequest(const SearchResult *search,
|
|
|
|
|
|
const ReplacementData &replacementData,
|
|
|
|
|
|
const QString &newSymbolName,
|
|
|
|
|
|
const QList<SearchResultItem> &checkedItems,
|
|
|
|
|
|
bool preserveCase)
|
|
|
|
|
|
{
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const Utils::FilePaths filePaths = BaseFileFind::replaceAll(newSymbolName, checkedItems,
|
|
|
|
|
|
preserveCase);
|
2021-06-22 08:57:36 +02:00
|
|
|
|
if (!filePaths.isEmpty())
|
2021-05-18 12:59:15 +02:00
|
|
|
|
SearchResultWindow::instance()->hide();
|
|
|
|
|
|
|
|
|
|
|
|
const auto renameFilesCheckBox = qobject_cast<QCheckBox *>(search->additionalReplaceWidget());
|
|
|
|
|
|
QTC_ASSERT(renameFilesCheckBox, return);
|
|
|
|
|
|
if (!renameFilesCheckBox->isChecked())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
QVector<Node *> fileNodes;
|
|
|
|
|
|
for (const Utils::FilePath &file : replacementData.fileRenameCandidates) {
|
|
|
|
|
|
Node * const node = ProjectTree::nodeForFile(file);
|
|
|
|
|
|
if (node)
|
|
|
|
|
|
fileNodes << node;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!fileNodes.isEmpty())
|
2021-08-30 10:58:08 +02:00
|
|
|
|
CppEditor::renameFilesForSymbol(replacementData.oldSymbolName, newSymbolName, fileNodes);
|
2021-05-18 12:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::Private::addSearchResultsForFile(ReferencesData &refData,
|
2021-04-21 14:29:49 +02:00
|
|
|
|
const Utils::FilePath &file,
|
|
|
|
|
|
const ReferencesFileData &fileData)
|
|
|
|
|
|
{
|
|
|
|
|
|
QList<SearchResultItem> items;
|
|
|
|
|
|
qCDebug(clangdLog) << file << "has valid AST:" << fileData.ast.isValid();
|
|
|
|
|
|
for (const auto &rangeWithText : fileData.rangesAndLineText) {
|
|
|
|
|
|
const Range &range = rangeWithText.first;
|
|
|
|
|
|
const Usage::Type usageType = fileData.ast.isValid()
|
|
|
|
|
|
? getUsageType(getAstPath(fileData.ast, qAsConst(range)))
|
|
|
|
|
|
: Usage::Type::Other;
|
|
|
|
|
|
SearchResultItem item;
|
|
|
|
|
|
item.setUserData(int(usageType));
|
2021-08-30 10:58:08 +02:00
|
|
|
|
item.setStyle(CppEditor::colorStyleForUsageType(usageType));
|
2021-04-21 14:29:49 +02:00
|
|
|
|
item.setFilePath(file);
|
|
|
|
|
|
item.setMainRange(SymbolSupport::convertRange(range));
|
|
|
|
|
|
item.setUseTextEditorFont(true);
|
|
|
|
|
|
item.setLineText(rangeWithText.second);
|
2021-05-18 12:59:15 +02:00
|
|
|
|
if (refData.search->supportsReplace()) {
|
|
|
|
|
|
const bool fileInSession = SessionManager::projectForFile(file);
|
|
|
|
|
|
item.setSelectForReplacement(fileInSession);
|
2021-06-03 12:53:40 +02:00
|
|
|
|
if (fileInSession && file.baseName().compare(
|
2021-05-18 12:59:15 +02:00
|
|
|
|
refData.replacementData->oldSymbolName,
|
|
|
|
|
|
Qt::CaseInsensitive) == 0) {
|
2021-09-24 13:11:31 +02:00
|
|
|
|
refData.replacementData->fileRenameCandidates << file;
|
2021-05-18 12:59:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
items << item;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isTesting)
|
|
|
|
|
|
emit q->foundReferences(items);
|
|
|
|
|
|
else
|
|
|
|
|
|
refData.search->addResults(items, SearchResult::AddOrdered);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-18 12:59:15 +02:00
|
|
|
|
void ClangdClient::Private::reportAllSearchResultsAndFinish(ReferencesData &refData)
|
2021-04-21 14:29:49 +02:00
|
|
|
|
{
|
|
|
|
|
|
for (auto it = refData.fileData.begin(); it != refData.fileData.end(); ++it)
|
|
|
|
|
|
addSearchResultsForFile(refData, it.key().toFilePath(), it.value());
|
2021-05-18 12:59:15 +02:00
|
|
|
|
finishSearch(refData, refData.canceled);
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::Private::finishSearch(const ReferencesData &refData, bool canceled)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isTesting) {
|
|
|
|
|
|
emit q->findUsagesDone();
|
|
|
|
|
|
} else if (refData.search) {
|
|
|
|
|
|
refData.search->finishSearch(canceled);
|
|
|
|
|
|
refData.search->disconnect(q);
|
2021-05-18 12:59:15 +02:00
|
|
|
|
if (refData.replacementData) {
|
|
|
|
|
|
const auto renameCheckBox = qobject_cast<QCheckBox *>(
|
|
|
|
|
|
refData.search->additionalReplaceWidget());
|
|
|
|
|
|
QTC_CHECK(renameCheckBox);
|
|
|
|
|
|
const QSet<Utils::FilePath> files = refData.replacementData->fileRenameCandidates;
|
|
|
|
|
|
renameCheckBox->setText(tr("Re&name %n files", nullptr, files.size()));
|
|
|
|
|
|
const QStringList filesForUser = Utils::transform<QStringList>(files,
|
|
|
|
|
|
[](const Utils::FilePath &fp) { return fp.toUserOutput(); });
|
|
|
|
|
|
renameCheckBox->setToolTip(tr("Files:\n%1").arg(filesForUser.join('\n')));
|
|
|
|
|
|
renameCheckBox->setVisible(true);
|
|
|
|
|
|
refData.search->setUserData(QVariant::fromValue(*refData.replacementData));
|
|
|
|
|
|
}
|
2021-04-21 14:29:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
runningFindUsages.remove(refData.key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::followSymbol(TextDocument *document,
|
2021-05-19 15:51:25 +02:00
|
|
|
|
const QTextCursor &cursor,
|
2021-09-01 18:08:54 +02:00
|
|
|
|
CppEditor::CppEditorWidget *editorWidget,
|
2021-05-19 15:51:25 +02:00
|
|
|
|
Utils::ProcessLinkCallback &&callback,
|
|
|
|
|
|
bool resolveTarget,
|
|
|
|
|
|
bool openInSplit
|
|
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
QTC_ASSERT(documentOpen(document), openDocument(document));
|
2021-12-03 12:54:21 +01:00
|
|
|
|
const QTextCursor adjustedCursor = d->adjustedCursor(cursor, document);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
if (!resolveTarget) {
|
|
|
|
|
|
d->followSymbolData.reset();
|
2021-12-02 13:18:02 +01:00
|
|
|
|
symbolSupport().findLinkAt(document, adjustedCursor, std::move(callback), false);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-19 13:22:49 +02:00
|
|
|
|
qCDebug(clangdLog) << "follow symbol requested" << document->filePath()
|
2021-12-02 13:18:02 +01:00
|
|
|
|
<< adjustedCursor.blockNumber() << adjustedCursor.positionInBlock();
|
|
|
|
|
|
d->followSymbolData.emplace(this, ++d->nextJobId, adjustedCursor, editorWidget,
|
2021-05-19 15:51:25 +02:00
|
|
|
|
DocumentUri::fromFilePath(document->filePath()),
|
|
|
|
|
|
std::move(callback), openInSplit);
|
|
|
|
|
|
|
|
|
|
|
|
// Step 1: Follow the symbol via "Go to Definition". At the same time, request the
|
|
|
|
|
|
// AST node corresponding to the cursor position, so we can find out whether
|
|
|
|
|
|
// we have to look for overrides.
|
|
|
|
|
|
const auto gotoDefCallback = [this, id = d->followSymbolData->id](const Utils::Link &link) {
|
2021-05-19 13:22:49 +02:00
|
|
|
|
qCDebug(clangdLog) << "received go to definition response";
|
2021-05-19 15:51:25 +02:00
|
|
|
|
if (!link.hasValidTarget()) {
|
|
|
|
|
|
d->followSymbolData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!d->followSymbolData || id != d->followSymbolData->id)
|
|
|
|
|
|
return;
|
|
|
|
|
|
d->followSymbolData->defLink = link;
|
2021-06-29 11:01:50 +02:00
|
|
|
|
if (d->followSymbolData->cursorNode)
|
2021-05-19 15:51:25 +02:00
|
|
|
|
d->handleGotoDefinitionResult();
|
|
|
|
|
|
};
|
2021-12-02 13:18:02 +01:00
|
|
|
|
symbolSupport().findLinkAt(document, adjustedCursor, std::move(gotoDefCallback), true);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
|
2021-10-07 10:58:54 +02:00
|
|
|
|
const auto astHandler = [this, id = d->followSymbolData->id]
|
2021-09-27 16:24:37 +02:00
|
|
|
|
(const AstNode &ast, const MessageId &) {
|
2021-05-27 16:32:24 +02:00
|
|
|
|
qCDebug(clangdLog) << "received ast response for cursor";
|
2021-05-19 15:51:25 +02:00
|
|
|
|
if (!d->followSymbolData || d->followSymbolData->id != id)
|
|
|
|
|
|
return;
|
2021-10-07 10:58:54 +02:00
|
|
|
|
d->followSymbolData->cursorNode = ast;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
if (d->followSymbolData->defLink.hasValidTarget())
|
|
|
|
|
|
d->handleGotoDefinitionResult();
|
2021-09-27 16:24:37 +02:00
|
|
|
|
};
|
2021-12-02 13:18:02 +01:00
|
|
|
|
d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::AlwaysAsync,
|
|
|
|
|
|
Range(adjustedCursor));
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &cursor,
|
2021-09-01 18:08:54 +02:00
|
|
|
|
CppEditor::CppEditorWidget *editorWidget,
|
2021-05-31 15:57:44 +02:00
|
|
|
|
Utils::ProcessLinkCallback &&callback)
|
|
|
|
|
|
{
|
|
|
|
|
|
QTC_ASSERT(documentOpen(document), openDocument(document));
|
|
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLog) << "switch decl/dev requested" << document->filePath()
|
|
|
|
|
|
<< cursor.blockNumber() << cursor.positionInBlock();
|
2021-06-02 16:32:54 +02:00
|
|
|
|
d->switchDeclDefData.emplace(++d->nextJobId, document, cursor, editorWidget,
|
2021-05-31 15:57:44 +02:00
|
|
|
|
std::move(callback));
|
|
|
|
|
|
|
|
|
|
|
|
// Retrieve AST and document symbols.
|
2021-09-27 16:24:37 +02:00
|
|
|
|
const auto astHandler = [this, id = d->switchDeclDefData->id](const AstNode &ast,
|
|
|
|
|
|
const MessageId &) {
|
2021-05-31 15:57:44 +02:00
|
|
|
|
qCDebug(clangdLog) << "received ast for decl/def switch";
|
|
|
|
|
|
if (!d->switchDeclDefData || d->switchDeclDefData->id != id
|
|
|
|
|
|
|| !d->switchDeclDefData->document)
|
|
|
|
|
|
return;
|
2021-09-27 16:24:37 +02:00
|
|
|
|
if (!ast.isValid()) {
|
2021-05-31 15:57:44 +02:00
|
|
|
|
d->switchDeclDefData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-09-27 16:24:37 +02:00
|
|
|
|
d->switchDeclDefData->ast = ast;
|
2021-05-31 15:57:44 +02:00
|
|
|
|
if (d->switchDeclDefData->docSymbols)
|
|
|
|
|
|
d->handleDeclDefSwitchReplies();
|
|
|
|
|
|
|
2021-09-27 16:24:37 +02:00
|
|
|
|
};
|
|
|
|
|
|
d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::SyncIfPossible);
|
2021-09-14 17:00:16 +02:00
|
|
|
|
documentSymbolCache()->requestSymbols(d->switchDeclDefData->uri, Schedule::Now);
|
2021-05-31 15:57:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cursor,
|
2021-08-30 10:58:08 +02:00
|
|
|
|
CppEditor::RefactoringEngineInterface::RenameCallback &&callback)
|
2021-06-01 18:14:12 +02:00
|
|
|
|
{
|
|
|
|
|
|
QTC_ASSERT(documentOpen(document), openDocument(document));
|
|
|
|
|
|
|
|
|
|
|
|
qCDebug(clangdLog) << "local references requested" << document->filePath()
|
|
|
|
|
|
<< (cursor.blockNumber() + 1) << (cursor.positionInBlock() + 1);
|
|
|
|
|
|
|
2021-06-02 16:32:54 +02:00
|
|
|
|
d->localRefsData.emplace(++d->nextJobId, document, cursor, std::move(callback));
|
2021-06-01 18:14:12 +02:00
|
|
|
|
const QString searchTerm = d->searchTermFromCursor(cursor);
|
|
|
|
|
|
if (searchTerm.isEmpty()) {
|
|
|
|
|
|
d->localRefsData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 1: Go to definition
|
|
|
|
|
|
const auto gotoDefCallback = [this, id = d->localRefsData->id](const Utils::Link &link) {
|
|
|
|
|
|
qCDebug(clangdLog) << "received go to definition response" << link.targetFilePath
|
|
|
|
|
|
<< link.targetLine << (link.targetColumn + 1);
|
|
|
|
|
|
if (!d->localRefsData || id != d->localRefsData->id)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (!link.hasValidTarget()) {
|
|
|
|
|
|
d->localRefsData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 2: Get AST and check whether it's a local variable.
|
2021-09-27 16:24:37 +02:00
|
|
|
|
const auto astHandler = [this, link, id](const AstNode &ast, const MessageId &) {
|
2021-06-01 18:14:12 +02:00
|
|
|
|
qCDebug(clangdLog) << "received ast response";
|
|
|
|
|
|
if (!d->localRefsData || id != d->localRefsData->id)
|
|
|
|
|
|
return;
|
2021-09-27 16:24:37 +02:00
|
|
|
|
if (!ast.isValid() || !d->localRefsData->document) {
|
2021-06-01 18:14:12 +02:00
|
|
|
|
d->localRefsData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Position linkPos(link.targetLine - 1, link.targetColumn);
|
2021-10-05 12:27:36 +02:00
|
|
|
|
const QList<AstNode> astPath = getAstPath(ast, linkPos);
|
2021-06-01 18:14:12 +02:00
|
|
|
|
bool isVar = false;
|
|
|
|
|
|
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
|
2022-03-30 17:31:26 +02:00
|
|
|
|
if (it->role() == "declaration"
|
|
|
|
|
|
&& (it->kind() == "Function" || it->kind() == "CXXMethod"
|
|
|
|
|
|
|| it->kind() == "CXXConstructor" || it->kind() == "CXXDestructor"
|
|
|
|
|
|
|| it->kind() == "Lambda")) {
|
2021-06-01 18:14:12 +02:00
|
|
|
|
if (!isVar)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// Step 3: Find references.
|
|
|
|
|
|
qCDebug(clangdLog) << "finding references for local var";
|
|
|
|
|
|
symbolSupport().findUsages(d->localRefsData->document,
|
|
|
|
|
|
d->localRefsData->cursor,
|
|
|
|
|
|
[this, id](const QList<Location> &locations) {
|
|
|
|
|
|
qCDebug(clangdLog) << "found" << locations.size() << "local references";
|
|
|
|
|
|
if (!d->localRefsData || id != d->localRefsData->id)
|
|
|
|
|
|
return;
|
|
|
|
|
|
ClangBackEnd::SourceLocationsContainer container;
|
|
|
|
|
|
for (const Location &loc : locations) {
|
|
|
|
|
|
container.insertSourceLocation({}, loc.range().start().line() + 1,
|
|
|
|
|
|
loc.range().start().character() + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// The callback only uses the symbol length, so we just create a dummy.
|
|
|
|
|
|
// Note that the calculation will be wrong for identifiers with
|
|
|
|
|
|
// embedded newlines, but we've never supported that.
|
|
|
|
|
|
QString symbol;
|
|
|
|
|
|
if (!locations.isEmpty()) {
|
|
|
|
|
|
const Range r = locations.first().range();
|
|
|
|
|
|
symbol = QString(r.end().character() - r.start().character(), 'x');
|
|
|
|
|
|
}
|
|
|
|
|
|
d->localRefsData->callback(symbol, container, d->localRefsData->revision);
|
|
|
|
|
|
d->localRefsData->callback = {};
|
|
|
|
|
|
d->localRefsData.reset();
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isVar && it->role() == "declaration"
|
|
|
|
|
|
&& (it->kind() == "Var" || it->kind() == "ParmVar")) {
|
|
|
|
|
|
isVar = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
d->localRefsData.reset();
|
2021-09-27 16:24:37 +02:00
|
|
|
|
};
|
2021-06-01 18:14:12 +02:00
|
|
|
|
qCDebug(clangdLog) << "sending ast request for link";
|
2021-09-27 16:24:37 +02:00
|
|
|
|
d->getAndHandleAst(d->localRefsData->document, astHandler,
|
|
|
|
|
|
Private::AstCallbackMode::SyncIfPossible);
|
2021-06-01 18:14:12 +02:00
|
|
|
|
};
|
|
|
|
|
|
symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-04 12:40:26 +02:00
|
|
|
|
void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverResponse,
|
|
|
|
|
|
const DocumentUri &uri)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (const Utils::optional<Hover> result = hoverResponse.result()) {
|
|
|
|
|
|
const HoverContent content = result->content();
|
|
|
|
|
|
const MarkupContent * const markup = Utils::get_if<MarkupContent>(&content);
|
|
|
|
|
|
if (markup) {
|
|
|
|
|
|
const QString markupString = markup->content();
|
2021-07-29 16:50:55 +02:00
|
|
|
|
|
|
|
|
|
|
// Macros aren't locatable via the AST, so parse the formatted string.
|
2021-06-04 12:40:26 +02:00
|
|
|
|
static const QString magicMacroPrefix = "### macro `";
|
|
|
|
|
|
if (markupString.startsWith(magicMacroPrefix)) {
|
|
|
|
|
|
const int nameStart = magicMacroPrefix.length();
|
|
|
|
|
|
const int closingQuoteIndex = markupString.indexOf('`', nameStart);
|
|
|
|
|
|
if (closingQuoteIndex != -1) {
|
|
|
|
|
|
const QString macroName = markupString.mid(nameStart,
|
|
|
|
|
|
closingQuoteIndex - nameStart);
|
|
|
|
|
|
d->setHelpItemForTooltip(hoverResponse.id(), macroName, HelpItem::Macro);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-07-29 16:50:55 +02:00
|
|
|
|
|
|
|
|
|
|
// Is it the file path for an include directive?
|
|
|
|
|
|
QString cleanString = markupString;
|
|
|
|
|
|
cleanString.remove('`');
|
|
|
|
|
|
const QStringList lines = cleanString.trimmed().split('\n');
|
|
|
|
|
|
if (!lines.isEmpty()) {
|
|
|
|
|
|
const auto filePath = Utils::FilePath::fromUserInput(lines.last().simplified());
|
|
|
|
|
|
if (filePath.exists()) {
|
|
|
|
|
|
d->setHelpItemForTooltip(hoverResponse.id(), filePath.fileName(),
|
|
|
|
|
|
HelpItem::Brief);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-06-04 12:40:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const TextDocument * const doc = documentForFilePath(uri.toFilePath());
|
2021-09-27 16:24:37 +02:00
|
|
|
|
QTC_ASSERT(doc, return);
|
|
|
|
|
|
const auto astHandler = [this, uri, hoverResponse](const AstNode &ast, const MessageId &) {
|
2021-06-04 12:40:26 +02:00
|
|
|
|
const MessageId id = hoverResponse.id();
|
|
|
|
|
|
const Range range = hoverResponse.result()->range().value_or(Range());
|
|
|
|
|
|
const QList<AstNode> path = getAstPath(ast, range);
|
|
|
|
|
|
if (path.isEmpty()) {
|
|
|
|
|
|
d->setHelpItemForTooltip(id);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
AstNode node = path.last();
|
|
|
|
|
|
if (node.role() == "expression" && node.kind() == "ImplicitCast") {
|
|
|
|
|
|
const Utils::optional<QList<AstNode>> children = node.children();
|
|
|
|
|
|
if (children && !children->isEmpty())
|
|
|
|
|
|
node = children->first();
|
|
|
|
|
|
}
|
|
|
|
|
|
while (node.kind() == "Qualified") {
|
|
|
|
|
|
const Utils::optional<QList<AstNode>> children = node.children();
|
|
|
|
|
|
if (children && !children->isEmpty())
|
|
|
|
|
|
node = children->first();
|
|
|
|
|
|
}
|
2021-07-15 11:39:56 +02:00
|
|
|
|
if (clangdLogAst().isDebugEnabled())
|
2021-06-04 12:40:26 +02:00
|
|
|
|
node.print(0);
|
|
|
|
|
|
|
|
|
|
|
|
QString type = node.type();
|
|
|
|
|
|
const auto stripTemplatePartOffType = [&type] {
|
|
|
|
|
|
const int angleBracketIndex = type.indexOf('<');
|
|
|
|
|
|
if (angleBracketIndex != -1)
|
|
|
|
|
|
type = type.left(angleBracketIndex);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const bool isMemberFunction = node.role() == "expression" && node.kind() == "Member"
|
|
|
|
|
|
&& (node.arcanaContains("member function") || type.contains('('));
|
|
|
|
|
|
const bool isFunction = node.role() == "expression" && node.kind() == "DeclRef"
|
|
|
|
|
|
&& type.contains('(');
|
|
|
|
|
|
if (isMemberFunction || isFunction) {
|
|
|
|
|
|
const TextDocumentPositionParams params(TextDocumentIdentifier(uri), range.start());
|
|
|
|
|
|
SymbolInfoRequest symReq(params);
|
|
|
|
|
|
symReq.setResponseCallback([this, id, type, isFunction]
|
|
|
|
|
|
(const SymbolInfoRequest::Response &response) {
|
|
|
|
|
|
qCDebug(clangdLog) << "handling symbol info reply";
|
|
|
|
|
|
QString fqn;
|
|
|
|
|
|
if (const auto result = response.result()) {
|
|
|
|
|
|
if (const auto list = Utils::get_if<QList<SymbolDetails>>(&result.value())) {
|
|
|
|
|
|
if (!list->isEmpty()) {
|
|
|
|
|
|
const SymbolDetails &sd = list->first();
|
|
|
|
|
|
fqn = sd.containerName() + sd.name();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unfortunately, the arcana string contains the signature only for
|
|
|
|
|
|
// free functions, so we can't distinguish member function overloads.
|
|
|
|
|
|
// But since HtmlDocExtractor::getFunctionDescription() is always called
|
|
|
|
|
|
// with mainOverload = true, such information would get ignored anyway.
|
|
|
|
|
|
d->setHelpItemForTooltip(id, fqn, HelpItem::Function, isFunction ? type : "()");
|
|
|
|
|
|
});
|
2021-07-08 12:23:12 +02:00
|
|
|
|
sendContent(symReq, SendDocUpdates::Ignore);
|
2021-06-04 12:40:26 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((node.role() == "expression" && node.kind() == "DeclRef")
|
|
|
|
|
|
|| (node.role() == "declaration"
|
|
|
|
|
|
&& (node.kind() == "Var" || node.kind() == "ParmVar"
|
|
|
|
|
|
|| node.kind() == "Field"))) {
|
|
|
|
|
|
if (node.arcanaContains("EnumConstant")) {
|
|
|
|
|
|
d->setHelpItemForTooltip(id, node.detail().value_or(QString()),
|
|
|
|
|
|
HelpItem::Enum, type);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
stripTemplatePartOffType();
|
|
|
|
|
|
type.remove("&").remove("*").remove("const ").remove(" const")
|
|
|
|
|
|
.remove("volatile ").remove(" volatile");
|
|
|
|
|
|
type = type.simplified();
|
|
|
|
|
|
if (type != "int" && !type.contains(" int")
|
|
|
|
|
|
&& type != "char" && !type.contains(" char")
|
|
|
|
|
|
&& type != "double" && !type.contains(" double")
|
|
|
|
|
|
&& type != "float" && type != "bool") {
|
|
|
|
|
|
d->setHelpItemForTooltip(id, type, node.qdocCategoryForDeclaration(
|
|
|
|
|
|
HelpItem::ClassOrNamespace));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
d->setHelpItemForTooltip(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (node.isNamespace()) {
|
|
|
|
|
|
QString ns = node.detail().value_or(QString());
|
|
|
|
|
|
for (auto it = path.rbegin() + 1; it != path.rend(); ++it) {
|
|
|
|
|
|
if (it->isNamespace()) {
|
|
|
|
|
|
const QString name = it->detail().value_or(QString());
|
|
|
|
|
|
if (!name.isEmpty())
|
|
|
|
|
|
ns.prepend("::").prepend(name);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
d->setHelpItemForTooltip(hoverResponse.id(), ns, HelpItem::ClassOrNamespace);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (node.role() == "type") {
|
|
|
|
|
|
if (node.kind() == "Enum") {
|
|
|
|
|
|
d->setHelpItemForTooltip(id, node.detail().value_or(QString()), HelpItem::Enum);
|
|
|
|
|
|
} else if (node.kind() == "Record" || node.kind() == "TemplateSpecialization") {
|
|
|
|
|
|
stripTemplatePartOffType();
|
|
|
|
|
|
d->setHelpItemForTooltip(id, type, HelpItem::ClassOrNamespace);
|
|
|
|
|
|
} else if (node.kind() == "Typedef") {
|
|
|
|
|
|
d->setHelpItemForTooltip(id, type, HelpItem::Typedef);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
d->setHelpItemForTooltip(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (node.role() == "expression" && node.kind() == "CXXConstruct") {
|
|
|
|
|
|
const QString name = node.detail().value_or(QString());
|
|
|
|
|
|
if (!name.isEmpty())
|
|
|
|
|
|
type = name;
|
|
|
|
|
|
d->setHelpItemForTooltip(id, type, HelpItem::ClassOrNamespace);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (node.role() == "specifier" && node.kind() == "NamespaceAlias") {
|
|
|
|
|
|
d->setHelpItemForTooltip(id, node.detail().value_or(QString()).chopped(2),
|
|
|
|
|
|
HelpItem::ClassOrNamespace);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
d->setHelpItemForTooltip(id);
|
2021-09-27 16:24:37 +02:00
|
|
|
|
};
|
|
|
|
|
|
d->getAndHandleAst(doc, astHandler, Private::AstCallbackMode::SyncIfPossible);
|
2021-06-04 12:40:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-28 11:59:48 +02:00
|
|
|
|
void ClangdClient::setVirtualRanges(const Utils::FilePath &filePath, const QList<Range> &ranges,
|
|
|
|
|
|
int revision)
|
|
|
|
|
|
{
|
|
|
|
|
|
TextDocument * const doc = documentForFilePath(filePath);
|
|
|
|
|
|
if (doc && doc->document()->revision() == revision)
|
2022-04-21 13:02:09 +02:00
|
|
|
|
d->highlightingData[doc].virtualRanges = {ranges, revision};
|
2021-10-28 11:59:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
void ClangdClient::Private::handleGotoDefinitionResult()
|
|
|
|
|
|
{
|
|
|
|
|
|
QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return);
|
|
|
|
|
|
|
2021-05-19 13:22:49 +02:00
|
|
|
|
qCDebug(clangdLog) << "handling go to definition result";
|
|
|
|
|
|
|
2021-05-19 15:51:25 +02:00
|
|
|
|
// No dis-ambiguation necessary. Call back with the link and finish.
|
2021-10-28 11:59:48 +02:00
|
|
|
|
if (!followSymbolData->defLinkIsAmbiguous()) {
|
2021-05-19 15:51:25 +02:00
|
|
|
|
followSymbolData->callback(followSymbolData->defLink);
|
|
|
|
|
|
followSymbolData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 2: Get all possible overrides via "Go to Implementation".
|
|
|
|
|
|
// Note that we have to do this for all member function calls, because
|
|
|
|
|
|
// we cannot tell here whether the member function is virtual.
|
2021-05-28 09:40:53 +02:00
|
|
|
|
followSymbolData->allLinks << followSymbolData->defLink;
|
|
|
|
|
|
sendGotoImplementationRequest(followSymbolData->defLink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::Private::sendGotoImplementationRequest(const Utils::Link &link)
|
|
|
|
|
|
{
|
2021-06-28 17:09:06 +02:00
|
|
|
|
if (!q->documentForFilePath(link.targetFilePath)
|
|
|
|
|
|
&& followSymbolData->openedFiles.insert(link.targetFilePath).second) {
|
|
|
|
|
|
q->openExtraFile(link.targetFilePath);
|
|
|
|
|
|
}
|
2021-05-28 09:40:53 +02:00
|
|
|
|
const Position position(link.targetLine - 1, link.targetColumn);
|
|
|
|
|
|
const TextDocumentIdentifier documentId(DocumentUri::fromFilePath(link.targetFilePath));
|
|
|
|
|
|
GotoImplementationRequest req(TextDocumentPositionParams(documentId, position));
|
|
|
|
|
|
req.setResponseCallback([this, id = followSymbolData->id, reqId = req.id()](
|
2021-05-19 15:51:25 +02:00
|
|
|
|
const GotoImplementationRequest::Response &response) {
|
2021-05-28 09:40:53 +02:00
|
|
|
|
qCDebug(clangdLog) << "received go to implementation reply";
|
2021-05-19 15:51:25 +02:00
|
|
|
|
if (!followSymbolData || id != followSymbolData->id)
|
|
|
|
|
|
return;
|
2021-05-28 09:40:53 +02:00
|
|
|
|
followSymbolData->pendingGotoImplRequests.removeOne(reqId);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
handleGotoImplementationResult(response);
|
|
|
|
|
|
});
|
2021-07-08 12:23:12 +02:00
|
|
|
|
q->sendContent(req, SendDocUpdates::Ignore);
|
2021-05-28 09:40:53 +02:00
|
|
|
|
followSymbolData->pendingGotoImplRequests << req.id();
|
|
|
|
|
|
qCDebug(clangdLog) << "sending go to implementation request" << link.targetLine;
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::Private::handleGotoImplementationResult(
|
|
|
|
|
|
const GotoImplementationRequest::Response &response)
|
|
|
|
|
|
{
|
2021-05-28 09:40:53 +02:00
|
|
|
|
if (const Utils::optional<GotoResult> &result = response.result()) {
|
|
|
|
|
|
QList<Utils::Link> newLinks;
|
|
|
|
|
|
if (const auto ploc = Utils::get_if<Location>(&*result))
|
|
|
|
|
|
newLinks = {ploc->toLink()};
|
|
|
|
|
|
if (const auto plloc = Utils::get_if<QList<Location>>(&*result))
|
|
|
|
|
|
newLinks = Utils::transform(*plloc, &Location::toLink);
|
|
|
|
|
|
for (const Utils::Link &link : qAsConst(newLinks)) {
|
|
|
|
|
|
if (!followSymbolData->allLinks.contains(link)) {
|
|
|
|
|
|
followSymbolData->allLinks << link;
|
|
|
|
|
|
|
|
|
|
|
|
// We must do this recursively, because clangd reports only the first
|
|
|
|
|
|
// level of overrides.
|
|
|
|
|
|
sendGotoImplementationRequest(link);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-28 09:40:53 +02:00
|
|
|
|
// We didn't find any further candidates, so jump to the original definition link.
|
|
|
|
|
|
if (followSymbolData->allLinks.size() == 1
|
|
|
|
|
|
&& followSymbolData->pendingGotoImplRequests.isEmpty()) {
|
|
|
|
|
|
followSymbolData->callback(followSymbolData->allLinks.first());
|
2021-05-19 15:51:25 +02:00
|
|
|
|
followSymbolData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-28 09:40:53 +02:00
|
|
|
|
// As soon as we know that there is more than one candidate, we start the code assist
|
|
|
|
|
|
// procedure, to let the user know that things are happening.
|
|
|
|
|
|
if (followSymbolData->allLinks.size() > 1 && !followSymbolData->virtualFuncAssistProcessor
|
2021-09-01 18:08:54 +02:00
|
|
|
|
&& followSymbolData->editorWidget) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
followSymbolData->editorWidget->invokeTextEditorWidgetAssist(FollowSymbol,
|
|
|
|
|
|
&followSymbolData->virtualFuncAssistProvider);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-28 09:40:53 +02:00
|
|
|
|
if (!followSymbolData->pendingGotoImplRequests.isEmpty())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// Step 3: We are done looking for overrides, and we found at least one.
|
|
|
|
|
|
// Make a symbol info request for each link to get the class names.
|
|
|
|
|
|
// Also get the AST for the base declaration, so we can find out whether it's
|
|
|
|
|
|
// pure virtual and mark it accordingly.
|
2021-05-28 13:12:00 +02:00
|
|
|
|
// In addition, we need to follow all override links, because for these, clangd
|
|
|
|
|
|
// gives us the declaration instead of the definition.
|
2021-05-28 09:40:53 +02:00
|
|
|
|
for (const Utils::Link &link : qAsConst(followSymbolData->allLinks)) {
|
2021-05-19 15:51:25 +02:00
|
|
|
|
if (!q->documentForFilePath(link.targetFilePath)
|
|
|
|
|
|
&& followSymbolData->openedFiles.insert(link.targetFilePath).second) {
|
|
|
|
|
|
q->openExtraFile(link.targetFilePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
const TextDocumentIdentifier doc(DocumentUri::fromFilePath(link.targetFilePath));
|
|
|
|
|
|
const Position pos(link.targetLine - 1, link.targetColumn);
|
2021-05-28 13:12:00 +02:00
|
|
|
|
const TextDocumentPositionParams params(doc, pos);
|
|
|
|
|
|
SymbolInfoRequest symReq(params);
|
|
|
|
|
|
symReq.setResponseCallback([this, link, id = followSymbolData->id, reqId = symReq.id()](
|
2021-05-19 15:51:25 +02:00
|
|
|
|
const SymbolInfoRequest::Response &response) {
|
|
|
|
|
|
qCDebug(clangdLog) << "handling symbol info reply"
|
|
|
|
|
|
<< link.targetFilePath.toUserOutput() << link.targetLine;
|
|
|
|
|
|
if (!followSymbolData || id != followSymbolData->id)
|
|
|
|
|
|
return;
|
|
|
|
|
|
if (const auto result = response.result()) {
|
|
|
|
|
|
if (const auto list = Utils::get_if<QList<SymbolDetails>>(&result.value())) {
|
|
|
|
|
|
if (!list->isEmpty()) {
|
|
|
|
|
|
// According to the documentation, we should receive a single
|
|
|
|
|
|
// object here, but it's a list. No idea what it means if there's
|
|
|
|
|
|
// more than one entry. We choose the first one.
|
|
|
|
|
|
const SymbolDetails &sd = list->first();
|
|
|
|
|
|
followSymbolData->symbolsToDisplay << qMakePair(sd.containerName()
|
|
|
|
|
|
+ sd.name(), link);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
followSymbolData->pendingSymbolInfoRequests.removeOne(reqId);
|
2021-05-31 11:21:30 +02:00
|
|
|
|
followSymbolData->virtualFuncAssistProcessor->update();
|
2021-05-27 16:32:24 +02:00
|
|
|
|
if (followSymbolData->pendingSymbolInfoRequests.isEmpty()
|
2021-05-28 13:12:00 +02:00
|
|
|
|
&& followSymbolData->pendingGotoDefRequests.isEmpty()
|
2021-05-27 16:32:24 +02:00
|
|
|
|
&& followSymbolData->defLinkNode.isValid()) {
|
2021-05-19 15:51:25 +02:00
|
|
|
|
handleDocumentInfoResults();
|
2021-05-27 16:32:24 +02:00
|
|
|
|
}
|
2021-05-19 15:51:25 +02:00
|
|
|
|
});
|
2021-05-28 13:12:00 +02:00
|
|
|
|
followSymbolData->pendingSymbolInfoRequests << symReq.id();
|
2021-05-19 15:51:25 +02:00
|
|
|
|
qCDebug(clangdLog) << "sending symbol info request";
|
2021-07-08 12:23:12 +02:00
|
|
|
|
q->sendContent(symReq, SendDocUpdates::Ignore);
|
2021-05-28 13:12:00 +02:00
|
|
|
|
|
|
|
|
|
|
if (link == followSymbolData->defLink)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
GotoDefinitionRequest defReq(params);
|
|
|
|
|
|
defReq.setResponseCallback([this, link, id = followSymbolData->id, reqId = defReq.id()]
|
|
|
|
|
|
(const GotoDefinitionRequest::Response &response) {
|
|
|
|
|
|
qCDebug(clangdLog) << "handling additional go to definition reply for"
|
|
|
|
|
|
<< link.targetFilePath << link.targetLine;
|
|
|
|
|
|
if (!followSymbolData || id != followSymbolData->id)
|
|
|
|
|
|
return;
|
|
|
|
|
|
Utils::Link newLink;
|
|
|
|
|
|
if (Utils::optional<GotoResult> _result = response.result()) {
|
|
|
|
|
|
const GotoResult result = _result.value();
|
|
|
|
|
|
if (const auto ploc = Utils::get_if<Location>(&result)) {
|
|
|
|
|
|
newLink = ploc->toLink();
|
|
|
|
|
|
} else if (const auto plloc = Utils::get_if<QList<Location>>(&result)) {
|
|
|
|
|
|
if (!plloc->isEmpty())
|
|
|
|
|
|
newLink = plloc->value(0).toLink();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
qCDebug(clangdLog) << "def link is" << newLink.targetFilePath << newLink.targetLine;
|
|
|
|
|
|
followSymbolData->declDefMap.insert(link, newLink);
|
|
|
|
|
|
followSymbolData->pendingGotoDefRequests.removeOne(reqId);
|
|
|
|
|
|
if (followSymbolData->pendingSymbolInfoRequests.isEmpty()
|
|
|
|
|
|
&& followSymbolData->pendingGotoDefRequests.isEmpty()
|
|
|
|
|
|
&& followSymbolData->defLinkNode.isValid()) {
|
|
|
|
|
|
handleDocumentInfoResults();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
followSymbolData->pendingGotoDefRequests << defReq.id();
|
|
|
|
|
|
qCDebug(clangdLog) << "sending additional go to definition request"
|
|
|
|
|
|
<< link.targetFilePath << link.targetLine;
|
2021-07-08 12:23:12 +02:00
|
|
|
|
q->sendContent(defReq, SendDocUpdates::Ignore);
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
2021-05-27 16:32:24 +02:00
|
|
|
|
|
2021-09-27 16:24:37 +02:00
|
|
|
|
const Utils::FilePath defLinkFilePath = followSymbolData->defLink.targetFilePath;
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const TextDocument * const defLinkDoc = q->documentForFilePath(defLinkFilePath);
|
2021-09-27 16:24:37 +02:00
|
|
|
|
const auto defLinkDocVariant = defLinkDoc ? TextDocOrFile(defLinkDoc)
|
|
|
|
|
|
: TextDocOrFile(defLinkFilePath);
|
2021-05-27 16:32:24 +02:00
|
|
|
|
const Position defLinkPos(followSymbolData->defLink.targetLine - 1,
|
|
|
|
|
|
followSymbolData->defLink.targetColumn);
|
2021-10-07 10:58:54 +02:00
|
|
|
|
const auto astHandler = [this, id = followSymbolData->id]
|
2021-09-27 16:24:37 +02:00
|
|
|
|
(const AstNode &ast, const MessageId &) {
|
2021-05-27 16:32:24 +02:00
|
|
|
|
qCDebug(clangdLog) << "received ast response for def link";
|
|
|
|
|
|
if (!followSymbolData || followSymbolData->id != id)
|
|
|
|
|
|
return;
|
2021-10-07 10:58:54 +02:00
|
|
|
|
followSymbolData->defLinkNode = ast;
|
2021-05-28 13:12:00 +02:00
|
|
|
|
if (followSymbolData->pendingSymbolInfoRequests.isEmpty()
|
|
|
|
|
|
&& followSymbolData->pendingGotoDefRequests.isEmpty()) {
|
2021-05-27 16:32:24 +02:00
|
|
|
|
handleDocumentInfoResults();
|
2021-05-28 13:12:00 +02:00
|
|
|
|
}
|
2021-09-27 16:24:37 +02:00
|
|
|
|
};
|
2021-10-07 10:58:54 +02:00
|
|
|
|
getAndHandleAst(defLinkDocVariant, astHandler, AstCallbackMode::AlwaysAsync,
|
|
|
|
|
|
Range(defLinkPos, defLinkPos));
|
2021-05-19 15:51:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::Private::handleDocumentInfoResults()
|
|
|
|
|
|
{
|
|
|
|
|
|
followSymbolData->closeTempDocuments();
|
|
|
|
|
|
|
|
|
|
|
|
// If something went wrong, we just follow the original link.
|
|
|
|
|
|
if (followSymbolData->symbolsToDisplay.isEmpty()) {
|
|
|
|
|
|
followSymbolData->callback(followSymbolData->defLink);
|
|
|
|
|
|
followSymbolData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (followSymbolData->symbolsToDisplay.size() == 1) {
|
|
|
|
|
|
followSymbolData->callback(followSymbolData->symbolsToDisplay.first().second);
|
|
|
|
|
|
followSymbolData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
QTC_ASSERT(followSymbolData->virtualFuncAssistProcessor
|
|
|
|
|
|
&& followSymbolData->virtualFuncAssistProcessor->running(),
|
|
|
|
|
|
followSymbolData.reset(); return);
|
|
|
|
|
|
followSymbolData->virtualFuncAssistProcessor->finalize();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-31 15:57:44 +02:00
|
|
|
|
void ClangdClient::Private::handleDeclDefSwitchReplies()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!switchDeclDefData->document) {
|
|
|
|
|
|
switchDeclDefData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Find the function declaration or definition associated with the cursor.
|
|
|
|
|
|
// For instance, the cursor could be somwehere inside a function body or
|
|
|
|
|
|
// on a function return type, or ...
|
2021-07-15 11:39:56 +02:00
|
|
|
|
if (clangdLogAst().isDebugEnabled())
|
2021-05-31 15:57:44 +02:00
|
|
|
|
switchDeclDefData->ast->print(0);
|
|
|
|
|
|
const Utils::optional<AstNode> functionNode = switchDeclDefData->getFunctionNode();
|
|
|
|
|
|
if (!functionNode) {
|
|
|
|
|
|
switchDeclDefData.reset();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unfortunately, the AST does not contain the location of the actual function name symbol,
|
|
|
|
|
|
// so we have to look for it in the document symbols.
|
|
|
|
|
|
const QTextCursor funcNameCursor = switchDeclDefData->cursorForFunctionName(*functionNode);
|
|
|
|
|
|
if (!funcNameCursor.isNull()) {
|
|
|
|
|
|
q->followSymbol(switchDeclDefData->document.data(), funcNameCursor,
|
|
|
|
|
|
switchDeclDefData->editorWidget, std::move(switchDeclDefData->callback),
|
|
|
|
|
|
true, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
switchDeclDefData.reset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-12-02 13:18:02 +01:00
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-01 18:14:12 +02:00
|
|
|
|
QString ClangdClient::Private::searchTermFromCursor(const QTextCursor &cursor) const
|
|
|
|
|
|
{
|
|
|
|
|
|
QTextCursor termCursor(cursor);
|
|
|
|
|
|
termCursor.select(QTextCursor::WordUnderCursor);
|
|
|
|
|
|
return termCursor.selectedText();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-12-02 13:18:02 +01:00
|
|
|
|
// 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;
|
2021-12-03 12:54:21 +01:00
|
|
|
|
const QList<AST *> builtinAstPath = ASTPath(cppDoc)(cursor);
|
2021-12-07 11:40:13 +01:00
|
|
|
|
const TranslationUnit * const tu = cppDoc->translationUnit();
|
|
|
|
|
|
const auto posForToken = [doc, tu](int tok) {
|
|
|
|
|
|
int line, column;
|
|
|
|
|
|
tu->getTokenPosition(tok, &line, &column);
|
|
|
|
|
|
return Utils::Text::positionInText(doc->document(), line, column);
|
|
|
|
|
|
};
|
|
|
|
|
|
const auto leftMovedCursor = [cursor] {
|
|
|
|
|
|
QTextCursor c = cursor;
|
|
|
|
|
|
c.setPosition(cursor.position() - 1);
|
|
|
|
|
|
return c;
|
|
|
|
|
|
};
|
2021-12-03 12:54:21 +01:00
|
|
|
|
for (auto it = builtinAstPath.rbegin(); it != builtinAstPath.rend(); ++it) {
|
2021-12-07 11:40:13 +01:00
|
|
|
|
|
|
|
|
|
|
// s|.x or s|->x
|
|
|
|
|
|
if (const MemberAccessAST * const memberAccess = (*it)->asMemberAccess()) {
|
|
|
|
|
|
switch (tu->tokenAt(memberAccess->access_token).kind()) {
|
|
|
|
|
|
case T_DOT:
|
|
|
|
|
|
break;
|
|
|
|
|
|
case T_ARROW: {
|
|
|
|
|
|
const Utils::optional<AstNode> clangdAst = astCache.get(doc);
|
|
|
|
|
|
if (!clangdAst)
|
2021-12-03 12:54:21 +01:00
|
|
|
|
return cursor;
|
2021-12-07 11:40:13 +01:00
|
|
|
|
const QList<AstNode> clangdAstPath = getAstPath(*clangdAst, Range(cursor));
|
|
|
|
|
|
for (auto it = clangdAstPath.rbegin(); it != clangdAstPath.rend(); ++it) {
|
|
|
|
|
|
if (it->detailIs("operator->") && it->arcanaContains("CXXMethod"))
|
|
|
|
|
|
return cursor;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2021-12-03 12:54:21 +01:00
|
|
|
|
}
|
2021-12-07 11:40:13 +01:00
|
|
|
|
default:
|
|
|
|
|
|
return cursor;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (posForToken(memberAccess->access_token) != cursor.position())
|
|
|
|
|
|
return cursor;
|
|
|
|
|
|
return leftMovedCursor();
|
2021-12-03 12:54:21 +01:00
|
|
|
|
}
|
2021-12-07 11:40:13 +01:00
|
|
|
|
|
|
|
|
|
|
// f(arg1|, arg2)
|
|
|
|
|
|
if (const CallAST *const callAst = (*it)->asCall()) {
|
|
|
|
|
|
const int tok = builtinAstPath.last()->lastToken();
|
|
|
|
|
|
if (posForToken(tok) != cursor.position())
|
|
|
|
|
|
return cursor;
|
|
|
|
|
|
if (tok == callAst->rparen_token)
|
|
|
|
|
|
return leftMovedCursor();
|
|
|
|
|
|
if (tu->tokenKind(tok) != T_COMMA)
|
|
|
|
|
|
return cursor;
|
|
|
|
|
|
|
|
|
|
|
|
// Guard against edge case of overloaded comma operator.
|
|
|
|
|
|
for (auto list = callAst->expression_list; list; list = list->next) {
|
|
|
|
|
|
if (list->value->lastToken() == tok)
|
|
|
|
|
|
return leftMovedCursor();
|
|
|
|
|
|
}
|
2021-12-02 13:18:02 +01:00
|
|
|
|
return cursor;
|
2021-12-03 12:54:21 +01:00
|
|
|
|
}
|
2022-01-26 12:40:12 +01:00
|
|
|
|
|
|
|
|
|
|
// ~My|Class
|
|
|
|
|
|
if (const DestructorNameAST * const destrAst = (*it)->asDestructorName()) {
|
|
|
|
|
|
QTextCursor c = cursor;
|
|
|
|
|
|
c.setPosition(posForToken(destrAst->tilde_token));
|
|
|
|
|
|
return c;
|
|
|
|
|
|
}
|
2021-12-02 13:18:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
return cursor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-04 12:40:26 +02:00
|
|
|
|
void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const QString &fqn,
|
|
|
|
|
|
HelpItem::Category category,
|
|
|
|
|
|
const QString &type)
|
|
|
|
|
|
{
|
|
|
|
|
|
QStringList helpIds;
|
|
|
|
|
|
QString mark;
|
|
|
|
|
|
if (!fqn.isEmpty()) {
|
|
|
|
|
|
helpIds << fqn;
|
|
|
|
|
|
int sepSearchStart = 0;
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
sepSearchStart = fqn.indexOf("::", sepSearchStart);
|
|
|
|
|
|
if (sepSearchStart == -1)
|
|
|
|
|
|
break;
|
|
|
|
|
|
sepSearchStart += 2;
|
|
|
|
|
|
helpIds << fqn.mid(sepSearchStart);
|
|
|
|
|
|
}
|
|
|
|
|
|
mark = helpIds.last();
|
|
|
|
|
|
if (category == HelpItem::Function)
|
|
|
|
|
|
mark += type.mid(type.indexOf('('));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (category == HelpItem::Enum && !type.isEmpty())
|
|
|
|
|
|
mark = type;
|
|
|
|
|
|
|
|
|
|
|
|
HelpItem helpItem(helpIds, mark, category);
|
|
|
|
|
|
if (isTesting)
|
|
|
|
|
|
emit q->helpItemGathered(helpItem);
|
|
|
|
|
|
else
|
|
|
|
|
|
q->hoverHandler()->setHelpItem(token, helpItem);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-04 14:25:35 +02:00
|
|
|
|
class ExtraHighlightingResultsCollector
|
2021-06-09 09:47:26 +02:00
|
|
|
|
{
|
2021-10-04 14:25:35 +02:00
|
|
|
|
public:
|
|
|
|
|
|
ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future,
|
2021-10-14 12:47:24 +02:00
|
|
|
|
HighlightingResults &results,
|
|
|
|
|
|
const Utils::FilePath &filePath, const AstNode &ast,
|
2021-10-05 15:22:09 +02:00
|
|
|
|
const QTextDocument *doc, const QString &docContent);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
|
2021-10-04 14:25:35 +02:00
|
|
|
|
void collect();
|
|
|
|
|
|
private:
|
|
|
|
|
|
static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2);
|
|
|
|
|
|
static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0);
|
|
|
|
|
|
int posForNodeStart(const AstNode &node) const;
|
|
|
|
|
|
int posForNodeEnd(const AstNode &node) const;
|
|
|
|
|
|
void insertResult(const HighlightingResult &result);
|
2022-02-25 10:26:48 +01:00
|
|
|
|
void insertResult(const AstNode &node, TextStyle style);
|
2021-10-04 14:25:35 +02:00
|
|
|
|
void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2);
|
|
|
|
|
|
void setResultPosFromRange(HighlightingResult &result, const Range &range);
|
|
|
|
|
|
void collectFromNode(const AstNode &node);
|
|
|
|
|
|
void visitNode(const AstNode&node);
|
|
|
|
|
|
|
|
|
|
|
|
QFutureInterface<HighlightingResult> &m_future;
|
|
|
|
|
|
HighlightingResults &m_results;
|
2021-10-14 12:47:24 +02:00
|
|
|
|
const Utils::FilePath m_filePath;
|
2021-10-04 14:25:35 +02:00
|
|
|
|
const AstNode &m_ast;
|
|
|
|
|
|
const QTextDocument * const m_doc;
|
|
|
|
|
|
const QString &m_docContent;
|
2022-04-14 16:56:06 +02:00
|
|
|
|
AstNode::FileStatus m_currentFileStatus = AstNode::FileStatus::Unknown;
|
2021-10-04 14:25:35 +02:00
|
|
|
|
};
|
2021-06-09 09:47:26 +02:00
|
|
|
|
|
|
|
|
|
|
// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled,
|
|
|
|
|
|
// and not even in a consistent manner. We don't want this, so we have to clean up here.
|
2021-09-16 18:13:25 +02:00
|
|
|
|
// But note that we require this behavior, as otherwise we would not be able to grey out
|
2022-02-09 12:08:04 +01:00
|
|
|
|
// e.g. empty lines after an #ifdef, due to the lack of symbols.
|
2021-10-05 15:22:09 +02:00
|
|
|
|
static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc,
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const QString &docContent)
|
2021-06-09 09:47:26 +02:00
|
|
|
|
{
|
2021-09-28 13:21:59 +02:00
|
|
|
|
QList<BlockRange> ifdefedOutRanges;
|
2021-09-16 18:13:25 +02:00
|
|
|
|
int rangeStartPos = -1;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
for (auto it = results.begin(); it != results.end();) {
|
2021-09-16 18:13:25 +02:00
|
|
|
|
const bool wasIfdefedOut = rangeStartPos != -1;
|
2022-02-10 15:11:18 +01:00
|
|
|
|
const bool isIfDefedOut = it->textStyles.mainStyle == C_DISABLED_CODE;
|
|
|
|
|
|
if (!isIfDefedOut) {
|
2021-09-16 18:13:25 +02:00
|
|
|
|
if (wasIfdefedOut) {
|
|
|
|
|
|
const QTextBlock block = doc->findBlockByNumber(it->line - 1);
|
2021-09-28 13:21:59 +02:00
|
|
|
|
ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
|
2021-09-16 18:13:25 +02:00
|
|
|
|
rangeStartPos = -1;
|
|
|
|
|
|
}
|
2021-06-09 09:47:26 +02:00
|
|
|
|
++it;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-16 18:13:25 +02:00
|
|
|
|
if (!wasIfdefedOut)
|
|
|
|
|
|
rangeStartPos = doc->findBlockByNumber(it->line - 1).position();
|
2022-02-10 15:11:18 +01:00
|
|
|
|
|
|
|
|
|
|
// Does the current line contain a potential "ifdefed-out switcher"?
|
|
|
|
|
|
// If not, no state change is possible and we continue with the next line.
|
|
|
|
|
|
const auto isPreprocessorControlStatement = [&] {
|
|
|
|
|
|
const int pos = Utils::Text::positionInText(doc, it->line, it->column);
|
|
|
|
|
|
const QStringView content = subViewLen(docContent, pos, it->length).trimmed();
|
|
|
|
|
|
if (content.isEmpty() || content.first() != '#')
|
|
|
|
|
|
return false;
|
|
|
|
|
|
int offset = 1;
|
|
|
|
|
|
while (offset < content.size() && content.at(offset).isSpace())
|
|
|
|
|
|
++offset;
|
|
|
|
|
|
if (offset == content.size())
|
|
|
|
|
|
return false;
|
|
|
|
|
|
const QStringView ppDirective = content.mid(offset);
|
|
|
|
|
|
return ppDirective.startsWith(QLatin1String("if"))
|
|
|
|
|
|
|| ppDirective.startsWith(QLatin1String("elif"))
|
|
|
|
|
|
|| ppDirective.startsWith(QLatin1String("else"))
|
|
|
|
|
|
|| ppDirective.startsWith(QLatin1String("endif"));
|
|
|
|
|
|
};
|
|
|
|
|
|
if (!isPreprocessorControlStatement()) {
|
|
|
|
|
|
++it;
|
|
|
|
|
|
continue;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-16 18:13:25 +02:00
|
|
|
|
if (!wasIfdefedOut) {
|
2021-06-09 09:47:26 +02:00
|
|
|
|
// The #if or #else that starts disabled code should not be disabled.
|
2021-09-16 18:13:25 +02:00
|
|
|
|
const QTextBlock nextBlock = doc->findBlockByNumber(it->line);
|
|
|
|
|
|
rangeStartPos = nextBlock.isValid() ? nextBlock.position() : -1;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
it = results.erase(it);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-16 18:13:25 +02:00
|
|
|
|
if (wasIfdefedOut && (it + 1 == results.end()
|
2022-02-10 10:15:54 +01:00
|
|
|
|
|| (it + 1)->textStyles.mainStyle != C_DISABLED_CODE
|
|
|
|
|
|
|| (it + 1)->line != it->line + 1)) {
|
2021-06-09 09:47:26 +02:00
|
|
|
|
// The #else or #endif that ends disabled code should not be disabled.
|
2021-09-16 18:13:25 +02:00
|
|
|
|
const QTextBlock block = doc->findBlockByNumber(it->line - 1);
|
2021-09-28 13:21:59 +02:00
|
|
|
|
ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
|
2021-09-16 18:13:25 +02:00
|
|
|
|
rangeStartPos = -1;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
it = results.erase(it);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
++it;
|
|
|
|
|
|
}
|
2021-09-16 18:13:25 +02:00
|
|
|
|
|
|
|
|
|
|
if (rangeStartPos != -1)
|
2021-09-28 13:21:59 +02:00
|
|
|
|
ifdefedOutRanges << BlockRange(rangeStartPos, doc->characterCount());
|
2021-09-16 18:13:25 +02:00
|
|
|
|
|
2022-02-10 15:11:18 +01:00
|
|
|
|
qCDebug(clangdLogHighlight) << "found" << ifdefedOutRanges.size() << "ifdefed-out ranges";
|
|
|
|
|
|
if (clangdLogHighlight().isDebugEnabled()) {
|
|
|
|
|
|
for (const BlockRange &r : qAsConst(ifdefedOutRanges))
|
|
|
|
|
|
qCDebug(clangdLogHighlight) << r.first() << r.last();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-16 18:13:25 +02:00
|
|
|
|
return ifdefedOutRanges;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
2021-10-14 12:47:24 +02:00
|
|
|
|
const Utils::FilePath &filePath,
|
2021-06-09 09:47:26 +02:00
|
|
|
|
const QList<ExpandedSemanticToken> &tokens,
|
2021-09-16 18:13:25 +02:00
|
|
|
|
const QString &docContents, const AstNode &ast,
|
2021-12-03 11:19:59 +01:00
|
|
|
|
const QPointer<TextDocument> &textDocument,
|
2022-03-04 19:42:46 +01:00
|
|
|
|
int docRevision, const QVersionNumber &clangdVersion,
|
|
|
|
|
|
const TaskTimer &taskTimer)
|
2021-06-09 09:47:26 +02:00
|
|
|
|
{
|
2022-03-04 19:42:46 +01:00
|
|
|
|
ThreadedSubtaskTimer t("highlighting", taskTimer);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (future.isCanceled()) {
|
|
|
|
|
|
future.reportFinished();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-05 15:22:09 +02:00
|
|
|
|
const QTextDocument doc(docContents);
|
2021-10-05 12:27:36 +02:00
|
|
|
|
const auto tokenRange = [&doc](const ExpandedSemanticToken &token) {
|
|
|
|
|
|
const Position startPos(token.line - 1, token.column - 1);
|
|
|
|
|
|
const Position endPos = startPos.withOffset(token.length, &doc);
|
|
|
|
|
|
return Range(startPos, endPos);
|
|
|
|
|
|
};
|
2021-12-01 19:19:09 +01:00
|
|
|
|
const auto isOutputParameter = [&ast, &tokenRange](const ExpandedSemanticToken &token) {
|
2021-12-15 18:47:16 +01:00
|
|
|
|
if (token.modifiers.contains(QLatin1String("usedAsMutableReference")))
|
2021-09-15 12:39:04 +02:00
|
|
|
|
return true;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (token.type != "variable" && token.type != "property" && token.type != "parameter")
|
|
|
|
|
|
return false;
|
2021-11-03 18:20:13 +01:00
|
|
|
|
const Range range = tokenRange(token);
|
|
|
|
|
|
const QList<AstNode> path = getAstPath(ast, range);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (path.size() < 2)
|
|
|
|
|
|
return false;
|
2022-02-15 11:05:28 +01:00
|
|
|
|
if (token.type == "property"
|
|
|
|
|
|
&& (path.rbegin()->kind() == "MemberInitializer"
|
|
|
|
|
|
|| path.rbegin()->kind() == "CXXConstruct")) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2022-02-22 15:27:47 +01:00
|
|
|
|
if (path.rbegin()->hasConstType())
|
|
|
|
|
|
return false;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
for (auto it = path.rbegin() + 1; it != path.rend(); ++it) {
|
2022-04-04 12:01:39 +02:00
|
|
|
|
if (it->kind() == "CXXConstruct" || it->kind() == "MemberInitializer")
|
2021-11-03 18:20:13 +01:00
|
|
|
|
return true;
|
2022-04-04 12:01:39 +02:00
|
|
|
|
|
|
|
|
|
|
if (it->kind() == "Call") {
|
2022-04-12 11:12:20 +02:00
|
|
|
|
// The first child is e.g. a called lambda or an object on which
|
|
|
|
|
|
// the call happens, and should not be highlighted as an output argument.
|
2022-04-12 11:50:12 +02:00
|
|
|
|
// If the call is not fully resolved (as in templates), we don't
|
|
|
|
|
|
// know whether the argument is passed as const or not.
|
2022-04-12 12:59:40 +02:00
|
|
|
|
if (it->arcanaContains("dependent type"))
|
|
|
|
|
|
return false;
|
2022-04-04 12:01:39 +02:00
|
|
|
|
const QList<AstNode> children = it->children().value_or(QList<AstNode>());
|
2022-04-12 11:50:12 +02:00
|
|
|
|
return children.isEmpty()
|
|
|
|
|
|
|| (children.first().range() != (it - 1)->range()
|
|
|
|
|
|
&& children.first().kind() != "UnresolvedLookup");
|
2021-11-03 18:20:13 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// The token should get marked for e.g. lambdas, but not for assignment operators,
|
|
|
|
|
|
// where the user sees that it's being written.
|
|
|
|
|
|
if (it->kind() == "CXXOperatorCall") {
|
|
|
|
|
|
const QList<AstNode> children = it->children().value_or(QList<AstNode>());
|
2022-02-11 13:08:20 +01:00
|
|
|
|
|
|
|
|
|
|
// Child 1 is the call itself, Child 2 is the named entity on which the call happens
|
|
|
|
|
|
// (a lambda or a class instance), after that follow the actual call arguments.
|
2021-11-03 18:20:13 +01:00
|
|
|
|
if (children.size() < 2)
|
|
|
|
|
|
return false;
|
2022-02-11 13:08:20 +01:00
|
|
|
|
|
|
|
|
|
|
// The call itself is never modifiable.
|
|
|
|
|
|
if (children.first().range() == range)
|
2021-11-03 18:20:13 +01:00
|
|
|
|
return false;
|
2022-02-11 13:08:20 +01:00
|
|
|
|
|
|
|
|
|
|
// The callable is never displayed as an output parameter.
|
|
|
|
|
|
// TODO: A good argument can be made to display objects on which a non-const
|
|
|
|
|
|
// operator or function is called as output parameters.
|
2022-04-08 16:29:36 +02:00
|
|
|
|
if (children.at(1).range().contains(range))
|
2021-11-03 18:20:13 +01:00
|
|
|
|
return false;
|
2022-02-11 13:08:20 +01:00
|
|
|
|
|
2021-11-03 18:20:13 +01:00
|
|
|
|
QList<AstNode> firstChildTree{children.first()};
|
|
|
|
|
|
while (!firstChildTree.isEmpty()) {
|
|
|
|
|
|
const AstNode n = firstChildTree.takeFirst();
|
|
|
|
|
|
const QString detail = n.detail().value_or(QString());
|
2021-11-22 14:33:51 +01:00
|
|
|
|
if (detail.startsWith("operator")) {
|
2022-02-22 14:17:31 +01:00
|
|
|
|
return !detail.contains('=')
|
|
|
|
|
|
&& !detail.contains("++") && !detail.contains("--")
|
2022-02-28 16:02:59 +01:00
|
|
|
|
&& !detail.contains("<<") && !detail.contains(">>")
|
|
|
|
|
|
&& !detail.contains("*");
|
2021-11-22 14:33:51 +01:00
|
|
|
|
}
|
2021-11-03 18:20:13 +01:00
|
|
|
|
firstChildTree << n.children().value_or(QList<AstNode>());
|
|
|
|
|
|
}
|
2021-06-09 09:47:26 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2021-11-03 18:20:13 +01:00
|
|
|
|
|
2021-11-10 15:35:26 +01:00
|
|
|
|
if (it->kind() == "Lambda")
|
|
|
|
|
|
return false;
|
2022-04-12 12:51:54 +02:00
|
|
|
|
if (it->kind() == "BinaryOperator")
|
|
|
|
|
|
return false;
|
2021-12-09 16:01:46 +01:00
|
|
|
|
if (it->hasConstType())
|
2021-06-09 09:47:26 +02:00
|
|
|
|
return false;
|
2022-02-28 17:00:27 +01:00
|
|
|
|
|
|
|
|
|
|
if (it->kind() == "CXXMemberCall") {
|
|
|
|
|
|
if (it == path.rbegin())
|
|
|
|
|
|
return false;
|
|
|
|
|
|
const QList<AstNode> children = it->children().value_or(QList<AstNode>());
|
|
|
|
|
|
QTC_ASSERT(!children.isEmpty(), return false);
|
|
|
|
|
|
|
|
|
|
|
|
// The called object is never displayed as an output parameter.
|
|
|
|
|
|
// TODO: A good argument can be made to display objects on which a non-const
|
|
|
|
|
|
// operator or function is called as output parameters.
|
|
|
|
|
|
return (it - 1)->range() != children.first().range();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-16 13:37:14 +02:00
|
|
|
|
if (it->kind() == "Member" && it->arcanaContains("(")
|
|
|
|
|
|
&& !it->arcanaContains("bound member function type")) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-10-05 15:22:09 +02:00
|
|
|
|
const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult
|
2021-10-05 12:27:36 +02:00
|
|
|
|
= [&ast, &isOutputParameter, &clangdVersion, &tokenRange]
|
|
|
|
|
|
(const ExpandedSemanticToken &token) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
TextStyles styles;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (token.type == "variable") {
|
2021-12-15 18:47:16 +01:00
|
|
|
|
if (token.modifiers.contains(QLatin1String("functionScope"))) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_LOCAL;
|
2021-12-15 18:47:16 +01:00
|
|
|
|
} else if (token.modifiers.contains(QLatin1String("classScope"))) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_FIELD;
|
2021-12-15 18:47:16 +01:00
|
|
|
|
} else if (token.modifiers.contains(QLatin1String("fileScope"))
|
|
|
|
|
|
|| token.modifiers.contains(QLatin1String("globalScope"))) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_GLOBAL;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
} else if (token.type == "function" || token.type == "method") {
|
2021-12-15 18:47:16 +01:00
|
|
|
|
styles.mainStyle = token.modifiers.contains(QLatin1String("virtual"))
|
|
|
|
|
|
? C_VIRTUAL_METHOD : C_FUNCTION;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (ast.isValid()) {
|
2021-10-05 12:27:36 +02:00
|
|
|
|
const QList<AstNode> path = getAstPath(ast, tokenRange(token));
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (path.length() > 1) {
|
|
|
|
|
|
const AstNode declNode = path.at(path.length() - 2);
|
|
|
|
|
|
if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") {
|
2021-09-28 13:28:05 +02:00
|
|
|
|
if (clangdVersion < QVersionNumber(14)
|
|
|
|
|
|
&& declNode.arcanaContains("' virtual")) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_VIRTUAL_METHOD;
|
2021-09-28 13:28:05 +02:00
|
|
|
|
}
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (declNode.hasChildWithRole("statement"))
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (token.type == "class") {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_TYPE;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
|
|
|
|
|
|
// clang hardly ever differentiates between constructors and the associated class,
|
|
|
|
|
|
// whereas we highlight constructors as functions.
|
|
|
|
|
|
if (ast.isValid()) {
|
2021-10-05 12:27:36 +02:00
|
|
|
|
const QList<AstNode> path = getAstPath(ast, tokenRange(token));
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (!path.isEmpty()) {
|
|
|
|
|
|
if (path.last().kind() == "CXXConstructor") {
|
|
|
|
|
|
if (!path.last().arcanaContains("implicit"))
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_FUNCTION;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (path.last().kind() == "Record" && path.length() > 1) {
|
|
|
|
|
|
const AstNode node = path.at(path.length() - 2);
|
|
|
|
|
|
if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_FUNCTION;
|
2021-09-24 14:10:30 +02:00
|
|
|
|
|
|
|
|
|
|
// https://github.com/clangd/clangd/issues/872
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (node.role() == "declaration")
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mixinStyles.push_back(C_DECLARATION);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (token.type == "comment") { // "comment" means code disabled via the preprocessor
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_DISABLED_CODE;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "namespace") {
|
2021-10-27 15:52:20 +02:00
|
|
|
|
styles.mainStyle = C_NAMESPACE;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "property") {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_FIELD;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "enum") {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_TYPE;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "enumMember") {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_ENUMERATION;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "parameter") {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_PARAMETER;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "macro") {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_PREPROCESSOR;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "type") {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mainStyle = C_TYPE;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
} else if (token.type == "typeParameter") {
|
2021-09-29 13:00:36 +02:00
|
|
|
|
// clangd reports both type and non-type template parameters as type parameters,
|
|
|
|
|
|
// but the latter can be distinguished by the readonly modifier.
|
2021-12-15 18:47:16 +01:00
|
|
|
|
styles.mainStyle = token.modifiers.contains(QLatin1String("readonly"))
|
|
|
|
|
|
? C_PARAMETER : C_TYPE;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
2021-12-15 18:47:16 +01:00
|
|
|
|
if (token.modifiers.contains(QLatin1String("declaration")))
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mixinStyles.push_back(C_DECLARATION);
|
2022-03-01 11:10:54 +01:00
|
|
|
|
if (token.modifiers.contains(QLatin1String("static"))) {
|
|
|
|
|
|
if (styles.mainStyle != C_FIELD && styles.mainStyle != C_TEXT)
|
|
|
|
|
|
styles.mixinStyles.push_back(styles.mainStyle);
|
|
|
|
|
|
styles.mainStyle = C_STATIC_MEMBER;
|
|
|
|
|
|
}
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (isOutputParameter(token))
|
2021-09-28 13:21:59 +02:00
|
|
|
|
styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT);
|
2021-07-15 11:39:56 +02:00
|
|
|
|
qCDebug(clangdLogHighlight) << "adding highlighting result"
|
2021-06-09 09:47:26 +02:00
|
|
|
|
<< token.line << token.column << token.length << int(styles.mainStyle);
|
2021-09-28 13:21:59 +02:00
|
|
|
|
return HighlightingResult(token.line, token.column, token.length, styles);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-10-05 15:22:09 +02:00
|
|
|
|
auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, toResult);
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const QList<BlockRange> ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents);
|
2021-10-14 12:47:24 +02:00
|
|
|
|
ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents).collect();
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (!future.isCanceled()) {
|
|
|
|
|
|
qCDebug(clangdLog) << "reporting" << results.size() << "highlighting results";
|
2022-02-10 15:11:18 +01:00
|
|
|
|
QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] {
|
|
|
|
|
|
if (textDocument && textDocument->document()->revision() == docRevision)
|
|
|
|
|
|
textDocument->setIfdefedOutBlocks(ifdefedOutBlocks);
|
|
|
|
|
|
}, Qt::QueuedConnection);
|
2021-10-28 11:59:48 +02:00
|
|
|
|
QList<Range> virtualRanges;
|
|
|
|
|
|
for (const HighlightingResult &r : results) {
|
|
|
|
|
|
if (r.textStyles.mainStyle != C_VIRTUAL_METHOD)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
const Position startPos(r.line - 1, r.column - 1);
|
|
|
|
|
|
virtualRanges << Range(startPos, startPos.withOffset(r.length, &doc));
|
|
|
|
|
|
}
|
|
|
|
|
|
QMetaObject::invokeMethod(ClangModelManagerSupport::instance(),
|
|
|
|
|
|
[filePath, virtualRanges, docRevision] {
|
|
|
|
|
|
if (ClangdClient * const client
|
|
|
|
|
|
= ClangModelManagerSupport::instance()->clientForFile(filePath)) {
|
|
|
|
|
|
client->setVirtualRanges(filePath, virtualRanges, docRevision);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, Qt::QueuedConnection);
|
2022-02-10 15:11:18 +01:00
|
|
|
|
future.reportResults(QVector<HighlightingResult>(results.cbegin(), results.cend()));
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
future.reportFinished();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unfortunately, clangd ignores almost everything except symbols when sending
|
|
|
|
|
|
// semantic token info, so we need to consult the AST for additional information.
|
|
|
|
|
|
// In particular, we inspect the following constructs:
|
|
|
|
|
|
// - Raw string literals, because our built-in lexer does not parse them properly.
|
|
|
|
|
|
// While we're at it, we also handle other types of literals.
|
|
|
|
|
|
// - Ternary expressions (for the matching of "?" and ":").
|
|
|
|
|
|
// - Template declarations and instantiations (for the matching of "<" and ">").
|
|
|
|
|
|
// - Function declarations, to find out whether a declaration is also a definition.
|
|
|
|
|
|
// - Function arguments, to find out whether they correspond to output parameters.
|
|
|
|
|
|
// - We consider most other tokens to be simple enough to be handled by the built-in code model.
|
|
|
|
|
|
// Sometimes we have no choice, as for #include directives, which appear neither
|
|
|
|
|
|
// in the semantic tokens nor in the AST.
|
2021-09-28 13:21:59 +02:00
|
|
|
|
void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
|
2021-10-22 15:06:46 +02:00
|
|
|
|
const QList<ExpandedSemanticToken> &tokens,
|
2021-11-15 14:58:40 +01:00
|
|
|
|
int version, bool force)
|
2021-06-09 09:47:26 +02:00
|
|
|
|
{
|
2021-10-01 12:23:05 +02:00
|
|
|
|
SubtaskTimer t(highlightingTimer);
|
2022-02-17 15:54:37 +01:00
|
|
|
|
qCInfo(clangdLogHighlight) << "handling LSP tokens" << doc->filePath()
|
|
|
|
|
|
<< version << tokens.size();
|
2021-10-22 15:06:46 +02:00
|
|
|
|
if (version != q->documentVersion(doc->filePath())) {
|
2022-02-17 15:54:37 +01:00
|
|
|
|
qCInfo(clangdLogHighlight) << "LSP tokens outdated; aborting highlighting procedure"
|
2021-10-22 15:06:46 +02:00
|
|
|
|
<< version << q->documentVersion(doc->filePath());
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-12-10 14:34:26 +01:00
|
|
|
|
force = force || isTesting;
|
2022-04-21 13:02:09 +02:00
|
|
|
|
const auto data = highlightingData.find(doc);
|
|
|
|
|
|
if (data != highlightingData.end()) {
|
|
|
|
|
|
if (!force && data->previousTokens.first == tokens
|
|
|
|
|
|
&& data->previousTokens.second == version) {
|
2022-02-17 15:54:37 +01:00
|
|
|
|
qCInfo(clangdLogHighlight) << "tokens and version same as last time; nothing to do";
|
2021-11-03 15:30:57 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2022-04-21 13:02:09 +02:00
|
|
|
|
data->previousTokens.first = tokens;
|
|
|
|
|
|
data->previousTokens.second = version;
|
2021-11-03 15:30:57 +01:00
|
|
|
|
} else {
|
2022-04-21 13:02:09 +02:00
|
|
|
|
HighlightingData data;
|
|
|
|
|
|
data.previousTokens = qMakePair(tokens, version);
|
|
|
|
|
|
highlightingData.insert(doc, data);
|
2021-11-03 15:30:57 +01:00
|
|
|
|
}
|
2021-06-09 09:47:26 +02:00
|
|
|
|
for (const ExpandedSemanticToken &t : tokens)
|
2021-07-15 11:39:56 +02:00
|
|
|
|
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
|
|
|
|
|
|
<< t.modifiers;
|
2021-06-09 09:47:26 +02:00
|
|
|
|
|
2021-10-22 15:06:46 +02:00
|
|
|
|
const auto astHandler = [this, tokens, doc, version](const AstNode &ast, const MessageId &) {
|
2021-10-01 12:23:05 +02:00
|
|
|
|
FinalizingSubtaskTimer t(highlightingTimer);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
if (!q->documentOpen(doc))
|
|
|
|
|
|
return;
|
2021-10-22 15:06:46 +02:00
|
|
|
|
if (version != q->documentVersion(doc->filePath())) {
|
2022-02-17 15:54:37 +01:00
|
|
|
|
qCInfo(clangdLogHighlight) << "AST not up to date; aborting highlighting procedure"
|
2021-10-22 15:06:46 +02:00
|
|
|
|
<< version << q->documentVersion(doc->filePath());
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-09-27 16:24:37 +02:00
|
|
|
|
if (clangdLogAst().isDebugEnabled())
|
|
|
|
|
|
ast.print();
|
2021-06-09 09:47:26 +02:00
|
|
|
|
|
2021-10-14 12:47:24 +02:00
|
|
|
|
const auto runner = [tokens, filePath = doc->filePath(),
|
|
|
|
|
|
text = doc->document()->toPlainText(), ast,
|
2021-12-03 11:19:59 +01:00
|
|
|
|
doc = QPointer(doc), rev = doc->document()->revision(),
|
2022-03-04 19:42:46 +01:00
|
|
|
|
clangdVersion = q->versionNumber(),
|
|
|
|
|
|
this] {
|
2021-12-03 11:19:59 +01:00
|
|
|
|
return Utils::runAsync(semanticHighlighter, filePath, tokens, text, ast, doc, rev,
|
2022-03-04 19:42:46 +01:00
|
|
|
|
clangdVersion, highlightingTimer);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (isTesting) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const auto watcher = new QFutureWatcher<HighlightingResult>(q);
|
|
|
|
|
|
connect(watcher, &QFutureWatcher<HighlightingResult>::finished,
|
2021-06-18 16:30:03 +02:00
|
|
|
|
q, [this, watcher, fp = doc->filePath()] {
|
|
|
|
|
|
emit q->highlightingResultsReady(watcher->future().results(), fp);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
watcher->deleteLater();
|
|
|
|
|
|
});
|
|
|
|
|
|
watcher->setFuture(runner());
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-21 13:02:09 +02:00
|
|
|
|
auto &data = highlightingData[doc];
|
|
|
|
|
|
if (!data.highlighter)
|
|
|
|
|
|
data.highlighter = new CppEditor::SemanticHighlighter(doc);
|
|
|
|
|
|
else
|
|
|
|
|
|
data.highlighter->updateFormatMapFromFontSettings();
|
|
|
|
|
|
data.highlighter->setHighlightingRunner(runner);
|
|
|
|
|
|
data.highlighter->run();
|
2021-09-27 16:24:37 +02:00
|
|
|
|
};
|
|
|
|
|
|
getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible);
|
2021-06-09 09:47:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-13 16:09:04 +02:00
|
|
|
|
void ClangdClient::VirtualFunctionAssistProcessor::cancel()
|
|
|
|
|
|
{
|
2022-04-11 13:18:05 +02:00
|
|
|
|
resetData(true);
|
2021-09-13 16:09:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::VirtualFunctionAssistProcessor::update()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_data->followSymbolData->editorWidget)
|
|
|
|
|
|
return;
|
|
|
|
|
|
setAsyncProposalAvailable(createProposal(false));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClangdClient::VirtualFunctionAssistProcessor::finalize()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_data->followSymbolData->editorWidget)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const auto proposal = createProposal(true);
|
|
|
|
|
|
if (m_data->followSymbolData->editorWidget->isInTestMode()) {
|
|
|
|
|
|
m_data->followSymbolData->symbolsToDisplay.clear();
|
|
|
|
|
|
const auto immediateProposal = createProposal(false);
|
|
|
|
|
|
m_data->followSymbolData->editorWidget->setProposals(immediateProposal, proposal);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setAsyncProposalAvailable(proposal);
|
|
|
|
|
|
}
|
2022-04-11 13:18:05 +02:00
|
|
|
|
resetData(true);
|
2021-09-13 16:09:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-11 13:18:05 +02:00
|
|
|
|
void ClangdClient::VirtualFunctionAssistProcessor::resetData(bool resetFollowSymbolData)
|
2021-09-13 16:09:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (!m_data)
|
|
|
|
|
|
return;
|
|
|
|
|
|
m_data->followSymbolData->virtualFuncAssistProcessor = nullptr;
|
2022-04-11 13:18:05 +02:00
|
|
|
|
if (resetFollowSymbolData)
|
|
|
|
|
|
m_data->followSymbolData.reset();
|
2021-09-13 16:09:04 +02:00
|
|
|
|
m_data = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProposal *ClangdClient::VirtualFunctionAssistProcessor::createProposal(bool final) const
|
2021-09-13 16:09:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
QTC_ASSERT(m_data && m_data->followSymbolData, return nullptr);
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
QList<AssistProposalItemInterface *> items;
|
2021-09-13 16:09:04 +02:00
|
|
|
|
bool needsBaseDeclEntry = !m_data->followSymbolData->defLinkNode.range()
|
|
|
|
|
|
.contains(Position(m_data->followSymbolData->cursor));
|
|
|
|
|
|
for (const SymbolData &symbol : qAsConst(m_data->followSymbolData->symbolsToDisplay)) {
|
|
|
|
|
|
Utils::Link link = symbol.second;
|
|
|
|
|
|
if (m_data->followSymbolData->defLink == link) {
|
|
|
|
|
|
if (!needsBaseDeclEntry)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
needsBaseDeclEntry = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const Utils::Link defLink = m_data->followSymbolData->declDefMap.value(symbol.second);
|
|
|
|
|
|
if (defLink.hasValidTarget())
|
|
|
|
|
|
link = defLink;
|
|
|
|
|
|
}
|
|
|
|
|
|
items << createEntry(symbol.first, link);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (needsBaseDeclEntry)
|
|
|
|
|
|
items << createEntry({}, m_data->followSymbolData->defLink);
|
|
|
|
|
|
if (!final) {
|
|
|
|
|
|
const auto infoItem = new CppEditor::VirtualFunctionProposalItem({}, false);
|
|
|
|
|
|
infoItem->setText(ClangdClient::tr("collecting overrides ..."));
|
|
|
|
|
|
infoItem->setOrder(-1);
|
|
|
|
|
|
items << infoItem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new CppEditor::VirtualFunctionProposal(
|
|
|
|
|
|
m_data->followSymbolData->cursor.position(),
|
|
|
|
|
|
items, m_data->followSymbolData->openInSplit);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CppEditor::VirtualFunctionProposalItem *
|
|
|
|
|
|
ClangdClient::VirtualFunctionAssistProcessor::createEntry(const QString &name,
|
|
|
|
|
|
const Utils::Link &link) const
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto item = new CppEditor::VirtualFunctionProposalItem(
|
|
|
|
|
|
link, m_data->followSymbolData->openInSplit);
|
|
|
|
|
|
QString text = name;
|
|
|
|
|
|
if (link == m_data->followSymbolData->defLink) {
|
|
|
|
|
|
item->setOrder(1000); // Ensure base declaration is on top.
|
|
|
|
|
|
if (text.isEmpty()) {
|
|
|
|
|
|
text = ClangdClient::tr("<base declaration>");
|
|
|
|
|
|
} else if (m_data->followSymbolData->defLinkNode.isPureVirtualDeclaration()
|
|
|
|
|
|
|| m_data->followSymbolData->defLinkNode.isPureVirtualDefinition()) {
|
|
|
|
|
|
text += " = 0";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
item->setText(text);
|
|
|
|
|
|
return item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProcessor *ClangdClient::VirtualFunctionAssistProvider::createProcessor(
|
|
|
|
|
|
const AssistInterface *) const
|
2021-09-13 16:09:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
return m_data->followSymbolData->virtualFuncAssistProcessor
|
|
|
|
|
|
= new VirtualFunctionAssistProcessor(m_data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Utils::optional<QList<CodeAction> > ClangdDiagnostic::codeActions() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return optionalArray<LanguageServerProtocol::CodeAction>("codeActions");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString ClangdDiagnostic::category() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return typedValue<QString>("category");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-15 11:35:19 +02:00
|
|
|
|
class ClangdClient::ClangdFunctionHintProcessor : public FunctionHintProcessor
|
2021-09-13 16:09:04 +02:00
|
|
|
|
{
|
2021-09-15 11:35:19 +02:00
|
|
|
|
public:
|
|
|
|
|
|
ClangdFunctionHintProcessor(ClangdClient *client)
|
|
|
|
|
|
: FunctionHintProcessor(client)
|
|
|
|
|
|
, m_client(client)
|
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProposal *perform(const AssistInterface *interface) override
|
2021-09-15 11:35:19 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (m_client->d->isTesting) {
|
2021-09-28 13:21:59 +02:00
|
|
|
|
setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) {
|
2021-09-15 11:35:19 +02:00
|
|
|
|
emit m_client->proposalReady(proposal);
|
|
|
|
|
|
});
|
2021-09-13 16:09:04 +02:00
|
|
|
|
}
|
2021-09-15 11:35:19 +02:00
|
|
|
|
return FunctionHintProcessor::perform(interface);
|
|
|
|
|
|
}
|
2021-09-13 16:09:04 +02:00
|
|
|
|
|
2021-09-15 11:35:19 +02:00
|
|
|
|
ClangdClient * const m_client;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ClangdClient::ClangdCompletionAssistProvider::ClangdCompletionAssistProvider(ClangdClient *client)
|
|
|
|
|
|
: LanguageClientCompletionAssistProvider(client)
|
|
|
|
|
|
, m_client(client)
|
|
|
|
|
|
{}
|
2021-09-13 16:09:04 +02:00
|
|
|
|
|
2021-09-28 13:21:59 +02:00
|
|
|
|
IAssistProcessor *ClangdClient::ClangdCompletionAssistProvider::createProcessor(
|
2021-10-08 10:15:51 +02:00
|
|
|
|
const AssistInterface *interface) const
|
2021-09-15 08:10:30 +02:00
|
|
|
|
{
|
2021-10-15 11:31:13 +02:00
|
|
|
|
qCDebug(clangdLogCompletion) << "completion processor requested for" << interface->filePath();
|
|
|
|
|
|
qCDebug(clangdLogCompletion) << "text before cursor is"
|
|
|
|
|
|
<< interface->textAt(interface->position(), -10);
|
|
|
|
|
|
qCDebug(clangdLogCompletion) << "text after cursor is"
|
|
|
|
|
|
<< interface->textAt(interface->position(), 10);
|
2021-10-08 10:15:51 +02:00
|
|
|
|
ClangCompletionContextAnalyzer contextAnalyzer(interface->textDocument(),
|
|
|
|
|
|
interface->position(), false, {});
|
2021-09-15 08:10:30 +02:00
|
|
|
|
contextAnalyzer.analyze();
|
|
|
|
|
|
switch (contextAnalyzer.completionAction()) {
|
|
|
|
|
|
case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen:
|
2021-10-15 11:31:13 +02:00
|
|
|
|
qCDebug(clangdLogCompletion) << "creating function hint processor";
|
2021-09-15 11:35:19 +02:00
|
|
|
|
return new ClangdFunctionHintProcessor(m_client);
|
2021-09-15 08:10:30 +02:00
|
|
|
|
case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword:
|
2021-10-15 11:31:13 +02:00
|
|
|
|
qCDebug(clangdLogCompletion) << "creating doxygen processor";
|
2021-09-28 15:10:47 +02:00
|
|
|
|
return new CustomAssistProcessor(m_client,
|
|
|
|
|
|
contextAnalyzer.positionForProposal(),
|
2021-11-10 12:35:06 +01:00
|
|
|
|
contextAnalyzer.positionEndOfExpression(),
|
2021-09-28 15:10:47 +02:00
|
|
|
|
contextAnalyzer.completionOperator(),
|
|
|
|
|
|
CustomAssistMode::Doxygen);
|
|
|
|
|
|
case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
|
2021-10-15 11:31:13 +02:00
|
|
|
|
qCDebug(clangdLogCompletion) << "creating macro processor";
|
2021-09-28 15:10:47 +02:00
|
|
|
|
return new CustomAssistProcessor(m_client,
|
|
|
|
|
|
contextAnalyzer.positionForProposal(),
|
2021-11-10 12:35:06 +01:00
|
|
|
|
contextAnalyzer.positionEndOfExpression(),
|
2021-09-28 15:10:47 +02:00
|
|
|
|
contextAnalyzer.completionOperator(),
|
|
|
|
|
|
CustomAssistMode::Preprocessor);
|
2021-11-10 12:35:06 +01:00
|
|
|
|
case ClangCompletionContextAnalyzer::CompleteIncludePath:
|
|
|
|
|
|
if (m_client->versionNumber() < QVersionNumber(14)) { // https://reviews.llvm.org/D112996
|
|
|
|
|
|
qCDebug(clangdLogCompletion) << "creating include processor";
|
|
|
|
|
|
return new CustomAssistProcessor(m_client,
|
|
|
|
|
|
contextAnalyzer.positionForProposal(),
|
|
|
|
|
|
contextAnalyzer.positionEndOfExpression(),
|
|
|
|
|
|
contextAnalyzer.completionOperator(),
|
|
|
|
|
|
CustomAssistMode::IncludePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
[[fallthrough]];
|
2021-09-15 08:10:30 +02:00
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-10-08 10:15:51 +02:00
|
|
|
|
const QString snippetsGroup = contextAnalyzer.addSnippets() && !isInCommentOrString(interface)
|
2021-09-15 11:35:19 +02:00
|
|
|
|
? CppEditor::Constants::CPP_SNIPPETS_GROUP_ID
|
|
|
|
|
|
: QString();
|
2021-10-15 11:31:13 +02:00
|
|
|
|
qCDebug(clangdLogCompletion) << "creating proper completion processor"
|
|
|
|
|
|
<< (snippetsGroup.isEmpty() ? "without" : "with") << "snippets";
|
2021-09-15 11:35:19 +02:00
|
|
|
|
return new ClangdCompletionAssistProcessor(m_client, snippetsGroup);
|
2021-09-15 08:10:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ClangdClient::ClangdCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
|
2021-09-13 16:09:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
const QChar &ch = sequence.at(2);
|
|
|
|
|
|
const QChar &ch2 = sequence.at(1);
|
|
|
|
|
|
const QChar &ch3 = sequence.at(0);
|
|
|
|
|
|
unsigned kind = T_EOF_SYMBOL;
|
|
|
|
|
|
const int pos = CppEditor::CppCompletionAssistProvider::activationSequenceChar(
|
|
|
|
|
|
ch, ch2, ch3, &kind, false, false);
|
|
|
|
|
|
if (pos == 0)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// We want to minimize unneeded completion requests, as those trigger document updates,
|
|
|
|
|
|
// which trigger re-highlighting and diagnostics, which we try to delay.
|
|
|
|
|
|
// Therefore, we do not trigger on syntax elements that often occur in non-applicable
|
|
|
|
|
|
// contexts, such as '(', '<' or '/'.
|
|
|
|
|
|
switch (kind) {
|
|
|
|
|
|
case T_DOT: case T_COLON_COLON: case T_ARROW: case T_DOT_STAR: case T_ARROW_STAR: case T_POUND:
|
2021-10-15 11:31:13 +02:00
|
|
|
|
qCDebug(clangdLogCompletion) << "detected" << sequence << "as activation char sequence";
|
2021-09-13 16:09:04 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-15 08:10:30 +02:00
|
|
|
|
bool ClangdClient::ClangdCompletionAssistProvider::isContinuationChar(const QChar &c) const
|
2021-09-13 16:09:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
return CppEditor::isValidIdentifierChar(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-08 10:15:51 +02:00
|
|
|
|
bool ClangdClient::ClangdCompletionAssistProvider::isInCommentOrString(
|
|
|
|
|
|
const AssistInterface *interface) const
|
|
|
|
|
|
{
|
2021-10-13 17:18:07 +02:00
|
|
|
|
LanguageFeatures features = LanguageFeatures::defaultFeatures();
|
|
|
|
|
|
features.objCEnabled = CppEditor::ProjectFile::isObjC(interface->filePath().toString());
|
|
|
|
|
|
return CppEditor::isInCommentOrString(interface, features);
|
2021-10-08 10:15:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-27 11:08:59 +02:00
|
|
|
|
void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
|
|
|
|
|
|
int /*basePosition*/) const
|
2021-06-18 16:30:03 +02:00
|
|
|
|
{
|
2021-09-27 11:08:59 +02:00
|
|
|
|
const LanguageServerProtocol::CompletionItem item = this->item();
|
|
|
|
|
|
QChar typedChar = triggeredCommitCharacter();
|
2021-06-18 16:30:03 +02:00
|
|
|
|
const auto edit = item.textEdit();
|
|
|
|
|
|
if (!edit)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2021-11-23 18:02:41 +01:00
|
|
|
|
const int labelOpenParenOffset = item.label().indexOf('(');
|
|
|
|
|
|
const int labelClosingParenOffset = item.label().indexOf(')');
|
2021-11-03 15:08:38 +01:00
|
|
|
|
const auto kind = static_cast<CompletionItemKind::Kind>(
|
|
|
|
|
|
item.kind().value_or(CompletionItemKind::Text));
|
2021-11-23 18:02:41 +01:00
|
|
|
|
const bool isMacroCall = kind == CompletionItemKind::Text && labelOpenParenOffset != -1
|
|
|
|
|
|
&& labelClosingParenOffset > labelOpenParenOffset; // Heuristic
|
2021-11-03 15:08:38 +01:00
|
|
|
|
const bool isFunctionLike = kind == CompletionItemKind::Function
|
2021-11-22 13:53:59 +01:00
|
|
|
|
|| kind == CompletionItemKind::Method || kind == CompletionItemKind::Constructor
|
|
|
|
|
|
|| isMacroCall;
|
|
|
|
|
|
|
2021-11-03 15:08:38 +01:00
|
|
|
|
QString rawInsertText = edit->newText();
|
|
|
|
|
|
|
|
|
|
|
|
// Some preparation for our magic involving (non-)insertion of parentheses and
|
|
|
|
|
|
// cursor placement.
|
|
|
|
|
|
if (isFunctionLike && !rawInsertText.contains('(')) {
|
2021-11-23 18:02:41 +01:00
|
|
|
|
if (labelOpenParenOffset != -1) {
|
|
|
|
|
|
if (labelClosingParenOffset == labelOpenParenOffset + 1) // function takes no arguments
|
|
|
|
|
|
rawInsertText += "()";
|
|
|
|
|
|
else // function takes arguments
|
|
|
|
|
|
rawInsertText += "( )";
|
|
|
|
|
|
}
|
2021-11-03 15:08:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-18 16:30:03 +02:00
|
|
|
|
const int firstParenOffset = rawInsertText.indexOf('(');
|
|
|
|
|
|
const int lastParenOffset = rawInsertText.lastIndexOf(')');
|
|
|
|
|
|
const QString detail = item.detail().value_or(QString());
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const CompletionSettings &completionSettings = TextEditorSettings::completionSettings();
|
2021-06-18 16:30:03 +02:00
|
|
|
|
QString textToBeInserted = rawInsertText.left(firstParenOffset);
|
|
|
|
|
|
QString extraCharacters;
|
2021-10-12 17:45:26 +02:00
|
|
|
|
int extraLength = 0;
|
2021-06-18 16:30:03 +02:00
|
|
|
|
int cursorOffset = 0;
|
|
|
|
|
|
bool setAutoCompleteSkipPos = false;
|
2021-10-12 17:45:26 +02:00
|
|
|
|
int currentPos = manipulator.currentPosition();
|
|
|
|
|
|
const QTextDocument * const doc = manipulator.textCursorAt(currentPos).document();
|
2021-06-18 16:30:03 +02:00
|
|
|
|
const Range range = edit->range();
|
|
|
|
|
|
const int rangeStart = range.start().toPositionInDocument(doc);
|
2021-10-12 17:45:26 +02:00
|
|
|
|
if (isFunctionLike && completionSettings.m_autoInsertBrackets) {
|
2021-06-18 16:30:03 +02:00
|
|
|
|
// If the user typed the opening parenthesis, they'll likely also type the closing one,
|
|
|
|
|
|
// in which case it would be annoying if we put the cursor after the already automatically
|
|
|
|
|
|
// inserted closing parenthesis.
|
|
|
|
|
|
const bool skipClosingParenthesis = typedChar != '(';
|
|
|
|
|
|
QTextCursor cursor = manipulator.textCursorAt(rangeStart);
|
|
|
|
|
|
|
|
|
|
|
|
bool abandonParen = false;
|
|
|
|
|
|
if (matchPreviousWord(manipulator, cursor, "&")) {
|
|
|
|
|
|
moveToPreviousWord(manipulator, cursor);
|
|
|
|
|
|
moveToPreviousChar(manipulator, cursor);
|
|
|
|
|
|
const QChar prevChar = manipulator.characterAt(cursor.position());
|
|
|
|
|
|
cursor.setPosition(rangeStart);
|
|
|
|
|
|
abandonParen = QString("(;,{}=").contains(prevChar);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!abandonParen)
|
|
|
|
|
|
abandonParen = isAtUsingDeclaration(manipulator, rangeStart);
|
2021-11-22 13:53:59 +01:00
|
|
|
|
if (!abandonParen && !isMacroCall && matchPreviousWord(manipulator, cursor, detail))
|
|
|
|
|
|
abandonParen = true; // function definition
|
2021-06-18 16:30:03 +02:00
|
|
|
|
if (!abandonParen) {
|
|
|
|
|
|
if (completionSettings.m_spaceAfterFunctionName)
|
|
|
|
|
|
extraCharacters += ' ';
|
|
|
|
|
|
extraCharacters += '(';
|
|
|
|
|
|
if (typedChar == '(')
|
|
|
|
|
|
typedChar = {};
|
|
|
|
|
|
|
|
|
|
|
|
// If the function doesn't return anything, automatically place the semicolon,
|
|
|
|
|
|
// unless we're doing a scope completion (then it might be function definition).
|
2021-10-12 17:45:26 +02:00
|
|
|
|
const QChar characterAtCursor = manipulator.characterAt(currentPos);
|
2021-06-18 16:30:03 +02:00
|
|
|
|
bool endWithSemicolon = typedChar == ';';
|
|
|
|
|
|
const QChar semicolon = typedChar.isNull() ? QLatin1Char(';') : typedChar;
|
|
|
|
|
|
if (endWithSemicolon && characterAtCursor == semicolon) {
|
|
|
|
|
|
endWithSemicolon = false;
|
|
|
|
|
|
typedChar = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If the function takes no arguments, automatically place the closing parenthesis
|
|
|
|
|
|
if (firstParenOffset + 1 == lastParenOffset && skipClosingParenthesis) {
|
|
|
|
|
|
extraCharacters += QLatin1Char(')');
|
|
|
|
|
|
if (endWithSemicolon) {
|
|
|
|
|
|
extraCharacters += semicolon;
|
|
|
|
|
|
typedChar = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2021-10-12 17:45:26 +02:00
|
|
|
|
const QChar lookAhead = manipulator.characterAt(currentPos + 1);
|
2021-06-18 16:30:03 +02:00
|
|
|
|
if (MatchingText::shouldInsertMatchingText(lookAhead)) {
|
|
|
|
|
|
extraCharacters += ')';
|
|
|
|
|
|
--cursorOffset;
|
|
|
|
|
|
setAutoCompleteSkipPos = true;
|
|
|
|
|
|
if (endWithSemicolon) {
|
|
|
|
|
|
extraCharacters += semicolon;
|
|
|
|
|
|
--cursorOffset;
|
|
|
|
|
|
typedChar = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Append an unhandled typed character, adjusting cursor offset when it had been adjusted before
|
|
|
|
|
|
if (!typedChar.isNull()) {
|
|
|
|
|
|
extraCharacters += typedChar;
|
|
|
|
|
|
if (cursorOffset != 0)
|
|
|
|
|
|
--cursorOffset;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-12 17:45:26 +02:00
|
|
|
|
// Avoid inserting characters that are already there
|
|
|
|
|
|
QTextCursor cursor = manipulator.textCursorAt(rangeStart);
|
|
|
|
|
|
cursor.movePosition(QTextCursor::EndOfWord);
|
|
|
|
|
|
const QString textAfterCursor = manipulator.textAt(currentPos, cursor.position() - currentPos);
|
2022-02-10 14:01:14 +01:00
|
|
|
|
if (currentPos < cursor.position()
|
|
|
|
|
|
&& textToBeInserted != textAfterCursor
|
2021-10-12 17:45:26 +02:00
|
|
|
|
&& textToBeInserted.indexOf(textAfterCursor, currentPos - rangeStart) >= 0) {
|
|
|
|
|
|
currentPos = cursor.position();
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < extraCharacters.length(); ++i) {
|
|
|
|
|
|
const QChar a = extraCharacters.at(i);
|
|
|
|
|
|
const QChar b = manipulator.characterAt(currentPos + i);
|
|
|
|
|
|
if (a == b)
|
|
|
|
|
|
++extraLength;
|
|
|
|
|
|
else
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-06-18 16:30:03 +02:00
|
|
|
|
|
2021-10-12 17:45:26 +02:00
|
|
|
|
textToBeInserted += extraCharacters;
|
|
|
|
|
|
const int length = currentPos - rangeStart + extraLength;
|
|
|
|
|
|
const bool isReplaced = manipulator.replace(rangeStart, length, textToBeInserted);
|
2021-06-18 16:30:03 +02:00
|
|
|
|
manipulator.setCursorPosition(rangeStart + textToBeInserted.length());
|
|
|
|
|
|
if (isReplaced) {
|
|
|
|
|
|
if (cursorOffset)
|
|
|
|
|
|
manipulator.setCursorPosition(manipulator.currentPosition() + cursorOffset);
|
|
|
|
|
|
if (setAutoCompleteSkipPos)
|
|
|
|
|
|
manipulator.setAutoCompleteSkipPosition(manipulator.currentPosition());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (auto additionalEdits = item.additionalTextEdits()) {
|
|
|
|
|
|
for (const auto &edit : *additionalEdits)
|
|
|
|
|
|
applyTextEdit(manipulator, edit);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-10 14:44:41 +01:00
|
|
|
|
ClangdCompletionItem::SpecialQtType ClangdCompletionItem::getQtType(const CompletionItem &item)
|
|
|
|
|
|
{
|
|
|
|
|
|
const Utils::optional<MarkupOrString> doc = item.documentation();
|
|
|
|
|
|
if (!doc)
|
|
|
|
|
|
return SpecialQtType::None;
|
|
|
|
|
|
QString docText;
|
|
|
|
|
|
if (Utils::holds_alternative<QString>(*doc))
|
|
|
|
|
|
docText = Utils::get<QString>(*doc);
|
|
|
|
|
|
else if (Utils::holds_alternative<MarkupContent>(*doc))
|
|
|
|
|
|
docText = Utils::get<MarkupContent>(*doc).content();
|
|
|
|
|
|
if (docText.contains("Annotation: qt_signal"))
|
|
|
|
|
|
return SpecialQtType::Signal;
|
|
|
|
|
|
if (docText.contains("Annotation: qt_slot"))
|
|
|
|
|
|
return SpecialQtType::Slot;
|
|
|
|
|
|
return SpecialQtType::None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QIcon ClangdCompletionItem::icon() const
|
|
|
|
|
|
{
|
|
|
|
|
|
const SpecialQtType qtType = getQtType(item());
|
|
|
|
|
|
switch (qtType) {
|
|
|
|
|
|
case SpecialQtType::Signal:
|
|
|
|
|
|
return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Signal);
|
|
|
|
|
|
case SpecialQtType::Slot:
|
|
|
|
|
|
// FIXME: Add visibility info to completion item tags in clangd?
|
|
|
|
|
|
return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::SlotPublic);
|
|
|
|
|
|
case SpecialQtType::None:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2022-04-01 14:58:06 +02:00
|
|
|
|
if (item().kind().value_or(CompletionItemKind::Text) == CompletionItemKind::Property)
|
|
|
|
|
|
return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::VarPublicStatic);
|
2021-11-10 14:44:41 +01:00
|
|
|
|
return LanguageClientCompletionItem::icon();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-27 16:24:37 +02:00
|
|
|
|
MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc,
|
|
|
|
|
|
const AstHandler &astHandler,
|
2021-10-07 10:58:54 +02:00
|
|
|
|
AstCallbackMode callbackMode, const Range &range)
|
2021-09-27 16:24:37 +02:00
|
|
|
|
{
|
2021-09-29 09:32:08 +02:00
|
|
|
|
const auto textDocPtr = Utils::get_if<const TextDocument *>(&doc);
|
2021-09-28 13:21:59 +02:00
|
|
|
|
const TextDocument * const textDoc = textDocPtr ? *textDocPtr : nullptr;
|
2021-09-29 09:32:08 +02:00
|
|
|
|
const Utils::FilePath filePath = textDoc ? textDoc->filePath()
|
|
|
|
|
|
: Utils::get<Utils::FilePath>(doc);
|
2021-09-27 16:24:37 +02:00
|
|
|
|
|
2021-10-07 10:58:54 +02:00
|
|
|
|
// If the entire AST is requested and the document's AST is in the cache and it is up to date,
|
|
|
|
|
|
// call the handler.
|
|
|
|
|
|
const bool fullAstRequested = !range.isValid();
|
|
|
|
|
|
if (fullAstRequested) {
|
|
|
|
|
|
if (const auto ast = textDoc ? astCache.get(textDoc) : externalAstCache.get(filePath)) {
|
|
|
|
|
|
qCDebug(clangdLog) << "using AST from cache";
|
|
|
|
|
|
switch (callbackMode) {
|
|
|
|
|
|
case AstCallbackMode::SyncIfPossible:
|
|
|
|
|
|
astHandler(*ast, {});
|
|
|
|
|
|
break;
|
|
|
|
|
|
case AstCallbackMode::AlwaysAsync:
|
|
|
|
|
|
QMetaObject::invokeMethod(q, [ast, astHandler] { astHandler(*ast, {}); },
|
2021-09-27 16:24:37 +02:00
|
|
|
|
Qt::QueuedConnection);
|
2021-10-07 10:58:54 +02:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
return {};
|
2021-09-27 16:24:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Otherwise retrieve the AST from clangd.
|
|
|
|
|
|
|
|
|
|
|
|
class AstParams : public JsonObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
AstParams(const TextDocumentIdentifier &document, const Range &range = {})
|
|
|
|
|
|
{
|
|
|
|
|
|
setTextDocument(document);
|
|
|
|
|
|
if (range.isValid())
|
|
|
|
|
|
setRange(range);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
using JsonObject::JsonObject;
|
|
|
|
|
|
|
|
|
|
|
|
// The open file to inspect.
|
|
|
|
|
|
TextDocumentIdentifier textDocument() const
|
|
|
|
|
|
{ return typedValue<TextDocumentIdentifier>(textDocumentKey); }
|
|
|
|
|
|
void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); }
|
|
|
|
|
|
|
|
|
|
|
|
// The region of the source code whose AST is fetched. The highest-level node that entirely
|
|
|
|
|
|
// contains the range is returned.
|
|
|
|
|
|
Utils::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
|
|
|
|
|
|
void setRange(const Range &range) { insert(rangeKey, range); }
|
|
|
|
|
|
|
|
|
|
|
|
bool isValid() const override { return contains(textDocumentKey); }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class AstRequest : public Request<AstNode, std::nullptr_t, AstParams>
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using Request::Request;
|
|
|
|
|
|
explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2021-10-07 10:58:54 +02:00
|
|
|
|
AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)),
|
|
|
|
|
|
range));
|
2021-09-27 16:24:37 +02:00
|
|
|
|
request.setResponseCallback([this, filePath, guardedTextDoc = QPointer(textDoc), astHandler,
|
2021-10-07 10:58:54 +02:00
|
|
|
|
fullAstRequested, docRev = textDoc ? getRevision(textDoc) : -1,
|
2021-09-27 16:24:37 +02:00
|
|
|
|
fileRev = getRevision(filePath), reqId = request.id()]
|
|
|
|
|
|
(AstRequest::Response response) {
|
|
|
|
|
|
qCDebug(clangdLog) << "retrieved AST from clangd";
|
|
|
|
|
|
const auto result = response.result();
|
|
|
|
|
|
const AstNode ast = result ? *result : AstNode();
|
2021-10-07 10:58:54 +02:00
|
|
|
|
if (fullAstRequested) {
|
|
|
|
|
|
if (guardedTextDoc) {
|
|
|
|
|
|
if (docRev == getRevision(guardedTextDoc))
|
|
|
|
|
|
astCache.insert(guardedTextDoc, ast);
|
|
|
|
|
|
} else if (fileRev == getRevision(filePath) && !q->documentForFilePath(filePath)) {
|
|
|
|
|
|
externalAstCache.insert(filePath, ast);
|
|
|
|
|
|
}
|
2021-09-27 16:24:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
astHandler(ast, reqId);
|
|
|
|
|
|
});
|
|
|
|
|
|
qCDebug(clangdLog) << "requesting AST for" << filePath;
|
|
|
|
|
|
q->sendContent(request, SendDocUpdates::Ignore);
|
|
|
|
|
|
return request.id();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-04 14:25:35 +02:00
|
|
|
|
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
|
|
|
|
|
|
QFutureInterface<HighlightingResult> &future, HighlightingResults &results,
|
2021-10-14 12:47:24 +02:00
|
|
|
|
const Utils::FilePath &filePath, const AstNode &ast, const QTextDocument *doc,
|
|
|
|
|
|
const QString &docContent)
|
|
|
|
|
|
: m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
|
|
|
|
|
|
m_docContent(docContent)
|
2021-10-04 14:25:35 +02:00
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ExtraHighlightingResultsCollector::collect()
|
|
|
|
|
|
{
|
2021-11-26 12:54:50 +01:00
|
|
|
|
for (int i = 0; i < m_results.length(); ++i) {
|
|
|
|
|
|
const HighlightingResult res = m_results.at(i);
|
|
|
|
|
|
if (res.textStyles.mainStyle != C_PREPROCESSOR || res.length != 10)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
const int pos = Utils::Text::positionInText(m_doc, res.line, res.column);
|
|
|
|
|
|
if (subViewLen(m_docContent, pos, 10) != QLatin1String("Q_PROPERTY"))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
int endPos;
|
|
|
|
|
|
if (i < m_results.length() - 1) {
|
|
|
|
|
|
const HighlightingResult nextRes = m_results.at(i + 1);
|
|
|
|
|
|
endPos = Utils::Text::positionInText(m_doc, nextRes.line, nextRes.column);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
endPos = m_docContent.length();
|
|
|
|
|
|
}
|
|
|
|
|
|
const QString qPropertyString = m_docContent.mid(pos, endPos - pos);
|
|
|
|
|
|
QPropertyHighlighter propHighlighter(m_doc, qPropertyString, pos);
|
|
|
|
|
|
for (const HighlightingResult &newRes : propHighlighter.highlight())
|
|
|
|
|
|
m_results.insert(++i, newRes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-04 14:25:35 +02:00
|
|
|
|
if (!m_ast.isValid())
|
|
|
|
|
|
return;
|
|
|
|
|
|
visitNode(m_ast);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1,
|
|
|
|
|
|
const HighlightingResult &r2)
|
|
|
|
|
|
{
|
|
|
|
|
|
return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column)
|
|
|
|
|
|
|| (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text,
|
|
|
|
|
|
const QStringView &subString, int from)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int firstIndex = text.indexOf(subString, from);
|
|
|
|
|
|
if (firstIndex == -1)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
const int nextIndex = text.indexOf(subString, firstIndex + 1);
|
|
|
|
|
|
|
|
|
|
|
|
// The second condion deals with the off-by-one error in TemplateSpecialization nodes;
|
|
|
|
|
|
// see collectFromNode().
|
|
|
|
|
|
return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Unfortunately, the exact position of a specific token is usually not
|
|
|
|
|
|
// recorded in the AST, so if we need that, we have to search for it textually.
|
|
|
|
|
|
// In corner cases, this might get sabotaged by e.g. comments, in which case we give up.
|
|
|
|
|
|
int ExtraHighlightingResultsCollector::posForNodeStart(const AstNode &node) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return Utils::Text::positionInText(m_doc, node.range().start().line() + 1,
|
|
|
|
|
|
node.range().start().character() + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int ExtraHighlightingResultsCollector::posForNodeEnd(const AstNode &node) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return Utils::Text::positionInText(m_doc, node.range().end().line() + 1,
|
|
|
|
|
|
node.range().end().character() + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!result.isValid()) // Some nodes don't have a range.
|
|
|
|
|
|
return;
|
|
|
|
|
|
const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan);
|
|
|
|
|
|
if (it == m_results.end() || *it != result) {
|
2021-11-01 14:44:32 +01:00
|
|
|
|
|
|
|
|
|
|
// Prevent inserting expansions for function-like macros. For instance:
|
|
|
|
|
|
// #define TEST() "blubb"
|
|
|
|
|
|
// const char *s = TEST();
|
|
|
|
|
|
// The macro name is always shorter than the expansion and starts at the same
|
|
|
|
|
|
// location, so it should occur right before the insertion position.
|
|
|
|
|
|
if (it > m_results.begin() && (it - 1)->line == result.line
|
|
|
|
|
|
&& (it - 1)->column == result.column
|
|
|
|
|
|
&& (it - 1)->textStyles.mainStyle == C_PREPROCESSOR) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-04 14:25:35 +02:00
|
|
|
|
qCDebug(clangdLogHighlight) << "adding additional highlighting result"
|
|
|
|
|
|
<< result.line << result.column << result.length;
|
|
|
|
|
|
m_results.insert(it, result);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This is for conversion operators, whose type part is only reported as a type by clangd.
|
|
|
|
|
|
if ((it->textStyles.mainStyle == C_TYPE
|
|
|
|
|
|
|| it->textStyles.mainStyle == C_PRIMITIVE_TYPE)
|
|
|
|
|
|
&& !result.textStyles.mixinStyles.empty()
|
|
|
|
|
|
&& result.textStyles.mixinStyles.at(0) == C_OPERATOR) {
|
|
|
|
|
|
it->textStyles.mixinStyles = result.textStyles.mixinStyles;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-25 10:26:48 +01:00
|
|
|
|
void ExtraHighlightingResultsCollector::insertResult(const AstNode &node, TextStyle style)
|
|
|
|
|
|
{
|
|
|
|
|
|
HighlightingResult result;
|
|
|
|
|
|
result.useTextSyles = true;
|
|
|
|
|
|
result.textStyles.mainStyle = style;
|
|
|
|
|
|
setResultPosFromRange(result, node.range());
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-04 14:25:35 +02:00
|
|
|
|
// For matching the "<" and ">" brackets of template declarations, specializations
|
|
|
|
|
|
// and instantiations.
|
|
|
|
|
|
void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1,
|
|
|
|
|
|
int searchStart2, int searchEnd2)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int openingAngleBracketPos = onlyIndexOf(
|
2021-10-13 14:24:25 +02:00
|
|
|
|
subViewEnd(m_docContent, searchStart1, searchEnd1),
|
2021-10-04 14:25:35 +02:00
|
|
|
|
QStringView(QStringLiteral("<")));
|
|
|
|
|
|
if (openingAngleBracketPos == -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos;
|
|
|
|
|
|
if (absOpeningAngleBracketPos > searchStart2)
|
|
|
|
|
|
searchStart2 = absOpeningAngleBracketPos + 1;
|
|
|
|
|
|
if (searchStart2 >= searchEnd2)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const int closingAngleBracketPos = onlyIndexOf(
|
2021-10-13 14:24:25 +02:00
|
|
|
|
subViewEnd(m_docContent, searchStart2, searchEnd2),
|
2021-10-04 14:25:35 +02:00
|
|
|
|
QStringView(QStringLiteral(">")));
|
|
|
|
|
|
if (closingAngleBracketPos == -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos;
|
|
|
|
|
|
if (absOpeningAngleBracketPos > absClosingAngleBracketPos)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
HighlightingResult result;
|
|
|
|
|
|
result.useTextSyles = true;
|
|
|
|
|
|
result.textStyles.mainStyle = C_PUNCTUATION;
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column);
|
|
|
|
|
|
result.length = 1;
|
|
|
|
|
|
result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen;
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column);
|
|
|
|
|
|
result.kind = CppEditor::SemanticHighlighter::AngleBracketClose;
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result,
|
|
|
|
|
|
const Range &range)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!range.isValid())
|
|
|
|
|
|
return;
|
|
|
|
|
|
const Position startPos = range.start();
|
|
|
|
|
|
const Position endPos = range.end();
|
|
|
|
|
|
result.line = startPos.line() + 1;
|
|
|
|
|
|
result.column = startPos.character() + 1;
|
|
|
|
|
|
result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
|
|
|
|
|
{
|
2021-10-14 14:23:05 +02:00
|
|
|
|
if (node.kind() == "UserDefinedLiteral")
|
|
|
|
|
|
return;
|
2021-10-04 14:25:35 +02:00
|
|
|
|
if (node.kind().endsWith("Literal")) {
|
2022-02-15 11:24:02 +01:00
|
|
|
|
const bool isKeyword = node.kind() == "CXXBoolLiteral"
|
|
|
|
|
|
|| node.kind() == "CXXNullPtrLiteral";
|
|
|
|
|
|
const bool isStringLike = !isKeyword && (node.kind().startsWith("String")
|
|
|
|
|
|
|| node.kind().startsWith("Character"));
|
2022-02-25 10:26:48 +01:00
|
|
|
|
const TextStyle style = isKeyword ? C_KEYWORD : isStringLike ? C_STRING : C_NUMBER;
|
|
|
|
|
|
insertResult(node, style);
|
2021-10-04 14:25:35 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (node.role() == "type" && node.kind() == "Builtin") {
|
2022-02-25 10:26:48 +01:00
|
|
|
|
insertResult(node, C_PRIMITIVE_TYPE);
|
2021-10-04 14:25:35 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) {
|
2022-02-25 10:26:48 +01:00
|
|
|
|
insertResult(node, C_KEYWORD);
|
2021-10-04 14:25:35 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bool isExpression = node.role() == "expression";
|
2022-02-25 10:15:42 +01:00
|
|
|
|
if (isExpression && node.kind() == "Predefined") {
|
2022-02-25 10:26:48 +01:00
|
|
|
|
insertResult(node, C_PREPROCESSOR);
|
2022-02-25 10:15:42 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-10-04 14:25:35 +02:00
|
|
|
|
|
2022-02-25 10:15:42 +01:00
|
|
|
|
const bool isDeclaration = node.role() == "declaration";
|
2021-10-04 14:25:35 +02:00
|
|
|
|
const int nodeStartPos = posForNodeStart(node);
|
|
|
|
|
|
const int nodeEndPos = posForNodeEnd(node);
|
|
|
|
|
|
const QList<AstNode> children = node.children().value_or(QList<AstNode>());
|
|
|
|
|
|
|
|
|
|
|
|
// Match question mark and colon in ternary operators.
|
|
|
|
|
|
if (isExpression && node.kind() == "ConditionalOperator") {
|
|
|
|
|
|
if (children.size() != 3)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// The question mark is between sub-expressions 1 and 2, the colon is between
|
|
|
|
|
|
// sub-expressions 2 and 3.
|
|
|
|
|
|
const int searchStartPosQuestionMark = posForNodeEnd(children.first());
|
|
|
|
|
|
const int searchEndPosQuestionMark = posForNodeStart(children.at(1));
|
2021-10-13 14:24:25 +02:00
|
|
|
|
QStringView content = subViewEnd(m_docContent, searchStartPosQuestionMark,
|
|
|
|
|
|
searchEndPosQuestionMark);
|
2021-10-04 14:25:35 +02:00
|
|
|
|
const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?")));
|
|
|
|
|
|
if (questionMarkPos == -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const int searchStartPosColon = posForNodeEnd(children.at(1));
|
|
|
|
|
|
const int searchEndPosColon = posForNodeStart(children.at(2));
|
2021-10-13 14:24:25 +02:00
|
|
|
|
content = subViewEnd(m_docContent, searchStartPosColon, searchEndPosColon);
|
2021-10-04 14:25:35 +02:00
|
|
|
|
const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":")));
|
|
|
|
|
|
if (colonPos == -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos;
|
|
|
|
|
|
const int absColonPos = searchStartPosColon + colonPos;
|
|
|
|
|
|
if (absQuestionMarkPos > absColonPos)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
HighlightingResult result;
|
|
|
|
|
|
result.useTextSyles = true;
|
|
|
|
|
|
result.textStyles.mainStyle = C_PUNCTUATION;
|
|
|
|
|
|
result.textStyles.mixinStyles.push_back(C_OPERATOR);
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column);
|
|
|
|
|
|
result.length = 1;
|
|
|
|
|
|
result.kind = CppEditor::SemanticHighlighter::TernaryIf;
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column);
|
|
|
|
|
|
result.kind = CppEditor::SemanticHighlighter::TernaryElse;
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isDeclaration && (node.kind() == "FunctionTemplate"
|
|
|
|
|
|
|| node.kind() == "ClassTemplate")) {
|
|
|
|
|
|
// The child nodes are the template parameters and and the function or class.
|
|
|
|
|
|
// The opening angle bracket is before the first child node, the closing angle
|
|
|
|
|
|
// bracket is before the function child node and after the last param node.
|
|
|
|
|
|
const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate"
|
|
|
|
|
|
? "Function" : "CXXRecord");
|
|
|
|
|
|
const auto functionOrClassIt = std::find_if(children.begin(), children.end(),
|
|
|
|
|
|
[&classOrFunctionKind](const AstNode &n) {
|
|
|
|
|
|
return n.role() == "declaration" && n.kind() == classOrFunctionKind;
|
|
|
|
|
|
});
|
|
|
|
|
|
if (functionOrClassIt == children.end() || functionOrClassIt == children.begin())
|
|
|
|
|
|
return;
|
|
|
|
|
|
const int firstTemplateParamStartPos = posForNodeStart(children.first());
|
|
|
|
|
|
const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1));
|
|
|
|
|
|
const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt);
|
|
|
|
|
|
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
|
|
|
|
|
|
lastTemplateParamEndPos, functionOrClassStartPos);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const auto isTemplateParamDecl = [](const AstNode &node) {
|
|
|
|
|
|
return node.isTemplateParameterDeclaration();
|
|
|
|
|
|
};
|
|
|
|
|
|
if (isDeclaration && node.kind() == "TypeAliasTemplate") {
|
|
|
|
|
|
// Children are one node of type TypeAlias and the template parameters.
|
|
|
|
|
|
// The opening angle bracket is before the first parameter and the closing
|
|
|
|
|
|
// angle bracket is after the last parameter.
|
|
|
|
|
|
// The TypeAlias node seems to appear first in the AST, even though lexically
|
|
|
|
|
|
// is comes after the parameters. We don't rely on the order here.
|
|
|
|
|
|
// Note that there is a second pair of angle brackets. That one is part of
|
|
|
|
|
|
// a TemplateSpecialization, which is handled further below.
|
|
|
|
|
|
const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
|
|
|
|
|
|
isTemplateParamDecl);
|
|
|
|
|
|
if (firstTemplateParam == children.end())
|
|
|
|
|
|
return;
|
|
|
|
|
|
const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
|
|
|
|
|
|
isTemplateParamDecl);
|
|
|
|
|
|
QTC_ASSERT(lastTemplateParam != children.rend(), return);
|
|
|
|
|
|
const auto typeAlias = std::find_if(children.begin(), children.end(),
|
|
|
|
|
|
[](const AstNode &n) { return n.kind() == "TypeAlias"; });
|
|
|
|
|
|
if (typeAlias == children.end())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
|
|
|
|
|
|
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
|
|
|
|
|
|
const int searchEndPos = posForNodeStart(*typeAlias);
|
|
|
|
|
|
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
|
|
|
|
|
|
lastTemplateParamEndPos, searchEndPos);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isDeclaration && node.kind() == "ClassTemplateSpecialization") {
|
|
|
|
|
|
// There is one child of kind TemplateSpecialization. The first pair
|
|
|
|
|
|
// of angle brackets comes before that.
|
|
|
|
|
|
if (children.size() == 1) {
|
|
|
|
|
|
const int childNodePos = posForNodeStart(children.first());
|
|
|
|
|
|
insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isDeclaration && node.kind() == "TemplateTemplateParm") {
|
|
|
|
|
|
// The child nodes are template arguments and template parameters.
|
|
|
|
|
|
// Arguments seem to appear before parameters in the AST, even though they
|
|
|
|
|
|
// come after them in the source code. We don't rely on the order here.
|
|
|
|
|
|
const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
|
|
|
|
|
|
isTemplateParamDecl);
|
|
|
|
|
|
if (firstTemplateParam == children.end())
|
|
|
|
|
|
return;
|
|
|
|
|
|
const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
|
|
|
|
|
|
isTemplateParamDecl);
|
|
|
|
|
|
QTC_ASSERT(lastTemplateParam != children.rend(), return);
|
|
|
|
|
|
const auto templateArg = std::find_if(children.begin(), children.end(),
|
|
|
|
|
|
[](const AstNode &n) { return n.role() == "template argument"; });
|
|
|
|
|
|
|
|
|
|
|
|
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
|
|
|
|
|
|
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
|
|
|
|
|
|
const int searchEndPos = templateArg == children.end()
|
|
|
|
|
|
? nodeEndPos : posForNodeStart(*templateArg);
|
|
|
|
|
|
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
|
|
|
|
|
|
lastTemplateParamEndPos, searchEndPos);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// {static,dynamic,reinterpret}_cast<>().
|
|
|
|
|
|
if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) {
|
|
|
|
|
|
// First child is type, second child is expression.
|
|
|
|
|
|
// The opening angle bracket is before the first child, the closing angle bracket
|
|
|
|
|
|
// is between the two children.
|
|
|
|
|
|
if (children.size() == 2) {
|
|
|
|
|
|
insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()),
|
|
|
|
|
|
posForNodeEnd(children.first()),
|
|
|
|
|
|
posForNodeStart(children.last()));
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (node.kind() == "TemplateSpecialization") {
|
|
|
|
|
|
// First comes the template type, then the template arguments.
|
|
|
|
|
|
// The opening angle bracket is before the first template argument,
|
|
|
|
|
|
// the closing angle bracket is after the last template argument.
|
|
|
|
|
|
// The first child node has no range, so we start searching at the parent node.
|
|
|
|
|
|
if (children.size() >= 2) {
|
|
|
|
|
|
int searchStart2 = posForNodeEnd(children.last());
|
|
|
|
|
|
int searchEnd2 = nodeEndPos;
|
|
|
|
|
|
|
|
|
|
|
|
// There is a weird off-by-one error on the clang side: If there is a
|
|
|
|
|
|
// nested template instantiation *and* there is no space between
|
|
|
|
|
|
// the closing angle brackets, then the inner TemplateSpecialization node's range
|
|
|
|
|
|
// will extend one character too far, covering the outer's closing angle bracket.
|
|
|
|
|
|
// This is what we are correcting for here.
|
|
|
|
|
|
// This issue is tracked at https://github.com/clangd/clangd/issues/871.
|
|
|
|
|
|
if (searchStart2 == searchEnd2)
|
|
|
|
|
|
--searchStart2;
|
|
|
|
|
|
insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)),
|
|
|
|
|
|
searchStart2, searchEnd2);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isExpression && !isDeclaration)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// Operators, overloaded ones in particular.
|
|
|
|
|
|
static const QString operatorPrefix = "operator";
|
|
|
|
|
|
QString detail = node.detail().value_or(QString());
|
|
|
|
|
|
const bool isCallToNew = node.kind() == "CXXNew";
|
|
|
|
|
|
const bool isCallToDelete = node.kind() == "CXXDelete";
|
2022-04-20 17:27:51 +02:00
|
|
|
|
const auto isProperOperator = [&] {
|
|
|
|
|
|
if (isCallToNew || isCallToDelete)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
if (!detail.startsWith(operatorPrefix))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
if (detail == operatorPrefix)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
const QChar nextChar = detail.at(operatorPrefix.length());
|
|
|
|
|
|
return !nextChar.isLetterOrNumber() && nextChar != '_';
|
|
|
|
|
|
};
|
|
|
|
|
|
if (!isProperOperator())
|
2021-10-04 14:25:35 +02:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!isCallToNew && !isCallToDelete)
|
|
|
|
|
|
detail.remove(0, operatorPrefix.length());
|
|
|
|
|
|
|
|
|
|
|
|
HighlightingResult result;
|
|
|
|
|
|
result.useTextSyles = true;
|
|
|
|
|
|
const bool isConversionOp = node.kind() == "CXXConversion";
|
|
|
|
|
|
const bool isOverloaded = !isConversionOp
|
|
|
|
|
|
&& (isDeclaration || ((!isCallToNew && !isCallToDelete)
|
|
|
|
|
|
|| node.arcanaContains("CXXMethod")));
|
|
|
|
|
|
result.textStyles.mainStyle = isConversionOp
|
|
|
|
|
|
? C_PRIMITIVE_TYPE
|
|
|
|
|
|
: isCallToNew || isCallToDelete || detail.at(0).isSpace()
|
|
|
|
|
|
? C_KEYWORD : C_PUNCTUATION;
|
|
|
|
|
|
result.textStyles.mixinStyles.push_back(C_OPERATOR);
|
|
|
|
|
|
if (isOverloaded)
|
|
|
|
|
|
result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR);
|
|
|
|
|
|
if (isDeclaration)
|
|
|
|
|
|
result.textStyles.mixinStyles.push_back(C_DECLARATION);
|
|
|
|
|
|
|
2021-10-13 14:24:25 +02:00
|
|
|
|
const QStringView nodeText = subViewEnd(m_docContent, nodeStartPos, nodeEndPos);
|
2021-10-04 14:25:35 +02:00
|
|
|
|
|
|
|
|
|
|
if (isCallToNew || isCallToDelete) {
|
|
|
|
|
|
result.line = node.range().start().line() + 1;
|
|
|
|
|
|
result.column = node.range().start().character() + 1;
|
|
|
|
|
|
result.length = isCallToNew ? 3 : 6;
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
if (node.arcanaContains("array")) {
|
|
|
|
|
|
const int openingBracketOffset = nodeText.indexOf('[');
|
|
|
|
|
|
if (openingBracketOffset == -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const int closingBracketOffset = nodeText.lastIndexOf(']');
|
|
|
|
|
|
if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
result.textStyles.mainStyle = C_PUNCTUATION;
|
|
|
|
|
|
result.length = 1;
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc,
|
|
|
|
|
|
nodeStartPos + openingBracketOffset,
|
|
|
|
|
|
&result.line, &result.column);
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc,
|
|
|
|
|
|
nodeStartPos + closingBracketOffset,
|
|
|
|
|
|
&result.line, &result.column);
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) {
|
|
|
|
|
|
result.line = node.range().start().line() + 1;
|
|
|
|
|
|
result.column = node.range().start().character() + 1;
|
|
|
|
|
|
result.length = 1;
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
result.line = node.range().end().line() + 1;
|
|
|
|
|
|
result.column = node.range().end().character();
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length();
|
|
|
|
|
|
|
|
|
|
|
|
// The simple case: Call to operator+, +=, * etc.
|
|
|
|
|
|
if (nodeEndPos - nodeStartPos == opStringLen) {
|
|
|
|
|
|
setResultPosFromRange(result, node.range());
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const int prefixOffset = nodeText.indexOf(operatorPrefix);
|
|
|
|
|
|
if (prefixOffset == -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const bool isArray = detail == "[]";
|
|
|
|
|
|
const bool isCall = detail == "()";
|
|
|
|
|
|
const bool isArrayNew = detail == " new[]";
|
|
|
|
|
|
const bool isArrayDelete = detail == " delete[]";
|
|
|
|
|
|
const QStringView searchTerm = isArray || isCall
|
|
|
|
|
|
? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete
|
|
|
|
|
|
? QStringView(detail).chopped(2) : detail;
|
|
|
|
|
|
const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset
|
|
|
|
|
|
+ operatorPrefix.length());
|
|
|
|
|
|
if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const int opStringOffsetInDoc = nodeStartPos + opStringOffset
|
|
|
|
|
|
+ detail.length() - opStringLen;
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column);
|
|
|
|
|
|
result.length = opStringLen;
|
|
|
|
|
|
if (isArray || isCall)
|
|
|
|
|
|
result.length = 1;
|
|
|
|
|
|
else if (isArrayNew || isArrayDelete)
|
|
|
|
|
|
result.length -= 2;
|
|
|
|
|
|
if (!isArray && !isCall)
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
if (!isArray && !isCall && !isArrayNew && !isArrayDelete)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
result.textStyles.mainStyle = C_PUNCTUATION;
|
|
|
|
|
|
result.length = 1;
|
|
|
|
|
|
const int openingParenOffset = nodeText.indexOf(
|
|
|
|
|
|
isCall ? '(' : '[', prefixOffset + operatorPrefix.length());
|
|
|
|
|
|
if (openingParenOffset == -1)
|
|
|
|
|
|
return;
|
|
|
|
|
|
const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1);
|
|
|
|
|
|
if (closingParenOffset == -1 || closingParenOffset < openingParenOffset)
|
|
|
|
|
|
return;
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset,
|
|
|
|
|
|
&result.line, &result.column);
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset,
|
|
|
|
|
|
&result.line, &result.column);
|
|
|
|
|
|
insertResult(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ExtraHighlightingResultsCollector::visitNode(const AstNode &node)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_future.isCanceled())
|
|
|
|
|
|
return;
|
2022-04-14 16:56:06 +02:00
|
|
|
|
const AstNode::FileStatus prevFileStatus = m_currentFileStatus;
|
|
|
|
|
|
m_currentFileStatus = node.fileStatus(m_filePath);
|
|
|
|
|
|
if (m_currentFileStatus == AstNode::FileStatus::Unknown
|
|
|
|
|
|
&& prevFileStatus != AstNode::FileStatus::Ours) {
|
|
|
|
|
|
m_currentFileStatus = prevFileStatus;
|
|
|
|
|
|
}
|
|
|
|
|
|
switch (m_currentFileStatus) {
|
2021-10-14 12:47:24 +02:00
|
|
|
|
case AstNode::FileStatus::Ours:
|
|
|
|
|
|
case AstNode::FileStatus::Unknown:
|
|
|
|
|
|
collectFromNode(node);
|
|
|
|
|
|
[[fallthrough]];
|
2021-11-17 13:39:54 +01:00
|
|
|
|
case AstNode::FileStatus::Foreign:
|
2021-10-14 12:47:24 +02:00
|
|
|
|
case ClangCodeModel::Internal::AstNode::FileStatus::Mixed: {
|
|
|
|
|
|
const auto children = node.children();
|
|
|
|
|
|
if (!children)
|
|
|
|
|
|
return;
|
|
|
|
|
|
for (const AstNode &childNode : *children)
|
|
|
|
|
|
visitNode(childNode);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-04-14 16:56:06 +02:00
|
|
|
|
m_currentFileStatus = prevFileStatus;
|
2021-10-04 14:25:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-28 11:59:48 +02:00
|
|
|
|
bool ClangdClient::FollowSymbolData::defLinkIsAmbiguous() const
|
|
|
|
|
|
{
|
2021-11-24 16:03:38 +01:00
|
|
|
|
// Even if the call is to a virtual function, it might not be ambiguous:
|
|
|
|
|
|
// class A { virtual void f(); }; class B : public A { void f() override { A::f(); } };
|
|
|
|
|
|
if (!cursorNode->mightBeAmbiguousVirtualCall() && !cursorNode->isPureVirtualDeclaration())
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// If we have up-to-date highlighting info, we know whether we are dealing with
|
|
|
|
|
|
// a virtual call.
|
2021-10-28 11:59:48 +02:00
|
|
|
|
if (editorWidget) {
|
2022-04-21 13:02:09 +02:00
|
|
|
|
const auto highlightingData =
|
|
|
|
|
|
q->d->highlightingData.constFind(editorWidget->textDocument());
|
|
|
|
|
|
if (highlightingData != q->d->highlightingData.constEnd()
|
|
|
|
|
|
&& highlightingData->virtualRanges.second == docRevision) {
|
2021-10-28 11:59:48 +02:00
|
|
|
|
const auto matcher = [cursorRange = cursorNode->range()](const Range &r) {
|
|
|
|
|
|
return cursorRange.overlaps(r);
|
|
|
|
|
|
};
|
2022-04-21 13:02:09 +02:00
|
|
|
|
return Utils::contains(highlightingData->virtualRanges.first, matcher);
|
2021-10-28 11:59:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-24 16:03:38 +01:00
|
|
|
|
// Otherwise, we accept potentially doing more work than needed rather than not catching
|
|
|
|
|
|
// possible overrides.
|
|
|
|
|
|
return true;
|
2021-10-28 11:59:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-16 14:12:41 +01:00
|
|
|
|
class MemoryTree : public JsonObject
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
using JsonObject::JsonObject;
|
|
|
|
|
|
|
|
|
|
|
|
// number of bytes used, including child components
|
|
|
|
|
|
qint64 total() const { return qint64(typedValue<double>(totalKey())); }
|
|
|
|
|
|
|
|
|
|
|
|
// number of bytes used, excluding child components
|
|
|
|
|
|
qint64 self() const { return qint64(typedValue<double>(selfKey())); }
|
|
|
|
|
|
|
|
|
|
|
|
// named child components
|
|
|
|
|
|
using NamedComponent = std::pair<MemoryTree, QString>;
|
|
|
|
|
|
QList<NamedComponent> children() const
|
|
|
|
|
|
{
|
|
|
|
|
|
QList<NamedComponent> components;
|
|
|
|
|
|
const auto obj = operator const QJsonObject &();
|
|
|
|
|
|
for (auto it = obj.begin(); it != obj.end(); ++it) {
|
|
|
|
|
|
if (it.key() == totalKey() || it.key() == selfKey())
|
|
|
|
|
|
continue;
|
|
|
|
|
|
components << std::make_pair(MemoryTree(it.value()), it.key());
|
|
|
|
|
|
}
|
|
|
|
|
|
return components;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
static QString totalKey() { return QLatin1String("_total"); }
|
|
|
|
|
|
static QString selfKey() { return QLatin1String("_self"); }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class MemoryTreeItem : public Utils::TreeItem
|
|
|
|
|
|
{
|
|
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(MemoryTreeItem)
|
|
|
|
|
|
public:
|
|
|
|
|
|
MemoryTreeItem(const QString &displayName, const MemoryTree &tree)
|
|
|
|
|
|
: m_displayName(displayName), m_bytesUsed(tree.total())
|
|
|
|
|
|
{
|
|
|
|
|
|
for (const MemoryTree::NamedComponent &component : tree.children())
|
|
|
|
|
|
appendChild(new MemoryTreeItem(component.second, component.first));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
QVariant data(int column, int role) const override
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (role) {
|
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
|
if (column == 0)
|
|
|
|
|
|
return m_displayName;
|
|
|
|
|
|
return memString();
|
|
|
|
|
|
case Qt::TextAlignmentRole:
|
|
|
|
|
|
if (column == 1)
|
|
|
|
|
|
return Qt::AlignRight;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString memString() const
|
|
|
|
|
|
{
|
|
|
|
|
|
static const QList<std::pair<int, QString>> factors{
|
|
|
|
|
|
std::make_pair(1000000000, QString("GB")),
|
|
|
|
|
|
std::make_pair(1000000, QString("MB")),
|
|
|
|
|
|
std::make_pair(1000, QString("KB")),
|
|
|
|
|
|
};
|
|
|
|
|
|
for (const auto &factor : factors) {
|
|
|
|
|
|
if (m_bytesUsed > factor.first)
|
|
|
|
|
|
return QString::number(qint64(std::round(double(m_bytesUsed) / factor.first)))
|
|
|
|
|
|
+ ' ' + factor.second;
|
|
|
|
|
|
}
|
|
|
|
|
|
return QString::number(m_bytesUsed) + " B";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const QString m_displayName;
|
|
|
|
|
|
const qint64 m_bytesUsed;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class MemoryTreeModel : public Utils::BaseTreeModel
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
MemoryTreeModel(QObject *parent) : BaseTreeModel(parent)
|
|
|
|
|
|
{
|
2022-02-16 14:32:48 +01:00
|
|
|
|
setHeader({MemoryUsageWidget::tr("Component"), MemoryUsageWidget::tr("Total Memory")});
|
2021-11-16 14:12:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void update(const MemoryTree &tree)
|
|
|
|
|
|
{
|
|
|
|
|
|
setRootItem(new MemoryTreeItem({}, tree));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
MemoryUsageWidget::MemoryUsageWidget(ClangdClient *client)
|
|
|
|
|
|
: m_client(client), m_model(new MemoryTreeModel(this))
|
|
|
|
|
|
{
|
|
|
|
|
|
setupUi();
|
|
|
|
|
|
getMemoryTree();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-09 09:58:48 +01:00
|
|
|
|
MemoryUsageWidget::~MemoryUsageWidget()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_currentRequest.has_value())
|
|
|
|
|
|
m_client->cancelRequest(m_currentRequest.value());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-16 14:12:41 +01:00
|
|
|
|
void MemoryUsageWidget::setupUi()
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto layout = new QVBoxLayout(this);
|
|
|
|
|
|
m_view.setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
|
|
m_view.header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
|
|
|
|
|
m_view.header()->setStretchLastSection(false);
|
|
|
|
|
|
m_view.setModel(m_model);
|
|
|
|
|
|
layout->addWidget(&m_view);
|
|
|
|
|
|
connect(&m_view, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) {
|
|
|
|
|
|
QMenu menu;
|
|
|
|
|
|
menu.addAction(tr("Update"), [this] { getMemoryTree(); });
|
|
|
|
|
|
menu.exec(m_view.mapToGlobal(pos));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MemoryUsageWidget::getMemoryTree()
|
|
|
|
|
|
{
|
|
|
|
|
|
Request<MemoryTree, std::nullptr_t, JsonObject> request("$/memoryUsage", {});
|
|
|
|
|
|
request.setResponseCallback([this](decltype(request)::Response response) {
|
2022-02-09 09:58:48 +01:00
|
|
|
|
m_currentRequest.reset();
|
2021-11-16 14:12:41 +01:00
|
|
|
|
qCDebug(clangdLog) << "received memory usage response";
|
|
|
|
|
|
if (const auto result = response.result())
|
|
|
|
|
|
m_model->update(*result);
|
|
|
|
|
|
});
|
|
|
|
|
|
qCDebug(clangdLog) << "sending memory usage request";
|
2022-02-09 09:58:48 +01:00
|
|
|
|
m_currentRequest = request.id();
|
2021-11-16 14:12:41 +01:00
|
|
|
|
m_client->sendContent(request, ClangdClient::SendDocUpdates::Ignore);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-20 14:42:29 +02:00
|
|
|
|
} // namespace Internal
|
|
|
|
|
|
} // namespace ClangCodeModel
|
2021-05-18 12:59:15 +02:00
|
|
|
|
|
|
|
|
|
|
Q_DECLARE_METATYPE(ClangCodeModel::Internal::ReplacementData)
|