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,16 @@ /// 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 { + return IncLocations; + }; 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 +130,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 @@ -75,12 +75,56 @@ 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. + return {{static_cast(M.getSpellingLineNumber(R.getBegin())) - 1, + static_cast(M.getSpellingColumnNumber(R.getBegin())) - 1}, + {static_cast(M.getSpellingLineNumber(R.getEnd())) - 1, + static_cast(M.getSpellingColumnNumber(R.getEnd())) - 1}}; +} + +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(FilenameRange.getAsRange().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); + } + + CppFilePreambleCallbacks() : SourceMgr(nullptr) {} + void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); for (Decl *D : TopLevelDecls) { @@ -99,9 +143,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; + SourceManager *SourceMgr; + InclusionLocations IncLocations; }; /// Convert from clang diagnostic level to LSP severity. @@ -133,16 +189,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. - return {{static_cast(M.getSpellingLineNumber(R.getBegin())) - 1, - static_cast(M.getSpellingColumnNumber(R.getBegin())) - 1}, - {static_cast(M.getSpellingLineNumber(R.getEnd())) - 1, - static_cast(M.getSpellingColumnNumber(R.getEnd())) - 1}}; -} - // 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) { @@ -247,6 +293,11 @@ IntrusiveRefCntPtr VFS) { std::vector ASTDiags; StoreDiagsConsumer UnitDiagsConsumer(/*ref*/ ASTDiags); + InclusionLocations IncLocations; + // Copy over the includes from the preamble, then combine with the + // non-preamble includes below. + if (Preamble) + IncLocations = Preamble->IncLocations; const PrecompiledPreamble *PreamblePCH = Preamble ? &Preamble->Preamble : nullptr; @@ -267,6 +318,11 @@ MainInput.getFile()); return llvm::None; } + + Clang->getPreprocessor().addPPCallbacks( + llvm::make_unique(Clang->getSourceManager(), + IncLocations)); + if (!Action->Execute()) log("Execute() failed when building AST for " + MainInput.getFile()); @@ -276,7 +332,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 { @@ -357,19 +414,22 @@ 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); } @@ -513,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/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; @@ -36,6 +38,10 @@ return {Lines, static_cast(Offset - StartOfLine)}; } +Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc) { + return {static_cast(SM.getSpellingLineNumber(Loc)) - 1, + static_cast(SM.getSpellingColumnNumber(Loc)) - 1}; +} + } // 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" @@ -129,12 +130,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; @@ -167,6 +164,21 @@ indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), DeclMacrosFinder, IndexOpts); + /// Process targets for paths inside #include directive. + std::vector IncludeTargets; + for (auto &IncludeLoc : AST.getInclusionLocations()) { + Range R = IncludeLoc.first; + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + Position Pos = sourceLocToPosition(SourceMgr, SourceLocationBeg); + + if (R.start.line == Pos.line && R.start.character <= Pos.character && + Pos.character <= R.end.character) { + IncludeTargets.push_back(Location{URIForFile{IncludeLoc.second}, + Range{Position{0, 0}, Position{0, 0}}}); + return IncludeTargets; + } + } + std::vector Decls = DeclMacrosFinder->takeDecls(); std::vector MacroInfos = DeclMacrosFinder->takeMacroInfos(); @@ -242,13 +254,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 @@ -50,6 +50,11 @@ return std::move(*AST); } +class IgnoreDiagnostics : public DiagnosticsConsumer { + void onDiagnosticsReady( + PathRef File, Tagged> Diagnostics) override {} +}; + // Extracts ranges from an annotated example, and constructs a matcher for a // highlight set. Ranges should be named $read/$write as appropriate. Matcher &> @@ -227,6 +232,59 @@ } } +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^invalid.h" + #include "^foo.h" + int b = a; + // test + int foo; + #include "$3^foo.h" + )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()); + + ExpectedLocations = + Server.findDefinitions(FooCpp, SourceAnnoations.point("2")); + ASSERT_TRUE(!!ExpectedLocations); + Locations = ExpectedLocations->Value; + EXPECT_TRUE(Locations.empty()); + + // Test include outside of Preamble + 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()); +} + } // namespace } // namespace clangd } // namespace clang