diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -173,6 +173,9 @@ void onCommandApplyEdit(const WorkspaceEdit &, Callback); void onCommandApplyTweak(const TweakArgs &, Callback); + /// Implement code lens. + void onCodeLens(const CodeLensParams &Params, Callback> Reply); + /// Outgoing LSP calls. LSPBinder::OutgoingMethod diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -586,6 +586,7 @@ {"clangdInlayHintsProvider", true}, {"inlayHintProvider", true}, {"foldingRangeProvider", true}, + {"codeLensProvider", true}, }; { @@ -1379,6 +1380,8 @@ void ClangdLSPServer::onReference( const ReferenceParams &Params, Callback> Reply) { + llvm::errs() << "On reference call with doc: " << Params.textDocument.uri.file() << "\n"; + llvm::errs() << "On reference call with position: " << Params.position << "\n"; Server->findReferences(Params.textDocument.uri.file(), Params.position, Opts.ReferencesLimit, SupportsReferenceContainer, [Reply = std::move(Reply), @@ -1399,6 +1402,11 @@ }); } +void ClangdLSPServer::onCodeLens(const CodeLensParams &Params, + Callback> Reply) { + Server->findCodeLens(Params.textDocument.uri.file(), std::move(Reply)); +} + void ClangdLSPServer::onGoToType(const TextDocumentPositionParams &Params, Callback> Reply) { Server->findType( @@ -1589,6 +1597,7 @@ Bind.method("textDocument/typeDefinition", this, &ClangdLSPServer::onGoToType); Bind.method("textDocument/implementation", this, &ClangdLSPServer::onGoToImplementation); Bind.method("textDocument/references", this, &ClangdLSPServer::onReference); + Bind.method("textDocument/codeLens", this, &ClangdLSPServer::onCodeLens); Bind.method("textDocument/switchSourceHeader", this, &ClangdLSPServer::onSwitchSourceHeader); Bind.method("textDocument/prepareRename", this, &ClangdLSPServer::onPrepareRename); Bind.method("textDocument/rename", this, &ClangdLSPServer::onRename); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -305,6 +305,9 @@ void findReferences(PathRef File, Position Pos, uint32_t Limit, bool AddContainer, Callback CB); + /// Retrieve code lenses. + void findCodeLens(PathRef File, Callback> CB); + /// Run formatting for the \p File with content \p Code. /// If \p Rng is non-null, formats only that region. void formatFile(PathRef File, std::optional Rng, diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -911,6 +911,17 @@ WorkScheduler->runWithAST("References", File, std::move(Action)); } +void ClangdServer::findCodeLens(PathRef File, Callback> CB) { + auto Action = [CB = std::move(CB)](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + + CB(clangd::findCodeLens(InpAST->AST)); + }; + + WorkScheduler->runWithAST("CodeLens", File, std::move(Action)); +} + void ClangdServer::symbolInfo(PathRef File, Position Pos, Callback> CB) { auto Action = diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -998,6 +998,7 @@ // This is `arguments?: []any` in LSP. // All clangd's commands accept a single argument (or none => null). llvm::json::Value argument = nullptr; + llvm::json::Value argument2 = nullptr; }; bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &, llvm::json::Path); @@ -1876,6 +1877,23 @@ llvm::json::Value toJSON(const ASTNode &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ASTNode &); +/// Parameters for the code lens request. +struct CodeLensParams { + TextDocumentIdentifier textDocument; +}; +bool fromJSON(const llvm::json::Value &, CodeLensParams &, + llvm::json::Path); + +/// Code lens response payload. +struct CodeLens { + /// The range in which this code lens is valid. + Range range; + /// The command this code lens represents. + Command command; +}; +llvm::json::Value toJSON(const CodeLens &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CodeLens &); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -535,6 +535,23 @@ return O && O.map("textDocument", R.textDocument); } +bool fromJSON(const llvm::json::Value &Params, CodeLensParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("textDocument", R.textDocument); +} + +llvm::json::Value toJSON(const CodeLens &CL) { + return llvm::json::Object { + {"range", CL.range}, + {"command", CL.command} + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CodeLens &CL) { + return OS << "Code lens: " << CL.range.start << '-' << CL.range.end; +} + bool fromJSON(const llvm::json::Value &Params, DidSaveTextDocumentParams &R, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); @@ -809,10 +826,10 @@ mapOptOrNull(Params, "limit", R.limit, P); } -llvm::json::Value toJSON(const Command &C) { +llvm::json::Value toJSON(const Command &C) { auto Cmd = llvm::json::Object{{"title", C.title}, {"command", C.command}}; if (!C.argument.getAsNull()) - Cmd["arguments"] = llvm::json::Array{C.argument}; + Cmd["arguments"] = llvm::json::Array{C.argument, C.argument2}; return std::move(Cmd); } @@ -1301,8 +1318,11 @@ bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R, llvm::json::Path P) { TextDocumentPositionParams &Base = R; + bool ReferenceParamsFromJson = fromJSON(Params, Base, P); + llvm::errs() << "Reference params from JSON document: " << R.textDocument.uri << "\n"; + llvm::errs() << "Reference params from JSON position: " << R.position << "\n"; llvm::json::ObjectMapper O(Params, P); - return fromJSON(Params, Base, P) && O && O.mapOptional("context", R.context); + return ReferenceParamsFromJson && O && O.mapOptional("context", R.context); } llvm::json::Value toJSON(SymbolTag Tag) { diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -32,6 +32,8 @@ namespace clangd { class ParsedAST; +std::vector findCodeLens(ParsedAST &AST); + // Describes where a symbol is declared and defined (as far as clangd knows). // There are three cases: // - a declaration only, no definition is known (e.g. only header seen) diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -10,12 +10,15 @@ #include "FindSymbols.h" #include "FindTarget.h" #include "HeuristicResolver.h" +#include "IncludeCleaner.h" #include "ParsedAST.h" #include "Protocol.h" #include "Quality.h" #include "Selection.h" #include "SourceCode.h" #include "URI.h" +#include "clang-include-cleaner/Analysis.h" +#include "clang-include-cleaner/Types.h" #include "index/Index.h" #include "index/Merge.h" #include "index/Relation.h" @@ -51,6 +54,7 @@ #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallSet.h" @@ -61,6 +65,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include +#include #include namespace clang { @@ -1312,8 +1317,67 @@ } } // namespace +std::vector findCodeLens(ParsedAST &AST) { + const auto &SM = AST.getSourceManager(); + std::vector Result; + auto Includes = AST.getIncludeStructure().MainFileIncludes; + auto ConvertedMainFileIncludes = convertIncludes(SM, Includes); + for (auto &Inc : Includes) { + llvm::DenseSet UsedSyms; + auto AnalyzedInclude = convertIncludes(SM, Inc); + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), collectMacroReferences(AST), + AST.getPragmaIncludes(), SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + if (Ref.RT != include_cleaner::RefType::Explicit) + return; + + for (const auto &H : Providers) { + auto MatchingIncludes = ConvertedMainFileIncludes.match(H); + // No match for this provider in the main file. + if (MatchingIncludes.empty()) + continue; + + // Check if the referenced include matches this provider. + if (!AnalyzedInclude.match(H).empty()) { + UsedSyms.insert(Ref.Target); + } + + // Don't look for rest of the providers once we've found a match + // in the main file. + break; + } + }); + + // Compute the length of the #include line + auto IncludeLen = + std::string{"#include"}.length() + Inc.Written.length() + 1; + CodeLens Lens; + Lens.range = clangd::Range{Position{Inc.HashLine, 0}, + Position{Inc.HashLine, (int)IncludeLen}}; + + Command Cmd; + Cmd.title = std::to_string(UsedSyms.size()) + " used symbols"; + Cmd.command = "clangd/findReferences"; + + auto MainFilePath = AST.tuPath(); + auto URIMainFile = URIForFile::canonicalize(MainFilePath, MainFilePath); + + Cmd.argument = URIMainFile; + Position P = sourceLocToPosition(SM, SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset)); + Cmd.argument2 = P; + Lens.command = Cmd; + Result.push_back(Lens); + } + + return Result; +} + ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index, bool AddContext) { + llvm::errs() << "Find references for path: " << AST.tuPath() << "\n"; + llvm::errs() << "Find references for position: " << Pos << "\n"; ReferencesResult Results; const SourceManager &SM = AST.getSourceManager(); auto MainFilePath = AST.tuPath(); @@ -1324,6 +1388,58 @@ return {}; } + auto Includes = AST.getIncludeStructure().MainFileIncludes; + auto ConvertedMainFileIncludes = convertIncludes(SM, Includes); + for (auto &Inc : Includes) { + if (Inc.HashLine != Pos.line) + continue; + + auto ReferencedInclude = convertIncludes(SM, Inc); + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), collectMacroReferences(AST), + AST.getPragmaIncludes(), SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + if (Ref.RT != include_cleaner::RefType::Explicit) + return; + + auto Loc = SM.getFileLoc(Ref.RefLocation); + for (const auto &H : Providers) { + auto MatchingIncludes = ConvertedMainFileIncludes.match(H); + // No match for this provider in the main file. + if (MatchingIncludes.empty()) + continue; + + // Check if the referenced include matches this provider. + if (!ReferencedInclude.match(H).empty()) { + ReferencesResult::Reference Result; + auto TokLen = + Lexer::MeasureTokenLength(Loc, SM, AST.getLangOpts()); + Result.Loc.range = + halfOpenToRange(SM, CharSourceRange::getCharRange( + Loc, Loc.getLocWithOffset(TokLen))); + Result.Loc.uri = URIMainFile; + Results.References.push_back(std::move(Result)); + } + + // Don't look for rest of the providers once we've found a match + // in the main file. + return; + } + }); + if (Results.References.empty()) + return {}; + + // Add the #include line to the references list. + auto IncludeLen = + std::string{"#include"}.length() + Inc.Written.length() + 1; + ReferencesResult::Reference Result; + Result.Loc.range = clangd::Range{Position{Inc.HashLine, 0}, + Position{Inc.HashLine, (int)IncludeLen}}; + Result.Loc.uri = URIMainFile; + Results.References.push_back(std::move(Result)); + } + llvm::DenseSet IDsToQuery, OverriddenMethods; const auto *IdentifierAtCursor = diff --git a/clang-tools-extra/clangd/test/code-action-request.test b/clang-tools-extra/clangd/test/code-action-request.test --- a/clang-tools-extra/clangd/test/code-action-request.test +++ b/clang-tools-extra/clangd/test/code-action-request.test @@ -44,7 +44,8 @@ # CHECK-NEXT: } # CHECK-NEXT: }, # CHECK-NEXT: "tweakID": "ExpandDeducedType" -# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: null # CHECK-NEXT: ], # CHECK-NEXT: "command": "clangd.applyTweak", # CHECK-NEXT: "title": "Replace with deduced type" diff --git a/clang-tools-extra/clangd/test/fixits-command.test b/clang-tools-extra/clangd/test/fixits-command.test --- a/clang-tools-extra/clangd/test/fixits-command.test +++ b/clang-tools-extra/clangd/test/fixits-command.test @@ -63,7 +63,8 @@ # CHECK-NEXT: } # CHECK-NEXT: ] # CHECK-NEXT: } -# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: null # CHECK-NEXT: ], # CHECK-NEXT: "command": "clangd.applyFix", # CHECK-NEXT: "title": "Apply fix: place parentheses around the assignment to silence this warning" @@ -88,7 +89,8 @@ # CHECK-NEXT: } # CHECK-NEXT: ] # CHECK-NEXT: } -# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: null # CHECK-NEXT: ], # CHECK-NEXT: "command": "clangd.applyFix", # CHECK-NEXT: "title": "Apply fix: use '==' to turn this assignment into an equality comparison" @@ -133,7 +135,8 @@ # CHECK-NEXT: } # CHECK-NEXT: ] # CHECK-NEXT: } -# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: null # CHECK-NEXT: ], # CHECK-NEXT: "command": "clangd.applyFix", # CHECK-NEXT: "title": "Apply fix: place parentheses around the assignment to silence this warning" @@ -158,7 +161,8 @@ # CHECK-NEXT: } # CHECK-NEXT: ] # CHECK-NEXT: } -# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: null # CHECK-NEXT: ], # CHECK-NEXT: "command": "clangd.applyFix", # CHECK-NEXT: "title": "Apply fix: use '==' to turn this assignment into an equality comparison" diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -9,6 +9,7 @@ # CHECK-NEXT: "callHierarchyProvider": true, # CHECK-NEXT: "clangdInlayHintsProvider": true, # CHECK-NEXT: "codeActionProvider": true, +# CHECK-NEXT: "codeLensProvider": true, # CHECK-NEXT: "compilationDatabase": { # CHECK-NEXT: "automaticReload": true # CHECK-NEXT: },