rwols created this revision.
Makes clangd respond to a client's "textDocument/signatureHelp" request by
presenting function/method overloads.
Still need to add tests.
Also, there is duplicate code in clangd::codeComplete and clangd::signatureHelp
now, maybe refactor this to a common function?
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
@@ -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<clang::GlobalCodeCompletionAllocator>()),
+ 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<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;
+ 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<clang::GlobalCodeCompletionAllocator> Allocator;
+ CodeCompletionTUInfo CCTUInfo;
+
+}; // SignatureHelpCollector
+
} // namespace
std::vector<CompletionItem>
@@ -594,6 +683,73 @@
return Items;
}
+SignatureHelp
+clangd::signatureHelp(PathRef FileName, tooling::CompileCommand Command,
+ PrecompiledPreamble const *Preamble, StringRef Contents,
+ Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
+ std::shared_ptr<PCHContainerOperations> PCHs) {
+ std::vector<const char *> ArgStrs;
+ for (const auto &S : Command.CommandLine)
+ ArgStrs.push_back(S.c_str());
+
+ VFS->setCurrentWorkingDirectory(Command.Directory);
+
+ std::unique_ptr<CompilerInvocation> CI;
+ EmptyDiagsConsumer DummyDiagsConsumer;
+ {
+ IntrusiveRefCntPtr<DiagnosticsEngine> CommandLineDiagsEngine =
+ CompilerInstance::createDiagnostics(new DiagnosticOptions,
+ &DummyDiagsConsumer, false);
+ CI = createCompilerInvocation(ArgStrs, CommandLineDiagsEngine, VFS);
+ }
+ assert(CI && "Couldn't create CompilerInvocation");
+
+ std::unique_ptr<llvm::MemoryBuffer> 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/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 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>
+ 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
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits