rwols updated this revision to Diff 116466. rwols added a comment. - Add signature-help.test - Modify formatting.test to make the test pass. The test failed because clangd's response to the initialize request was changed to advertise signature help trigger characters, so the expected content length changed.
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 test/clangd/formatting.test test/clangd/signature-help.test
Index: test/clangd/signature-help.test =================================================================== --- /dev/null +++ test/clangd/signature-help.test @@ -0,0 +1,46 @@ +# RUN: clangd -run-synchronously < %s | FileCheck %s +# It is absolutely vital that this file has CRLF line endings. + +# Start a session. +Content-Length: 125 + +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} + +# Modify the document. +Content-Length: 333 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"void foo(int x, int y);\nvoid foo(int x, float y);\nvoid foo(float x, int y);\nvoid foo(float x, float y);\nvoid bar(int x, int y = 0);\nvoid bar(float x = 0, int y = 42);\nint main() { foo("}}} + +# Ask for signature help. +Content-Length: 151 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":8,"character":9}}} +# CHECK: {"jsonrpc":"2.0","id":1,"result":{"activeSignature":0,"activeParameter":0,"signatures":[ +# CHECK-DAG: {"label":"foo(float x, float y) -> void","parameters":[{"label":"float x"},{"label":"float y"}]} +# CHECK-DAG: {"label":"foo(float x, int y) -> void","parameters":[{"label":"float x"},{"label":"int y"}]} +# CHECK-DAG: {"label":"foo(int x, float y) -> void","parameters":[{"label":"int x"},{"label":"float y"}]} +# CHECK-DAG: {"label":"foo(int x, int y) -> void","parameters":[{"label":"int x"},{"label":"int y"}]} +# CHECK: ]} + +# Modify the document +Content-Length: 333 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":2,"text":"void foo(int x, int y);\nvoid foo(int x, float y);\nvoid foo(float x, int y);\nvoid foo(float x, float y);\nvoid bar(int x, int y = 0);\nvoid bar(float x = 0, int y = 42);\nint main() { bar("}}} + +# Ask for signature help (this checks default argument handling). +Content-Length: 151 + +{"jsonrpc":"2.0","id":2,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":8,"character":9}}} +# CHECK: {"jsonrpc":"2.0","id":2,"result":{"activeSignature":0,"activeParameter":0,"signatures":[ +# CHECK-DAG: {"label":"bar(int x, int y = 0) -> void","parameters":[{"label":"int x"},{"label":"int y = 0"}]} +# +# FIXME(rwols): Multiple defaulted parameters are in a single CK_Optional chunk... +# I'm just putting the questionable result in here now as the expected result. +# CHECK-DAG: {"label":"bar(float x = 0, int y = 42) -> void","parameters":[{"label":"float x = 0, int y = 42"}]} +# +# CHECK: ]} + +# Shutdown. +Content-Length: 49 + +{"jsonrpc":"2.0","id":100000,"method":"shutdown"} Index: test/clangd/formatting.test =================================================================== --- test/clangd/formatting.test +++ test/clangd/formatting.test @@ -4,7 +4,6 @@ Content-Length: 125 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} -# CHECK: Content-Length: 466 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ # CHECK: "textDocumentSync": 1, # CHECK: "documentFormattingProvider": true, 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