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 @@ -144,7 +144,9 @@ void onCallHierarchyOutgoingCalls( const CallHierarchyOutgoingCallsParams &, Callback>); - void onInlayHints(const InlayHintsParams &, Callback>); + void onClangdInlayHints(const InlayHintsParams &, + Callback); + void onInlayHint(const InlayHintsParams &, Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, Callback>); 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 @@ -573,7 +573,6 @@ {"compilationDatabase", // clangd extension llvm::json::Object{{"automaticReload", true}}}, {"callHierarchyProvider", true}, - {"clangdInlayHintsProvider", true}, }; { @@ -584,6 +583,15 @@ Mod.initializeLSP(Binder, Params.rawCapabilities, ServerCaps); } + // Only advertise inlayHints extension if client doesn't support the standard + // implementation. + // FIXME: Get rid of this with clangd-17. + if (!Params.capabilities.InlayHints) { + ServerCaps["clangdInlayHintsProvider"] = true; + log("clangd/inlayHints is deprecated and will go away. See the standard " + "textDocument/inlayHint request for a replacement."); + } + // Per LSP, renameProvider can be either boolean or RenameOptions. // RenameOptions will be specified if the client states it supports prepare. ServerCaps["renameProvider"] = @@ -1207,8 +1215,33 @@ Server->incomingCalls(Params.item, std::move(Reply)); } -void ClangdLSPServer::onInlayHints(const InlayHintsParams &Params, - Callback> Reply) { +void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params, + Callback Reply) { + // Our extension has a different representation on the wire than the standard. + // We have a "range" property and "kind" is represented as a string, not as an + // enum value. + auto Serialize = [Reply = std::move(Reply)]( + llvm::Expected> Hints) mutable { + if (!Hints) { + Reply(Hints.takeError()); + return; + } + llvm::json::Array Result; + Result.reserve(Hints->size()); + for (auto &Hint : *Hints) { + llvm::json::Object Current(*toJSON(Hint).getAsObject()); + Current["kind"] = llvm::to_string(Hint.kind); + Current["range"] = Hint.range; + Result.emplace_back(std::move(Current)); + } + Reply(std::move(Result)); + }; + Server->inlayHints(Params.textDocument.uri.file(), Params.range, + std::move(Serialize)); +} + +void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params, + Callback> Reply) { Server->inlayHints(Params.textDocument.uri.file(), Params.range, std::move(Reply)); } @@ -1491,7 +1524,8 @@ 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/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -411,7 +411,7 @@ if (NameHint || ReferenceHint) { addInlayHint(Args[I]->getSourceRange(), HintSide::Left, - InlayHintKind::ParameterHint, ReferenceHint ? "&" : "", + InlayHintKind::Parameter, ReferenceHint ? "&" : "", NameHint ? Name : "", ": "); } } @@ -593,9 +593,9 @@ 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 } @@ -629,12 +629,12 @@ 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.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -493,6 +493,13 @@ /// Whether the client implementation supports a refresh request sent from the /// server to the client. bool SemanticTokenRefreshSupport = false; + + /// Whether the client supports inlayHints requests. This is tracked purely to + /// prevent clangd from advertising it's protocol extension, which predates + /// the standardized inlayHints implementation. + /// FIXME: Get rid of this with clangd-17. + /// textdocument.InlayHints + bool InlayHints = false; }; bool fromJSON(const llvm::json::Value &, ClientCapabilities &, llvm::json::Path); @@ -1526,23 +1533,24 @@ /// A set of predefined 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 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, + + /// 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. + 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,11 +1558,8 @@ /// in a chain of function calls. /// * Hints indicating implicit conversions or implicit constructor calls. }; -llvm::json::Value toJSON(InlayHintKind); /// An annotation to be displayed inline next to a range of source code. -/// -/// This is a clangd extension. struct InlayHint { /// The position between two characters where the hint should be displayed. /// @@ -1565,6 +1570,7 @@ /// /// 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 a clangd extension. Range range; /// The type of hint, such as a parameter hint. 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 @@ -1321,23 +1321,21 @@ static llvm::StringLiteral toString(InlayHintKind K) { switch (K) { - case InlayHintKind::ParameterHint: + case InlayHintKind::Parameter: return "parameter"; - case InlayHintKind::TypeHint: + case InlayHintKind::Type: return "type"; - case InlayHintKind::DesignatorHint: + case InlayHintKind::Designator: return "designator"; } 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}}; + if (H.kind == InlayHintKind::Type || H.kind == InlayHintKind::Parameter) + Result["kind"] = static_cast(H.kind); + return Result; } bool operator==(const InlayHint &A, const InlayHint &B) { return std::tie(A.position, A.range, A.kind, A.label) == diff --git a/clang-tools-extra/clangd/test/inlayHints.test b/clang-tools-extra/clangd/test/inlayHints.test --- a/clang-tools-extra/clangd/test/inlayHints.test +++ b/clang-tools-extra/clangd/test/inlayHints.test @@ -39,6 +39,27 @@ # 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: "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 --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -99,14 +99,14 @@ void assertParameterHints(llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { ignore(Expected.Side = Left...); - assertHints(InlayHintKind::ParameterHint, AnnotatedSource, Expected...); + assertHints(InlayHintKind::Parameter, AnnotatedSource, Expected...); } template void assertTypeHints(llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { ignore(Expected.Side = Right...); - assertHints(InlayHintKind::TypeHint, AnnotatedSource, Expected...); + assertHints(InlayHintKind::Type, AnnotatedSource, Expected...); } template @@ -115,7 +115,7 @@ 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 +570,7 @@ 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 +818,7 @@ 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) {