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) { Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -238,6 +238,19 @@ codeComplete(PathRef File, Position Pos, llvm::Optional OverridenContents = llvm::None, IntrusiveRefCntPtr *UsedFS = nullptr); + + /// Provide signature help for \p File at \p Pos. If \p OverridenContents is + /// not None, they will used only for code completion, 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 completion. This method should only be called for + /// currently tracked files. + Tagged + signatureHelp(PathRef File, Position Pos, + llvm::Optional OverridenContents = llvm::None, + IntrusiveRefCntPtr *UsedFS = nullptr); + /// Get definition of symbol at a specified \p Line and \p Column in \p File. Tagged> 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 +ClangdServer::signatureHelp(PathRef File, Position Pos, + llvm::Optional OverridenContents, + IntrusiveRefCntPtr *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 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 ClangdServer::formatRange(PathRef File, Range Rng) { std::string Code = getDocument(File); Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -256,6 +256,13 @@ std::shared_ptr 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, + std::shared_ptr PCHs); + /// Get definition of symbol at a specified \p Pos. std::vector findDefinitions(ParsedAST &AST, Position Pos); Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -518,6 +518,95 @@ } } }; // SnippetCompletionItemsCollector + +class SignatureHelpCollector final : public CodeCompleteConsumer { + +public: + SignatureHelpCollector(const CodeCompleteOptions &CodeCompleteOpts, + SignatureHelp &SigHelp) + : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false), + SigHelp(SigHelp), + Allocator(std::make_shared()), + CCTUInfo(Allocator) {} + + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates) override { + SigHelp.signatures.reserve(NumCandidates); + // TODO(rwols): How can we determine the "active overload candidate"? + SigHelp.activeSignature = 0; + assert(CurrentArg <= std::numeric_limits::max() && + "too many arguments"); + SigHelp.activeParameter = static_cast(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; + const char *ReturnType = nullptr; + if (CCS.getBriefComment()) + Result.documentation = CCS.getBriefComment(); + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_Placeholder: { + // A string that acts as a placeholder for, e.g., a function call + // argument. + Result.label += Chunk.Text; + ParameterInformation Info; + Info.label = Chunk.Text; + Result.parameters.push_back(std::move(Info)); + break; + } + 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_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: + 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 Allocator; + CodeCompletionTUInfo CCTUInfo; + +}; // SignatureHelpCollector + } // namespace std::vector @@ -594,6 +683,73 @@ return Items; } +SignatureHelp +clangd::signatureHelp(PathRef FileName, tooling::CompileCommand Command, + PrecompiledPreamble const *Preamble, StringRef Contents, + Position Pos, IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs) { + std::vector ArgStrs; + for (const auto &S : Command.CommandLine) + ArgStrs.push_back(S.c_str()); + + VFS->setCurrentWorkingDirectory(Command.Directory); + + std::unique_ptr CI; + EmptyDiagsConsumer DummyDiagsConsumer; + { + IntrusiveRefCntPtr CommandLineDiagsEngine = + CompilerInstance::createDiagnostics(new DiagnosticOptions, + &DummyDiagsConsumer, false); + CI = createCompilerInvocation(ArgStrs, CommandLineDiagsEngine, VFS); + } + assert(CI && "Couldn't create CompilerInvocation"); + + std::unique_ptr ContentsBuffer = + llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName); + + // Attempt to reuse the PCH from precompiled preamble, if it was built. + if (Preamble) { + auto Bounds = + ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0); + if (!Preamble->CanReuse(*CI, ContentsBuffer.get(), Bounds, VFS.get())) + Preamble = nullptr; + } + + auto Clang = prepareCompilerInstance(std::move(CI), Preamble, + std::move(ContentsBuffer), PCHs, VFS, + DummyDiagsConsumer); + auto &DiagOpts = Clang->getDiagnosticOpts(); + DiagOpts.IgnoreWarnings = true; + + auto &FrontendOpts = Clang->getFrontendOpts(); + FrontendOpts.SkipFunctionBodies = true; + + FrontendOpts.CodeCompleteOpts.IncludeGlobals = false; + FrontendOpts.CodeCompleteOpts.IncludeMacros = false; + FrontendOpts.CodeCompleteOpts.IncludeCodePatterns = false; + FrontendOpts.CodeCompleteOpts.IncludeBriefComments = true; + + FrontendOpts.CodeCompletionAt.FileName = FileName; + FrontendOpts.CodeCompletionAt.Line = Pos.line + 1; + FrontendOpts.CodeCompletionAt.Column = Pos.character + 1; + + SignatureHelp Result; + Clang->setCodeCompletionConsumer( + new SignatureHelpCollector(FrontendOpts.CodeCompleteOpts, Result)); + + SyntaxOnlyAction Action; + if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) { + // FIXME(ibiryukov): log errors + return Result; + } + if (!Action.Execute()) { + // FIXME(ibiryukov): log errors + } + Action.EndSourceFile(); + + return Result; +} + void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) { AST.getASTContext().getTranslationUnitDecl()->dump(OS, true); } 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 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 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/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(Out, Callbacks)); Dispatcher.registerHandler( + "textDocument/signatureHelp", + llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( "textDocument/definition", llvm::make_unique(Out, Callbacks)); }