[clangd] Add textDocument/signatureHelp
Summary: Makes clangd respond to a client's "textDocument/signatureHelp" request by presenting function/method overloads. Patch by Raoul Wols. Reviewers: bkramer, ilya-biryukov, krasimir Reviewed By: ilya-biryukov Subscribers: cfe-commits Differential Revision: https://reviews.llvm.org/D38048 llvm-svn: 315055
This commit is contained in:
parent
8f3a6c8143
commit
d9bdfe0578
|
@ -48,6 +48,7 @@ void ClangdLSPServer::onInitialize(StringRef ID, InitializeParams IP,
|
|||
"documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
|
||||
"codeActionProvider": true,
|
||||
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
|
||||
"signatureHelpProvider": {"triggerCharacters": ["(",","]},
|
||||
"definitionProvider": true
|
||||
}}})");
|
||||
if (IP.rootUri && !IP.rootUri->file.empty())
|
||||
|
@ -166,6 +167,18 @@ void ClangdLSPServer::onCompletion(TextDocumentPositionParams Params,
|
|||
R"(,"result":[)" + Completions + R"(]})");
|
||||
}
|
||||
|
||||
void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams Params,
|
||||
StringRef ID, JSONOutput &Out) {
|
||||
const auto SigHelp = SignatureHelp::unparse(
|
||||
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::onGoToDefinition(TextDocumentPositionParams Params,
|
||||
StringRef ID, JSONOutput &Out) {
|
||||
|
||||
|
|
|
@ -67,6 +67,8 @@ private:
|
|||
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;
|
||||
void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
|
||||
|
|
|
@ -248,6 +248,35 @@ ClangdServer::codeComplete(PathRef File, Position Pos,
|
|||
return Future;
|
||||
}
|
||||
|
||||
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, Logger);
|
||||
return make_tagged(std::move(Result), TaggedFS.Tag);
|
||||
}
|
||||
|
||||
std::vector<tooling::Replacement> ClangdServer::formatRange(PathRef File,
|
||||
Range Rng) {
|
||||
std::string Code = getDocument(File);
|
||||
|
|
|
@ -253,6 +253,18 @@ public:
|
|||
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);
|
||||
|
||||
|
|
|
@ -119,6 +119,44 @@ static int getSeverity(DiagnosticsEngine::Level L) {
|
|||
llvm_unreachable("Unknown diagnostic level!");
|
||||
}
|
||||
|
||||
/// Get the optional chunk as a string. This function is possibly recursive.
|
||||
///
|
||||
/// The parameter info for each parameter is appended to the Parameters.
|
||||
std::string
|
||||
getOptionalParameters(const CodeCompletionString &CCS,
|
||||
std::vector<ParameterInformation> &Parameters) {
|
||||
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 += getOptionalParameters(*Chunk.Optional, Parameters);
|
||||
break;
|
||||
case CodeCompletionString::CK_VerticalSpace:
|
||||
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 += Chunk.Text;
|
||||
ParameterInformation Info;
|
||||
Info.label = Chunk.Text;
|
||||
Parameters.push_back(std::move(Info));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Result += Chunk.Text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
llvm::Optional<DiagWithFixIts> toClangdDiag(StoredDiagnostic D) {
|
||||
auto Location = D.getLocation();
|
||||
if (!Location.isValid() || !Location.getManager().isInMainFile(Location))
|
||||
|
@ -284,8 +322,36 @@ std::string escapeSnippet(const llvm::StringRef Text) {
|
|||
return Result;
|
||||
}
|
||||
|
||||
class CompletionItemsCollector : public CodeCompleteConsumer {
|
||||
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;
|
||||
}
|
||||
|
||||
class CompletionItemsCollector : public CodeCompleteConsumer {
|
||||
public:
|
||||
CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
|
||||
std::vector<CompletionItem> &Items)
|
||||
|
@ -322,7 +388,7 @@ private:
|
|||
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.
|
||||
|
@ -339,35 +405,6 @@ private:
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
static int GetSortPriority(const CodeCompletionString &CCS) {
|
||||
int Score = CCS.getPriority();
|
||||
// Fill in the sortText of the CompletionItem.
|
||||
|
@ -560,14 +597,107 @@ private:
|
|||
}
|
||||
}
|
||||
}; // 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, clangd::Logger &Logger) {
|
||||
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;
|
||||
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: {
|
||||
// The rest of the parameters are defaulted/optional.
|
||||
assert(Chunk.Optional &&
|
||||
"Expected the optional code completion string to be non-null.");
|
||||
Result.label +=
|
||||
getOptionalParameters(*Chunk.Optional, Result.parameters);
|
||||
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
|
||||
|
||||
bool invokeCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
|
||||
const CodeCompleteOptions &Options, PathRef FileName,
|
||||
const tooling::CompileCommand &Command,
|
||||
PrecompiledPreamble const *Preamble, StringRef Contents,
|
||||
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
clangd::Logger &Logger) {
|
||||
std::vector<const char *> ArgStrs;
|
||||
for (const auto &S : Command.CommandLine)
|
||||
ArgStrs.push_back(S.c_str());
|
||||
|
@ -603,38 +733,73 @@ clangd::codeComplete(PathRef FileName, tooling::CompileCommand Command,
|
|||
|
||||
auto &FrontendOpts = Clang->getFrontendOpts();
|
||||
FrontendOpts.SkipFunctionBodies = true;
|
||||
|
||||
FrontendOpts.CodeCompleteOpts.IncludeGlobals = true;
|
||||
FrontendOpts.CodeCompleteOpts.IncludeMacros = true;
|
||||
FrontendOpts.CodeCompleteOpts.IncludeBriefComments = true;
|
||||
|
||||
FrontendOpts.CodeCompleteOpts = Options;
|
||||
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));
|
||||
}
|
||||
Clang->setCodeCompletionConsumer(Consumer.release());
|
||||
|
||||
SyntaxOnlyAction Action;
|
||||
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
|
||||
Logger.log("BeginSourceFile() failed when running codeComplete for " +
|
||||
FileName);
|
||||
return Items;
|
||||
return false;
|
||||
}
|
||||
if (!Action.Execute())
|
||||
if (!Action.Execute()) {
|
||||
Logger.log("Execute() failed when running codeComplete for " + FileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
Action.EndSourceFile();
|
||||
|
||||
return Items;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // 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, clangd::Logger &Logger) {
|
||||
std::vector<CompletionItem> Results;
|
||||
CodeCompleteOptions Options;
|
||||
std::unique_ptr<CodeCompleteConsumer> Consumer;
|
||||
Options.IncludeGlobals = true;
|
||||
Options.IncludeMacros = true;
|
||||
Options.IncludeBriefComments = true;
|
||||
if (SnippetCompletions) {
|
||||
Options.IncludeCodePatterns = true;
|
||||
Consumer =
|
||||
llvm::make_unique<SnippetCompletionItemsCollector>(Options, Results);
|
||||
} else {
|
||||
Options.IncludeCodePatterns = false;
|
||||
Consumer =
|
||||
llvm::make_unique<PlainTextCompletionItemsCollector>(Options, Results);
|
||||
}
|
||||
invokeCodeComplete(std::move(Consumer), Options, FileName, Command, Preamble,
|
||||
Contents, Pos, std::move(VFS), std::move(PCHs), Logger);
|
||||
return Results;
|
||||
}
|
||||
|
||||
SignatureHelp
|
||||
clangd::signatureHelp(PathRef FileName, tooling::CompileCommand Command,
|
||||
PrecompiledPreamble const *Preamble, StringRef Contents,
|
||||
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
clangd::Logger &Logger) {
|
||||
SignatureHelp Result;
|
||||
CodeCompleteOptions Options;
|
||||
Options.IncludeGlobals = false;
|
||||
Options.IncludeMacros = false;
|
||||
Options.IncludeCodePatterns = false;
|
||||
Options.IncludeBriefComments = true;
|
||||
invokeCodeComplete(llvm::make_unique<SignatureHelpCollector>(Options, Result),
|
||||
Options, FileName, Command, Preamble, Contents, Pos,
|
||||
std::move(VFS), std::move(PCHs), Logger);
|
||||
return Result;
|
||||
}
|
||||
|
||||
void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
|
||||
|
|
|
@ -259,6 +259,14 @@ codeComplete(PathRef FileName, tooling::CompileCommand Command,
|
|||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
bool SnippetCompletions, clangd::Logger &Logger);
|
||||
|
||||
/// 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,
|
||||
clangd::Logger &Logger);
|
||||
|
||||
/// Get definition of symbol at a specified \p Pos.
|
||||
std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos,
|
||||
clangd::Logger &Logger);
|
||||
|
|
|
@ -909,3 +909,58 @@ std::string CompletionItem::unparse(const CompletionItem &CI) {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -480,6 +480,48 @@ struct CompletionItem {
|
|||
static std::string unparse(const CompletionItem &P);
|
||||
};
|
||||
|
||||
/// A single parameter of a particular signature.
|
||||
struct ParameterInformation {
|
||||
|
||||
/// The label of this parameter. Mandatory.
|
||||
std::string label;
|
||||
|
||||
/// The documentation of this parameter. Optional.
|
||||
std::string documentation;
|
||||
|
||||
static std::string unparse(const ParameterInformation &);
|
||||
};
|
||||
|
||||
/// Represents the signature of something callable.
|
||||
struct SignatureInformation {
|
||||
|
||||
/// The label of this signature. Mandatory.
|
||||
std::string label;
|
||||
|
||||
/// The documentation of this signature. Optional.
|
||||
std::string documentation;
|
||||
|
||||
/// The parameters of this signature.
|
||||
std::vector<ParameterInformation> parameters;
|
||||
|
||||
static std::string unparse(const SignatureInformation &);
|
||||
};
|
||||
|
||||
/// Represents the signature of a callable.
|
||||
struct SignatureHelp {
|
||||
|
||||
/// The resulting signatures.
|
||||
std::vector<SignatureInformation> signatures;
|
||||
|
||||
/// The active signature.
|
||||
int activeSignature = 0;
|
||||
|
||||
/// The active parameter of the active signature.
|
||||
int activeParameter = 0;
|
||||
|
||||
static std::string unparse(const SignatureHelp &);
|
||||
};
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
|
|
@ -192,6 +192,23 @@ private:
|
|||
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, Output);
|
||||
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) {}
|
||||
|
@ -278,6 +295,9 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
|
|||
Dispatcher.registerHandler(
|
||||
"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));
|
||||
|
|
|
@ -47,6 +47,8 @@ public:
|
|||
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;
|
||||
virtual void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
Content-Length: 142
|
||||
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
|
||||
# CHECK: Content-Length: 466
|
||||
# CHECK: Content-Length: 535
|
||||
# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
|
||||
# CHECK: "textDocumentSync": 1,
|
||||
# CHECK: "documentFormattingProvider": true,
|
||||
|
@ -13,6 +13,7 @@ Content-Length: 142
|
|||
# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
|
||||
# CHECK: "codeActionProvider": true,
|
||||
# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
|
||||
# CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]},
|
||||
# CHECK: "definitionProvider": true
|
||||
# CHECK: }}}
|
||||
#
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
Content-Length: 143
|
||||
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
|
||||
# CHECK: Content-Length: 466
|
||||
# CHECK: Content-Length: 535
|
||||
# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
|
||||
# CHECK: "textDocumentSync": 1,
|
||||
# CHECK: "documentFormattingProvider": true,
|
||||
|
@ -13,6 +13,7 @@ Content-Length: 143
|
|||
# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
|
||||
# CHECK: "codeActionProvider": true,
|
||||
# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
|
||||
# CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]},
|
||||
# CHECK: "definitionProvider": true
|
||||
# CHECK: }}}
|
||||
#
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# 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"}]}
|
||||
# CHECK-DAG: {"label":"bar(float x = 0, int y = 42) -> void","parameters":[{"label":"float x = 0"},{"label":"int y = 42"}]}
|
||||
# CHECK: ]}
|
||||
|
||||
# Shutdown.
|
||||
Content-Length: 49
|
||||
|
||||
{"jsonrpc":"2.0","id":100000,"method":"shutdown"}
|
Loading…
Reference in New Issue