[clang-tools-extra] 3137ca8 - [clangd] Support for standard inlayHint protocol
Kadir Cetinkaya via cfe-commits
cfe-commits at lists.llvm.org
Tue May 10 09:59:42 PDT 2022
Author: Kadir Cetinkaya
Date: 2022-05-10T18:59:15+02:00
New Revision: 3137ca80b9ef99359a4adf77512925c2edc461b9
URL: https://github.com/llvm/llvm-project/commit/3137ca80b9ef99359a4adf77512925c2edc461b9
DIFF: https://github.com/llvm/llvm-project/commit/3137ca80b9ef99359a4adf77512925c2edc461b9.diff
LOG: [clangd] Support for standard inlayHint protocol
- Make clangd's internal representation more aligned with the standard.
We keep range and extra inlayhint kinds around, but don't serialize
them on standard version.
- Have custom serialization for extension (ugly, but going to go away).
- Support both versions until clangd-17.
- Don't advertise extension if client has support for standard
implementation.
- Log a warning at startup about extension being deprecated, if client
doesn't have support.
Differential Revision: https://reviews.llvm.org/D125228
Added:
Modified:
clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/ClangdLSPServer.h
clang-tools-extra/clangd/InlayHints.cpp
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/test/initialize-params.test
clang-tools-extra/clangd/test/inlayHints.test
clang-tools-extra/clangd/unittests/InlayHintTests.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 9b826bb373159..49c882e6c8d4b 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -29,6 +29,7 @@
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Twine.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
@@ -574,6 +575,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
llvm::json::Object{{"automaticReload", true}}},
{"callHierarchyProvider", true},
{"clangdInlayHintsProvider", true},
+ {"inlayHintProvider", true},
};
{
@@ -1207,8 +1209,41 @@ void ClangdLSPServer::onCallHierarchyIncomingCalls(
Server->incomingCalls(Params.item, std::move(Reply));
}
-void ClangdLSPServer::onInlayHints(const InlayHintsParams &Params,
- Callback<std::vector<InlayHint>> Reply) {
+void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params,
+ Callback<llvm::json::Value> Reply) {
+ // Our extension has a
diff erent representation on the wire than the standard.
+ // We have a "range" property and "kind" is represented as a string, not as an
+ // enum value.
+ // https://clangd.llvm.org/extensions#inlay-hints
+ auto Serialize = [Reply = std::move(Reply)](
+ llvm::Expected<std::vector<InlayHint>> Hints) mutable {
+ if (!Hints) {
+ Reply(Hints.takeError());
+ return;
+ }
+ llvm::json::Array Result;
+ Result.reserve(Hints->size());
+ for (auto &Hint : *Hints) {
+ Result.emplace_back(llvm::json::Object{
+ {"kind", llvm::to_string(Hint.kind)},
+ {"range", Hint.range},
+ {"position", Hint.position},
+ // Extension doesn't have paddingLeft/Right so adjust the label
+ // accordingly.
+ {"label",
+ ((Hint.paddingLeft ? " " : "") + llvm::StringRef(Hint.label) +
+ (Hint.paddingRight ? " " : ""))
+ .str()},
+ });
+ }
+ Reply(std::move(Result));
+ };
+ Server->inlayHints(Params.textDocument.uri.file(), Params.range,
+ std::move(Serialize));
+}
+
+void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params,
+ Callback<std::vector<InlayHint>> Reply) {
Server->inlayHints(Params.textDocument.uri.file(), Params.range,
std::move(Reply));
}
@@ -1491,7 +1526,8 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);
Bind.method("textDocument/semanticTokens/full/delta", this, &ClangdLSPServer::onSemanticTokensDelta);
- Bind.method("clangd/inlayHints", this, &ClangdLSPServer::onInlayHints);
+ Bind.method("clangd/inlayHints", this, &ClangdLSPServer::onClangdInlayHints);
+ Bind.method("textDocument/inlayHint", this, &ClangdLSPServer::onInlayHint);
Bind.method("$/memoryUsage", this, &ClangdLSPServer::onMemoryUsage);
if (Opts.FoldingRanges)
Bind.method("textDocument/foldingRange", this, &ClangdLSPServer::onFoldingRange);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 1c52ebfe97c92..562f8e060161f 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -144,7 +144,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onCallHierarchyOutgoingCalls(
const CallHierarchyOutgoingCallsParams &,
Callback<std::vector<CallHierarchyOutgoingCall>>);
- void onInlayHints(const InlayHintsParams &, Callback<std::vector<InlayHint>>);
+ void onClangdInlayHints(const InlayHintsParams &,
+ Callback<llvm::json::Value>);
+ void onInlayHint(const InlayHintsParams &, Callback<std::vector<InlayHint>>);
void onChangeConfiguration(const DidChangeConfigurationParams &);
void onSymbolInfo(const TextDocumentPositionParams &,
Callback<std::vector<SymbolDetails>>);
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 315dd5109d7c9..77dc727bba099 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -411,7 +411,7 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
if (NameHint || ReferenceHint) {
addInlayHint(Args[I]->getSourceRange(), HintSide::Left,
- InlayHintKind::ParameterHint, ReferenceHint ? "&" : "",
+ InlayHintKind::Parameter, ReferenceHint ? "&" : "",
NameHint ? Name : "", ": ");
}
}
@@ -593,9 +593,9 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
if (!Cfg.InlayHints.ConfigProperty) \
return; \
break
- CHECK_KIND(ParameterHint, Parameters);
- CHECK_KIND(TypeHint, DeducedTypes);
- CHECK_KIND(DesignatorHint, Designators);
+ CHECK_KIND(Parameter, Parameters);
+ CHECK_KIND(Type, DeducedTypes);
+ CHECK_KIND(Designator, Designators);
#undef CHECK_KIND
}
@@ -614,8 +614,10 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
// file that was included after the preamble), do not show in that case.
if (!AST.getSourceManager().isWrittenInMainFile(FileRange->getBegin()))
return;
- Results.push_back(
- InlayHint{LSPPos, LSPRange, Kind, (Prefix + Label + Suffix).str()});
+ bool PadLeft = Prefix.consume_front(" ");
+ bool PadRight = Suffix.consume_back(" ");
+ Results.push_back(InlayHint{LSPPos, (Prefix + Label + Suffix).str(), Kind,
+ PadLeft, PadRight, LSPRange});
}
void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) {
@@ -629,12 +631,12 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
std::string TypeName = T.getAsString(Policy);
if (TypeName.length() < TypeNameLimit)
- addInlayHint(R, HintSide::Right, InlayHintKind::TypeHint, Prefix,
- TypeName, /*Suffix=*/"");
+ addInlayHint(R, HintSide::Right, InlayHintKind::Type, Prefix, TypeName,
+ /*Suffix=*/"");
}
void addDesignatorHint(SourceRange R, llvm::StringRef Text) {
- addInlayHint(R, HintSide::Left, InlayHintKind::DesignatorHint,
+ addInlayHint(R, HintSide::Left, InlayHintKind::Designator,
/*Prefix=*/"", Text, /*Suffix=*/"=");
}
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 824151c25f15c..5f8e4938c65ad 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1319,25 +1319,27 @@ bool fromJSON(const llvm::json::Value &Params, InlayHintsParams &R,
return O && O.map("textDocument", R.textDocument) && O.map("range", R.range);
}
-static llvm::StringLiteral toString(InlayHintKind K) {
- switch (K) {
- case InlayHintKind::ParameterHint:
- return "parameter";
- case InlayHintKind::TypeHint:
- return "type";
- case InlayHintKind::DesignatorHint:
- return "designator";
+llvm::json::Value toJSON(const InlayHintKind &Kind) {
+ switch (Kind) {
+ case InlayHintKind::Type:
+ return 1;
+ case InlayHintKind::Parameter:
+ return 2;
+ case InlayHintKind::Designator: // This is an extension, don't serialize.
+ return nullptr;
}
llvm_unreachable("Unknown clang.clangd.InlayHintKind");
}
-llvm::json::Value toJSON(InlayHintKind K) { return toString(K); }
-
llvm::json::Value toJSON(const InlayHint &H) {
- return llvm::json::Object{{"position", H.position},
- {"range", H.range},
- {"kind", H.kind},
- {"label", H.label}};
+ llvm::json::Object Result{{"position", H.position},
+ {"label", H.label},
+ {"paddingLeft", H.paddingLeft},
+ {"paddingRight", H.paddingRight}};
+ auto K = toJSON(H.kind);
+ if (!K.getAsNull())
+ Result["kind"] = std::move(K);
+ return std::move(Result);
}
bool operator==(const InlayHint &A, const InlayHint &B) {
return std::tie(A.position, A.range, A.kind, A.label) ==
@@ -1349,7 +1351,18 @@ bool operator<(const InlayHint &A, const InlayHint &B) {
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, InlayHintKind Kind) {
- return OS << toString(Kind);
+ auto ToString = [](InlayHintKind K) {
+ switch (K) {
+ case InlayHintKind::Parameter:
+ return "parameter";
+ case InlayHintKind::Type:
+ return "type";
+ case InlayHintKind::Designator:
+ return "designator";
+ }
+ llvm_unreachable("Unknown clang.clangd.InlayHintKind");
+ };
+ return OS << ToString(Kind);
}
static const char *toString(OffsetEncoding OE) {
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 8a68d69197b4f..3f66cb2a5fcdd 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1512,37 +1512,41 @@ struct CallHierarchyOutgoingCall {
};
llvm::json::Value toJSON(const CallHierarchyOutgoingCall &);
-/// The parameter of a `clangd/inlayHints` request.
-///
-/// This is a clangd extension.
+/// A parameter literal used in inlay hint requests.
struct InlayHintsParams {
- /// The text document for which inlay hints are requested.
+ /// The text document.
TextDocumentIdentifier textDocument;
- /// If set, requests inlay hints for only part of the document.
+ /// The visible document range for which inlay hints should be computed.
+ ///
+ /// None is a clangd extension, which hints for computing hints on the whole
+ /// file.
llvm::Optional<Range> range;
};
bool fromJSON(const llvm::json::Value &, InlayHintsParams &, llvm::json::Path);
-/// A set of predefined hint kinds.
+/// Inlay hint kinds.
enum class InlayHintKind {
- /// The hint corresponds to parameter information.
- /// An example of a parameter hint is a hint in this position:
- /// func(^arg);
- /// which shows the name of the corresponding parameter.
- ParameterHint,
-
- /// The hint corresponds to information about a deduced type.
+ /// An inlay hint that for a type annotation.
+ ///
/// An example of a type hint is a hint in this position:
/// auto var ^ = expr;
/// which shows the deduced type of the variable.
- TypeHint,
+ Type = 1,
+
+ /// An inlay hint that is for a parameter.
+ ///
+ /// An example of a parameter hint is a hint in this position:
+ /// func(^arg);
+ /// which shows the name of the corresponding parameter.
+ Parameter = 2,
/// A hint before an element of an aggregate braced initializer list,
/// indicating what it is initializing.
/// Pair{^1, ^2};
/// Uses designator syntax, e.g. `.first:`.
- DesignatorHint,
+ /// This is a clangd extension.
+ Designator = 3,
/// Other ideas for hints that are not currently implemented:
///
@@ -1550,31 +1554,43 @@ enum class InlayHintKind {
/// in a chain of function calls.
/// * Hints indicating implicit conversions or implicit constructor calls.
};
-llvm::json::Value toJSON(InlayHintKind);
+llvm::json::Value toJSON(const InlayHintKind &);
-/// An annotation to be displayed inline next to a range of source code.
-///
-/// This is a clangd extension.
+/// Inlay hint information.
struct InlayHint {
- /// The position between two characters where the hint should be displayed.
- ///
- /// For example, n parameter hint may be positioned before an argument.
+ /// The position of this hint.
Position position;
- /// The range of source code to which the hint applies.
+ /// The label of this hint. A human readable string or an array of
+ /// InlayHintLabelPart label parts.
///
- /// For example, a parameter hint may have the argument as its range.
- /// The range allows clients more flexibility of when/how to display the hint.
- Range range;
+ /// *Note* that neither the string nor the label part can be empty.
+ std::string label;
- /// The type of hint, such as a parameter hint.
+ /// The kind of this hint. Can be omitted in which case the client should fall
+ /// back to a reasonable default.
InlayHintKind kind;
- /// The label that is displayed in the editor, such as a parameter name.
+ /// Render padding before the hint.
///
- /// The label may contain punctuation and/or whitespace to allow it to read
- /// naturally when placed inline with the code.
- std::string label;
+ /// Note: Padding should use the editor's background color, not the
+ /// background color of the hint itself. That means padding can be used
+ /// to visually align/separate an inlay hint.
+ bool paddingLeft = false;
+
+ /// Render padding after the hint.
+ ///
+ /// Note: Padding should use the editor's background color, not the
+ /// background color of the hint itself. That means padding can be used
+ /// to visually align/separate an inlay hint.
+ bool paddingRight = false;
+
+ /// The range of source code to which the hint applies.
+ ///
+ /// For example, a parameter hint may have the argument as its range.
+ /// The range allows clients more flexibility of when/how to display the hint.
+ /// This is an (unserialized) clangd extension.
+ Range range;
};
llvm::json::Value toJSON(const InlayHint &);
bool operator==(const InlayHint &, const InlayHint &);
diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test
index 14d7895cfa35b..da964ecbd14bb 100644
--- a/clang-tools-extra/clangd/test/initialize-params.test
+++ b/clang-tools-extra/clangd/test/initialize-params.test
@@ -74,6 +74,7 @@
# CHECK-NEXT: },
# CHECK-NEXT: "hoverProvider": true,
# CHECK-NEXT: "implementationProvider": true,
+# CHECK-NEXT: "inlayHintProvider": true,
# CHECK-NEXT: "memoryUsageProvider": true,
# CHECK-NEXT: "referencesProvider": true,
# CHECK-NEXT: "renameProvider": true,
diff --git a/clang-tools-extra/clangd/test/inlayHints.test b/clang-tools-extra/clangd/test/inlayHints.test
index 48b1d83dca25f..8f302dd17a549 100644
--- a/clang-tools-extra/clangd/test/inlayHints.test
+++ b/clang-tools-extra/clangd/test/inlayHints.test
@@ -39,6 +39,29 @@
# CHECK-NEXT: ]
# CHECK-NEXT:}
---
+{"jsonrpc":"2.0","id":2,"method":"textDocument/inlayHint","params":{
+ "textDocument":{"uri":"test:///main.cpp"},
+ "range":{
+ "start": {"line":1,"character":0},
+ "end": {"line":2,"character":0}
+ }
+}}
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "bar:",
+# CHECK-NEXT: "paddingLeft": false,
+# CHECK-NEXT: "paddingRight": true,
+# CHECK-NEXT: "position": {
+# CHECK-NEXT: "character": 12,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT:}
+---
{"jsonrpc":"2.0","id":100,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 31ce7d0df0dc5..8807cae64ce05 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -13,6 +13,7 @@
#include "TestWorkspace.h"
#include "XRefs.h"
#include "support/Context.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ScopedPrinter.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -53,8 +54,11 @@ struct ExpectedHint {
};
MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
- if (arg.label != Expected.Label) {
- *result_listener << "label is " << arg.label;
+ llvm::StringRef ExpectedView(Expected.Label);
+ if (arg.label != ExpectedView.trim(" ") ||
+ arg.paddingLeft != ExpectedView.startswith(" ") ||
+ arg.paddingRight != ExpectedView.endswith(" ")) {
+ *result_listener << "label is '" << arg.label << "'";
return false;
}
if (arg.range != Code.range(Expected.RangeName)) {
@@ -99,14 +103,14 @@ template <typename... ExpectedHints>
void assertParameterHints(llvm::StringRef AnnotatedSource,
ExpectedHints... Expected) {
ignore(Expected.Side = Left...);
- assertHints(InlayHintKind::ParameterHint, AnnotatedSource, Expected...);
+ assertHints(InlayHintKind::Parameter, AnnotatedSource, Expected...);
}
template <typename... ExpectedHints>
void assertTypeHints(llvm::StringRef AnnotatedSource,
ExpectedHints... Expected) {
ignore(Expected.Side = Right...);
- assertHints(InlayHintKind::TypeHint, AnnotatedSource, Expected...);
+ assertHints(InlayHintKind::Type, AnnotatedSource, Expected...);
}
template <typename... ExpectedHints>
@@ -115,7 +119,7 @@ void assertDesignatorHints(llvm::StringRef AnnotatedSource,
Config Cfg;
Cfg.InlayHints.Designators = true;
WithContextValue WithCfg(Config::Key, std::move(Cfg));
- assertHints(InlayHintKind::DesignatorHint, AnnotatedSource, Expected...);
+ assertHints(InlayHintKind::Designator, AnnotatedSource, Expected...);
}
TEST(ParameterHints, Smoke) {
@@ -570,7 +574,7 @@ TEST(ParameterHints, IncludeAtNonGlobalScope) {
ASSERT_TRUE(bool(AST));
// Ensure the hint for the call in foo.inc is NOT materialized in foo.cc.
- EXPECT_EQ(hintsOfKind(*AST, InlayHintKind::ParameterHint).size(), 0u);
+ EXPECT_EQ(hintsOfKind(*AST, InlayHintKind::Parameter).size(), 0u);
}
TEST(TypeHints, Smoke) {
@@ -818,7 +822,7 @@ TEST(TypeHints, Aliased) {
TU.ExtraArgs.push_back("-xc");
auto AST = TU.build();
- EXPECT_THAT(hintsOfKind(AST, InlayHintKind::TypeHint), IsEmpty());
+ EXPECT_THAT(hintsOfKind(AST, InlayHintKind::Type), IsEmpty());
}
TEST(DesignatorHints, Basic) {
More information about the cfe-commits
mailing list