Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -18,6 +18,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include +#include using namespace clang; using namespace clang::clangd; @@ -364,11 +365,26 @@ llvm::errc::invalid_argument); std::vector Result; - Resources->getAST().get()->runUnderLock([Pos, &Result, this](ParsedAST *AST) { - if (!AST) - return; - Result = clangd::findDefinitions(*AST, Pos, Logger); - }); + std::unordered_map IncludeLocationMap; + std::vector> DataVector; + std::vector RangeVector; + + std::shared_future> Preamble = + Resources->getPreamble(); + if (Preamble.get()) { + IncludeLocationMap = Preamble.get()->IncludeMap; + DataVector = Preamble.get()->DataVector; + RangeVector = Preamble.get()->RangeVector; + } + Resources->getAST().get()->runUnderLock( + [Pos, &Result, Preamble, this](ParsedAST *AST) { + if (!AST) + return; + + Result = clangd::findDefinitions( + *AST, Pos, Logger, Preamble.get()->IncludeMap, + Preamble.get()->DataVector, Preamble.get()->RangeVector); + }); return make_tagged(std::move(Result), TaggedFS.Tag); } Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace llvm { class raw_ostream; @@ -128,11 +129,17 @@ struct PreambleData { PreambleData(PrecompiledPreamble Preamble, std::vector TopLevelDeclIDs, - std::vector Diags); + std::vector Diags, + std::unordered_map IncludeMap, + std::vector> DataVector, + std::vector RangeVector); PrecompiledPreamble Preamble; std::vector TopLevelDeclIDs; std::vector Diags; + std::unordered_map IncludeMap; + std::vector> DataVector; + std::vector RangeVector; }; /// Manages resources, required by clangd. Allows to rebuild file with new @@ -304,8 +311,11 @@ clangd::Logger &Logger); /// Get definition of symbol at a specified \p Pos. -std::vector findDefinitions(ParsedAST &AST, Position Pos, - clangd::Logger &Logger); +std::vector +findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger, + std::unordered_map IncludeLocationMap, + std::vector> DataVector, + std::vector RangeVector); /// For testing/debugging purposes. Note that this method deserializes all /// unserialized Decls, so use with care. Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -30,10 +30,30 @@ #include #include +#include using namespace clang::clangd; using namespace clang; +class DelegatingPPCallbacks : public PPCallbacks { + +public: + DelegatingPPCallbacks(PPCallbacks &PPCallbacks) : Callbacks(PPCallbacks) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override { + Callbacks.InclusionDirective(HashLoc, IncludeTok, FileName, IsAngled, + FilenameRange, File, SearchPath, RelativePath, + Imported); + } + +private: + PPCallbacks &Callbacks; +}; + namespace { class DeclTrackingASTConsumer : public ASTConsumer { @@ -72,12 +92,110 @@ std::vector TopLevelDecls; }; -class CppFilePreambleCallbacks : public PreambleCallbacks { +std::vector +fillRangeVector(const SourceManager &SM, + std::vector> DataVector, + std::vector RangeVector) { + if (RangeVector.empty()) { + for (unsigned I = 0; I < DataVector.size(); I++) { + Position Begin; + Begin.line = SM.getSpellingLineNumber(DataVector[I].first.getBegin()); + Begin.character = + SM.getSpellingColumnNumber(DataVector[I].first.getBegin()); + Position End; + End.line = SM.getSpellingLineNumber(DataVector[I].first.getEnd()); + End.character = SM.getSpellingColumnNumber(DataVector[I].first.getEnd()); + Range R = {Begin, End}; + RangeVector.push_back(R); + } + } + return RangeVector; +} + +void findPreambleIncludes( + const SourceManager &SM, const LangOptions &LangOpts, + std::unordered_map &IncludeLocationMap, + std::vector> DataVector, + std::vector RangeVector) { + + for (unsigned I = 0; I < DataVector.size(); I++) { + if (SM.isInMainFile(DataVector[I].first.getBegin())) + IncludeLocationMap.insert({RangeVector[I], DataVector[I].second}); + } +} + +std::unordered_map findIncludesOutsidePreamble( + const SourceManager &SM, const LangOptions &LangOpts, + std::unordered_map IncludeLocationMap) { + + unsigned NumSlocs = SM.local_sloc_entry_size(); + SmallVector InclusionStack; + + for (unsigned I = 0; I < NumSlocs; ++I) { + bool Invalid = false; + const SrcMgr::SLocEntry &SL = SM.getLocalSLocEntry(I, &Invalid); + if (!SL.isFile() || Invalid) + continue; + const SrcMgr::FileInfo &FI = SL.getFile(); + + SourceLocation L = FI.getIncludeLoc(); + InclusionStack.clear(); + + SourceLocation LocationToInsert; + + while (L.isValid()) { + PresumedLoc PLoc = SM.getPresumedLoc(L); + InclusionStack.push_back(L); + L = PLoc.isValid() ? PLoc.getIncludeLoc() : SourceLocation(); + } + if (InclusionStack.size() == 0) { + // Skip main file + continue; + } + + if (InclusionStack.size() > 1) { + // Don't care about includes of includes + continue; + } + + StringRef FilePath = FI.getContentCache()->OrigEntry->tryGetRealPathName(); + if (FilePath.empty()) { + continue; + } + + Position Begin; + Begin.line = SM.getSpellingLineNumber(InclusionStack.front()); + Begin.character = SM.getSpellingColumnNumber(InclusionStack.front()); + + SourceLocation LocEnd = + Lexer::getLocForEndOfToken(InclusionStack.front(), 0, SM, LangOpts); + + Position End; + End.line = SM.getSpellingLineNumber(LocEnd); + End.character = SM.getSpellingColumnNumber(LocEnd); + Range R = {Begin, End}; + IncludeLocationMap.insert({R, FilePath.str()}); + } + + return IncludeLocationMap; +} + +class CppFilePreambleCallbacks : public PreambleCallbacks, public PPCallbacks { public: std::vector takeTopLevelDeclIDs() { return std::move(TopLevelDeclIDs); } + std::unordered_map takeIncludeLocationMap() { + return std::move(IncludeLocationMap); + } + + std::vector> takeDataVector() { + return std::move(DataVector); + } + + std::vector takeRangeVector() { return std::move(RangeVector); } + void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); for (Decl *D : TopLevelDecls) { @@ -96,9 +214,29 @@ } } + void AfterExecute(CompilerInstance &CI) override { + RangeVector = + fillRangeVector(CI.getSourceManager(), DataVector, RangeVector); + findPreambleIncludes(CI.getSourceManager(), CI.getLangOpts(), + IncludeLocationMap, DataVector, RangeVector); + } + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override { + if (File && !File->tryGetRealPathName().empty()) + DataVector.push_back(std::pair( + FilenameRange.getAsRange(), File->tryGetRealPathName())); + } + private: std::vector TopLevelDecls; std::vector TopLevelDeclIDs; + std::vector> DataVector; + std::vector RangeVector; + std::unordered_map IncludeLocationMap; }; /// Convert from clang diagnostic level to LSP severity. @@ -915,12 +1053,20 @@ const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; + std::unordered_map IncludeLocationMap; + std::vector> DataVector; + std::vector RangeVector; public: - DeclarationLocationsFinder(raw_ostream &OS, - const SourceLocation &SearchedLocation, - ASTContext &AST, Preprocessor &PP) - : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} + DeclarationLocationsFinder( + raw_ostream &OS, const SourceLocation &SearchedLocation, ASTContext &AST, + Preprocessor &PP, + std::unordered_map IncludeLocationMap, + std::vector> DataVector, + std::vector RangeVector) + : SearchedLocation(SearchedLocation), AST(AST), PP(PP), + IncludeLocationMap(IncludeLocationMap), DataVector(DataVector), + RangeVector(RangeVector) {} std::vector takeLocations() { // Don't keep the same location multiple times. @@ -950,6 +1096,11 @@ SourceMgr.getFileID(SearchedLocation) == FID; } + bool isSameLine(unsigned Line) const { + const SourceManager &SourceMgr = AST.getSourceManager(); + return Line == SourceMgr.getSpellingLineNumber(SearchedLocation); + } + void addDeclarationLocation(const SourceRange &ValSourceRange) { const SourceManager &SourceMgr = AST.getSourceManager(); const LangOptions &LangOpts = AST.getLangOpts(); @@ -963,14 +1114,32 @@ End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; Range R = {Begin, End}; + addLocation(URI::fromFile( + SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))), + R); + } + + void addLocation(URI Uri, Range R) { + Location L; - L.uri = URI::fromFile( - SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))); + L.uri = Uri; L.range = R; DeclarationLocations.push_back(L); } void finish() override { + + IncludeLocationMap = ::findIncludesOutsidePreamble( + AST.getSourceManager(), AST.getLangOpts(), IncludeLocationMap); + + for (auto It = IncludeLocationMap.begin(); It != IncludeLocationMap.end(); + ++It) { + Range R = It->first; + Path P = It->second; + if (isSameLine(R.start.line)) + addLocation(URI::fromFile(P), R); + } + // Also handle possible macro at the searched location. Token Result; if (!Lexer::getRawToken(SearchedLocation, Result, AST.getSourceManager(), @@ -1040,8 +1209,11 @@ } } // namespace -std::vector clangd::findDefinitions(ParsedAST &AST, Position Pos, - clangd::Logger &Logger) { +std::vector clangd::findDefinitions( + ParsedAST &AST, Position Pos, clangd::Logger &Logger, + std::unordered_map IncludeLocationMap, + std::vector> DataVector, + std::vector RangeVector) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); if (!FE) @@ -1051,7 +1223,7 @@ auto DeclLocationsFinder = std::make_shared( llvm::errs(), SourceLocationBeg, AST.getASTContext(), - AST.getPreprocessor()); + AST.getPreprocessor(), IncludeLocationMap, DataVector, RangeVector); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -1133,11 +1305,17 @@ ParsedASTWrapper::ParsedASTWrapper(llvm::Optional AST) : AST(std::move(AST)) {} -PreambleData::PreambleData(PrecompiledPreamble Preamble, - std::vector TopLevelDeclIDs, - std::vector Diags) +PreambleData::PreambleData( + PrecompiledPreamble Preamble, + std::vector TopLevelDeclIDs, + std::vector Diags, + std::unordered_map IncludeMap, + std::vector> DataVector, + std::vector RangeVector) : Preamble(std::move(Preamble)), - TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)) {} + TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)), + IncludeMap(std::move(IncludeMap)), DataVector(std::move(DataVector)), + RangeVector(std::move(RangeVector)) {} std::shared_ptr CppFile::Create(PathRef FileName, tooling::CompileCommand Command, @@ -1291,15 +1469,20 @@ CompilerInstance::createDiagnostics( &CI->getDiagnosticOpts(), &PreambleDiagnosticsConsumer, false); CppFilePreambleCallbacks SerializedDeclsCollector; + std::unique_ptr DelegatedPPCallbacks = + llvm::make_unique(SerializedDeclsCollector); auto BuiltPreamble = PrecompiledPreamble::Build( *CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, VFS, PCHs, - SerializedDeclsCollector); + SerializedDeclsCollector, std::move(DelegatedPPCallbacks)); if (BuiltPreamble) { return std::make_shared( std::move(*BuiltPreamble), SerializedDeclsCollector.takeTopLevelDeclIDs(), - std::move(PreambleDiags)); + std::move(PreambleDiags), + SerializedDeclsCollector.takeIncludeLocationMap(), + SerializedDeclsCollector.takeDataVector(), + SerializedDeclsCollector.takeRangeVector()); } else { return nullptr; } Index: clangd/GlobalCompilationDatabase.cpp =================================================================== --- clangd/GlobalCompilationDatabase.cpp +++ clangd/GlobalCompilationDatabase.cpp @@ -9,6 +9,7 @@ #include "GlobalCompilationDatabase.h" #include "Logger.h" +#include "ProtocolHandlers.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -30,6 +31,8 @@ Command.CommandLine.insert(It, ExtraFlags.begin(), ExtraFlags.end()); } + + tooling::CompileCommand getDefaultCompileCommand(PathRef File) { std::vector CommandLine{"clang", "-fsyntax-only", File.str()}; return tooling::CompileCommand(llvm::sys::path::parent_path(File), Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -96,12 +96,21 @@ friend bool operator<(const Range &LHS, const Range &RHS) { return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end); } - static llvm::Optional parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); static std::string unparse(const Range &P); + +}; + +class RangeHash { +public: + std::size_t operator()(const Range &R) const + { + return ((R.start.line & 0x18) << 3) | ((R.start.character & 0x18) << 1) | ((R.end.line & 0x18) >> 1) | ((R.end.character & 0x18) >> 3); + } }; + struct Location { /// The text document's URI. URI uri; Index: test/clangd/documenthighlight.test =================================================================== --- /dev/null +++ test/clangd/documenthighlight.test @@ -0,0 +1,42 @@ +# RUN: clangd -run-synchronously < %s | FileCheck %s +# It is absolutely vital that this file has CRLF line endings. +# +Content-Length: 125 + +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +# CHECK: Content-Length: 580 +# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ +# CHECK: "textDocumentSync": 1, +# CHECK: "documentFormattingProvider": true, +# CHECK: "documentRangeFormattingProvider": true, +# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, +# CHECK: "codeActionProvider": true, +# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, +# CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]}, +# CHECK: "definitionProvider": true, +# CHECK: "documentHighlightProvider": true +# CHECK: }}} +# + +Content-Length: 455 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#define MACRO 1\nnamespace ns1 {\nstruct MyClass {\nint xasd;\nvoid anotherOperation() {\n}\nstatic int foo(MyClass*) {\nreturn 0;\n}\n\n};\nstruct Foo {\nint xasd;\n};\n}\nint main() {\nint bonjour;\nbonjour = 2;\nns1::Foo bar = { xasd : 1};\nbar.xasd = 3;\nns1::MyClass* Params;\nParams->anotherOperation();}\n"}}} + +Content-Length: 156 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":2}}} +# Go to local variable +# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 16, "character": 4}, "end": {"line": 16, "character": 12}}, "kind": 1},{"range": {"start": {"line": 17, "character": 0}, "end": {"line": 17, "character": 7}}, "kind": 1}]} + +Content-Length: 157 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":18,"character":17}}} +# Go to local variable +# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 12, "character": 4}, "end": {"line": 12, "character": 9}}, "kind": 1},{"range": {"start": {"line": 18, "character": 17}, "end": {"line": 18, "character": 21}}, "kind": 1},{"range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 8}}, "kind": 1}]} + +Content-Length: 157 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":21,"character":10}}} +# Go to local variable +# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 4, "character": 5}, "end": {"line": 4, "character": 22}}, "kind": 1},{"range": {"start": {"line": 21, "character": 8}, "end": {"line": 21, "character": 25}}, "kind": 1}]} + Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -7,7 +7,6 @@ // //===----------------------------------------------------------------------===// -#include "ClangdLSPServer.h" #include "ClangdServer.h" #include "Logger.h" #include "clang/Basic/VirtualFileSystem.h" @@ -1056,7 +1055,7 @@ AddDocument(FileIndex); Position Pos{LineDist(RandGen), ColumnDist(RandGen)}; - ASSERT_TRUE(!!Server.findDefinitions(FilePaths[FileIndex], Pos)); + Server.findDefinitions(FilePaths[FileIndex], Pos); }; std::vector> AsyncRequests = { @@ -1185,6 +1184,57 @@ EXPECT_FALSE(PathResult.hasValue()); } +TEST_F(ClangdVFSTest, CheckDefinitionIncludes) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true); + + ClangdServer Server(CDB, DiagConsumer, FS, 0, clangd::CodeCompleteOptions(), + EmptyLogger::getInstance()); + + auto FooCpp = getVirtualTestFilePath("foo.cpp"); + const auto SourceContents = R"cpp( + #include "foo.h" + #include "invalid.h" + int b = a; + )cpp"; + FS.Files[FooCpp] = SourceContents; + auto FooH = getVirtualTestFilePath("foo.h"); + const auto HeaderContents = "int a;"; + + FS.Files[FooCpp] = SourceContents; + FS.Files[FooH] = HeaderContents; + + Server.addDocument(FooH, HeaderContents); + Server.addDocument(FooCpp, SourceContents); + + Position P = Position{1, 11}; + + std::vector Locations = Server.findDefinitions(FooCpp, P).get().Value; + EXPECT_TRUE(!Locations.empty()); + std::string s("file:///"); + std::string check = URI::unparse(Locations[0].uri); + check = check.erase(0, s.size()); + check = check.substr(0, check.size() - 1); + ASSERT_EQ(check, FooH); + ASSERT_EQ(Locations[0].range.start.line, 0); + ASSERT_EQ(Locations[0].range.start.character, 0); + ASSERT_EQ(Locations[0].range.end.line, 0); + ASSERT_EQ(Locations[0].range.end.character, 0); + + // Test ctrl-clicking on the #include part on the statement + Position P3 = Position{1, 3}; + + Locations = Server.findDefinitions(FooCpp, P3).get().Value; + EXPECT_TRUE(!Locations.empty()); + + // Test invalid include + Position P2 = Position{2, 11}; + + Locations = Server.findDefinitions(FooCpp, P2).get().Value; + EXPECT_TRUE(Locations.empty()); +} + TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { public: