lightmelodies updated this revision to Diff 306916.
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D91930/new/
https://reviews.llvm.org/D91930
Files:
clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/ClangdLSPServer.h
clang-tools-extra/clangd/ClangdServer.cpp
clang-tools-extra/clangd/ClangdServer.h
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/TUScheduler.h
clang-tools-extra/clangd/XRefs.cpp
clang-tools-extra/clangd/XRefs.h
clang-tools-extra/clangd/test/initialize-params.test
clang-tools-extra/clangd/tool/ClangdMain.cpp
clang-tools-extra/clangd/unittests/XRefsTests.cpp
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<CodeLens> 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
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<bool> EnableCodeLens{
+ "code-lens", cat(Features), desc("Enable preview of CodeLens feature"),
+ init(false), Hidden,
+};
+
opt<unsigned> 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/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/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<TypeHierarchyItem>
+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<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
const FunctionDecl *FD);
+
+/// Get codelens
+llvm::Expected<std::vector<CodeLens>>
+getDocumentCodeLens(ParsedAST &AST, const SymbolIndex *Index, PathRef Path);
+
+llvm::Expected<CodeLens> 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<ClassTemplateSpecializationDecl>(CXXRD))
+ if (auto *CTSD = dyn_cast_or_null<ClassTemplateSpecializationDecl>(CXXRD))
CXXRD = CTSD->getTemplateInstantiationPattern();
}
+ if (!CXXRD)
+ return llvm::None;
+
Optional<TypeHierarchyItem> Result =
declToTypeHierarchyItem(AST.getASTContext(), *CXXRD);
if (!Result)
@@ -1617,6 +1618,102 @@
}
}
+static void fillSuperMethods(const CXXMethodDecl &CXXMD, ASTContext &ASTCtx,
+ std::vector<TypeHierarchyItem> &SuperMethods) {
+ for (const CXXMethodDecl *ParentDecl : CXXMD.overridden_methods()) {
+ if (Optional<TypeHierarchyItem> 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<TypeHierarchyItem> &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<TypeHierarchyItem> 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<TypeHierarchyItem>
+getMemberHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
+ TypeHierarchyDirection Direction, const SymbolIndex *Index,
+ PathRef TUPath) {
+ auto CXXMD = dyn_cast_or_null<CXXMethodDecl>(findNamedDeclAt(AST, Pos));
+ if (!CXXMD || !CXXMD->isVirtual())
+ return llvm::None;
+
+ Optional<TypeHierarchyItem> 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<const Decl *> 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<CodeLens> &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<CXXRecordDecl>(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<CXXMethodDecl>(
+ 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<std::vector<CodeLens>>
+getDocumentCodeLens(ParsedAST &AST, const SymbolIndex *Index, PathRef Path) {
+ auto DocumentSymbols = getDocumentSymbols(AST);
+ if (!DocumentSymbols)
+ return DocumentSymbols.takeError();
+ std::vector<CodeLens> Result;
+ for (const auto &Symbol : *DocumentSymbols) {
+ collectCodeLens(AST, Symbol, Index, Path, Result);
+ }
+ return Result;
+}
+
+llvm::Expected<CodeLens> 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/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/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> workspaceEdit;
llvm::Optional<TweakArgs> tweakArgs;
+ llvm::Optional<CodeLensResolveData> 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> command;
+ // CodeLens resolve data.
+ llvm::Optional<CodeLensResolveData> 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<int>(CodeLensType::Reference) ||
+ *T > static_cast<int>(CodeLensType::MemberChildren))
+ return false;
+ R = static_cast<CodeLensType>(*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<int>(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/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<Tweak::Effect> CB);
+ /// Implement 'clangd.server.codeLens'
+ void onCodeLensCommand(const CodeLensResolveData &Params, uint32_t Limit,
+ Callback<llvm::json::Value> 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<std::vector<HighlightingToken>>);
+ /// CodeLenses.
+ void provideCodeLens(PathRef File, Callback<std::vector<CodeLens>> CB);
+ void resolveCodeLens(const CodeLens &Params, uint32_t Limit,
+ Callback<CodeLens> CB);
/// Describe the AST subtree for a piece of code.
void getAST(PathRef File, Range R, Callback<llvm::Optional<ASTNode>> 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<llvm::json::Value> 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<ReferencesResult> 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<InputsAndAST> InpAST) mutable {
+ std::vector<Location> 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<InputsAndAST> InpAST) mutable {
+ std::vector<Location> 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<InputsAndAST> InpAST) mutable {
+ std::vector<Location> 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<InputsAndAST> InpAST) mutable {
+ std::vector<Location> 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<std::vector<LocatedSymbol>> 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<std::vector<CodeLens>> CB) {
+ auto Action = [CB = std::move(CB), File = File.str(),
+ this](llvm::Expected<InputsAndAST> 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<CodeLens> CB) {
+ auto File = Params.data->location.uri.file();
+ auto Action = [CB = std::move(CB), File = File.str(), Params, Limit,
+ this](llvm::Expected<InputsAndAST> 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<InputsAndAST> Action) {
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<SemanticTokens>);
void onSemanticTokensDelta(const SemanticTokensDeltaParams &,
Callback<SemanticTokensOrDelta>);
+ void onCodeLens(const CodeLensParams &, Callback<std::vector<CodeLens>>);
+ void onCodeLensResolve(const CodeLens &, Callback<CodeLens>);
/// This is a clangd extension. Provides a json tree representing memory usage
/// hierarchy.
void onMemoryUsage(const NoParams &, Callback<MemoryTree>);
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<std::mutex> Lock(FixItsMutex);
@@ -1398,6 +1413,30 @@
});
}
+void ClangdLSPServer::onCodeLens(const CodeLensParams &Params,
+ Callback<std::vector<CodeLens>> Reply) {
+ URIForFile FileURI = Params.textDocument.uri;
+ Server->provideCodeLens(
+ FileURI.file(),
+ [Reply = std::move(Reply)](
+ llvm::Expected<std::vector<CodeLens>> CodeLens) mutable {
+ if (!CodeLens)
+ return Reply(CodeLens.takeError());
+ return Reply(std::move(*CodeLens));
+ });
+}
+
+void ClangdLSPServer::onCodeLensResolve(const CodeLens &Params,
+ Callback<CodeLens> Reply) {
+ Server->resolveCodeLens(
+ Params, Opts.CodeComplete.Limit,
+ [Reply = std::move(Reply)](llvm::Expected<CodeLens> CodeLens) mutable {
+ if (!CodeLens)
+ return Reply(CodeLens.takeError());
+ return Reply(std::move(*CodeLens));
+ });
+}
+
void ClangdLSPServer::onMemoryUsage(const NoParams &,
Callback<MemoryTree> 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.
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits