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); @@ -1213,6 +1220,27 @@ return std::nullopt; } +std::vector +collectMacroReferences(ParsedAST &AST) { + const auto &SM = AST.getSourceManager(); + // FIXME: !!this is a hacky way to collect macro references. + std::vector Macros; + auto &PP = AST.getPreprocessor(); + for (const syntax::Token &Tok : + AST.getTokens().spelledTokens(SM.getMainFileID())) { + auto Macro = locateMacroAt(Tok, PP); + if (!Macro) + continue; + if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) + Macros.push_back( + {Tok.location(), + include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), + DefLoc}, + include_cleaner::RefType::Explicit}); + } + return Macros; +} + } // namespace std::vector findDocumentHighlights(ParsedAST &AST, @@ -1231,11 +1259,96 @@ DeclRelation::TemplatePattern | DeclRelation::Alias; auto TargetDecls = targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver()); + + const IncludeStructure &Includes = AST.getIncludeStructure(); + include_cleaner::Includes ConvertedIncludes = + convertIncludes(AST.getSourceManager(), Includes.MainFileIncludes); + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), + collectMacroReferences(AST), + AST.getPragmaIncludes(), + AST.getSourceManager(), + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + // std::string SymbolName; + // switch (Ref.Target.kind()) { + // case include_cleaner::Symbol::Macro: + // break; + // case include_cleaner::Symbol::Declaration: + // SymbolName = static_cast(Ref.Target.declaration()).getNameAsString(); + // if (SymbolName == "string") { + // llvm::errs() << "FOUND STRING" << "\n"; + // llvm::errs() << "CUR LOCATION: " << "\n"; + // llvm::errs() << SM.getDecomposedLoc(*CurLoc).second << "\n"; + // llvm::errs() << "REF LOCATION: " << "\n"; + // llvm::errs() << SM.getDecomposedLoc(Ref.RefLocation).second << "\n"; + // } + // break; + // } + if (Ref.RefLocation != *CurLoc) { + return; + } + llvm::errs() << "GOT PAST THE GUARD" << "\n"; + // the location under cursor corresponds to the current symbol reference + // need to find the position of the include + for (const auto &H : Providers) { + // switch (H.kind()) { + // case include_cleaner::Header::Physical: + // llvm::errs() << "It's a physical provider\n"; + // if (auto HeaderID = Includes.getID(H.physical())) { + // llvm::errs() << "Physical provider: " << Includes.getRealPath(*HeaderID) << "\n"; + // } + // break; + // case include_cleaner::Header::Standard: + // llvm::errs() << "It's a standard provider\n"; + // for (auto HeaderID : Includes.StdlibHeaders.lookup(H.standard())) { + // llvm::errs() << "Standard provider: " << Includes.getRealPath(HeaderID) << "\n"; + // } + // break; + // case include_cleaner::Header::Verbatim: + // llvm::errs() << "It's a verbatim provider\n"; + // for (auto Inc : Includes.MainFileIncludes) { + // if (auto HeaderID = Inc.HeaderID) { + // llvm::errs() << "Verbatim provider: " << + // Includes.getRealPath(static_cast(*HeaderID)) << "\n"; + // } + // } + // } + for (auto Inc : Includes.MainFileIncludes) { + if (Inc.Written == "") { + llvm::errs() << "Included in main: " << Inc.Written << "\n"; + } + } + for (auto *&Inc : ConvertedIncludes.match(H)) { + llvm::errs() << "Found match\n"; + auto FID = SM.getFileID(SM.getFileLoc(Inc->HashLocation)); + llvm::errs() << "FID: " << FID.getHashValue() << "\n"; + auto Highlight = toHighlight(Inc->HashLocation, AST.getTokens()); + llvm::errs() <<(Highlight.has_value() ? "Highlight" : "No highlight") << "\n"; + // found the include in the includes list + // navigate to location + if (auto Highlight = toHighlight(Inc->HashLocation, AST.getTokens())) { + for (unsigned i = 1; i < 17; i++) { + if (auto HighlightNext = toHighlight(SM.getComposedLoc(SM.getMainFileID(), + SM.getDecomposedLoc(Inc->HashLocation).second + i), AST.getTokens())) { + Result.push_back(*HighlightNext); + } + } + llvm::errs() << "COULD BUILD THE HIGHLIGHT FOR " << Inc->Spelled << "\n"; + Result.push_back(*Highlight); + } else { + llvm::errs() << "COULD NOT BUILD THE HIGHLIGHT FOR " << Inc->Spelled << "\n"; + } + } + } + }); + if (!TargetDecls.empty()) { // FIXME: we may get multiple DocumentHighlights with the same location // and different kinds, deduplicate them. for (const auto &Ref : findRefs(TargetDecls, AST, /*PerToken=*/true)) Result.push_back(toHighlight(Ref, SM)); + // TODO (bakalova) include-cleaner analysis here return true; } auto ControlFlow = relatedControlFlow(*N); @@ -1312,8 +1425,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 +1530,74 @@ return {}; } + // TODO(bakalova) Look closer at the proper directive kind handling. + std::vector Includes = AST.getIncludeStructure().MainFileIncludes; + for (auto &Inc : Includes) { + if (Inc.Directive != tok::PPKeywordKind::pp_include) { + // Ignore non-C++ include directives. + continue; + } + // It is enough to compare line numbers, since one include per line is a + // standard. + if (Pos.line != Inc.HashLine) { + continue; + } + + const auto Macros = collectMacroReferences(AST); + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), Macros, + AST.getPragmaIncludes(), SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + if (!Ref.RefLocation.isFileID() || + Ref.RT != include_cleaner::RefType::Explicit || + !SM.isWrittenInMainFile(SM.getSpellingLoc(Ref.RefLocation))) { + return; + } + const syntax::Token *Tok = + AST.getTokens().spelledTokenAt(Ref.RefLocation); + if (Tok == nullptr) { + elog("Can't find spelled token at the symbol reference location."); + return; + } + const SourceLocation &RefEndLocation = Tok->endLocation(); + clangd::Range Range = + clangd::Range{sourceLocToPosition(SM, Ref.RefLocation), + sourceLocToPosition(SM, RefEndLocation)}; + ReferencesResult::Reference Result; + Result.Loc.range = Range; + Result.Loc.uri = URIMainFile; + + // TODO(bakalova) does this differentiation make any sense in this + // context? Possibly not if (Ref.IsDefinition) { + // Result.Attributes |= ReferencesResult::Declaration; + // Result.Attributes |= ReferencesResult::Definition; + // } + for (const include_cleaner::Header &H : Providers) { + switch (H.kind()) { + case include_cleaner::Header::Physical: + if (H.physical()->tryGetRealPathName() == Inc.Resolved) { + Results.References.push_back(std::move(Result)); + } + break; + case include_cleaner::Header::Standard: + if (Inc.Written == H.standard().name()) { + Results.References.push_back(std::move(Result)); + } + break; + case include_cleaner::Header::Verbatim: + if (Inc.Written == H.verbatim()) { + Results.References.push_back(std::move(Result)); + } + break; + } + } + }); + if (!Results.References.empty()) { + return Results; + } + } + llvm::DenseSet IDsToQuery, OverriddenMethods; const auto *IdentifierAtCursor =