[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