[clangd] #include statements support for Open definition

Summary: ctrl-clicking on #include statements now opens the file being pointed by that statement.

Reviewers: malaperle, krasimir, bkramer, ilya-biryukov

Reviewed By: ilya-biryukov

Subscribers: jkorous-apple, ioeric, mgrang, klimek, ilya-biryukov, arphaman, cfe-commits

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

llvm-svn: 325662
This commit is contained in:
Marc-Andre Laperle 2018-02-21 02:39:08 +00:00
parent 52525730a1
commit 63a1098d73
7 changed files with 198 additions and 39 deletions

View File

@ -81,12 +81,60 @@ private:
std::vector<const Decl *> TopLevelDecls;
};
// Converts a half-open clang source range to an LSP range.
// Note that clang also uses closed source ranges, which this can't handle!
Range toRange(CharSourceRange R, const SourceManager &M) {
// Clang is 1-based, LSP uses 0-based indexes.
Position Begin;
Begin.line = static_cast<int>(M.getSpellingLineNumber(R.getBegin())) - 1;
Begin.character =
static_cast<int>(M.getSpellingColumnNumber(R.getBegin())) - 1;
Position End;
End.line = static_cast<int>(M.getSpellingLineNumber(R.getEnd())) - 1;
End.character = static_cast<int>(M.getSpellingColumnNumber(R.getEnd())) - 1;
return {Begin, End};
}
class InclusionLocationsCollector : public PPCallbacks {
public:
InclusionLocationsCollector(SourceManager &SourceMgr,
InclusionLocations &IncLocations)
: SourceMgr(SourceMgr), IncLocations(IncLocations) {}
void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
StringRef FileName, bool IsAngled,
CharSourceRange FilenameRange, const FileEntry *File,
StringRef SearchPath, StringRef RelativePath,
const Module *Imported) override {
auto SR = FilenameRange.getAsRange();
if (SR.isInvalid() || !File || File->tryGetRealPathName().empty())
return;
if (SourceMgr.isInMainFile(SR.getBegin())) {
// Only inclusion directives in the main file make sense. The user cannot
// select directives not in the main file.
IncLocations.emplace_back(toRange(FilenameRange, SourceMgr),
File->tryGetRealPathName());
}
}
private:
SourceManager &SourceMgr;
InclusionLocations &IncLocations;
};
class CppFilePreambleCallbacks : public PreambleCallbacks {
public:
std::vector<serialization::DeclID> takeTopLevelDeclIDs() {
return std::move(TopLevelDeclIDs);
}
InclusionLocations takeInclusionLocations() {
return std::move(IncLocations);
}
void AfterPCHEmitted(ASTWriter &Writer) override {
TopLevelDeclIDs.reserve(TopLevelDecls.size());
for (Decl *D : TopLevelDecls) {
@ -105,9 +153,21 @@ public:
}
}
void BeforeExecute(CompilerInstance &CI) override {
SourceMgr = &CI.getSourceManager();
}
std::unique_ptr<PPCallbacks> createPPCallbacks() override {
assert(SourceMgr && "SourceMgr must be set at this point");
return llvm::make_unique<InclusionLocationsCollector>(*SourceMgr,
IncLocations);
}
private:
std::vector<Decl *> TopLevelDecls;
std::vector<serialization::DeclID> TopLevelDeclIDs;
InclusionLocations IncLocations;
SourceManager *SourceMgr = nullptr;
};
/// Convert from clang diagnostic level to LSP severity.
@ -139,22 +199,6 @@ bool locationInRange(SourceLocation L, CharSourceRange R,
return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd());
}
// Converts a half-open clang source range to an LSP range.
// Note that clang also uses closed source ranges, which this can't handle!
Range toRange(CharSourceRange R, const SourceManager &M) {
// Clang is 1-based, LSP uses 0-based indexes.
Position Begin;
Begin.line = static_cast<int>(M.getSpellingLineNumber(R.getBegin())) - 1;
Begin.character =
static_cast<int>(M.getSpellingColumnNumber(R.getBegin())) - 1;
Position End;
End.line = static_cast<int>(M.getSpellingLineNumber(R.getEnd())) - 1;
End.character = static_cast<int>(M.getSpellingColumnNumber(R.getEnd())) - 1;
return {Begin, End};
}
// Clang diags have a location (shown as ^) and 0 or more ranges (~~~~).
// LSP needs a single range.
Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) {
@ -267,6 +311,17 @@ ParsedAST::Build(std::unique_ptr<clang::CompilerInvocation> CI,
MainInput.getFile());
return llvm::None;
}
InclusionLocations IncLocations;
// Copy over the includes from the preamble, then combine with the
// non-preamble includes below.
if (Preamble)
IncLocations = Preamble->IncLocations;
Clang->getPreprocessor().addPPCallbacks(
llvm::make_unique<InclusionLocationsCollector>(Clang->getSourceManager(),
IncLocations));
if (!Action->Execute())
log("Execute() failed when building AST for " + MainInput.getFile());
@ -276,7 +331,8 @@ ParsedAST::Build(std::unique_ptr<clang::CompilerInvocation> CI,
std::vector<const Decl *> ParsedDecls = Action->takeTopLevelDecls();
return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action),
std::move(ParsedDecls), std::move(ASTDiags));
std::move(ParsedDecls), std::move(ASTDiags),
std::move(IncLocations));
}
namespace {
@ -355,21 +411,28 @@ std::size_t ParsedAST::getUsedBytes() const {
::getUsedBytes(TopLevelDecls) + ::getUsedBytes(Diags);
}
const InclusionLocations &ParsedAST::getInclusionLocations() const {
return IncLocations;
}
PreambleData::PreambleData(PrecompiledPreamble Preamble,
std::vector<serialization::DeclID> TopLevelDeclIDs,
std::vector<DiagWithFixIts> Diags)
std::vector<DiagWithFixIts> Diags,
InclusionLocations IncLocations)
: Preamble(std::move(Preamble)),
TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)) {}
TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)),
IncLocations(std::move(IncLocations)) {}
ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action,
std::vector<const Decl *> TopLevelDecls,
std::vector<DiagWithFixIts> Diags)
std::vector<DiagWithFixIts> Diags,
InclusionLocations IncLocations)
: Preamble(std::move(Preamble)), Clang(std::move(Clang)),
Action(std::move(Action)), Diags(std::move(Diags)),
TopLevelDecls(std::move(TopLevelDecls)),
PreambleDeclsDeserialized(false) {
TopLevelDecls(std::move(TopLevelDecls)), PreambleDeclsDeserialized(false),
IncLocations(std::move(IncLocations)) {
assert(this->Clang);
assert(this->Action);
}
@ -521,7 +584,8 @@ CppFile::rebuildPreamble(CompilerInvocation &CI,
return std::make_shared<PreambleData>(
std::move(*BuiltPreamble),
SerializedDeclsCollector.takeTopLevelDeclIDs(),
std::move(PreambleDiags));
std::move(PreambleDiags),
SerializedDeclsCollector.takeInclusionLocations());
} else {
log("Could not build a preamble for file " + Twine(FileName));
return nullptr;

View File

@ -45,15 +45,19 @@ struct DiagWithFixIts {
llvm::SmallVector<TextEdit, 1> FixIts;
};
using InclusionLocations = std::vector<std::pair<Range, Path>>;
// Stores Preamble and associated data.
struct PreambleData {
PreambleData(PrecompiledPreamble Preamble,
std::vector<serialization::DeclID> TopLevelDeclIDs,
std::vector<DiagWithFixIts> Diags);
std::vector<DiagWithFixIts> Diags,
InclusionLocations IncLocations);
PrecompiledPreamble Preamble;
std::vector<serialization::DeclID> TopLevelDeclIDs;
std::vector<DiagWithFixIts> Diags;
InclusionLocations IncLocations;
};
/// Information required to run clang, e.g. to parse AST or do code completion.
@ -97,13 +101,14 @@ public:
/// Returns the esitmated size of the AST and the accessory structures, in
/// bytes. Does not include the size of the preamble.
std::size_t getUsedBytes() const;
const InclusionLocations &getInclusionLocations() const;
private:
ParsedAST(std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action,
std::vector<const Decl *> TopLevelDecls,
std::vector<DiagWithFixIts> Diags);
std::vector<DiagWithFixIts> Diags, InclusionLocations IncLocations);
private:
void ensurePreambleDeclsDeserialized();
@ -123,6 +128,7 @@ private:
std::vector<DiagWithFixIts> Diags;
std::vector<const Decl *> TopLevelDecls;
bool PreambleDeclsDeserialized;
InclusionLocations IncLocations;
};
using ASTParsedCallback = std::function<void(PathRef Path, ParsedAST *)>;

