forked from OSchip/llvm-project
[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:
parent
52525730a1
commit
63a1098d73
|
@ -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;
|
||||
|
|
|
@ -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 *)>;
|
||||
|
|
|
@ -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 &);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue