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() << "DOC: " << Params.textDocument.uri.file() << "\n"; + llvm::errs() << "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); @@ -810,9 +827,19 @@ } llvm::json::Value toJSON(const Command &C) { + llvm::errs() << "ENCODING COMMAND: "; + llvm::errs() << C.title << "\n"; + llvm::errs() << C.command << "\n"; + llvm::errs() << C.argument << "\n"; + llvm::errs() << C.argument2 << "\n"; 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}; + + llvm::errs() << "DECODING COMMAND: "; + llvm::json::Array *Args = Cmd.getArray("arguments"); + llvm::errs() << "FIRST ARG: " << (*Args)[0] << "\n"; + llvm::errs() << "SECOND ARG: " << (*Args)[1] << "\n"; return std::move(Cmd); } @@ -888,8 +915,10 @@ bool fromJSON(const llvm::json::Value &Params, TextDocumentPositionParams &R, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); + const auto &PositionSuccess = O.map("position", R.position); + llvm::errs() << "REF PARAMS FROM JSON: " << R.position << "\n"; return O && O.map("textDocument", R.textDocument) && - O.map("position", R.position); + PositionSuccess; } bool fromJSON(const llvm::json::Value &Params, CompletionContext &R, 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" @@ -1210,9 +1213,9 @@ CharSourceRange::getCharRange(Tok->location(), Tok->endLocation())); return Result; } + return std::nullopt; } - } // namespace std::vector findDocumentHighlights(ParsedAST &AST, @@ -1312,8 +1315,101 @@ } } // namespace +std::vector findCodeLens(ParsedAST &AST) { + // TODO(bakalova) find ranges + // TODO(bakalova) find used symbols + + std::vector Result; + std::vector Includes = AST.getIncludeStructure().MainFileIncludes; + + for (auto Inclusion : Includes) { + std::set UsedSymbols; + walkUsed(AST.getLocalTopLevelDecls(), + collectMacroReferences(AST), + AST.getPragmaIncludes(), + AST.getSourceManager(), + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + + std::string Name; + switch(Ref.Target.kind()) { + case include_cleaner::Symbol::Declaration: + Name = cast(Ref.Target.declaration()) + .getDeclName() + .getAsString(); + break; + case include_cleaner::Symbol::Macro: + Name = Ref.Target.macro().Name->getName(); + break; + } + for (const include_cleaner::Header &H : Providers) { + switch (H.kind()) { + case include_cleaner::Header::Physical: + if (H.physical()->tryGetRealPathName().contains( + llvm::StringRef(Inclusion.Written).trim("\"<>"))) { + UsedSymbols.insert(Name); + } + break; + case include_cleaner::Header::Standard: + if (Inclusion.Written == H.standard().name()) { + UsedSymbols.insert(Name); + } + break; + // TODO(bakalova) does the verbatim thing include quotes or not? + case include_cleaner::Header::Verbatim: + if (Inclusion.Written == H.verbatim()) { + UsedSymbols.insert(Name); + } + break; + } + } + }); + + CodeLens Lens; + Lens.range = clangd::Range{Position{Inclusion.HashLine, 0}, Position{Inclusion.HashLine, 0}}; + Command cmd; + + std::vector UsedSymbolsDeduped; + for (auto Sym : UsedSymbols) { + UsedSymbolsDeduped.push_back(Sym); + } + + cmd.title = std::to_string(UsedSymbolsDeduped.size()) + " used symbols"; + cmd.command = "references-view.findReferences"; + + // for (unsigned i = 0; i < 4 && i < UsedSymbolsDeduped.size(); i++) { + // std::string Name = UsedSymbolsDeduped[i]; + // cmd.title += Name; + // cmd.title += " | "; + // } + // if (UsedSymbols.size() >= 5) { + // cmd.title += UsedSymbolsDeduped[4]; + // cmd.title += " | ..."; + // } + + auto MainFilePath = AST.tuPath(); + auto URIMainFile = URIForFile::canonicalize(MainFilePath, MainFilePath); + + cmd.argument = TextDocumentIdentifier{URIMainFile}; + const auto &SM = AST.getSourceManager(); + const Position &P = sourceLocToPosition(SM, SM.getComposedLoc(SM.getMainFileID(), Inclusion.HashOffset)); + llvm::errs() << "Position in code lens computation: " << P << "\n"; + cmd.argument2 = P; + Lens.command = cmd; + Result.push_back(Lens); + } + + + for (auto CodeLens : Result) { + llvm::errs() << "CODE LENS: " << CodeLens << "\n"; + } + return Result; +} + ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index, bool AddContext) { + llvm::errs() << "Path: " << AST.tuPath() << "\n"; + llvm::errs() << "Position: " << Pos << "\n"; ReferencesResult Results; const SourceManager &SM = AST.getSourceManager(); auto MainFilePath = AST.tuPath(); @@ -1324,6 +1420,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 =