View File

@ -100,6 +100,10 @@ struct Position {
return std::tie(LHS.line, LHS.character) <
std::tie(RHS.line, RHS.character);
}
friend bool operator<=(const Position &LHS, const Position &RHS) {
return std::tie(LHS.line, LHS.character) <=
std::tie(RHS.line, RHS.character);
}
};
bool fromJSON(const json::Expr &, Position &);
json::Expr toJSON(const Position &);
@ -118,6 +122,8 @@ struct Range {
friend bool operator<(const Range &LHS, const Range &RHS) {
return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end);
}
bool contains(Position Pos) const { return start <= Pos && Pos < end; }
};
bool fromJSON(const json::Expr &, Range &);
json::Expr toJSON(const Range &);

View File

@ -8,6 +8,8 @@
//===----------------------------------------------------------------------===//
#include "SourceCode.h"
#include "clang/Basic/SourceManager.h"
namespace clang {
namespace clangd {
using namespace llvm;
@ -39,6 +41,12 @@ Position offsetToPosition(StringRef Code, size_t Offset) {
return Pos;
}
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc) {
Position P;
P.line = static_cast<int>(SM.getSpellingLineNumber(Loc)) - 1;
P.character = static_cast<int>(SM.getSpellingColumnNumber(Loc)) - 1;
return P;
}
} // namespace clangd
} // namespace clang

