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
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) {
}
}
+ 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.
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) {
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());
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 {
::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);
}
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;
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.
/// 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();
std::vector<DiagWithFixIts> Diags;
std::vector<const Decl *> TopLevelDecls;
bool PreambleDeclsDeserialized;
+ InclusionLocations IncLocations;
};
using ASTParsedCallback = std::function<void(PathRef Path, ParsedAST *)>;
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 &);
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 &);
//===----------------------------------------------------------------------===//
#include "SourceCode.h"
+#include "clang/Basic/SourceManager.h"
+
namespace clang {
namespace clangd {
using namespace llvm;
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
-
#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.
/// 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
//===---------------------------------------------------------------------===//
#include "XRefs.h"
#include "Logger.h"
+#include "SourceCode.h"
#include "URI.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/Index/IndexDataConsumer.h"
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;
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;
}
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;
namespace {
using testing::ElementsAre;
using testing::Field;
+using testing::IsEmpty;
using testing::Matcher;
using testing::UnorderedElementsAreArray;
}
}
+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