rwols updated this revision to Diff 116461. rwols added a comment. - Update doxygen comment to say "signature help" instead of "code complete", - Refactor: put the FillDocumentation member function outside of `CompletionItemsCollector` so that `SignatureHelpCollector` can also use it. - Refactor: put the FillSortText member function outside of `CompletionItemsCollector` because it has nothing to do with `CompletionItemsCollector`. - Refactor: Make a templated function called `invokeClangAction` that will invoke the given CodeCompleteConsumer type. Both `clangd::codeComplete` and `clangd::signatureHelp` now call this function. - Present optional/defaulted parameters too (using `CodeCompletionString::CK_Optional` chunks).
https://reviews.llvm.org/D38048 Files: clangd/ClangdLSPServer.cpp clangd/ClangdServer.cpp clangd/ClangdServer.h clangd/ClangdUnit.cpp clangd/ClangdUnit.h clangd/Protocol.cpp clangd/Protocol.h clangd/ProtocolHandlers.cpp clangd/ProtocolHandlers.h
Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -46,6 +46,8 @@ JSONOutput &Out) = 0; virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) = 0; + virtual void onSignatureHelp(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) = 0; virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) = 0; }; Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -186,6 +186,23 @@ ProtocolCallbacks &Callbacks; }; +struct SignatureHelpHandler : Handler { + SignatureHelpHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { + auto TDPP = TextDocumentPositionParams::parse(Params); + if (!TDPP) { + Output.log("Failed to decode TextDocumentPositionParams!\n"); + return; + } + Callbacks.onSignatureHelp(*TDPP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + struct GotoDefinitionHandler : Handler { GotoDefinitionHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks) : Handler(Output), Callbacks(Callbacks) {} @@ -238,6 +255,9 @@ "textDocument/completion", llvm::make_unique<CompletionHandler>(Out, Callbacks)); Dispatcher.registerHandler( + "textDocument/signatureHelp", + llvm::make_unique<SignatureHelpHandler>(Out, Callbacks)); + Dispatcher.registerHandler( "textDocument/definition", llvm::make_unique<GotoDefinitionHandler>(Out, Callbacks)); } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -400,6 +400,71 @@ static std::string unparse(const CompletionItem &P); }; +/// Represents a parameter of a callable-signature. +/// +/// A parameter can have a label and a doc-comment. +struct ParameterInformation { + + /// The label of this parameter. Will be shown in the UI. + std::string label; + + /// The human-readable doc-comment of this parameter. Will be shown in the UI + /// but can be omitted. + std::string documentation; + + static std::string unparse(const ParameterInformation &); +}; + +/// Represents the signature of something callable. +/// +/// A signature can have a label, like a function-name, a doc-comment, and a set +/// of parameters. +struct SignatureInformation { + + /// The label of this signature. Will be shown in the UI. + std::string label; + + /// The human-readable doc-comment of this signature. Will be shown in the UI + /// but can be omitted. + std::string documentation; + + /// The parameters of this signature. + std::vector<ParameterInformation> parameters; + + static std::string unparse(const SignatureInformation &); +}; + +/// Signature help represents the signature of something callable. +/// +/// There can be multiple signature but only one active and only one active +/// parameter. +struct SignatureHelp { + + /// One or more signatures. + std::vector<SignatureInformation> signatures; + + /// The active signature. + /// + /// If omitted or the value lies outside the range of `signatures` the value + /// defaults to zero or is ignored if `signatures.length === 0`. Whenever + /// possible implementors should make an active decision about the active + /// signature and shouldn't rely on a default value. In future version of the + /// protocol this property might become mandantory to better express this. + int activeSignature = 0; + + /// The active parameter of the active signature. + /// + /// If omitted or the value lies outside the range of + /// `signatures[activeSignature].parameters` defaults to 0 if the active + /// signature has parameters. If the active signature has no parameters it is + /// ignored. In future version of the protocol this property might become + /// mandantory to better express the active parameter if the active signature + /// does have any. + int activeParameter = 0; + + static std::string unparse(const SignatureHelp &); +}; + } // namespace clangd } // namespace clang Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -743,3 +743,58 @@ Result.back() = '}'; return Result; } + +std::string ParameterInformation::unparse(const ParameterInformation &PI) { + std::string Result = "{"; + llvm::raw_string_ostream Os(Result); + assert(!PI.label.empty() && "parameter information label is required"); + Os << R"("label":")" << llvm::yaml::escape(PI.label) << '\"'; + if (!PI.documentation.empty()) + Os << R"(,"documentation":")" << llvm::yaml::escape(PI.documentation) + << '\"'; + Os << '}'; + Os.flush(); + return Result; +} + +std::string SignatureInformation::unparse(const SignatureInformation &SI) { + std::string Result = "{"; + llvm::raw_string_ostream Os(Result); + assert(!SI.label.empty() && "signature information label is required"); + Os << R"("label":")" << llvm::yaml::escape(SI.label) << '\"'; + if (!SI.documentation.empty()) + Os << R"(,"documentation":")" << llvm::yaml::escape(SI.documentation) + << '\"'; + Os << R"(,"parameters":[)"; + for (const auto &Parameter : SI.parameters) { + Os << ParameterInformation::unparse(Parameter) << ','; + } + Os.flush(); + if (SI.parameters.empty()) + Result.push_back(']'); + else + Result.back() = ']'; // Replace the last `,` with an `]`. + Result.push_back('}'); + return Result; +} + +std::string SignatureHelp::unparse(const SignatureHelp &SH) { + std::string Result = "{"; + llvm::raw_string_ostream Os(Result); + assert(SH.activeSignature >= 0 && + "Unexpected negative value for number of active signatures."); + assert(SH.activeParameter >= 0 && + "Unexpected negative value for active parameter index"); + Os << R"("activeSignature":)" << SH.activeSignature + << R"(,"activeParameter":)" << SH.activeParameter << R"(,"signatures":[)"; + for (const auto &Signature : SH.signatures) { + Os << SignatureInformation::unparse(Signature) << ','; + } + Os.flush(); + if (SH.signatures.empty()) + Result.push_back(']'); + else + Result.back() = ']'; // Replace the last `,` with an `]`. + Result.push_back('}'); + return Result; +} Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -256,6 +256,13 @@ std::shared_ptr<PCHContainerOperations> PCHs, bool SnippetCompletions); +/// Get signature help at a specified \p Pos in \p FileName. +SignatureHelp signatureHelp(PathRef FileName, tooling::CompileCommand Command, + PrecompiledPreamble const *Preamble, + StringRef Contents, Position Pos, + IntrusiveRefCntPtr<vfs::FileSystem> VFS, + std::shared_ptr<PCHContainerOperations> PCHs); + /// Get definition of symbol at a specified \p Pos. std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos); Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -118,6 +118,25 @@ llvm_unreachable("Unknown diagnostic level!"); } +/// Get the optional chunk as a string. This function is possibly recursive. +std::string getOptionalString(const CodeCompletionString &CCS) { + std::string Result; + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_Optional: + assert(Chunk.Optional && + "Expected the optional code completion string to be non-null."); + Result += getOptionalString(*Chunk.Optional); + break; + case CodeCompletionString::CK_VerticalSpace: + break; + default: + Result += Chunk.Text; + } + } + return Result; +} + llvm::Optional<DiagWithFixIts> toClangdDiag(StoredDiagnostic D) { auto Location = D.getLocation(); if (!Location.isValid() || !Location.getManager().isInMainFile(Location)) @@ -283,6 +302,43 @@ return Result; } +std::string getDocumentation(const CodeCompletionString &CCS) { + // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this + // information in the documentation field. + std::string Result; + const unsigned AnnotationCount = CCS.getAnnotationCount(); + if (AnnotationCount > 0) { + Result += "Annotation"; + if (AnnotationCount == 1) { + Result += ": "; + } else /* AnnotationCount > 1 */ { + Result += "s: "; + } + for (unsigned I = 0; I < AnnotationCount; ++I) { + Result += CCS.getAnnotation(I); + Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); + } + } + // Add brief documentation (if there is any). + if (CCS.getBriefComment() != nullptr) { + if (!Result.empty()) { + // This means we previously added annotations. Add an extra newline + // character to make the annotations stand out. + Result.push_back('\n'); + } + Result += CCS.getBriefComment(); + } + return Result; +} + +void fillSortText(const CodeCompletionString &CCS, CompletionItem &Item) { + // Fill in the sortText of the CompletionItem. + assert(CCS.getPriority() < 99999 && "Expecting code completion result " + "priority to have at most 5-digits"); + llvm::raw_string_ostream(Item.sortText) + << llvm::format("%05d%s", CCS.getPriority(), Item.filterText.c_str()); +} + class CompletionItemsCollector : public CodeCompleteConsumer { public: @@ -321,61 +377,23 @@ CompletionItem Item; Item.insertTextFormat = InsertTextFormat::PlainText; - FillDocumentation(CCS, Item); + Item.documentation = getDocumentation(CCS); // Fill in the label, detail, insertText and filterText fields of the // CompletionItem. ProcessChunks(CCS, Item); // Fill in the kind field of the CompletionItem. Item.kind = getKind(Result.CursorKind); - FillSortText(CCS, Item); + fillSortText(CCS, Item); return Item; } virtual void ProcessChunks(const CodeCompletionString &CCS, CompletionItem &Item) const = 0; - void FillDocumentation(const CodeCompletionString &CCS, - CompletionItem &Item) const { - // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this - // information in the documentation field. - const unsigned AnnotationCount = CCS.getAnnotationCount(); - if (AnnotationCount > 0) { - Item.documentation += "Annotation"; - if (AnnotationCount == 1) { - Item.documentation += ": "; - } else /* AnnotationCount > 1 */ { - Item.documentation += "s: "; - } - for (unsigned I = 0; I < AnnotationCount; ++I) { - Item.documentation += CCS.getAnnotation(I); - Item.documentation.push_back(I == AnnotationCount - 1 ? '\n' : ' '); - } - } - - // Add brief documentation (if there is any). - if (CCS.getBriefComment() != nullptr) { - if (!Item.documentation.empty()) { - // This means we previously added annotations. Add an extra newline - // character to make the annotations stand out. - Item.documentation.push_back('\n'); - } - Item.documentation += CCS.getBriefComment(); - } - } - - void FillSortText(const CodeCompletionString &CCS, - CompletionItem &Item) const { - // Fill in the sortText of the CompletionItem. - assert(CCS.getPriority() < 99999 && "Expecting code completion result " - "priority to have at most 5-digits"); - llvm::raw_string_ostream(Item.sortText) - << llvm::format("%05d%s", CCS.getPriority(), Item.filterText.c_str()); - } - std::vector<CompletionItem> &Items; std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator; CodeCompletionTUInfo CCTUInfo; @@ -518,14 +536,125 @@ } } }; // SnippetCompletionItemsCollector -} // namespace -std::vector<CompletionItem> -clangd::codeComplete(PathRef FileName, tooling::CompileCommand Command, - PrecompiledPreamble const *Preamble, StringRef Contents, - Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS, - std::shared_ptr<PCHContainerOperations> PCHs, - bool SnippetCompletions) { +class SignatureHelpCollector final : public CodeCompleteConsumer { + +public: + SignatureHelpCollector(const CodeCompleteOptions &CodeCompleteOpts, + SignatureHelp &SigHelp) + : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false), + SigHelp(SigHelp), + Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()), + CCTUInfo(Allocator) {} + + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates) override { + SigHelp.signatures.reserve(NumCandidates); + // FIXME(rwols): How can we determine the "active overload candidate"? + // Right now the overloaded candidates seem to be provided in a "best fit" + // order, so I'm not too worried about this. + SigHelp.activeSignature = 0; + assert(CurrentArg <= std::numeric_limits<int>::max() && + "too many arguments"); + SigHelp.activeParameter = static_cast<int>(CurrentArg); + for (unsigned I = 0; I < NumCandidates; ++I) { + const auto &Candidate = Candidates[I]; + const auto *CCS = Candidate.CreateSignatureString( + CurrentArg, S, *Allocator, CCTUInfo, true); + assert(CCS && "Expected the CodeCompletionString to be non-null"); + SigHelp.signatures.push_back(ProcessOverloadCandidate(Candidate, *CCS)); + } + } + + GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + +private: + SignatureInformation + ProcessOverloadCandidate(const OverloadCandidate &Candidate, + const CodeCompletionString &CCS) const { + SignatureInformation Result; + std::string OptionalParameterLabel; + const char *ReturnType = nullptr; + + Result.documentation = getDocumentation(CCS); + + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_ResultType: + // A piece of text that describes the type of an entity or, + // for functions and methods, the return type. + assert(!ReturnType && "Unexpected CK_ResultType"); + ReturnType = Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + case CodeCompletionString::CK_CurrentParameter: { + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + Result.label += Chunk.Text; + ParameterInformation Info; + Info.label = Chunk.Text; + Result.parameters.push_back(std::move(Info)); + break; + } + case CodeCompletionString::CK_Optional: { + assert(Chunk.Optional && + "Expected the optional code completion string to be non-null."); + ParameterInformation Info; + OptionalParameterLabel = getOptionalString(*Chunk.Optional); + Result.label += OptionalParameterLabel; + // Find the first non-whitespace character. + auto CharIndex = OptionalParameterLabel.find_first_not_of(" \t"); + if (OptionalParameterLabel[CharIndex] == ',') { + // If it's a comma, then that means we have an optional parameter that + // is *not* the first parameter. In this case. Now find the first + // non-whitespace character after the first comma. + CharIndex = + OptionalParameterLabel.find_first_not_of(" \t", CharIndex + 1); + // Use the substring [CharIndex, size()) as the parameter label. + Info.label = OptionalParameterLabel.substr(CharIndex); + } else { + // Otherwise, we must have a function signature where the first + // parameter is optional. + Info.label = std::move(OptionalParameterLabel); + } + Result.parameters.push_back(std::move(Info)); + break; + } + case CodeCompletionString::CK_VerticalSpace: + break; + default: + Result.label += Chunk.Text; + break; + } + } + if (ReturnType) { + Result.label += " -> "; + Result.label += ReturnType; + } + return Result; + } + + SignatureHelp &SigHelp; + std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator; + CodeCompletionTUInfo CCTUInfo; + +}; // SignatureHelpCollector + +template <class ReturnType, class CodeCompleteConsumerType> +ReturnType +invokeClangAction(PathRef FileName, tooling::CompileCommand Command, + PrecompiledPreamble const *Preamble, StringRef Contents, + Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS, + std::shared_ptr<PCHContainerOperations> PCHs, + bool IncludeGlobals, bool IncludeMacros, + bool IncludeCodePatterns, bool IncludeBriefComments) { std::vector<const char *> ArgStrs; for (const auto &S : Command.CommandLine) ArgStrs.push_back(S.c_str()); @@ -562,36 +691,64 @@ auto &FrontendOpts = Clang->getFrontendOpts(); FrontendOpts.SkipFunctionBodies = true; - FrontendOpts.CodeCompleteOpts.IncludeGlobals = true; - FrontendOpts.CodeCompleteOpts.IncludeMacros = true; - FrontendOpts.CodeCompleteOpts.IncludeBriefComments = true; + FrontendOpts.CodeCompleteOpts.IncludeGlobals = IncludeGlobals; + FrontendOpts.CodeCompleteOpts.IncludeMacros = IncludeMacros; + FrontendOpts.CodeCompleteOpts.IncludeCodePatterns = IncludeCodePatterns; + FrontendOpts.CodeCompleteOpts.IncludeBriefComments = IncludeBriefComments; FrontendOpts.CodeCompletionAt.FileName = FileName; FrontendOpts.CodeCompletionAt.Line = Pos.line + 1; FrontendOpts.CodeCompletionAt.Column = Pos.character + 1; - std::vector<CompletionItem> Items; - if (SnippetCompletions) { - FrontendOpts.CodeCompleteOpts.IncludeCodePatterns = true; - Clang->setCodeCompletionConsumer(new SnippetCompletionItemsCollector( - FrontendOpts.CodeCompleteOpts, Items)); - } else { - FrontendOpts.CodeCompleteOpts.IncludeCodePatterns = false; - Clang->setCodeCompletionConsumer(new PlainTextCompletionItemsCollector( - FrontendOpts.CodeCompleteOpts, Items)); - } + ReturnType Results; + Clang->setCodeCompletionConsumer( + new CodeCompleteConsumerType(FrontendOpts.CodeCompleteOpts, Results)); SyntaxOnlyAction Action; if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) { // FIXME(ibiryukov): log errors - return Items; + return Results; } if (!Action.Execute()) { // FIXME(ibiryukov): log errors } Action.EndSourceFile(); - return Items; + return Results; +} + +} // namespace + +std::vector<CompletionItem> +clangd::codeComplete(PathRef FileName, tooling::CompileCommand Command, + PrecompiledPreamble const *Preamble, StringRef Contents, + Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS, + std::shared_ptr<PCHContainerOperations> PCHs, + bool SnippetCompletions) { + if (SnippetCompletions) { + return invokeClangAction<std::vector<CompletionItem>, + SnippetCompletionItemsCollector>( + FileName, Command, Preamble, Contents, Pos, std::move(VFS), + std::move(PCHs), /*IncludeGlobals=*/true, /*IncludeMacros=*/true, + /*IncludeCodePatterns=*/true, /*IncludeBriefComments=*/true); + } else { + return invokeClangAction<std::vector<CompletionItem>, + PlainTextCompletionItemsCollector>( + FileName, Command, Preamble, Contents, Pos, std::move(VFS), + std::move(PCHs), /*IncludeGlobals=*/true, /*IncludeMacros=*/true, + /*IncludeCodePatterns=*/false, /*IncludeBriefComments=*/true); + } +} + +SignatureHelp +clangd::signatureHelp(PathRef FileName, tooling::CompileCommand Command, + PrecompiledPreamble const *Preamble, StringRef Contents, + Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS, + std::shared_ptr<PCHContainerOperations> PCHs) { + return invokeClangAction<SignatureHelp, SignatureHelpCollector>( + FileName, Command, Preamble, Contents, Pos, std::move(VFS), + std::move(PCHs), /*IncludeGlobals=*/false, /*IncludeMacros=*/false, + /*IncludeCodePatterns=*/false, /*IncludeBriefComments=*/true); } void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) { Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -238,6 +238,19 @@ codeComplete(PathRef File, Position Pos, llvm::Optional<StringRef> OverridenContents = llvm::None, IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr); + + /// Provide signature help for \p File at \p Pos. If \p OverridenContents is + /// not None, they will used only for signature help, i.e. no diagnostics + /// update will be scheduled and a draft for \p File will not be updated. If + /// \p OverridenContents is None, contents of the current draft for \p File + /// will be used. If \p UsedFS is non-null, it will be overwritten by + /// vfs::FileSystem used for signature help. This method should only be called + /// for currently tracked files. + Tagged<SignatureHelp> + signatureHelp(PathRef File, Position Pos, + llvm::Optional<StringRef> OverridenContents = llvm::None, + IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr); + /// Get definition of symbol at a specified \p Line and \p Column in \p File. Tagged<std::vector<Location>> findDefinitions(PathRef File, Position Pos); Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -214,6 +214,35 @@ return make_tagged(std::move(Result), TaggedFS.Tag); } +Tagged<SignatureHelp> +ClangdServer::signatureHelp(PathRef File, Position Pos, + llvm::Optional<StringRef> OverridenContents, + IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) { + std::string DraftStorage; + if (!OverridenContents) { + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && + "signatureHelp is called for non-added document"); + + DraftStorage = std::move(*FileContents.Draft); + OverridenContents = DraftStorage; + } + + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + if (UsedFS) + *UsedFS = TaggedFS.Value; + + std::shared_ptr<CppFile> Resources = Units.getFile(File); + assert(Resources && "Calling signatureHelp on non-added file"); + + auto Preamble = Resources->getPossiblyStalePreamble(); + auto Result = + clangd::signatureHelp(File, Resources->getCompileCommand(), + Preamble ? &Preamble->Preamble : nullptr, + *OverridenContents, Pos, TaggedFS.Value, PCHs); + return make_tagged(std::move(Result), TaggedFS.Tag); +} + std::vector<tooling::Replacement> ClangdServer::formatRange(PathRef File, Range Rng) { std::string Code = getDocument(File); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -69,6 +69,8 @@ JSONOutput &Out) override; void onCompletion(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) override; + void onSignatureHelp(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) override; void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) override; @@ -87,6 +89,7 @@ "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, "codeActionProvider": true, "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, + "signatureHelpProvider": {"triggerCharacters": ["(", ","]}, "definitionProvider": true }}})"); } @@ -199,6 +202,18 @@ R"(,"result":[)" + Completions + R"(]})"); } +void ClangdLSPServer::LSPProtocolCallbacks::onSignatureHelp( + TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { + const auto SigHelp = SignatureHelp::unparse( + LangServer.Server + .signatureHelp( + Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}) + .Value); + Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + R"(,"result":)" + + SigHelp + "}"); +} + void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition( TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits