Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -60,32 +60,6 @@ static URISchemeRegistry::Add X("test", "Test scheme for clangd lit tests."); -TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) { - Range ReplacementRange = { - offsetToPosition(Code, R.getOffset()), - offsetToPosition(Code, R.getOffset() + R.getLength())}; - return {ReplacementRange, R.getReplacementText()}; -} - -std::vector -replacementsToEdits(StringRef Code, - const std::vector &Replacements) { - // Turn the replacements into the format specified by the Language Server - // Protocol. Fuse them into one big JSON array. - std::vector Edits; - for (const auto &R : Replacements) - Edits.push_back(replacementToEdit(Code, R)); - return Edits; -} - -std::vector replacementsToEdits(StringRef Code, - const tooling::Replacements &Repls) { - std::vector Edits; - for (const auto &R : Repls) - Edits.push_back(replacementToEdit(Code, R)); - return Edits; -} - SymbolKindBitset defaultSymbolKinds() { SymbolKindBitset Defaults; for (size_t I = SymbolKindMin; I <= static_cast(SymbolKind::Array); Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -12,6 +12,7 @@ #include "Diagnostics.h" #include "Function.h" +#include "Headers.h" #include "Path.h" #include "Protocol.h" #include "clang/Frontend/FrontendAction.h" @@ -40,18 +41,16 @@ namespace clangd { -using InclusionLocations = std::vector>; - // Stores Preamble and associated data. struct PreambleData { PreambleData(PrecompiledPreamble Preamble, std::vector TopLevelDeclIDs, - std::vector Diags, InclusionLocations IncLocations); + std::vector Diags, std::vector Inclusions); PrecompiledPreamble Preamble; std::vector TopLevelDeclIDs; std::vector Diags; - InclusionLocations IncLocations; + std::vector Inclusions; }; /// Information required to run clang, e.g. to parse AST or do code completion. @@ -95,14 +94,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; + const std::vector &getInclusions() const; private: ParsedAST(std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, std::vector Diags, - InclusionLocations IncLocations); + std::vector Inclusions); private: void ensurePreambleDeclsDeserialized(); @@ -122,7 +121,7 @@ std::vector Diags; std::vector TopLevelDecls; bool PreambleDeclsDeserialized; - InclusionLocations IncLocations; + std::vector Inclusions; }; using ASTParsedCallback = std::function; Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -83,43 +83,13 @@ std::vector TopLevelDecls; }; -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(halfOpenToRange(SourceMgr, FilenameRange), - 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); - } + std::vector takeInclusions() { return std::move(Inclusions); } void AfterPCHEmitted(ASTWriter &Writer) override { TopLevelDeclIDs.reserve(TopLevelDecls.size()); @@ -145,14 +115,13 @@ std::unique_ptr createPPCallbacks() override { assert(SourceMgr && "SourceMgr must be set at this point"); - return llvm::make_unique(*SourceMgr, - IncLocations); + return collectInclusionsInMainFileCallback(*SourceMgr, Inclusions); } private: std::vector TopLevelDecls; std::vector TopLevelDeclIDs; - InclusionLocations IncLocations; + std::vector Inclusions; SourceManager *SourceMgr = nullptr; }; @@ -190,15 +159,14 @@ return llvm::None; } - InclusionLocations IncLocations; + std::vector Inclusions; // Copy over the includes from the preamble, then combine with the // non-preamble includes below. if (Preamble) - IncLocations = Preamble->IncLocations; + Inclusions = Preamble->Inclusions; - Clang->getPreprocessor().addPPCallbacks( - llvm::make_unique(Clang->getSourceManager(), - IncLocations)); + Clang->getPreprocessor().addPPCallbacks(collectInclusionsInMainFileCallback( + Clang->getSourceManager(), Inclusions)); if (!Action->Execute()) log("Execute() failed when building AST for " + MainInput.getFile()); @@ -212,7 +180,7 @@ std::vector ParsedDecls = Action->takeTopLevelDecls(); return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), std::move(ParsedDecls), ASTDiags.take(), - std::move(IncLocations)); + std::move(Inclusions)); } void ParsedAST::ensurePreambleDeclsDeserialized() { @@ -278,27 +246,27 @@ ::getUsedBytes(TopLevelDecls) + ::getUsedBytes(Diags); } -const InclusionLocations &ParsedAST::getInclusionLocations() const { - return IncLocations; +const std::vector &ParsedAST::getInclusions() const { + return Inclusions; } PreambleData::PreambleData(PrecompiledPreamble Preamble, std::vector TopLevelDeclIDs, std::vector Diags, - InclusionLocations IncLocations) + std::vector Inclusions) : Preamble(std::move(Preamble)), TopLevelDeclIDs(std::move(TopLevelDeclIDs)), Diags(std::move(Diags)), - IncLocations(std::move(IncLocations)) {} + Inclusions(std::move(Inclusions)) {} ParsedAST::ParsedAST(std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, std::vector TopLevelDecls, - std::vector Diags, InclusionLocations IncLocations) + std::vector Diags, std::vector Inclusions) : Preamble(std::move(Preamble)), Clang(std::move(Clang)), Action(std::move(Action)), Diags(std::move(Diags)), TopLevelDecls(std::move(TopLevelDecls)), PreambleDeclsDeserialized(false), - IncLocations(std::move(IncLocations)) { + Inclusions(std::move(Inclusions)) { assert(this->Clang); assert(this->Action); } @@ -447,8 +415,7 @@ return std::make_shared( std::move(*BuiltPreamble), SerializedDeclsCollector.takeTopLevelDeclIDs(), - PreambleDiagnostics.take(), - SerializedDeclsCollector.takeInclusionLocations()); + PreambleDiagnostics.take(), SerializedDeclsCollector.takeInclusions()); } else { log("Could not build a preamble for file " + Twine(FileName)); return nullptr; Index: clangd/Headers.h =================================================================== --- clangd/Headers.h +++ clangd/Headers.h @@ -11,7 +11,9 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H #include "Path.h" +#include "Protocol.h" #include "clang/Basic/VirtualFileSystem.h" +#include "clang/Lex/PPCallbacks.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -32,6 +34,19 @@ bool valid() const; }; +// An #include directive that we found in the main file. +struct Inclusion { + Range R; // Inclusion range. + std::string Written; // Inclusion name as written e.g. . + Path Resolved; // Resolved path of included file. Empty if not resolved. +}; + +/// Returns a PPCallback that collects all inclusions in the main file. +/// Inclusions are added to \p Includes. +std::unique_ptr +collectInclusionsInMainFileCallback(const SourceManager &SM, + std::vector &Inclusions); + /// Determines the preferred way to #include a file, taking into account the /// search path. Usually this will prefer a shorter representation like /// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'. Index: clangd/Headers.cpp =================================================================== --- clangd/Headers.cpp +++ clangd/Headers.cpp @@ -10,6 +10,7 @@ #include "Headers.h" #include "Compiler.h" #include "Logger.h" +#include "SourceCode.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" @@ -24,26 +25,32 @@ class RecordHeaders : public PPCallbacks { public: - RecordHeaders(llvm::StringSet<> &WrittenHeaders, - llvm::StringSet<> &ResolvedHeaders) - : WrittenHeaders(WrittenHeaders), ResolvedHeaders(ResolvedHeaders) {} + RecordHeaders(const SourceManager &SM, std::vector &Includes) + : SM(SM), Includes(Includes) {} - void InclusionDirective(SourceLocation /*HashLoc*/, - const Token & /*IncludeTok*/, + // Record existing #includes - both written and resolved paths. Only #includes + // in the main file are collected. + void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/, llvm::StringRef FileName, bool IsAngled, - CharSourceRange /*FilenameRange*/, - const FileEntry *File, llvm::StringRef /*SearchPath*/, + CharSourceRange FilenameRange, const FileEntry *File, + llvm::StringRef /*SearchPath*/, llvm::StringRef /*RelativePath*/, const Module * /*Imported*/) override { - WrittenHeaders.insert( - (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str()); - if (File != nullptr && !File->tryGetRealPathName().empty()) - ResolvedHeaders.insert(File->tryGetRealPathName()); + // Only inclusion directives in the main file make sense. The user cannot + // select directives not in the main file. + if (HashLoc.isInvalid() || !SM.isInMainFile(HashLoc)) + return; + std::string Written = + (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str(); + std::string Resolved = (!File || File->tryGetRealPathName().empty()) + ? "" + : File->tryGetRealPathName(); + Includes.push_back({halfOpenToRange(SM, FilenameRange), Written, Resolved}); } private: - llvm::StringSet<> &WrittenHeaders; - llvm::StringSet<> &ResolvedHeaders; + const SourceManager &SM; + std::vector &Includes; }; } // namespace @@ -57,6 +64,12 @@ (!Verbatim && llvm::sys::path::is_absolute(File)); } +std::unique_ptr +collectInclusionsInMainFileCallback(const SourceManager &SM, + std::vector &Includes) { + return llvm::make_unique(SM, Includes); +} + /// FIXME(ioeric): we might not want to insert an absolute include path if the /// path is not shortened. llvm::Expected @@ -109,17 +122,21 @@ return llvm::make_error( "Failed to begin preprocessor only action for file " + File, llvm::inconvertibleErrorCode()); - llvm::StringSet<> WrittenHeaders; - llvm::StringSet<> ResolvedHeaders; - Clang->getPreprocessor().addPPCallbacks( - llvm::make_unique(WrittenHeaders, ResolvedHeaders)); + std::vector Inclusions; + Clang->getPreprocessor().addPPCallbacks(collectInclusionsInMainFileCallback( + Clang->getSourceManager(), Inclusions)); if (!Action.Execute()) return llvm::make_error( "Failed to execute preprocessor only action for file " + File, llvm::inconvertibleErrorCode()); + llvm::StringSet<> IncludedHeaders; + for (const auto &Inc : Inclusions) { + IncludedHeaders.insert(Inc.Written); + if (!Inc.Resolved.empty()) + IncludedHeaders.insert(Inc.Resolved); + } auto Included = [&](llvm::StringRef Header) { - return WrittenHeaders.find(Header) != WrittenHeaders.end() || - ResolvedHeaders.find(Header) != ResolvedHeaders.end(); + return IncludedHeaders.find(Header) != IncludedHeaders.end(); }; if (Included(DeclaringHeader.File) || Included(InsertedHeader.File)) return ""; Index: clangd/SourceCode.h =================================================================== --- clangd/SourceCode.h +++ clangd/SourceCode.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H #include "Protocol.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Core/Replacement.h" namespace clang { class SourceManager; @@ -54,6 +55,14 @@ /// qualifier. std::pair splitQualifiedName(llvm::StringRef QName); +TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R); + +std::vector +replacementsToEdits(StringRef Code, + const std::vector &Replacements); + +std::vector replacementsToEdits(StringRef Code, + const tooling::Replacements &Repls); } // namespace clangd } // namespace clang Index: clangd/SourceCode.cpp =================================================================== --- clangd/SourceCode.cpp +++ clangd/SourceCode.cpp @@ -166,5 +166,31 @@ return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)}; } +TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) { + Range ReplacementRange = { + offsetToPosition(Code, R.getOffset()), + offsetToPosition(Code, R.getOffset() + R.getLength())}; + return {ReplacementRange, R.getReplacementText()}; +} + +std::vector +replacementsToEdits(StringRef Code, + const std::vector &Replacements) { + // Turn the replacements into the format specified by the Language Server + // Protocol. Fuse them into one big JSON array. + std::vector Edits; + for (const auto &R : Replacements) + Edits.push_back(replacementToEdit(Code, R)); + return Edits; +} + +std::vector replacementsToEdits(StringRef Code, + const tooling::Replacements &Repls) { + std::vector Edits; + for (const auto &R : Repls) + Edits.push_back(replacementToEdit(Code, R)); + return Edits; +} + } // namespace clangd } // namespace clang Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -234,12 +234,10 @@ std::vector Result; // Handle goto definition for #include. - for (auto &IncludeLoc : AST.getInclusionLocations()) { - Range R = IncludeLoc.first; + for (auto &Inc : AST.getInclusions()) { Position Pos = sourceLocToPosition(SourceMgr, SourceLocationBeg); - - if (R.contains(Pos)) - Result.push_back(Location{URIForFile{IncludeLoc.second}, {}}); + if (!Inc.Resolved.empty() && Inc.R.contains(Pos)) + Result.push_back(Location{URIForFile{Inc.Resolved}, {}}); } if (!Result.empty()) return Result;