Index: clang-tools-extra/clangd/ClangdLSPServer.h =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.h +++ clang-tools-extra/clangd/ClangdLSPServer.h @@ -143,6 +143,8 @@ void onSemanticTokens(const SemanticTokensParams &, Callback); void onSemanticTokensDelta(const SemanticTokensDeltaParams &, Callback); + void onCodeLens(const CodeLensParams &, Callback>); + void onCodeLensResolve(const CodeLens &, Callback); /// This is a clangd extension. Provides a json tree representing memory usage /// hierarchy. void onMemoryUsage(const NoParams &, Callback); Index: clang-tools-extra/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -620,7 +620,8 @@ llvm::json::Object{ {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, - ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, + ExecuteCommandParams::CLANGD_APPLY_TWEAK, + ExecuteCommandParams::CLANGD_SERVER_CODELENS}}, }}, {"typeHierarchyProvider", true}, {"memoryUsageProvider", true}, // clangd extension. @@ -634,6 +635,11 @@ llvm::json::Object{{"scopes", buildHighlightScopeLookupTable()}}}); if (Opts.FoldingRanges) Result.getObject("capabilities")->insert({"foldingRangeProvider", true}); + if (Opts.CodeLens) + Result.getObject("capabilities") + ->insert({"codeLensProvider", llvm::json::Object{ + {"resolveProvider", true}, + }}); Reply(std::move(Result)); } @@ -777,6 +783,10 @@ Server->applyTweak(Params.tweakArgs->file.file(), Params.tweakArgs->selection, Params.tweakArgs->tweakID, std::move(Action)); + } else if (Params.command == ExecuteCommandParams::CLANGD_SERVER_CODELENS && + Params.codeLens) { + Server->onCodeLensCommand(*Params.codeLens, Opts.CodeComplete.Limit, + std::move(Reply)); } else { // We should not get here because ExecuteCommandParams would not have // parsed in the first place and this handler should not be called. But if @@ -843,7 +853,12 @@ const DidCloseTextDocumentParams &Params) { PathRef File = Params.textDocument.uri.file(); DraftMgr.removeDraft(File); - Server->removeDocument(File); + // Rebuilding AST wastes a lot of time, which makes codeLens unacceptably + // slow (100x in common). So keep AST of the opened file for a better user + // experience. Maybe we need to change the current LRU Cache implementation. + // See also https://github.com/clangd/clangd/issues/589. + + // Server->removeDocument(File); { std::lock_guard Lock(FixItsMutex); @@ -1398,6 +1413,30 @@ }); } +void ClangdLSPServer::onCodeLens(const CodeLensParams &Params, + Callback> Reply) { + URIForFile FileURI = Params.textDocument.uri; + Server->provideCodeLens( + FileURI.file(), + [Reply = std::move(Reply)]( + llvm::Expected> CodeLens) mutable { + if (!CodeLens) + return Reply(CodeLens.takeError()); + return Reply(std::move(*CodeLens)); + }); +} + +void ClangdLSPServer::onCodeLensResolve(const CodeLens &Params, + Callback Reply) { + Server->resolveCodeLens( + Params, Opts.CodeComplete.Limit, + [Reply = std::move(Reply)](llvm::Expected CodeLens) mutable { + if (!CodeLens) + return Reply(CodeLens.takeError()); + return Reply(std::move(*CodeLens)); + }); +} + void ClangdLSPServer::onMemoryUsage(const NoParams &, Callback Reply) { llvm::BumpPtrAllocator DetailAlloc; @@ -1457,6 +1496,10 @@ MsgHandler->bind("$/memoryUsage", &ClangdLSPServer::onMemoryUsage); if (Opts.FoldingRanges) MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange); + if (Opts.CodeLens) { + MsgHandler->bind("textDocument/codeLens", &ClangdLSPServer::onCodeLens); + MsgHandler->bind("codeLens/resolve", &ClangdLSPServer::onCodeLensResolve); + } // clang-format on // Delay first profile until we've finished warming up. Index: clang-tools-extra/clangd/ClangdServer.h =================================================================== --- clang-tools-extra/clangd/ClangdServer.h +++ clang-tools-extra/clangd/ClangdServer.h @@ -94,7 +94,7 @@ /// thread, and callbacks are invoked before "async" functions return. unsigned AsyncThreadsCount = getDefaultAsyncThreadsCount(); - /// AST caching policy. The default is to keep up to 3 ASTs in memory. + /// AST caching policy. The default is to keep up to 16 ASTs in memory. ASTRetentionPolicy RetentionPolicy; /// Cached preambles are potentially large. If false, store them on disk. @@ -166,6 +166,9 @@ /// Enable preview of FoldingRanges feature. bool FoldingRanges = false; + /// Enable preview of CodeLens feature. + bool CodeLens = false; + explicit operator TUScheduler::Options() const; }; // Sensible default options for use in tests. @@ -300,6 +303,9 @@ /// Apply the code tweak with a specified \p ID. void applyTweak(PathRef File, Range Sel, StringRef ID, Callback CB); + /// Implement 'clangd.server.codeLens' + void onCodeLensCommand(const CodeLensResolveData &Params, uint32_t Limit, + Callback CB); /// Called when an event occurs for a watched file in the workspace. void onFileEvent(const DidChangeWatchedFilesParams &Params); @@ -318,6 +324,10 @@ void semanticHighlights(PathRef File, Callback>); + /// CodeLenses. + void provideCodeLens(PathRef File, Callback> CB); + void resolveCodeLens(const CodeLens &Params, uint32_t Limit, + Callback CB); /// Describe the AST subtree for a piece of code. void getAST(PathRef File, Range R, Callback> CB); Index: clang-tools-extra/clangd/ClangdServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdServer.cpp +++ clang-tools-extra/clangd/ClangdServer.cpp @@ -576,6 +576,89 @@ WorkScheduler.runWithAST("ApplyTweak", File, std::move(Action)); } +void ClangdServer::onCodeLensCommand(const CodeLensResolveData &Params, + uint32_t Limit, + Callback Reply) { + auto File = Params.location.uri.file(); + auto Pos = Params.location.range.start; + switch (Params.type) { + case CodeLensType::Reference: { + auto Action = + [Reply = std::move(Reply)](Expected Refs) mutable { + if (!Refs) + return Reply(Refs.takeError()); + Reply(Refs->References); + }; + findReferences(File, Pos, Limit, std::move(Action)); + break; + } + case CodeLensType::TypeParent: { + auto Action = [File, Pos, Reply = std::move(Reply), + this](Expected InpAST) mutable { + std::vector Results; + auto Items = getTypeHierarchy( + InpAST->AST, Pos, 1, TypeHierarchyDirection::Parents, Index, File); + if (Items && Items->parents) { + for (auto Item : *Items->parents) { + Results.emplace_back(Location{Item.uri, Item.selectionRange}); + } + } + Reply(std::move(Results)); + }; + WorkScheduler.runWithAST("CodeLensCommand", File, std::move(Action)); + break; + } + case CodeLensType::TypeChildren: { + auto Action = [File, Pos, Reply = std::move(Reply), + this](Expected InpAST) mutable { + std::vector Results; + auto Items = getTypeHierarchy( + InpAST->AST, Pos, 1, TypeHierarchyDirection::Children, Index, File); + if (Items && Items->children) { + for (auto Item : *Items->children) { + Results.emplace_back(Location{Item.uri, Item.selectionRange}); + } + } + Reply(std::move(Results)); + }; + WorkScheduler.runWithAST("CodeLensCommand", File, std::move(Action)); + break; + } + case CodeLensType::MemberParent: { + auto Action = [File, Pos, Reply = std::move(Reply), + this](Expected InpAST) mutable { + std::vector Results; + auto Items = getMemberHierarchy( + InpAST->AST, Pos, 1, TypeHierarchyDirection::Parents, Index, File); + if (Items && Items->parents) { + for (auto Item : *Items->parents) { + Results.emplace_back(Location{Item.uri, Item.selectionRange}); + } + } + Reply(std::move(Results)); + }; + WorkScheduler.runWithAST("CodeLensCommand", File, std::move(Action)); + break; + } + case CodeLensType::MemberChildren: { + auto Action = [File, Pos, Reply = std::move(Reply), + this](Expected InpAST) mutable { + std::vector Results; + auto Items = getMemberHierarchy( + InpAST->AST, Pos, 1, TypeHierarchyDirection::Children, Index, File); + if (Items && Items->children) { + for (auto Item : *Items->children) { + Results.emplace_back(Location{Item.uri, Item.selectionRange}); + } + } + Reply(std::move(Results)); + }; + WorkScheduler.runWithAST("CodeLensCommand", File, std::move(Action)); + break; + } + } +} + void ClangdServer::locateSymbolAt(PathRef File, Position Pos, Callback> CB) { auto Action = [Pos, CB = std::move(CB), @@ -816,6 +899,30 @@ }; WorkScheduler.runWithAST("GetAST", File, std::move(Action)); } +void ClangdServer::provideCodeLens(PathRef File, + Callback> CB) { + auto Action = [CB = std::move(CB), File = File.str(), + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getDocumentCodeLens(InpAST->AST, Index, File)); + }; + WorkScheduler.runWithAST("DocumentCodeLens", File, std::move(Action), + TUScheduler::InvalidateOnUpdate); +} + +void ClangdServer::resolveCodeLens(const CodeLens &Params, uint32_t Limit, + Callback CB) { + auto File = Params.data->location.uri.file(); + auto Action = [CB = std::move(CB), File = File.str(), Params, Limit, + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::resolveCodeLens(InpAST->AST, Params, Limit, Index, File)); + }; + WorkScheduler.runWithAST("ResolveCodeLens", File, std::move(Action), + TUScheduler::InvalidateOnUpdate); +} void ClangdServer::customAction(PathRef File, llvm::StringRef Name, Callback Action) { Index: clang-tools-extra/clangd/Protocol.h =================================================================== --- clang-tools-extra/clangd/Protocol.h +++ clang-tools-extra/clangd/Protocol.h @@ -216,6 +216,7 @@ return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range); } }; +bool fromJSON(const llvm::json::Value &, Location &, llvm::json::Path); llvm::json::Value toJSON(const Location &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Location &); @@ -898,6 +899,7 @@ /// Note: "documentChanges" is not currently used because currently there is /// no support for versioned edits. }; +bool operator==(const WorkspaceEdit &, const WorkspaceEdit &); bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path); llvm::json::Value toJSON(const WorkspaceEdit &WE); @@ -913,9 +915,28 @@ /// ID of the tweak that should be executed. Corresponds to Tweak::id(). std::string tweakID; }; +bool operator==(const TweakArgs &, const TweakArgs &); bool fromJSON(const llvm::json::Value &, TweakArgs &, llvm::json::Path); llvm::json::Value toJSON(const TweakArgs &A); +enum class CodeLensType { + Reference = 1, + TypeParent = 2, + TypeChildren = 3, + MemberParent = 4, + MemberChildren = 5 +}; +bool fromJSON(const llvm::json::Value &, CodeLensType &, llvm::json::Path); + +struct CodeLensResolveData { + CodeLensType type; + Location location; +}; +bool operator==(const CodeLensResolveData &, const CodeLensResolveData &); +bool fromJSON(const llvm::json::Value &, CodeLensResolveData &, + llvm::json::Path); +llvm::json::Value toJSON(const CodeLensResolveData &A); + /// Exact commands are not specified in the protocol so we define the /// ones supported by Clangd here. The protocol specifies the command arguments /// to be "any[]" but to make this safer and more manageable, each command we @@ -930,12 +951,20 @@ // Command to apply the code action. Uses TweakArgs as argument. const static llvm::StringLiteral CLANGD_APPLY_TWEAK; + // Command to identify codeLens. Only used from server to client. + // Note it should be implemented in client by calling CLANGD_SERVER_CODELENS, + // so that client can handle codeLens result themselves, e.g. invoke + // 'editor.action.showReferences' in vscode + const static llvm::StringLiteral CLANGD_CLIENT_CODELENS; + // The actucal codeLens commands implemented by clangd sever. + const static llvm::StringLiteral CLANGD_SERVER_CODELENS; /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND std::string command; // Arguments llvm::Optional workspaceEdit; llvm::Optional tweakArgs; + llvm::Optional codeLens; }; bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &, llvm::json::Path); @@ -943,8 +972,24 @@ struct Command : public ExecuteCommandParams { std::string title; }; +bool operator==(const Command &, const Command &); llvm::json::Value toJSON(const Command &C); +/// https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens +struct CodeLensParams : DocumentSymbolParams {}; + +struct CodeLens { + // CodeLens range. + Range range; + // CodeLens command. + llvm::Optional command; + // CodeLens resolve data. + llvm::Optional data; +}; +bool operator==(const CodeLens &, const CodeLens &); +bool fromJSON(const llvm::json::Value &, CodeLens &, llvm::json::Path); +llvm::json::Value toJSON(const CodeLens &); + /// A code action represents a change that can be performed in code, e.g. to fix /// a problem or to refactor code. /// Index: clang-tools-extra/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/clangd/Protocol.cpp +++ clang-tools-extra/clangd/Protocol.cpp @@ -137,6 +137,12 @@ return OS << R.start << '-' << R.end; } +bool fromJSON(const llvm::json::Value &Params, Location &L, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("uri", L.uri) && O.map("range", L.range); +} + llvm::json::Value toJSON(const Location &P) { return llvm::json::Object{ {"uri", P.uri}, @@ -634,16 +640,53 @@ O.map("range", R.range) && O.map("context", R.context); } +bool operator==(const WorkspaceEdit &LHS, const WorkspaceEdit &RHS) { + return LHS.changes == RHS.changes; +} + bool fromJSON(const llvm::json::Value &Params, WorkspaceEdit &R, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); return O && O.map("changes", R.changes); } +bool fromJSON(const llvm::json::Value &Params, CodeLensType &R, + llvm::json::Path P) { + if (auto T = Params.getAsInteger()) { + if (*T < static_cast(CodeLensType::Reference) || + *T > static_cast(CodeLensType::MemberChildren)) + return false; + R = static_cast(*T); + return true; + } + return false; +} + +bool operator==(const CodeLensResolveData &LHS, + const CodeLensResolveData &RHS) { + return LHS.location == RHS.location && LHS.type == RHS.type; +} + +bool fromJSON(const llvm::json::Value &Params, CodeLensResolveData &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("type", R.type) && O.map("location", R.location); +} + +llvm::json::Value toJSON(const CodeLensResolveData &P) { + llvm::json::Object O{{"type", static_cast(P.type)}, + {"location", P.location}}; + return std::move(O); +} + const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND = "clangd.applyFix"; const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_TWEAK = "clangd.applyTweak"; +const llvm::StringLiteral ExecuteCommandParams::CLANGD_CLIENT_CODELENS = + "clangd.client.codeLens"; +const llvm::StringLiteral ExecuteCommandParams::CLANGD_SERVER_CODELENS = + "clangd.server.codeLens"; bool fromJSON(const llvm::json::Value &Params, ExecuteCommandParams &R, llvm::json::Path P) { @@ -660,6 +703,9 @@ if (R.command == ExecuteCommandParams::CLANGD_APPLY_TWEAK) return Args && Args->size() == 1 && fromJSON(Args->front(), R.tweakArgs, P.field("arguments").index(0)); + if (R.command == ExecuteCommandParams::CLANGD_SERVER_CODELENS) + return Args && Args->size() == 1 && + fromJSON(Args->front(), R.codeLens, P.field("arguments").index(0)); return false; // Unrecognized command. } @@ -726,15 +772,43 @@ return O && O.map("query", R.query); } +bool operator==(const Command &LHS, const Command &RHS) { + return LHS.title == RHS.title && LHS.command == RHS.command && + LHS.codeLens == RHS.codeLens && LHS.tweakArgs == RHS.tweakArgs && + LHS.workspaceEdit == RHS.workspaceEdit; +} + llvm::json::Value toJSON(const Command &C) { auto Cmd = llvm::json::Object{{"title", C.title}, {"command", C.command}}; if (C.workspaceEdit) Cmd["arguments"] = {*C.workspaceEdit}; if (C.tweakArgs) Cmd["arguments"] = {*C.tweakArgs}; + if (C.codeLens) + Cmd["arguments"] = {*C.codeLens}; return std::move(Cmd); } +bool operator==(const CodeLens &LHS, const CodeLens &RHS) { + return LHS.command == RHS.command && LHS.data == RHS.data && + LHS.range == RHS.range; +} + +bool fromJSON(const llvm::json::Value &Params, CodeLens &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("range", R.range) && O.map("data", R.data); +} + +llvm::json::Value toJSON(const CodeLens &C) { + llvm::json::Object O{{"range", C.range}}; + if (C.command) + O["command"] = *C.command; + if (C.data) + O["data"] = *C.data; + return std::move(O); +} + const llvm::StringLiteral CodeAction::QUICKFIX_KIND = "quickfix"; const llvm::StringLiteral CodeAction::REFACTOR_KIND = "refactor"; const llvm::StringLiteral CodeAction::INFO_KIND = "info"; @@ -783,6 +857,11 @@ return llvm::json::Object{{"changes", std::move(FileChanges)}}; } +bool operator==(const TweakArgs &LHS, const TweakArgs &RHS) { + return LHS.file == RHS.file && LHS.tweakID == RHS.tweakID && + LHS.selection == RHS.selection; +} + bool fromJSON(const llvm::json::Value &Params, TweakArgs &A, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); Index: clang-tools-extra/clangd/TUScheduler.h =================================================================== --- clang-tools-extra/clangd/TUScheduler.h +++ clang-tools-extra/clangd/TUScheduler.h @@ -59,7 +59,7 @@ struct ASTRetentionPolicy { /// Maximum number of ASTs to be retained in memory when there are no pending /// requests for them. - unsigned MaxRetainedASTs = 3; + unsigned MaxRetainedASTs = 16; }; /// Clangd may wait after an update to see if another one comes along. Index: clang-tools-extra/clangd/XRefs.h =================================================================== --- clang-tools-extra/clangd/XRefs.h +++ clang-tools-extra/clangd/XRefs.h @@ -106,6 +106,12 @@ ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction, const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{}); +llvm::Optional +getMemberHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, + TypeHierarchyDirection Direction, + const SymbolIndex *Index = nullptr, + PathRef TUPath = PathRef{}); + void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, TypeHierarchyDirection Direction, const SymbolIndex *Index); @@ -113,6 +119,15 @@ /// Returns all decls that are referenced in the \p FD except local symbols. llvm::DenseSet getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD); + +/// Get codelens +llvm::Expected> +getDocumentCodeLens(ParsedAST &AST, const SymbolIndex *Index, PathRef Path); + +llvm::Expected resolveCodeLens(ParsedAST &AST, const CodeLens &Params, + uint32_t Limit, + const SymbolIndex *Index, + PathRef Path); } // namespace clangd } // namespace clang Index: clang-tools-extra/clangd/XRefs.cpp =================================================================== --- clang-tools-extra/clangd/XRefs.cpp +++ clang-tools-extra/clangd/XRefs.cpp @@ -1554,8 +1554,6 @@ TypeHierarchyDirection Direction, const SymbolIndex *Index, PathRef TUPath) { const CXXRecordDecl *CXXRD = findRecordTypeAt(AST, Pos); - if (!CXXRD) - return llvm::None; bool WantParents = Direction == TypeHierarchyDirection::Parents || Direction == TypeHierarchyDirection::Both; @@ -1570,10 +1568,13 @@ // specialization parameters, while the children would involve classes // that derive from other specializations of the template. if (WantChildren) { - if (auto *CTSD = dyn_cast(CXXRD)) + if (auto *CTSD = dyn_cast_or_null(CXXRD)) CXXRD = CTSD->getTemplateInstantiationPattern(); } + if (!CXXRD) + return llvm::None; + Optional Result = declToTypeHierarchyItem(AST.getASTContext(), *CXXRD); if (!Result) @@ -1617,6 +1618,102 @@ } } +static void fillSuperMethods(const CXXMethodDecl &CXXMD, ASTContext &ASTCtx, + std::vector &SuperMethods) { + for (const CXXMethodDecl *ParentDecl : CXXMD.overridden_methods()) { + if (Optional ParentSym = + declToTypeHierarchyItem(ASTCtx, *ParentDecl)) { + ParentSym->parents.emplace(); + fillSuperMethods(*ParentDecl, ASTCtx, *ParentSym->parents); + SuperMethods.emplace_back(std::move(*ParentSym)); + } + } +} + +static void fillSubMethods(const SymbolID &ID, + std::vector &SubMethods, + const SymbolIndex *Index, int Levels, + PathRef TUPath) { + RelationsRequest Req; + Req.Subjects.insert(ID); + Req.Predicate = RelationKind::OverriddenBy; + Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) { + if (Optional ChildSym = + symbolToTypeHierarchyItem(Object, Index, TUPath)) { + if (Levels > 1) { + ChildSym->children.emplace(); + fillSubMethods(Object.ID, *ChildSym->children, Index, Levels - 1, + TUPath); + } + SubMethods.emplace_back(std::move(*ChildSym)); + } + }); +} + +const NamedDecl *findNamedDeclAt(ParsedAST &AST, Position Pos) { + auto DeclFromNode = [](const SelectionTree::Node *N) -> const NamedDecl * { + if (!N) + return nullptr; + + // Note: explicitReferenceTargets() will search for both template + // instantiations and template patterns, and prefer the former if available + // (generally, one will be available for non-dependent specializations of a + // class template). + auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying); + if (Decls.empty()) + return nullptr; + + return Decls[0]; + }; + + const SourceManager &SM = AST.getSourceManager(); + const NamedDecl *Result = nullptr; + auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos); + if (!Offset) { + llvm::consumeError(Offset.takeError()); + return Result; + } + SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), *Offset, + *Offset, [&](SelectionTree ST) { + Result = DeclFromNode(ST.commonAncestor()); + return Result != nullptr; + }); + return Result; +} + +llvm::Optional +getMemberHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, + TypeHierarchyDirection Direction, const SymbolIndex *Index, + PathRef TUPath) { + auto CXXMD = dyn_cast_or_null(findNamedDeclAt(AST, Pos)); + if (!CXXMD || !CXXMD->isVirtual()) + return llvm::None; + + Optional Result = + declToTypeHierarchyItem(AST.getASTContext(), *CXXMD); + + if (!Result) + return Result; + + if (Direction == TypeHierarchyDirection::Parents || + Direction == TypeHierarchyDirection::Both) { + Result->parents.emplace(); + fillSuperMethods(*CXXMD, AST.getASTContext(), *Result->parents); + } + + if ((Direction == TypeHierarchyDirection::Children || + Direction == TypeHierarchyDirection::Both) && + ResolveLevels > 0) { + Result->children.emplace(); + + if (Index) { + if (auto ID = getSymbolID(CXXMD)) + fillSubMethods(ID, *Result->children, Index, ResolveLevels, TUPath); + } + } + return Result; +} + llvm::DenseSet getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD) { if (!FD->hasBody()) @@ -1631,5 +1728,95 @@ }); return DeclRefs; } + +void collectCodeLens(ParsedAST &AST, const DocumentSymbol &Symbol, + const SymbolIndex *Index, PathRef Path, + std::vector &Result) { + CodeLensResolveData Data; + Data.location = + Location{URIForFile::canonicalize(Path, Path), Symbol.selectionRange}; + Data.type = CodeLensType::Reference; + Result.emplace_back(CodeLens{Symbol.selectionRange, None, Data}); + // Collect codelens for hierarchy directly. + Position Pos = Symbol.selectionRange.start; + Command Cmd; + Cmd.command = std::string(ExecuteCommandParams::CLANGD_CLIENT_CODELENS); + switch (Symbol.kind) { + case SymbolKind::Class: + case SymbolKind::Struct: + case SymbolKind::Interface: { + auto CXXRD = dyn_cast_or_null(findNamedDeclAt(AST, Pos)); + if (CXXRD && CXXRD->hasDefinition() && CXXRD->getNumBases()) { + Data.type = CodeLensType::TypeParent; + Cmd.codeLens = Data; + Cmd.title = std::to_string(CXXRD->getNumBases()) + " base"; + Result.emplace_back(CodeLens{Symbol.selectionRange, Cmd, None}); + } + auto Items = getTypeHierarchy(AST, Pos, 1, TypeHierarchyDirection::Children, + Index, Path); + if (Items && Items->children && Items->children->size()) { + Data.type = CodeLensType::TypeChildren; + Cmd.codeLens = Data; + Cmd.title = std::to_string(Items->children->size()) + " derived"; + Result.emplace_back(CodeLens{Symbol.selectionRange, Cmd, None}); + } + break; + } + case SymbolKind::Method: { + auto CXXMD = dyn_cast_or_null( + findNamedDeclAt(AST, Symbol.selectionRange.start)); + if (CXXMD && CXXMD->isVirtual()) { + if (CXXMD->size_overridden_methods()) { + Data.type = CodeLensType::MemberParent; + Cmd.codeLens = Data; + Cmd.title = std::to_string(CXXMD->size_overridden_methods()) + " base"; + Result.emplace_back(CodeLens{Symbol.selectionRange, Cmd, None}); + } + auto Items = getMemberHierarchy( + AST, Pos, 1, TypeHierarchyDirection::Children, Index, Path); + if (Items && Items->children && Items->children->size()) { + Data.type = CodeLensType::MemberChildren; + Cmd.codeLens = Data; + Cmd.title = std::to_string(Items->children->size()) + " derived"; + Result.emplace_back(CodeLens{Symbol.selectionRange, Cmd, None}); + } + } + break; + } + default: + break; + } + for (auto &&Child : Symbol.children) { + collectCodeLens(AST, Child, Index, Path, Result); + } +} + +llvm::Expected> +getDocumentCodeLens(ParsedAST &AST, const SymbolIndex *Index, PathRef Path) { + auto DocumentSymbols = getDocumentSymbols(AST); + if (!DocumentSymbols) + return DocumentSymbols.takeError(); + std::vector Result; + for (const auto &Symbol : *DocumentSymbols) { + collectCodeLens(AST, Symbol, Index, Path, Result); + } + return Result; +} + +llvm::Expected resolveCodeLens(ParsedAST &AST, const CodeLens &Params, + uint32_t Limit, + const SymbolIndex *Index, + PathRef Path) { + CodeLensResolveData Data = *Params.data; + Command Cmd; + Cmd.command = std::string(ExecuteCommandParams::CLANGD_CLIENT_CODELENS); + Cmd.codeLens = Data; + Position Pos = Params.range.start; + if (Data.type == CodeLensType::Reference) { + auto Refs = findReferences(AST, Pos, Limit, Index).References; + Cmd.title = std::to_string(Refs.size()) + " refs"; + } + return CodeLens{Params.range, Cmd, None}; +} } // namespace clangd } // namespace clang Index: clang-tools-extra/clangd/test/initialize-params.test =================================================================== --- clang-tools-extra/clangd/test/initialize-params.test +++ clang-tools-extra/clangd/test/initialize-params.test @@ -63,7 +63,8 @@ # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ # CHECK-NEXT: "clangd.applyFix", -# CHECK-NEXT: "clangd.applyTweak" +# CHECK-NEXT: "clangd.applyTweak", +# CHECK-NEXT: "clangd.server.codeLens" # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "hoverProvider": true, Index: clang-tools-extra/clangd/tool/ClangdMain.cpp =================================================================== --- clang-tools-extra/clangd/tool/ClangdMain.cpp +++ clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -330,6 +330,11 @@ Hidden, }; +opt EnableCodeLens{ + "code-lens", cat(Features), desc("Enable preview of CodeLens feature"), + init(false), Hidden, +}; + opt WorkerThreadsCount{ "j", cat(Misc), @@ -762,6 +767,7 @@ Opts.BuildRecoveryAST = RecoveryAST; Opts.PreserveRecoveryASTType = RecoveryASTType; Opts.FoldingRanges = FoldingRanges; + Opts.CodeLens = EnableCodeLens; Opts.CodeComplete.IncludeIneligibleResults = IncludeIneligibleResults; Opts.CodeComplete.Limit = LimitResults; Index: clang-tools-extra/clangd/unittests/XRefsTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -1966,6 +1966,83 @@ URIForFile::canonicalize(testPath("bar.h"), "")}))); } +CodeLens lens(const URIForFile &URI, const CodeLensType &Type, + const Range &Range) { + return CodeLens{Range, None, CodeLensResolveData{Type, {URI, Range}}}; +} + +CodeLens lens(const URIForFile &URI, const std::string &Title, + const CodeLensType &Type, const Range &Range) { + Command Cmd; + Cmd.title = Title; + Cmd.command = std::string(ExecuteCommandParams::CLANGD_CLIENT_CODELENS); + Cmd.codeLens = CodeLensResolveData{Type, {URI, Range}}; + return CodeLens{Range, Cmd, None}; +} + +TEST(CodeLensTest, Reference) { + Annotations Main(R"cpp( + class $X[[X]] { + }; + int $main[[main]]() { + X x; + } + )cpp"); + TestTU TU; + TU.Code = std::string(Main.code()); + auto AST = TU.build(); + auto Path = URIForFile::canonicalize(testPath(TU.Filename), testRoot()); + if (auto CodeLens1 = + getDocumentCodeLens(AST, nullptr, testPath(TU.Filename))) { + EXPECT_THAT( + *CodeLens1, + ElementsAre(lens(Path, CodeLensType::Reference, Main.range("X")), + lens(Path, CodeLensType::Reference, Main.range("main")))); + std::vector CodeLens2; + for (auto &&CD : *CodeLens1) { + CodeLens2.emplace_back( + *resolveCodeLens(AST, CD, 0, nullptr, testPath(TU.Filename))); + } + EXPECT_THAT( + CodeLens2, + ElementsAre( + lens(Path, "2 refs", CodeLensType::Reference, Main.range("X")), + lens(Path, "1 refs", CodeLensType::Reference, Main.range("main")))); + } +} + +TEST(CodeLensTest, Inheritance) { + Annotations Main(R"cpp( + class $X[[X]] { + public: + virtual void $m1[[method]](); + }; + + class $Y[[Y]] : public X { + public: + void $m2[[method]]() override; + }; + )cpp"); + TestTU TU; + TU.Code = std::string(Main.code()); + auto AST = TU.build(); + auto URI = URIForFile::canonicalize(testPath(TU.Filename), testRoot()); + if (auto CodeLens = + getDocumentCodeLens(AST, TU.index().get(), testPath(TU.Filename))) { + EXPECT_THAT( + *CodeLens, + ElementsAre( + lens(URI, CodeLensType::Reference, Main.range("X")), + lens(URI, "1 derived", CodeLensType::TypeChildren, Main.range("X")), + lens(URI, CodeLensType::Reference, Main.range("m1")), + lens(URI, "1 derived", CodeLensType::MemberChildren, + Main.range("m1")), + lens(URI, CodeLensType::Reference, Main.range("Y")), + lens(URI, "1 base", CodeLensType::TypeParent, Main.range("Y")), + lens(URI, CodeLensType::Reference, Main.range("m2")), + lens(URI, "1 base", CodeLensType::MemberParent, Main.range("m2")))); + } +} } // namespace } // namespace clangd } // namespace clang