[clangd] Add "Go to Declaration" functionality

Summary: This change allows to navigate to most identifiers' declarations in code. This is a first step towards implementing "Go to Definition". It reuses clangIndex in order to detect which occurrences corresponds to the position requested. The occurrences' Decls are then used to generate locations suitable for navigating to the declarations.

Reviewers: krasimir, bkramer, ilya-biryukov

Reviewed By: ilya-biryukov

Subscribers: cfe-commits, mgorny

Tags: #clang-tools-extra

Differential Revision: https://reviews.llvm.org/D34269

llvm-svn: 306558
This commit is contained in:
Marc-Andre Laperle 2017-06-28 16:12:10 +00:00
parent 93cf232338
commit 2cbf03728a
12 changed files with 428 additions and 4 deletions

View File

@ -18,6 +18,7 @@ add_clang_library(clangDaemon
clangBasic
clangFormat
clangFrontend
clangIndex
clangSema
clangTooling
clangToolingCore

View File

@ -69,6 +69,8 @@ public:
JSONOutput &Out) override;
void onCompletion(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) override;
void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) override;
private:
ClangdLSPServer &LangServer;
@ -84,7 +86,8 @@ void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
"documentRangeFormattingProvider": true,
"documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
"codeActionProvider": true,
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]},
"definitionProvider": true
}}})");
}
@ -191,6 +194,25 @@ void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
R"(,"result":[)" + Completions + R"(]})");
}
void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
auto Items = LangServer.Server.findDefinitions(
Params.textDocument.uri.file,
Position{Params.position.line, Params.position.character}).Value;
std::string Locations;
for (const auto &Item : Items) {
Locations += Location::unparse(Item);
Locations += ",";
}
if (!Locations.empty())
Locations.pop_back();
Out.writeMessage(
R"({"jsonrpc":"2.0","id":)" + ID.str() +
R"(,"result":[)" + Locations + R"(]})");
}
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously)
: Out(Out), DiagConsumer(*this),
Server(CDB, DiagConsumer, FSProvider, RunSynchronously) {}

View File

@ -271,3 +271,17 @@ std::string ClangdServer::dumpAST(PathRef File) {
});
return DumpFuture.get();
}
Tagged<std::vector<Location>>
ClangdServer::findDefinitions(PathRef File, Position Pos) {
auto FileContents = DraftMgr.getDraft(File);
assert(FileContents.Draft && "findDefinitions is called for non-added document");
std::vector<Location> Result;
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
Units.runOnUnit(File, *FileContents.Draft, ResourceDir, CDB, PCHs,
TaggedFS.Value, [&](ClangdUnit &Unit) {
Result = Unit.findDefinitions(Pos);
});
return make_tagged(std::move(Result), TaggedFS.Tag);
}

View File

@ -179,6 +179,8 @@ public:
Tagged<std::vector<CompletionItem>>
codeComplete(PathRef File, Position Pos,
llvm::Optional<StringRef> OverridenContents = llvm::None);
/// Get definition of symbol at a specified \p Line and \p Column in \p File.
Tagged<std::vector<Location>> findDefinitions(PathRef File, Position Pos);
/// Run formatting for \p Rng inside \p File.
std::vector<tooling::Replacement> formatRange(PathRef File, Range Rng);

View File

