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); + void onCodeLens(const CodeLensParams &Params, Callback> Reply); + void onCommandShowUsedSymbols(const IncludeSpelling &Spelling, 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 @@ -71,6 +71,7 @@ const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix"; const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak"; +const llvm::StringLiteral ApplyShowUsedSymbols = "HelloWorld"; /// Transforms a tweak into a code action that would apply it if executed. /// EXPECTS: T.prepare() was called and returned true. @@ -586,6 +587,7 @@ {"clangdInlayHintsProvider", true}, {"inlayHintProvider", true}, {"foldingRangeProvider", true}, + {"codeLensProvider", true}, }; { @@ -736,6 +738,14 @@ applyEdit(WE, "Fix applied.", std::move(Reply)); } +void ClangdLSPServer::onCommandShowUsedSymbols(const IncludeSpelling &Spelling, Callback Reply) { + llvm::errs() << "ONCOMMAND SHOW USED SYMBOLS TRIGGERED\n"; + llvm::json::Value Response = llvm::json::Object{ + {"response", "Hello to you as well!!!"} + }; + return Reply(Response); +} + void ClangdLSPServer::onCommandApplyTweak(const TweakArgs &Args, Callback Reply) { auto Action = [this, Reply = std::move(Reply)]( @@ -1379,6 +1389,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 +1411,12 @@ }); } +void ClangdLSPServer::onCodeLens(const CodeLensParams &Params, + Callback> Reply) { + llvm::errs() << "TRIGGERED CODE LENS: " << Params.textDocument.uri.file() << "\n"; + Server->findCodeLens(Params.textDocument.uri.file(), std::move(Reply)); +} + void ClangdLSPServer::onGoToType(const TextDocumentPositionParams &Params, Callback> Reply) { Server->findType( @@ -1589,6 +1607,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); @@ -1622,6 +1641,7 @@ Bind.method("textDocument/foldingRange", this, &ClangdLSPServer::onFoldingRange); Bind.command(ApplyFixCommand, this, &ClangdLSPServer::onCommandApplyEdit); Bind.command(ApplyTweakCommand, this, &ClangdLSPServer::onCommandApplyTweak); + Bind.command(ApplyShowUsedSymbols, this, &ClangdLSPServer::onCommandShowUsedSymbols); ApplyWorkspaceEdit = Bind.outgoingMethod("workspace/applyEdit"); PublishDiagnostics = Bind.outgoingNotification("textDocument/publishDiagnostics"); 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,8 @@ void findReferences(PathRef File, Position Pos, uint32_t Limit, bool AddContainer, Callback CB); + 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 @@ -976,6 +976,12 @@ bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path); llvm::json::Value toJSON(const WorkspaceEdit &WE); +struct IncludeSpelling { + std::string spelling; +}; +bool fromJSON(const llvm::json::Value &, IncludeSpelling &, llvm::json::Path); +llvm::json::Value toJSON(const IncludeSpelling &IS); + /// Arguments for the 'applyTweak' command. The server sends these commands as a /// response to the textDocument/codeAction request. The client can later send a /// command back to the server if the user requests to execute a particular code @@ -998,6 +1004,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 +1883,19 @@ llvm::json::Value toJSON(const ASTNode &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ASTNode &); +struct CodeLensParams { + TextDocumentIdentifier textDocument; +}; +bool fromJSON(const llvm::json::Value &, CodeLensParams &, + llvm::json::Path); + +struct CodeLens { + Range range; + 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,17 @@ 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); +} + +bool fromJSON(const llvm::json::Value &Params, IncludeSpelling &R, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("spelling", R.spelling); +} + bool fromJSON(const llvm::json::Value &Params, DidSaveTextDocumentParams &R, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); @@ -613,6 +624,22 @@ }; } +llvm::json::Value toJSON(const IncludeSpelling &IS) { + return llvm::json::Object{ + {"spelling", IS.spelling} + }; +} + +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; +} + llvm::json::Value toJSON(DiagnosticTag Tag) { return static_cast(Tag); } llvm::json::Value toJSON(const CodeDescription &D) { @@ -810,9 +837,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 +925,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" @@ -62,9 +65,11 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include namespace clang { namespace clangd { +std::string HelloString = "Hello"; namespace { // Returns the single definition of the entity declared by D, if visible. @@ -74,6 +79,7 @@ // Kinds of nodes that always return nullptr here will not have definitions // reported by locateSymbolAt(). const NamedDecl *getDefinition(const NamedDecl *D) { + std::string AnotherString; assert(D); // Decl has one definition that we can find. if (const auto *TD = dyn_cast(D)) @@ -118,6 +124,7 @@ // FindSymbols. std::optional toLSPLocation(const SymbolLocation &Loc, llvm::StringRef TUPath) { + std::string ThirdString; if (!Loc) return std::nullopt; auto Uri = URI::parse(Loc.FileURI); @@ -1212,7 +1219,6 @@ } return std::nullopt; } - } // namespace std::vector findDocumentHighlights(ParsedAST &AST, @@ -1231,6 +1237,7 @@ DeclRelation::TemplatePattern | DeclRelation::Alias; auto TargetDecls = targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver()); + if (!TargetDecls.empty()) { // FIXME: we may get multiple DocumentHighlights with the same location // and different kinds, deduplicate them. @@ -1312,8 +1319,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 +1424,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 =