Skip to content

Commit

Permalink
[clangd] Support offsets for parameters in signatureHelp
Browse files Browse the repository at this point in the history
Summary: Added to LSP in version 3.14

Reviewers: hokein

Reviewed By: hokein

Subscribers: MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D62476

llvm-svn: 362481
  • Loading branch information
ilya-biryukov committed Jun 4, 2019
1 parent 6384603 commit 4ef0f82
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 123 deletions.
18 changes: 17 additions & 1 deletion clang-tools-extra/clangd/ClangdLSPServer.cpp
Expand Up @@ -360,6 +360,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
Params.capabilities.HierarchicalDocumentSymbol;
SupportFileStatus = Params.initializationOptions.FileStatus;
HoverContentFormat = Params.capabilities.HoverContentFormat;
SupportsOffsetsInSignatureHelp = Params.capabilities.OffsetsInSignatureHelp;
llvm::json::Object Result{
{{"capabilities",
llvm::json::Object{
Expand Down Expand Up @@ -761,7 +762,22 @@ void ClangdLSPServer::onCompletion(const CompletionParams &Params,
void ClangdLSPServer::onSignatureHelp(const TextDocumentPositionParams &Params,
Callback<SignatureHelp> Reply) {
Server->signatureHelp(Params.textDocument.uri.file(), Params.position,
std::move(Reply));
Bind(
[this](decltype(Reply) Reply,
llvm::Expected<SignatureHelp> Signature) {
if (!Signature)
return Reply(Signature.takeError());
if (SupportsOffsetsInSignatureHelp)
return Reply(std::move(*Signature));
// Strip out the offsets from signature help for
// clients that only support string labels.
for (auto &Signature : Signature->signatures) {
for (auto &Param : Signature.parameters)
Param.labelOffsets.reset();
}
return Reply(std::move(*Signature));
},
std::move(Reply)));
}

// Go to definition has a toggle function: if def and decl are distinct, then
Expand Down
5 changes: 3 additions & 2 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Expand Up @@ -156,8 +156,9 @@ class ClangdLSPServer : private DiagnosticsConsumer {
bool SupportFileStatus = false;
/// Which kind of markup should we use in textDocument/hover responses.
MarkupKind HoverContentFormat = MarkupKind::PlainText;

/// Store of the current versions of the open documents.
/// Whether the client supports offsets for parameter info labels.
bool SupportsOffsetsInSignatureHelp = false;
// Store of the current versions of the open documents.
DraftStore DraftMgr;

// The CDB is created by the "initialize" LSP method.
Expand Down
115 changes: 54 additions & 61 deletions clang-tools-extra/clangd/CodeComplete.cpp
Expand Up @@ -28,6 +28,7 @@
#include "FuzzyMatch.h"
#include "Headers.h"
#include "Logger.h"
#include "Protocol.h"
#include "Quality.h"
#include "SourceCode.h"
#include "TUScheduler.h"
Expand Down Expand Up @@ -56,6 +57,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Format.h"
Expand Down Expand Up @@ -148,46 +150,6 @@ toCompletionItemKind(CodeCompletionResult::ResultKind ResKind,
llvm_unreachable("Unhandled CodeCompletionResult::ResultKind.");
}

/// 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,
SignatureQualitySignals &Signal) {
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, Signal);
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));
Signal.ContainsActiveParameter = true;
Signal.NumberOfOptionalParameters++;
break;
}
default:
Result += Chunk.Text;
break;
}
}
return Result;
}

// Identifier code completion result.
struct RawIdentifier {
llvm::StringRef Name;
Expand Down Expand Up @@ -830,8 +792,7 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
public:
SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
const SymbolIndex *Index, SignatureHelp &SigHelp)
: CodeCompleteConsumer(CodeCompleteOpts),
SigHelp(SigHelp),
: CodeCompleteConsumer(CodeCompleteOpts), SigHelp(SigHelp),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator), Index(Index) {}

