[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:
Ilya Biryukov 2017-10-06 11:54:17 +00:00
parent 8f3a6c8143
commit d9bdfe0578
14 changed files with 450 additions and 59 deletions

View File

@ -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) {

View File

@ -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,

View File

@ -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);

View 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);

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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));

View File

@ -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,

View File

@ -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,

View File

@ -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: }}}
#

View File

@ -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: }}}
#

View File

@ -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"}