@ -8,13 +8,21 @@
//===---------------------------------------------------------------------===//
#include "ClangdUnit.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/Utils.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/MacroInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/Support/Format.h"
#include <algorithm>
using namespace clang::clangd;
using namespace clang;
@ -259,3 +267,145 @@ std::vector<DiagWithFixIts> ClangdUnit::getLocalDiagnostics() const {
void ClangdUnit::dumpAST(llvm::raw_ostream &OS) const {
Unit->getASTContext().getTranslationUnitDecl()->dump(OS, true);
}
namespace {
/// Finds declarations locations that a given source location refers to.
class DeclarationLocationsFinder : public index::IndexDataConsumer {
std::vector<Location> DeclarationLocations;
const SourceLocation &SearchedLocation;
ASTUnit &Unit;
public:
DeclarationLocationsFinder(raw_ostream &OS,
const SourceLocation &SearchedLocation, ASTUnit &Unit) :
SearchedLocation(SearchedLocation), Unit(Unit) {
}
std::vector<Location> takeLocations() {
// Don't keep the same location multiple times.
// This can happen when nodes in the AST are visited twice.
std::sort(DeclarationLocations.begin(), DeclarationLocations.end());
auto last = std::unique(DeclarationLocations.begin(), DeclarationLocations.end());
DeclarationLocations.erase(last, DeclarationLocations.end());
return std::move(DeclarationLocations);
}
bool handleDeclOccurence(const Decl* D, index::SymbolRoleSet Roles,
ArrayRef<index::SymbolRelation> Relations, FileID FID, unsigned Offset,
index::IndexDataConsumer::ASTNodeInfo ASTNode) override
{
if (isSearchedLocation(FID, Offset)) {
addDeclarationLocation(D->getSourceRange());
}
return true;
}
private:
bool isSearchedLocation(FileID FID, unsigned Offset) const {
const SourceManager &SourceMgr = Unit.getSourceManager();
return SourceMgr.getFileOffset(SearchedLocation) == Offset
&& SourceMgr.getFileID(SearchedLocation) == FID;
}
void addDeclarationLocation(const SourceRange& ValSourceRange) {
const SourceManager& SourceMgr = Unit.getSourceManager();
const LangOptions& LangOpts = Unit.getLangOpts();
SourceLocation LocStart = ValSourceRange.getBegin();
SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(),
0, SourceMgr, LangOpts);
Position P1;
P1.line = SourceMgr.getSpellingLineNumber(LocStart) - 1;
P1.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1;
Position P2;
P2.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1;
P2.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1;
Range R = { P1, P2 };
Location L;
L.uri = URI::fromFile(
SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart)));
L.range = R;
DeclarationLocations.push_back(L);
}
void finish() override
{
// Also handle possible macro at the searched location.
Token Result;
if (!Lexer::getRawToken(SearchedLocation, Result, Unit.getSourceManager(),
Unit.getASTContext().getLangOpts(), false)) {
if (Result.is(tok::raw_identifier)) {
Unit.getPreprocessor().LookUpIdentifierInfo(Result);
}
IdentifierInfo* IdentifierInfo = Result.getIdentifierInfo();
if (IdentifierInfo && IdentifierInfo->hadMacroDefinition()) {
std::pair<FileID, unsigned int> DecLoc =
Unit.getSourceManager().getDecomposedExpansionLoc(SearchedLocation);
// Get the definition just before the searched location so that a macro
// referenced in a '#undef MACRO' can still be found.
SourceLocation BeforeSearchedLocation = Unit.getLocation(
Unit.getSourceManager().getFileEntryForID(DecLoc.first),
DecLoc.second - 1);
MacroDefinition MacroDef =
Unit.getPreprocessor().getMacroDefinitionAtLoc(IdentifierInfo,
BeforeSearchedLocation);
MacroInfo* MacroInf = MacroDef.getMacroInfo();
if (MacroInf) {
addDeclarationLocation(
SourceRange(MacroInf->getDefinitionLoc(),
MacroInf->getDefinitionEndLoc()));
}
}
}
}
};
} // namespace
std::vector<Location> ClangdUnit::findDefinitions(Position Pos) {
const FileEntry *FE = Unit->getFileManager().getFile(Unit->getMainFileName());
if (!FE)
return {};
SourceLocation SourceLocationBeg = getBeginningOfIdentifier(Pos, FE);
auto DeclLocationsFinder = std::make_shared<DeclarationLocationsFinder>(llvm::errs(),
SourceLocationBeg, *Unit);
index::IndexingOptions IndexOpts;
IndexOpts.SystemSymbolFilter =
index::IndexingOptions::SystemSymbolFilterKind::All;
IndexOpts.IndexFunctionLocals = true;
index::indexASTUnit(*Unit, DeclLocationsFinder, IndexOpts);
return DeclLocationsFinder->takeLocations();
}
SourceLocation ClangdUnit::getBeginningOfIdentifier(const Position &Pos,
const FileEntry *FE) const {
// The language server protocol uses zero-based line and column numbers.
// Clang uses one-based numbers.
SourceLocation InputLocation = Unit->getLocation(FE, Pos.line + 1,
Pos.character + 1);
if (Pos.character == 0) {
return InputLocation;
}
// This handle cases where the position is in the middle of a token or right
// after the end of a token. In theory we could just use GetBeginningOfToken
// to find the start of the token at the input position, but this doesn't
// work when right after the end, i.e. foo|.
// So try to go back by one and see if we're still inside the an identifier
// token. If so, Take the beginning of this token.
// (It should be the same identifier because you can't have two adjacent
// identifiers without another token in between.)
SourceLocation PeekBeforeLocation = Unit->getLocation(FE, Pos.line + 1,
Pos.character);
const SourceManager &SourceMgr = Unit->getSourceManager();
Token Result;
Lexer::getRawToken(PeekBeforeLocation, Result, SourceMgr,
Unit->getASTContext().getLangOpts(), false);
if (Result.is(tok::raw_identifier)) {
return Lexer::GetBeginningOfToken(PeekBeforeLocation, SourceMgr,
Unit->getASTContext().getLangOpts());
}
return InputLocation;
}

View File

@ -59,6 +59,8 @@ public:
std::vector<CompletionItem>
codeComplete(StringRef Contents, Position Pos,
IntrusiveRefCntPtr<vfs::FileSystem> VFS);
/// Get definition of symbol at a specified \p Line and \p Column in \p File.
std::vector<Location> findDefinitions(Position Pos);
/// Returns diagnostics and corresponding FixIts for each diagnostic that are
/// located in the current file.
std::vector<DiagWithFixIts> getLocalDiagnostics() const;
@ -71,6 +73,8 @@ private:
Path FileName;
std::unique_ptr<ASTUnit> Unit;
std::shared_ptr<PCHContainerOperations> PCHs;
SourceLocation getBeginningOfIdentifier(const Position& Pos, const FileEntry* FE) const;
};
} // namespace clangd

View File

@ -54,7 +54,7 @@ URI URI::parse(llvm::yaml::ScalarNode *Param) {
}
std::string URI::unparse(const URI &U) {
return U.uri;
return "\"" + U.uri + "\"";
}
llvm::Optional<TextDocumentIdentifier>
@ -162,6 +162,14 @@ std::string Range::unparse(const Range &P) {
return Result;
}
std::string Location::unparse(const Location &P) {
std::string Result;
llvm::raw_string_ostream(Result) << llvm::format(
R"({"uri": %s, "range": %s})", URI::unparse(P.uri).c_str(),
Range::unparse(P.range).c_str());
return Result;
}
llvm::Optional<TextDocumentItem>
TextDocumentItem::parse(llvm::yaml::MappingNode *Params) {
TextDocumentItem Result;

View File

@ -38,6 +38,18 @@ struct URI {
static URI parse(llvm::yaml::ScalarNode *Param);
static std::string unparse(const URI &U);
friend bool operator==(const URI &LHS, const URI &RHS) {
return LHS.uri == RHS.uri;
}
friend bool operator!=(const URI &LHS, const URI &RHS) {
return !(LHS == RHS);
}
friend bool operator<(const URI &LHS, const URI &RHS) {
return LHS.uri < RHS.uri;
}
};
struct TextDocumentIdentifier {
@ -86,6 +98,26 @@ struct Range {
static std::string unparse(const Range &P);
};
struct Location {
/// The text document's URI.
URI uri;
Range range;
friend bool operator==(const Location &LHS, const Location &RHS) {
return LHS.uri == RHS.uri && LHS.range == RHS.range;
}
friend bool operator!=(const Location &LHS, const Location &RHS) {
return !(LHS == RHS);
}
friend bool operator<(const Location &LHS, const Location &RHS) {
return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range);
}
static std::string unparse(const Location &P);
};
struct TextEdit {
/// The range of the text document to be manipulated. To insert
/// text into a document create a range where start === end.

View File

@ -186,6 +186,24 @@ private:
ProtocolCallbacks &Callbacks;
};
struct GotoDefinitionHandler : Handler {
GotoDefinitionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
: Handler(Output), Callbacks(Callbacks) {}
void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
auto TDPP = TextDocumentPositionParams::parse(Params);
if (!TDPP) {
Output.log("Failed to decode TextDocumentPositionParams!\n");
return;
}
Callbacks.onGoToDefinition(*TDPP, ID, Output);
}
private:
ProtocolCallbacks &Callbacks;
};
} // namespace
void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher,
@ -219,4 +237,6 @@ void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher,
Dispatcher.registerHandler(
"textDocument/completion",
llvm::make_unique<CompletionHandler>(Out, Callbacks));
Dispatcher.registerHandler("textDocument/definition",
llvm::make_unique<GotoDefinitionHandler>(Out, Callbacks));
}

View File

@ -46,6 +46,8 @@ public:
JSONOutput &Out) = 0;
virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) = 0;
virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID,
JSONOutput &Out) = 0;
};
void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,

View File

@ -0,0 +1,168 @@
# RUN: clangd -run-synchronously < %s | FileCheck %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
Content-Length: 172
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"int main() {\nint a;\na;\n}\n"}}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":0}}}
# Go to local variable
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":1}}}
# Go to local variable, end of token
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
Content-Length: 214
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":2},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo bar = { x : 1 };\n}\n"}]}}
Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":14}}}
# Go to field, GNU old-style field designator
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
Content-Length: 215
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":3},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo baz = { .x = 2 };\n}\n"}]}}
Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":15}}}
# Go to field, field designator
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
Content-Length: 187
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":4},"contentChanges":[{"text":"int main() {\n main();\n return 0;\n}"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":3}}}
# Go to function declaration, function call
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 3, "character": 1}}}]}
Content-Length: 208
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"struct Foo {\n};\nint main() {\n Foo bar;\n return 0;\n}\n"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":3}}}
# Go to struct declaration, new struct instance
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 1}}}]}
Content-Length: 231
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"namespace n1 {\nstruct Foo {\n};\n}\nint main() {\n n1::Foo bar;\n return 0;\n}\n"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":4}}}
# Go to struct declaration, new struct instance, qualified name
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 3, "character": 1}}}]}
Content-Length: 215
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":6},"contentChanges":[{"text":"struct Foo {\n int x;\n};\nint main() {\n Foo bar;\n bar.x;\n}\n"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}}
# Go to field declaration, field reference
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 2}, "end": {"line": 1, "character": 7}}}]}
Content-Length: 220
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n void x();\n};\nint main() {\n Foo bar;\n bar.x();\n}\n"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}}
# Go to method declaration, method call
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 2}, "end": {"line": 1, "character": 10}}}]}
Content-Length: 240
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n};\ntypedef Foo TypedefFoo;\nint main() {\n TypedefFoo bar;\n return 0;\n}\n"}]}}
Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":10}}}
# Go to typedef
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 22}}}]}
Content-Length: 254
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"template <typename MyTemplateParam>\nvoid foo() {\n MyTemplateParam a;\n}\nint main() {\n return 0;\n}\n"}]}}
Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":13}}}
# Go to template type parameter. Fails until clangIndex is modified to handle those.
# no-CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 10}, "end": {"line": 0, "character": 34}}}]}
Content-Length: 256
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\nstatic void bar() {}\n};\n}\nint main() {\n ns::Foo::bar();\n return 0;\n}\n"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":6,"character":4}}}
# Go to namespace, static method call
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 4, "character": 1}}}]}
Content-Length: 265
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\n int field;\n Foo(int param) : field(param) {}\n};\n}\nint main() {\n return 0;\n}\n"}]}}
Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":21}}}
# Go to field, member initializer
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 2}, "end": {"line": 2, "character": 11}}}]}
Content-Length: 204
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define MY_MACRO 0\nint main() {\n return MY_MACRO;\n}\n"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":9}}}
# Go to macro
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 18}}}]}
Content-Length: 217
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define FOO 1\nint a = FOO;\n#define FOO 2\nint b = FOO;\n#undef FOO\n"}]}}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":8}}}
# Go to macro, re-defined later
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 13}}}]}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":8}}}
# Go to macro, undefined later
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 8}, "end": {"line": 2, "character": 13}}}]}
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}}
# Go to macro, being undefined
# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 8}, "end": {"line": 2, "character": 13}}}]}
Content-Length: 44
{"jsonrpc":"2.0","id":3,"method":"shutdown"}

View File

@ -4,14 +4,15 @@
Content-Length: 125
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
# CHECK: Content-Length: 424
# CHECK: Content-Length: 462
# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
# CHECK: "textDocumentSync": 1,
# CHECK: "documentFormattingProvider": true,
# CHECK: "documentRangeFormattingProvider": true,
# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
# CHECK: "codeActionProvider": true,
# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]},
# CHECK: "definitionProvider": true
# CHECK: }}}
#
Content-Length: 193