Expand Down Expand Up @@ -944,6 +905,50 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }

private:
void processParameterChunk(llvm::StringRef ChunkText,
SignatureInformation &Signature,
SignatureQualitySignals Signal) const {
// (!) this is O(n), should still be fast compared to building ASTs.
unsigned ParamStartOffset = lspLength(Signature.label);
unsigned ParamEndOffset = ParamStartOffset + lspLength(ChunkText);
// A piece of text that describes the parameter that corresponds to
// the code-completion location within a function call, message send,
// macro invocation, etc.
Signature.label += ChunkText;
ParameterInformation Info;
Info.labelOffsets.emplace(ParamStartOffset, ParamEndOffset);
// FIXME: only set 'labelOffsets' when all clients migrate out of it.
Info.labelString = ChunkText;

Signature.parameters.push_back(std::move(Info));
// FIXME: this should only be set on CK_CurrentParameter.
Signal.ContainsActiveParameter = true;
}

void processOptionalChunk(const CodeCompletionString &CCS,
SignatureInformation &Signature,
SignatureQualitySignals &Signal) const {
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.");
processOptionalChunk(*Chunk.Optional, Signature, Signal);
break;
case CodeCompletionString::CK_VerticalSpace:
break;
case CodeCompletionString::CK_CurrentParameter:
case CodeCompletionString::CK_Placeholder:
processParameterChunk(Chunk.Text, Signature, Signal);
Signal.NumberOfOptionalParameters++;
break;
default:
Signature.label += Chunk.Text;
break;
}
}
}