View File

@ -14,8 +14,11 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H
#include "Protocol.h"
#include "clang/Basic/SourceLocation.h"
namespace clang {
class SourceManager;
namespace clangd {
/// Turn a [line, column] pair into an offset in Code.
@ -24,6 +27,9 @@ size_t positionToOffset(llvm::StringRef Code, Position P);
/// Turn an offset in Code into a [line, column] pair.
Position offsetToPosition(llvm::StringRef Code, size_t Offset);
/// Turn a SourceLocation into a [line, column] pair.
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc);
} // namespace clangd
} // namespace clang
#endif

View File

@ -8,6 +8,7 @@
//===---------------------------------------------------------------------===//
#include "XRefs.h"
#include "Logger.h"
#include "SourceCode.h"
#include "URI.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/Index/IndexDataConsumer.h"
@ -143,12 +144,8 @@ getDeclarationLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
return llvm::None;
SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0,
SourceMgr, LangOpts);
Position Begin;
Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1;
Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1;
Position End;
End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1;
End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1;
Position Begin = sourceLocToPosition(SourceMgr, LocStart);
Position End = sourceLocToPosition(SourceMgr, LocEnd);
Range R = {Begin, End};
Location L;
@ -206,6 +203,15 @@ std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos) {
Result.push_back(*L);
}
/// Process targets for paths inside #include directive.
for (auto &IncludeLoc : AST.getInclusionLocations()) {
Range R = IncludeLoc.first;
Position Pos = sourceLocToPosition(SourceMgr, SourceLocationBeg);
if (R.contains(Pos))
Result.push_back(Location{URIForFile{IncludeLoc.second}, {}});
}
return Result;
}
@ -263,13 +269,8 @@ private:
DocumentHighlight getDocumentHighlight(SourceRange SR,
DocumentHighlightKind Kind) {
const SourceManager &SourceMgr = AST.getSourceManager();
SourceLocation LocStart = SR.getBegin();
Position Begin;
Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1;
Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1;
Position End;
End.line = SourceMgr.getSpellingLineNumber(SR.getEnd()) - 1;
End.character = SourceMgr.getSpellingColumnNumber(SR.getEnd()) - 1;
Position Begin = sourceLocToPosition(SourceMgr, SR.getBegin());
Position End = sourceLocToPosition(SourceMgr, SR.getEnd());
Range R = {Begin, End};
DocumentHighlight DH;
DH.range = R;

View File

@ -36,6 +36,7 @@ void PrintTo(const DocumentHighlight &V, std::ostream *O) {
namespace {
using testing::ElementsAre;
using testing::Field;
using testing::IsEmpty;
using testing::Matcher;
using testing::UnorderedElementsAreArray;
@ -563,6 +564,73 @@ TEST(Hover, All) {
}
}
TEST(GoToInclude, All) {
MockFSProvider FS;
IgnoreDiagnostics DiagConsumer;
MockCompilationDatabase CDB;
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
/*StorePreamblesInMemory=*/true);
auto FooCpp = testPath("foo.cpp");
const char *SourceContents = R"cpp(
#include ^"$2^foo.h$3^"
#include "$4^invalid.h"
int b = a;
// test
int foo;
#in$5^clude "$6^foo.h"$7^
)cpp";
Annotations SourceAnnotations(SourceContents);
FS.Files[FooCpp] = SourceAnnotations.code();
auto FooH = testPath("foo.h");
auto FooHUri = URIForFile{FooH};
const char *HeaderContents = R"cpp([[]]int a;)cpp";
Annotations HeaderAnnotations(HeaderContents);
FS.Files[FooH] = HeaderAnnotations.code();
Server.addDocument(FooH, HeaderAnnotations.code());
Server.addDocument(FooCpp, SourceAnnotations.code());
// Test include in preamble.
auto Locations =
runFindDefinitions(Server, FooCpp, SourceAnnotations.point());
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include in preamble, last char.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("2"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("3"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include outside of preamble.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("6"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test a few positions that do not result in Locations.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("4"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("5"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("7"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value, IsEmpty());
}
} // namespace
} // namespace clangd
} // namespace clang