Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -45,15 +45,19 @@ llvm::SmallVector FixIts; }; +using InclusionLocations = std::vector>; + // Stores Preamble and associated data. struct PreambleData { PreambleData(PrecompiledPreamble Preamble, std::vector TopLevelDeclIDs, - std::vector Diags); + std::vector Diags, + InclusionLocations IncLocations); PrecompiledPreamble Preamble; std::vector TopLevelDeclIDs; std::vector Diags; + InclusionLocations IncLocations; }; /// Information required to run clang, e.g. to parse AST or do code completion. @@ -97,13 +101,14 @@ /// 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 Preamble, std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, - std::vector Diags); + std::vector Diags, InclusionLocations IncLocations); private: void ensurePreambleDeclsDeserialized(); @@ -123,6 +128,7 @@ std::vector Diags; std::vector TopLevelDecls; bool PreambleDeclsDeserialized; + InclusionLocations IncLocations; }; using ASTParsedCallback = std::function; Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -74,12 +74,60 @@ std::vector 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(M.getSpellingLineNumber(R.getBegin())) - 1; + Begin.character = + static_cast(M.getSpellingColumnNumber(R.getBegin())) - 1; + + Position End; + End.line = static_cast(M.getSpellingLineNumber(R.getEnd())) - 1; + End.character = static_cast(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 takeTopLevelDeclIDs() { return std::move(TopLevelDeclIDs); } + InclusionLocations takeInclusionLocations() { + return std::move(IncLocations); + } + void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); for (Decl *D : TopLevelDecls) { @@ -98,9 +146,21 @@ } } + void BeforeExecute(CompilerInstance &CI) override { + SourceMgr = &CI.getSourceManager(); + } + + std::unique_ptr createPPCallbacks() override { + assert(SourceMgr && "SourceMgr must be set at this point"); + return llvm::make_unique(*SourceMgr, + IncLocations); + } + private: std::vector TopLevelDecls; std::vector TopLevelDeclIDs; + InclusionLocations IncLocations; + SourceManager *SourceMgr = nullptr; }; /// Convert from clang diagnostic level to LSP severity. @@ -132,22 +192,6 @@ 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(M.getSpellingLineNumber(R.getBegin())) - 1; - Begin.character = - static_cast(M.getSpellingColumnNumber(R.getBegin())) - 1; - - Position End; - End.line = static_cast(M.getSpellingLineNumber(R.getEnd())) - 1; - End.character = static_cast(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) { @@ -260,6 +304,17 @@ 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(Clang->getSourceManager(), + IncLocations)); + if (!Action->Execute()) log("Execute() failed when building AST for " + MainInput.getFile()); @@ -269,7 +324,8 @@ std::vector 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 { @@ -348,21 +404,28 @@ ::getUsedBytes(TopLevelDecls) + ::getUsedBytes(Diags); } +const InclusionLocations &ParsedAST::getInclusionLocations() const { + return IncLocations; +} + PreambleData::PreambleData(PrecompiledPreamble Preamble, std::vector TopLevelDeclIDs, - std::vector Diags) + std::vector 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 Preamble, std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, - std::vector Diags) + std::vector 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); } @@ -510,7 +573,8 @@ return std::make_shared( 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; Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -91,6 +91,10 @@ 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 &); @@ -109,6 +113,8 @@ 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 &); Index: clangd/SourceCode.h =================================================================== --- clangd/SourceCode.h +++ clangd/SourceCode.h @@ -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 @@ /// 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 Index: clangd/SourceCode.cpp =================================================================== --- clangd/SourceCode.cpp +++ clangd/SourceCode.cpp @@ -8,6 +8,8 @@ //===----------------------------------------------------------------------===// #include "SourceCode.h" +#include "clang/Basic/SourceManager.h" + namespace clang { namespace clangd { using namespace llvm; @@ -39,6 +41,12 @@ return Pos; } +Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc) { + Position P; + P.line = static_cast(SM.getSpellingLineNumber(Loc)) - 1; + P.character = static_cast(SM.getSpellingColumnNumber(Loc)) - 1; + return P; +} + } // namespace clangd } // namespace clang - Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -8,6 +8,7 @@ //===---------------------------------------------------------------------===// #include "XRefs.h" #include "Logger.h" +#include "SourceCode.h" #include "URI.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" @@ -130,12 +131,8 @@ 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; @@ -193,6 +190,15 @@ 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; } @@ -250,13 +256,8 @@ 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; Index: unittests/clangd/XRefsTests.cpp =================================================================== --- unittests/clangd/XRefsTests.cpp +++ unittests/clangd/XRefsTests.cpp @@ -257,6 +257,89 @@ SourceAnnotations.range()})); } +TEST(GoToInclude, All) { + MockFSProvider FS; + IgnoreDiagnostics DiagConsumer; + MockCompilationDatabase CDB; + + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + /*StorePreamblesInMemory=*/true); + + auto FooCpp = getVirtualTestFilePath("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 SourceAnnoations(SourceContents); + FS.Files[FooCpp] = SourceAnnoations.code(); + auto FooH = getVirtualTestFilePath("foo.h"); + + const char *HeaderContents = R"cpp([[]]int a;)cpp"; + Annotations HeaderAnnoations(HeaderContents); + FS.Files[FooH] = HeaderAnnoations.code(); + + Server.addDocument(FooH, HeaderAnnoations.code()); + Server.addDocument(FooCpp, SourceAnnoations.code()); + + // Test include in preamble. + auto ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point()); + ASSERT_TRUE(!!ExpectedLocations); + std::vector Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + ASSERT_EQ(Locations[0].uri.file, FooH); + ASSERT_EQ(Locations[0].range, HeaderAnnoations.range()); + + // Test include in preamble, last char. + ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point("2")); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + ASSERT_EQ(Locations[0].uri.file, FooH); + ASSERT_EQ(Locations[0].range, HeaderAnnoations.range()); + + ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point("3")); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + ASSERT_EQ(Locations[0].uri.file, FooH); + ASSERT_EQ(Locations[0].range, HeaderAnnoations.range()); + + // Test include outside of preamble. + ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point("6")); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(!Locations.empty()); + ASSERT_EQ(Locations[0].uri.file, FooH); + ASSERT_EQ(Locations[0].range, HeaderAnnoations.range()); + + // Test a few positions that do not result in Locations. + ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point("4")); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(Locations.empty()); + + ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point("5")); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(Locations.empty()); + + ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point("7")); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(Locations.empty()); +} + } // namespace } // namespace clangd } // namespace clang