// FIXME(ioeric): consider moving CodeCompletionString logic here to
// CompletionString.h.
ScoredSignature processOverloadCandidate(const OverloadCandidate &Candidate,
Expand All @@ -964,28 +969,16 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
assert(!ReturnType && "Unexpected CK_ResultType");
ReturnType = Chunk.Text;
break;
case CodeCompletionString::CK_CurrentParameter:
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.
Signature.label += Chunk.Text;
ParameterInformation Info;
Info.label = Chunk.Text;
Signature.parameters.push_back(std::move(Info));
processParameterChunk(Chunk.Text, Signature, Signal);
Signal.NumberOfParameters++;
Signal.ContainsActiveParameter = true;
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.");
Signature.label += getOptionalParameters(*Chunk.Optional,
Signature.parameters, Signal);
processOptionalChunk(*Chunk.Optional, Signature, Signal);
break;
}
case CodeCompletionString::CK_VerticalSpace:
Expand Down Expand Up @@ -1037,7 +1030,7 @@ void loadMainFilePreambleMacros(const Preprocessor &PP,
PP.getIdentifierTable().getExternalIdentifierLookup();
if (!PreambleIdentifiers || !PreambleMacros)
return;
for (const auto& MacroName : Preamble.MainFileMacros)
for (const auto &MacroName : Preamble.MainFileMacros)
if (auto *II = PreambleIdentifiers->get(MacroName))
if (II->isOutOfDate())
PreambleMacros->updateOutOfDateIdentifier(*II);
Expand Down Expand Up @@ -1213,7 +1206,7 @@ class CodeCompleteFlow {
int NSema = 0, NIndex = 0, NSemaAndIndex = 0, NIdent = 0;
bool Incomplete = false; // Would more be available with a higher limit?
CompletionPrefix HeuristicPrefix;
llvm::Optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
llvm::Optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
Range ReplacedRange;
std::vector<std::string> QueryScopes; // Initialized once Sema runs.
// Initialized once QueryScopes is initialized, if there are scopes.
Expand Down Expand Up @@ -1707,8 +1700,8 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
return Result;
}

CompletionPrefix
guessCompletionPrefix(llvm::StringRef Content, unsigned Offset) {
CompletionPrefix guessCompletionPrefix(llvm::StringRef Content,
unsigned Offset) {
assert(Offset <= Content.size());
StringRef Rest = Content.take_front(Offset);
CompletionPrefix Result;
Expand Down
18 changes: 16 additions & 2 deletions clang-tools-extra/clangd/Protocol.cpp
Expand Up @@ -314,6 +314,14 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R) {
}
}
}
if (auto *Help = TextDocument->getObject("signatureHelp")) {
if (auto *Info = Help->getObject("signatureInformation")) {
if (auto *Parameter = Info->getObject("parameterInformation")) {
if (auto OffsetSupport = Parameter->getBoolean("labelOffsetSupport"))
R.OffsetsInSignatureHelp = *OffsetSupport;
}
}
}
}
if (auto *Workspace = O->getObject("workspace")) {
if (auto *Symbol = Workspace->getObject("symbol")) {
Expand Down Expand Up @@ -824,8 +832,14 @@ llvm::json::Value toJSON(const CompletionList &L) {
}

llvm::json::Value toJSON(const ParameterInformation &PI) {
assert(!PI.label.empty() && "parameter information label is required");
llvm::json::Object Result{{"label", PI.label}};
assert(PI.labelOffsets.hasValue() ||
!PI.labelString.empty() && "parameter information label is required");
llvm::json::Object Result;
if (PI.labelOffsets)
Result["label"] =
llvm::json::Array({PI.labelOffsets->first, PI.labelOffsets->second});
else
Result["label"] = PI.labelString;
if (!PI.documentation.empty())
Result["documentation"] = PI.documentation;
return std::move(Result);
Expand Down
13 changes: 11 additions & 2 deletions clang-tools-extra/clangd/Protocol.h
Expand Up @@ -390,6 +390,9 @@ struct ClientCapabilities {
/// Client supports hierarchical document symbols.
bool HierarchicalDocumentSymbol = false;

/// Client supports processing label offsets instead of a simple label string.
bool OffsetsInSignatureHelp = false;

/// The supported set of CompletionItemKinds for textDocument/completion.
/// textDocument.completion.completionItemKind.valueSet
llvm::Optional<CompletionItemKindBitset> CompletionItemKinds;
Expand Down Expand Up @@ -979,8 +982,14 @@ llvm::json::Value toJSON(const CompletionList &);
/// A single parameter of a particular signature.
struct ParameterInformation {

/// The label of this parameter. Mandatory.
std::string label;
/// The label of this parameter. Ignored when labelOffsets is set.
std::string labelString;

/// Inclusive start and exclusive end offsets withing the containing signature
/// label.
/// Offsets are computed by lspLength(), which counts UTF-16 code units by
/// default but that can be overriden, see its documentation for details.
llvm::Optional<std::pair<unsigned, unsigned>> labelOffsets;

/// The documentation of this parameter. Optional.
std::string documentation;
Expand Down
50 changes: 50 additions & 0 deletions clang-tools-extra/clangd/test/signature-help-with-offsets.test
@@ -0,0 +1,50 @@
# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s
# Start a session.
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"processId": 123,
"rootPath": "clangd",
"capabilities": {
"textDocument": {
"signatureHelp": {
"signatureInformation": {
"parameterInformation": {
"labelOffsetSupport": true
}
}
}
}
},
"trace": "off"
}
}
---
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void x(int);\nint main(){\nx("}}}
---
{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":2}}}
# CHECK: "id": 1,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": {
# CHECK-NEXT: "activeParameter": 0,
# CHECK-NEXT: "activeSignature": 0,
# CHECK-NEXT: "signatures": [
# CHECK-NEXT: {
# CHECK-NEXT: "label": "x(int) -> void",
# CHECK-NEXT: "parameters": [
# CHECK-NEXT: {
# CHECK-NEXT: "label": [
# CHECK-NEXT: 2,
# CHECK-NEXT: 5
# CHECK-NEXT: ]
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT: }
---
{"jsonrpc":"2.0","id":100000,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}

0 comments on commit 4ef0f82

Please sign in to comment.