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-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>
# include <cplusplus/FindUsages.h>
2021-04-20 14:42:29 +02:00
# include <cpptools/cppcodemodelsettings.h>
2021-05-19 15:51:25 +02:00
# include <cpptools/cppeditorwidgetinterface.h>
2021-04-21 14:29:49 +02:00
# include <cpptools/cppfindreferences.h>
2021-04-20 14:42:29 +02:00
# include <cpptools/cpptoolsreuse.h>
2021-05-19 15:51:25 +02:00
# include <cpptools/cppvirtualfunctionassistprovider.h>
# include <cpptools/cppvirtualfunctionproposalitem.h>
2021-04-20 14:42:29 +02:00
# include <languageclient/languageclientinterface.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>
# include <texteditor/texteditor.h>
2021-05-18 12:59:15 +02:00
# include <utils/algorithm.h>
2021-04-20 14:42:29 +02:00
2021-05-18 12:59:15 +02:00
# include <QCheckBox>
2021-04-21 14:29:49 +02:00
# include <QFile>
# include <QHash>
2021-05-19 15:51:25 +02:00
# include <QPair>
2021-04-21 14:29:49 +02:00
# include <QPointer>
# include <QRegularExpression>
2021-05-19 15:51:25 +02:00
# include <set>
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-04-20 14:42:29 +02:00
namespace ClangCodeModel {
namespace Internal {
static Q_LOGGING_CATEGORY ( clangdLog , " qtc.clangcodemodel.clangd " , QtWarningMsg ) ;
2021-04-20 15:46:35 +02:00
static QString indexingToken ( ) { return " backgroundIndexProgress " ; }
2021-04-21 14:29:49 +02:00
class AstParams : public JsonObject
{
public :
AstParams ( ) { }
2021-05-19 15:51:25 +02:00
AstParams ( const TextDocumentIdentifier & document , const Range & range )
{
setTextDocument ( document ) ;
setRange ( range ) ;
}
2021-04-21 14:29:49 +02:00
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 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
{
return detail ( ) & & detail ( ) . value ( ) = = s ;
}
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-04-21 14:29:49 +02:00
QString type ( ) const
{
const Utils : : optional < QString > arcanaString = arcana ( ) ;
if ( ! arcanaString )
return { } ;
const int quote1Offset = arcanaString - > indexOf ( ' \' ' ) ;
if ( quote1Offset = = - 1 )
return { } ;
const int quote2Offset = arcanaString - > indexOf ( ' \' ' , quote1Offset + 1 ) ;
if ( quote2Offset = = - 1 )
return { } ;
return arcanaString - > mid ( quote1Offset + 1 , quote2Offset - quote1Offset - 1 ) ;
}
// 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 ) ;
const int ptrRefCount = theType . count ( ' * ' ) + theType . count ( ' & ' ) ;
const int constCount = theType . count ( " const " ) ;
if ( ptrRefCount = = 0 )
return constCount > 0 | | detailIs ( " LValueToRValue " ) ;
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 ) ;
}
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-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 ) ;
}
} ;
static QList < AstNode > getAstPath ( const AstNode & root , const Range & range )
{
QList < AstNode > path ;
QList < AstNode > queue { root } ;
bool isRoot = true ;
while ( ! queue . isEmpty ( ) ) {
AstNode curNode = queue . takeFirst ( ) ;
if ( ! isRoot & & ! curNode . hasRange ( ) )
continue ;
if ( curNode . range ( ) = = range )
return path < < curNode ;
if ( isRoot | | curNode . range ( ) . contains ( range ) ) {
path < < curNode ;
const auto children = curNode . children ( ) ;
if ( ! children )
break ;
queue = children . value ( ) ;
}
isRoot = false ;
}
return path ;
}
static Usage : : Type getUsageType ( const QList < AstNode > & path )
{
bool potentialWrite = false ;
const bool symbolIsDataType = path . last ( ) . role ( ) = = " type " & & path . last ( ) . kind ( ) = = " Record " ;
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 ;
if ( pathIt - > kind ( ) = = " Call " | | pathIt - > kind ( ) = = " CXXMemberCall " )
return potentialWrite ? Usage : : Type : : WritableRef : Usage : : Type : : Read ;
if ( ( pathIt - > kind ( ) = = " DeclRef " | | pathIt - > kind ( ) = = " Member " )
& & pathIt - > arcanaContains ( " lvalue " ) ) {
potentialWrite = true ;
}
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 ;
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 ;
return potentialWrite ? Usage : : Type : : WritableRef : Usage : : Type : : Read ;
}
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 ;
}
class AstRequest : public Request < AstNode , std : : nullptr_t , AstParams >
{
public :
using Request : : Request ;
explicit AstRequest ( const AstParams & params ) : Request ( " textDocument/ast " , params ) { }
} ;
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 & params )
: Request ( " textDocument/symbolInfo " , params ) { }
} ;
2021-04-20 14:42:29 +02:00
static BaseClientInterface * clientInterface ( const Utils : : FilePath & jsonDbDir )
{
2021-04-30 08:25:10 +02:00
Utils : : CommandLine cmd { CppTools : : codeModelSettings ( ) - > clangdFilePath ( ) ,
2021-04-21 14:29:49 +02:00
{ " --background-index " , " --limit-results=0 " } } ;
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-04-20 14:42:29 +02:00
if ( clangdLog ( ) . isDebugEnabled ( ) )
2021-04-30 08:25:10 +02:00
cmd . addArgs ( { " --log=verbose " , " --pretty " } ) ;
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-05-26 17:24:18 +02:00
bool categorize = CppTools : : 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 > ;
class ClangdClient : : VirtualFunctionAssistProcessor : public TextEditor : : IAssistProcessor
{
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 ( ) ;
private :
TextEditor : : IAssistProposal * perform ( const TextEditor : : AssistInterface * ) override
{
return nullptr ;
}
2021-05-31 11:21:30 +02:00
TextEditor : : IAssistProposal * immediateProposal ( const TextEditor : : AssistInterface * ) override
{
return createProposal ( false ) ;
}
void resetData ( ) ;
2021-05-19 15:51:25 +02:00
2021-05-27 16:32:24 +02:00
TextEditor : : IAssistProposal * immediateProposalImpl ( ) const ;
2021-05-31 11:21:30 +02:00
TextEditor : : IAssistProposal * createProposal ( bool final ) const ;
CppTools : : VirtualFunctionProposalItem * createEntry ( const QString & name ,
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 ;
} ;
class ClangdClient : : VirtualFunctionAssistProvider : public TextEditor : : IAssistProvider
{
public :
VirtualFunctionAssistProvider ( ClangdClient : : Private * data ) : m_data ( data ) { }
private :
RunType runType ( ) const override { return Asynchronous ; }
TextEditor : : IAssistProcessor * createProcessor ( ) const override ;
ClangdClient : : Private * const m_data ;
} ;
class ClangdClient : : FollowSymbolData {
public :
FollowSymbolData ( ClangdClient * q , quint64 id , const QTextCursor & cursor ,
CppTools : : CppEditorWidgetInterface * editorWidget ,
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 ) ,
openInSplit ( openInSplit ) { }
~ FollowSymbolData ( )
{
closeTempDocuments ( ) ;
if ( virtualFuncAssistProcessor )
virtualFuncAssistProcessor - > cancel ( ) ;
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 ( )
{
for ( const Utils : : FilePath & fp : qAsConst ( openedFiles ) )
q - > closeExtraFile ( fp ) ;
openedFiles . clear ( ) ;
}
2021-05-27 16:32:24 +02:00
bool isEditorWidgetStillAlive ( ) const
{
return Utils : : anyOf ( EditorManager : : visibleEditors ( ) , [ this ] ( IEditor * editor ) {
const auto textEditor = qobject_cast < TextEditor : : BaseTextEditor * > ( editor ) ;
return textEditor & & dynamic_cast < CppTools : : CppEditorWidgetInterface * > (
textEditor - > editorWidget ( ) ) = = editorWidget ;
} ) ;
}
2021-05-19 15:51:25 +02:00
ClangdClient * const q ;
const quint64 id ;
const QTextCursor cursor ;
CppTools : : CppEditorWidgetInterface * const editorWidget ;
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-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-05-19 15:51:25 +02:00
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 :
SwitchDeclDefData ( quint64 id , TextEditor : : TextDocument * doc , const QTextCursor & cursor ,
CppTools : : CppEditorWidgetInterface * editorWidget ,
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 ;
const QPointer < TextEditor : : TextDocument > document ;
const DocumentUri uri ;
const QTextCursor cursor ;
CppTools : : CppEditorWidgetInterface * const editorWidget ;
Utils : : ProcessLinkCallback callback ;
Utils : : optional < DocumentSymbolsResult > docSymbols ;
Utils : : optional < AstNode > ast ;
} ;
2021-05-19 15:51:25 +02:00
2021-04-21 14:29:49 +02:00
class ClangdClient : : Private
{
public :
Private ( ClangdClient * q ) : q ( q ) { }
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 ( ) ;
void closeTempDocuments ( ) ;
2021-05-31 15:57:44 +02:00
void handleDeclDefSwitchReplies ( ) ;
2021-04-21 14:29:49 +02:00
ClangdClient * const q ;
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-04-21 14:29:49 +02:00
Utils : : optional < QVersionNumber > versionNumber ;
quint64 nextFindUsagesKey = 0 ;
2021-05-19 15:51:25 +02:00
quint64 nextFollowSymbolId = 0 ;
2021-05-31 15:57:44 +02:00
quint64 nextSwitchDeclDefId = 0 ;
2021-04-21 14:29:49 +02:00
bool isFullyIndexed = false ;
bool isTesting = false ;
} ;
2021-05-18 12:59:15 +02:00
ClangdClient : : ClangdClient ( Project * project , const Utils : : FilePath & jsonDbDir )
2021-04-21 14:29:49 +02:00
: Client ( clientInterface ( jsonDbDir ) ) , d ( new Private ( this ) )
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 ) ;
LanguageServerProtocol : : ClientCapabilities caps = Client : : defaultClientCapabilities ( ) ;
caps . clearExperimental ( ) ;
setClientCapabilities ( caps ) ;
setLocatorsEnabled ( false ) ;
2021-04-20 15:46:35 +02:00
setProgressTitleForToken ( indexingToken ( ) , tr ( " Parsing C/C++ Files (clangd) " ) ) ;
2021-04-20 14:42:29 +02:00
setCurrentProject ( project ) ;
2021-05-19 13:22:49 +02:00
connect ( this , & Client : : workDone , this , [ this , 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
emit project - > indexingFinished ( " Indexer.Clangd " ) ;
# 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 ) ;
sendContent ( DidOpenTextDocumentNotification ( DidOpenTextDocumentParams ( item ) ) ) ;
}
void ClangdClient : : closeExtraFile ( const Utils : : FilePath & filePath )
{
sendContent ( DidCloseTextDocumentNotification ( DidCloseTextDocumentParams (
TextDocumentIdentifier { DocumentUri : : fromFilePath ( filePath ) } ) ) ) ;
}
2021-05-18 12:59:15 +02:00
void ClangdClient : : findUsages ( TextEditor : : TextDocument * document , const QTextCursor & cursor ,
const Utils : : optional < QString > & replacement )
2021-04-21 14:29:49 +02:00
{
QTextCursor termCursor ( cursor ) ;
termCursor . select ( QTextCursor : : WordUnderCursor ) ;
const QString searchTerm = termCursor . selectedText ( ) ; // TODO: This will be wrong for e.g. operators. Use a Symbol info request to get the real symbol string.
if ( searchTerm . isEmpty ( ) )
return ;
ReferencesData refData ;
2021-05-18 12:59:15 +02:00
refData . key = d - > nextFindUsagesKey + + ;
if ( replacement ) {
ReplacementData replacementData ;
replacementData . oldSymbolName = searchTerm ;
replacementData . newSymbolName = * replacement ;
if ( replacementData . newSymbolName . isEmpty ( ) )
replacementData . newSymbolName = replacementData . oldSymbolName ;
refData . replacementData = replacementData ;
}
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 )
refData . search - > setFilter ( new CppTools : : 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 ) ;
d - > runningFindUsages . insert ( refData . key , refData ) ;
const Utils : : optional < MessageId > requestId = symbolSupport ( ) . findUsages (
document , cursor , [ this , key = refData . key ] ( const QList < Location > & locations ) {
d - > handleFindUsagesResult ( key , locations ) ;
} ) ;
if ( ! requestId ) {
d - > finishSearch ( refData , false ) ;
return ;
}
connect ( refData . search , & SearchResult : : cancelled , this , [ this , requestId , key = refData . key ] {
const auto refData = d - > runningFindUsages . find ( key ) ;
if ( refData = = d - > runningFindUsages . end ( ) )
return ;
cancelRequest ( * requestId ) ;
2021-05-18 12:59:15 +02:00
refData - > canceled = true ;
2021-04-21 14:29:49 +02:00
refData - > search - > disconnect ( this ) ;
d - > finishSearch ( * refData , true ) ;
} ) ;
}
void ClangdClient : : enableTesting ( ) { d - > isTesting = true ; }
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 ( ) ;
}
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 ) ;
} ) ;
for ( const Location & loc : locations ) // TODO: Can contain duplicates. Rather fix in clang than work around it here.
refData - > fileData [ loc . uri ( ) ] . rangesAndLineText < < qMakePair ( loc . range ( ) , QString ( ) ) ; // TODO: Can we assume that locations for the same file are grouped?
for ( auto it = refData - > fileData . begin ( ) ; it ! = refData - > fileData . end ( ) ; + + it ) {
2021-05-18 07:05:39 +02:00
const QStringList lines = SymbolSupport : : getFileContents ( it . key ( ) . toFilePath ( ) ) ;
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 ) ;
}
}
qCDebug ( clangdLog ) < < " document count is " < < refData - > fileData . size ( ) ;
2021-05-18 12:59:15 +02:00
if ( refData - > replacementData | | q - > versionNumber ( ) < QVersionNumber ( 13 )
2021-05-26 17:24:18 +02:00
| | ! 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 ) {
const bool extraOpen = ! q - > documentForFilePath ( it . key ( ) . toFilePath ( ) ) ;
if ( extraOpen )
q - > openExtraFile ( it . key ( ) . toFilePath ( ) , it - > fileContent ) ;
it - > fileContent . clear ( ) ;
AstParams params ;
params . setTextDocument ( TextDocumentIdentifier ( it . key ( ) ) ) ;
AstRequest request ( params ) ;
request . setResponseCallback ( [ this , key , loc = it . key ( ) , request ]
( AstRequest : : Response response ) {
qCDebug ( clangdLog ) < < " AST response for " < < loc . toFilePath ( ) ;
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 ] ;
const auto result = response . result ( ) ;
if ( result )
data . ast = * result ;
refData - > pendingAstRequests . removeOne ( request . id ( ) ) ;
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 ) ;
}
} ) ;
qCDebug ( clangdLog ) < < " requesting AST for " < < it . key ( ) . toFilePath ( ) ;
refData - > pendingAstRequests < < request . id ( ) ;
q - > sendContent ( request ) ;
if ( extraOpen )
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 )
{
const QStringList fileNames = TextEditor : : BaseFileFind : : replaceAll ( newSymbolName , checkedItems ,
preserveCase ) ;
if ( ! fileNames . isEmpty ( ) )
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 ( ) )
CppTools : : renameFilesForSymbol ( replacementData . oldSymbolName , newSymbolName , fileNodes ) ;
}
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 ) ) ;
item . setStyle ( CppTools : : colorStyleForUsageType ( usageType ) ) ;
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 ) {
refData . replacementData - > fileRenameCandidates < < file ; // TODO: We want to do this only for types. Use SymbolInformation once we have it.
}
}
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-05-19 15:51:25 +02:00
void ClangdClient : : followSymbol (
TextEditor : : TextDocument * document ,
const QTextCursor & cursor ,
CppTools : : CppEditorWidgetInterface * editorWidget ,
Utils : : ProcessLinkCallback & & callback ,
bool resolveTarget ,
bool openInSplit
)
{
QTC_ASSERT ( documentOpen ( document ) , openDocument ( document ) ) ;
if ( ! resolveTarget ) {
d - > followSymbolData . reset ( ) ;
symbolSupport ( ) . findLinkAt ( document , cursor , std : : move ( callback ) , false ) ;
return ;
}
2021-05-19 13:22:49 +02:00
qCDebug ( clangdLog ) < < " follow symbol requested " < < document - > filePath ( )
< < cursor . blockNumber ( ) < < cursor . positionInBlock ( ) ;
2021-05-19 15:51:25 +02:00
d - > followSymbolData . emplace ( this , + + d - > nextFollowSymbolId , cursor , editorWidget ,
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 ;
if ( d - > followSymbolData - > cursorNode . isValid ( ) )
d - > handleGotoDefinitionResult ( ) ;
} ;
symbolSupport ( ) . findLinkAt ( document , cursor , std : : move ( gotoDefCallback ) , true ) ;
AstRequest astRequest ( AstParams ( TextDocumentIdentifier ( d - > followSymbolData - > uri ) ,
Range ( cursor ) ) ) ;
astRequest . setResponseCallback ( [ this , id = d - > followSymbolData - > id ] (
const AstRequest : : Response & response ) {
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 ;
const auto result = response . result ( ) ;
if ( ! result ) {
d - > followSymbolData . reset ( ) ;
return ;
}
d - > followSymbolData - > cursorNode = * result ;
if ( d - > followSymbolData - > defLink . hasValidTarget ( ) )
d - > handleGotoDefinitionResult ( ) ;
} ) ;
sendContent ( astRequest ) ;
}
2021-05-31 15:57:44 +02:00
void ClangdClient : : switchDeclDef ( TextEditor : : TextDocument * document , const QTextCursor & cursor ,
CppTools : : CppEditorWidgetInterface * editorWidget ,
Utils : : ProcessLinkCallback & & callback )
{
QTC_ASSERT ( documentOpen ( document ) , openDocument ( document ) ) ;
qCDebug ( clangdLog ) < < " switch decl/dev requested " < < document - > filePath ( )
< < cursor . blockNumber ( ) < < cursor . positionInBlock ( ) ;
d - > switchDeclDefData . emplace ( + + d - > nextSwitchDeclDefId , document , cursor , editorWidget ,
std : : move ( callback ) ) ;
// Retrieve AST and document symbols.
AstParams astParams ;
astParams . setTextDocument ( TextDocumentIdentifier ( d - > switchDeclDefData - > uri ) ) ;
AstRequest astRequest ( astParams ) ;
astRequest . setResponseCallback ( [ this , id = d - > switchDeclDefData - > id ]
( const AstRequest : : Response & response ) {
qCDebug ( clangdLog ) < < " received ast for decl/def switch " ;
if ( ! d - > switchDeclDefData | | d - > switchDeclDefData - > id ! = id
| | ! d - > switchDeclDefData - > document )
return ;
const auto result = response . result ( ) ;
if ( ! result ) {
d - > switchDeclDefData . reset ( ) ;
return ;
}
d - > switchDeclDefData - > ast = * result ;
if ( d - > switchDeclDefData - > docSymbols )
d - > handleDeclDefSwitchReplies ( ) ;
} ) ;
sendContent ( astRequest ) ;
documentSymbolCache ( ) - > requestSymbols ( d - > switchDeclDefData - > uri ) ;
}
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-05-27 16:32:24 +02:00
if ( ! followSymbolData - > cursorNode . mightBeAmbiguousVirtualCall ( )
2021-05-19 15:51:25 +02:00
& & ! followSymbolData - > cursorNode . isPureVirtualDeclaration ( ) ) {
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 )
{
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 ) ;
} ) ;
q - > sendContent ( req ) ;
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
& & followSymbolData - > isEditorWidgetStillAlive ( ) ) {
2021-05-19 15:51:25 +02:00
followSymbolData - > editorWidget - > invokeTextEditorWidgetAssist (
TextEditor : : FollowSymbol , & followSymbolData - > virtualFuncAssistProvider ) ;
}
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-05-28 13:12:00 +02:00
q - > sendContent ( symReq ) ;
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 ;
q - > sendContent ( defReq ) ;
2021-05-19 15:51:25 +02:00
}
2021-05-27 16:32:24 +02:00
const DocumentUri defLinkUri
= DocumentUri : : fromFilePath ( followSymbolData - > defLink . targetFilePath ) ;
const Position defLinkPos ( followSymbolData - > defLink . targetLine - 1 ,
followSymbolData - > defLink . targetColumn ) ;
AstRequest astRequest ( AstParams ( TextDocumentIdentifier ( defLinkUri ) ,
Range ( defLinkPos , defLinkPos ) ) ) ;
astRequest . setResponseCallback ( [ this , id = followSymbolData - > id ] (
const AstRequest : : Response & response ) {
qCDebug ( clangdLog ) < < " received ast response for def link " ;
if ( ! followSymbolData | | followSymbolData - > id ! = id )
return ;
const auto result = response . result ( ) ;
if ( result )
followSymbolData - > defLinkNode = * result ;
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-05-27 16:32:24 +02:00
} ) ;
qCDebug ( clangdLog ) < < " sending ast request for def link " ;
q - > sendContent ( astRequest ) ;
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 ...
if ( clangdLog ( ) . isDebugEnabled ( ) )
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-05-19 15:51:25 +02:00
void ClangdClient : : VirtualFunctionAssistProcessor : : cancel ( )
2021-05-31 11:21:30 +02:00
{
resetData ( ) ;
}
void ClangdClient : : VirtualFunctionAssistProcessor : : update ( )
{
if ( ! m_data - > followSymbolData - > isEditorWidgetStillAlive ( ) )
return ;
setAsyncProposalAvailable ( createProposal ( false ) ) ;
}
void ClangdClient : : VirtualFunctionAssistProcessor : : finalize ( )
{
if ( ! m_data - > followSymbolData - > isEditorWidgetStillAlive ( ) )
return ;
const auto proposal = createProposal ( true ) ;
if ( m_data - > followSymbolData - > editorWidget - > inTestMode ) {
m_data - > followSymbolData - > symbolsToDisplay . clear ( ) ;
const auto immediateProposal = createProposal ( false ) ;
m_data - > followSymbolData - > editorWidget - > setProposals ( immediateProposal , proposal ) ;
} else {
setAsyncProposalAvailable ( proposal ) ;
}
resetData ( ) ;
}
void ClangdClient : : VirtualFunctionAssistProcessor : : resetData ( )
2021-05-19 15:51:25 +02:00
{
if ( ! m_data )
return ;
m_data - > followSymbolData - > virtualFuncAssistProcessor = nullptr ;
m_data - > followSymbolData . reset ( ) ;
m_data = nullptr ;
}
2021-05-31 11:21:30 +02:00
TextEditor : : IAssistProposal * ClangdClient : : VirtualFunctionAssistProcessor : : createProposal ( bool final ) const
2021-05-19 15:51:25 +02:00
{
2021-05-31 11:21:30 +02:00
QTC_ASSERT ( m_data & & m_data - > followSymbolData , return nullptr ) ;
2021-05-19 15:51:25 +02:00
QList < TextEditor : : AssistProposalItemInterface * > items ;
2021-05-31 11:21:30 +02:00
bool needsBaseDeclEntry = ! m_data - > followSymbolData - > defLinkNode . range ( )
. contains ( Position ( m_data - > followSymbolData - > cursor ) ) ;
2021-05-19 15:51:25 +02:00
for ( const SymbolData & symbol : qAsConst ( m_data - > followSymbolData - > symbolsToDisplay ) ) {
2021-05-28 13:12:00 +02:00
Utils : : Link link = symbol . second ;
2021-05-31 11:21:30 +02:00
if ( m_data - > followSymbolData - > defLink = = link ) {
if ( ! needsBaseDeclEntry )
continue ;
needsBaseDeclEntry = false ;
} else {
2021-05-28 13:12:00 +02:00
const Utils : : Link defLink = m_data - > followSymbolData - > declDefMap . value ( symbol . second ) ;
if ( defLink . hasValidTarget ( ) )
link = defLink ;
}
2021-05-31 11:21:30 +02:00
items < < createEntry ( symbol . first , link ) ;
2021-05-19 15:51:25 +02:00
}
2021-05-31 11:21:30 +02:00
if ( needsBaseDeclEntry )
items < < createEntry ( { } , m_data - > followSymbolData - > defLink ) ;
if ( ! final ) {
const auto infoItem = new CppTools : : VirtualFunctionProposalItem ( { } , false ) ;
infoItem - > setText ( ClangdClient : : tr ( " collecting overrides ... " ) ) ;
infoItem - > setOrder ( - 1 ) ;
items < < infoItem ;
2021-05-27 16:32:24 +02:00
}
2021-05-19 15:51:25 +02:00
2021-05-31 11:21:30 +02:00
return new CppTools : : VirtualFunctionProposal (
m_data - > followSymbolData - > cursor . position ( ) ,
items , m_data - > followSymbolData - > openInSplit ) ;
2021-05-27 16:32:24 +02:00
}
2021-05-31 11:21:30 +02:00
CppTools : : VirtualFunctionProposalItem *
ClangdClient : : VirtualFunctionAssistProcessor : : createEntry ( const QString & name ,
const Utils : : Link & link ) const
2021-05-19 15:51:25 +02:00
{
2021-05-31 11:21:30 +02:00
const auto item = new CppTools : : 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 " ;
}
2021-05-19 15:51:25 +02:00
}
2021-05-31 11:21:30 +02:00
item - > setText ( text ) ;
return item ;
2021-05-19 15:51:25 +02:00
}
TextEditor : : IAssistProcessor * ClangdClient : : VirtualFunctionAssistProvider : : createProcessor ( ) const
{
return m_data - > followSymbolData - > virtualFuncAssistProcessor
= new VirtualFunctionAssistProcessor ( m_data ) ;
}
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 )