mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-22 07:35:08 +00:00
Signature help and snippets for code completion
This commit is contained in:
parent
7f33861526
commit
3001faf9a8
@ -42,71 +42,86 @@ int GetCompletionPriority(const CXCompletionString& str, CXCursorKind result_kin
|
|||||||
return priority;
|
return priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsCallKind(CXCursorKind kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case CXCursor_ObjCInstanceMethodDecl:
|
||||||
|
case CXCursor_CXXMethod:
|
||||||
|
case CXCursor_FunctionTemplate:
|
||||||
|
case CXCursor_FunctionDecl:
|
||||||
|
case CXCursor_Constructor:
|
||||||
|
case CXCursor_Destructor:
|
||||||
|
case CXCursor_ConversionFunction:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) {
|
lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) {
|
||||||
switch (cursor_kind) {
|
switch (cursor_kind) {
|
||||||
|
|
||||||
case CXCursor_ObjCInstanceMethodDecl:
|
case CXCursor_ObjCInstanceMethodDecl:
|
||||||
case CXCursor_CXXMethod:
|
case CXCursor_CXXMethod:
|
||||||
return lsCompletionItemKind::Method;
|
return lsCompletionItemKind::Method;
|
||||||
|
|
||||||
case CXCursor_FunctionTemplate:
|
case CXCursor_FunctionTemplate:
|
||||||
case CXCursor_FunctionDecl:
|
case CXCursor_FunctionDecl:
|
||||||
return lsCompletionItemKind::Function;
|
return lsCompletionItemKind::Function;
|
||||||
|
|
||||||
case CXCursor_Constructor:
|
case CXCursor_Constructor:
|
||||||
case CXCursor_Destructor:
|
case CXCursor_Destructor:
|
||||||
case CXCursor_ConversionFunction:
|
case CXCursor_ConversionFunction:
|
||||||
return lsCompletionItemKind::Constructor;
|
return lsCompletionItemKind::Constructor;
|
||||||
|
|
||||||
case CXCursor_FieldDecl:
|
case CXCursor_FieldDecl:
|
||||||
return lsCompletionItemKind::Field;
|
return lsCompletionItemKind::Field;
|
||||||
|
|
||||||
case CXCursor_VarDecl:
|
case CXCursor_VarDecl:
|
||||||
case CXCursor_ParmDecl:
|
case CXCursor_ParmDecl:
|
||||||
return lsCompletionItemKind::Variable;
|
return lsCompletionItemKind::Variable;
|
||||||
|
|
||||||
case CXCursor_UnionDecl:
|
case CXCursor_UnionDecl:
|
||||||
case CXCursor_ClassTemplate:
|
case CXCursor_ClassTemplate:
|
||||||
case CXCursor_ClassTemplatePartialSpecialization:
|
case CXCursor_ClassTemplatePartialSpecialization:
|
||||||
case CXCursor_ClassDecl:
|
case CXCursor_ClassDecl:
|
||||||
case CXCursor_StructDecl:
|
case CXCursor_StructDecl:
|
||||||
case CXCursor_UsingDeclaration:
|
case CXCursor_UsingDeclaration:
|
||||||
case CXCursor_TypedefDecl:
|
case CXCursor_TypedefDecl:
|
||||||
case CXCursor_TypeAliasDecl:
|
case CXCursor_TypeAliasDecl:
|
||||||
case CXCursor_TypeAliasTemplateDecl:
|
case CXCursor_TypeAliasTemplateDecl:
|
||||||
return lsCompletionItemKind::Class;
|
return lsCompletionItemKind::Class;
|
||||||
|
|
||||||
case CXCursor_EnumConstantDecl:
|
case CXCursor_EnumConstantDecl:
|
||||||
case CXCursor_EnumDecl:
|
case CXCursor_EnumDecl:
|
||||||
return lsCompletionItemKind::Enum;
|
return lsCompletionItemKind::Enum;
|
||||||
|
|
||||||
case CXCursor_MacroInstantiation:
|
case CXCursor_MacroInstantiation:
|
||||||
case CXCursor_MacroDefinition:
|
case CXCursor_MacroDefinition:
|
||||||
return lsCompletionItemKind::Interface;
|
return lsCompletionItemKind::Interface;
|
||||||
|
|
||||||
case CXCursor_Namespace:
|
case CXCursor_Namespace:
|
||||||
case CXCursor_NamespaceAlias:
|
case CXCursor_NamespaceAlias:
|
||||||
case CXCursor_NamespaceRef:
|
case CXCursor_NamespaceRef:
|
||||||
return lsCompletionItemKind::Module;
|
return lsCompletionItemKind::Module;
|
||||||
|
|
||||||
case CXCursor_MemberRef:
|
case CXCursor_MemberRef:
|
||||||
case CXCursor_TypeRef:
|
case CXCursor_TypeRef:
|
||||||
return lsCompletionItemKind::Reference;
|
return lsCompletionItemKind::Reference;
|
||||||
|
|
||||||
//return lsCompletionItemKind::Property;
|
//return lsCompletionItemKind::Property;
|
||||||
//return lsCompletionItemKind::Unit;
|
//return lsCompletionItemKind::Unit;
|
||||||
//return lsCompletionItemKind::Value;
|
//return lsCompletionItemKind::Value;
|
||||||
//return lsCompletionItemKind::Keyword;
|
//return lsCompletionItemKind::Keyword;
|
||||||
//return lsCompletionItemKind::Snippet;
|
//return lsCompletionItemKind::Snippet;
|
||||||
//return lsCompletionItemKind::Color;
|
//return lsCompletionItemKind::Color;
|
||||||
//return lsCompletionItemKind::File;
|
//return lsCompletionItemKind::File;
|
||||||
|
|
||||||
case CXCursor_NotImplemented:
|
case CXCursor_NotImplemented:
|
||||||
return lsCompletionItemKind::Text;
|
return lsCompletionItemKind::Text;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
std::cerr << "[complete] Unhandled completion kind " << cursor_kind << std::endl;
|
std::cerr << "[complete] Unhandled completion kind " << cursor_kind << std::endl;
|
||||||
return lsCompletionItemKind::Text;
|
return lsCompletionItemKind::Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,9 +140,7 @@ std::string BuildLabelString(CXCompletionString completion_string) {
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string BuildDetailString(CXCompletionString completion_string) {
|
void BuildDetailString(CXCompletionString completion_string, std::string& detail, std::string& insert, std::vector<std::string>* parameters) {
|
||||||
std::string detail;
|
|
||||||
|
|
||||||
int num_chunks = clang_getNumCompletionChunks(completion_string);
|
int num_chunks = clang_getNumCompletionChunks(completion_string);
|
||||||
for (int i = 0; i < num_chunks; ++i) {
|
for (int i = 0; i < num_chunks; ++i) {
|
||||||
CXCompletionChunkKind kind = clang_getCompletionChunkKind(completion_string, i);
|
CXCompletionChunkKind kind = clang_getCompletionChunkKind(completion_string, i);
|
||||||
@ -135,23 +148,29 @@ std::string BuildDetailString(CXCompletionString completion_string) {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case CXCompletionChunk_Optional: {
|
case CXCompletionChunk_Optional: {
|
||||||
CXCompletionString nested = clang_getCompletionChunkCompletionString(completion_string, i);
|
CXCompletionString nested = clang_getCompletionChunkCompletionString(completion_string, i);
|
||||||
detail += BuildDetailString(nested);
|
BuildDetailString(nested, detail, insert, parameters);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CXCompletionChunk_Placeholder: {
|
case CXCompletionChunk_Placeholder: {
|
||||||
// TODO: send this info to vscode.
|
std::string text = clang::ToString(clang_getCompletionChunkText(completion_string, i));
|
||||||
CXString text = clang_getCompletionChunkText(completion_string, i);
|
parameters->push_back(text);
|
||||||
detail += clang::ToString(text);
|
detail += text;
|
||||||
|
insert += "${" + std::to_string(parameters->size()) + ":" + text + "}";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CXCompletionChunk_CurrentParameter:
|
||||||
|
// We have our own parsing logic for active parameter. This doesn't seem
|
||||||
|
// to be very reliable.
|
||||||
|
break;
|
||||||
|
|
||||||
case CXCompletionChunk_TypedText:
|
case CXCompletionChunk_TypedText:
|
||||||
case CXCompletionChunk_Text:
|
case CXCompletionChunk_Text:
|
||||||
case CXCompletionChunk_Informative:
|
case CXCompletionChunk_Informative: {
|
||||||
case CXCompletionChunk_CurrentParameter: {
|
std::string text = clang::ToString(clang_getCompletionChunkText(completion_string, i));
|
||||||
CXString text = clang_getCompletionChunkText(completion_string, i);
|
detail += text;
|
||||||
detail += clang::ToString(text);
|
insert += text;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,48 +183,59 @@ std::string BuildDetailString(CXCompletionString completion_string) {
|
|||||||
|
|
||||||
case CXCompletionChunk_LeftParen:
|
case CXCompletionChunk_LeftParen:
|
||||||
detail += "(";
|
detail += "(";
|
||||||
|
insert += "(";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_RightParen:
|
case CXCompletionChunk_RightParen:
|
||||||
detail += ")";
|
detail += ")";
|
||||||
|
insert += ")";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_LeftBracket:
|
case CXCompletionChunk_LeftBracket:
|
||||||
detail += "]";
|
detail += "[";
|
||||||
|
insert += "[";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_RightBracket:
|
case CXCompletionChunk_RightBracket:
|
||||||
detail += "[";
|
detail += "]";
|
||||||
|
insert += "]";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_LeftBrace:
|
case CXCompletionChunk_LeftBrace:
|
||||||
detail += "{";
|
detail += "{";
|
||||||
|
insert += "{";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_RightBrace:
|
case CXCompletionChunk_RightBrace:
|
||||||
detail += "}";
|
detail += "}";
|
||||||
|
insert += "}";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_LeftAngle:
|
case CXCompletionChunk_LeftAngle:
|
||||||
detail += "<";
|
detail += "<";
|
||||||
|
insert += "<";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_RightAngle:
|
case CXCompletionChunk_RightAngle:
|
||||||
detail += ">";
|
detail += ">";
|
||||||
|
insert += ">";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_Comma:
|
case CXCompletionChunk_Comma:
|
||||||
detail += ", ";
|
detail += ", ";
|
||||||
|
insert += ", ";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_Colon:
|
case CXCompletionChunk_Colon:
|
||||||
detail += ":";
|
detail += ":";
|
||||||
|
insert += ":";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_SemiColon:
|
case CXCompletionChunk_SemiColon:
|
||||||
detail += ";";
|
detail += ";";
|
||||||
|
insert += ";";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_Equal:
|
case CXCompletionChunk_Equal:
|
||||||
detail += "=";
|
detail += "=";
|
||||||
|
insert += "=";
|
||||||
break;
|
break;
|
||||||
case CXCompletionChunk_HorizontalSpace:
|
case CXCompletionChunk_HorizontalSpace:
|
||||||
case CXCompletionChunk_VerticalSpace:
|
case CXCompletionChunk_VerticalSpace:
|
||||||
detail += " ";
|
detail += " ";
|
||||||
|
insert += " ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return detail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnsureDocumentParsed(CompletionSession* session,
|
void EnsureDocumentParsed(CompletionSession* session,
|
||||||
@ -288,6 +318,16 @@ void CompletionQueryMain(CompletionManager* completion_manager) {
|
|||||||
timer.Reset();
|
timer.Reset();
|
||||||
for (unsigned i = 0; i < cx_results->NumResults; ++i) {
|
for (unsigned i = 0; i < cx_results->NumResults; ++i) {
|
||||||
CXCompletionResult& result = cx_results->Results[i];
|
CXCompletionResult& result = cx_results->Results[i];
|
||||||
|
|
||||||
|
// TODO: Try to figure out how we can hide base method calls without also
|
||||||
|
// hiding method implementation assistance, ie,
|
||||||
|
//
|
||||||
|
// void Foo::* {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
|
if (clang_getCompletionAvailability(result.CompletionString) == CXAvailability_NotAvailable)
|
||||||
|
continue;
|
||||||
|
|
||||||
// TODO: fill in more data
|
// TODO: fill in more data
|
||||||
lsCompletionItem ls_completion_item;
|
lsCompletionItem ls_completion_item;
|
||||||
@ -295,9 +335,13 @@ void CompletionQueryMain(CompletionManager* completion_manager) {
|
|||||||
// kind/label/detail/docs/sortText
|
// kind/label/detail/docs/sortText
|
||||||
ls_completion_item.kind = GetCompletionKind(result.CursorKind);
|
ls_completion_item.kind = GetCompletionKind(result.CursorKind);
|
||||||
ls_completion_item.label = BuildLabelString(result.CompletionString);
|
ls_completion_item.label = BuildLabelString(result.CompletionString);
|
||||||
ls_completion_item.detail = BuildDetailString(result.CompletionString);
|
BuildDetailString(result.CompletionString, ls_completion_item.detail, ls_completion_item.insertText, &ls_completion_item.parameters_);
|
||||||
ls_completion_item.documentation = clang::ToString(clang_getCompletionBriefComment(result.CompletionString));
|
ls_completion_item.documentation = clang::ToString(clang_getCompletionBriefComment(result.CompletionString));
|
||||||
ls_completion_item.sortText = uint64_t(GetCompletionPriority(result.CompletionString, result.CursorKind, ls_completion_item.label));
|
ls_completion_item.sortText = uint64_t(GetCompletionPriority(result.CompletionString, result.CursorKind, ls_completion_item.label));
|
||||||
|
|
||||||
|
// If this function is slow we can skip building insertText at the cost of some code duplication.
|
||||||
|
if (!IsCallKind(result.CursorKind))
|
||||||
|
ls_completion_item.insertText = "";
|
||||||
|
|
||||||
ls_result.push_back(ls_completion_item);
|
ls_result.push_back(ls_completion_item);
|
||||||
}
|
}
|
||||||
|
@ -812,6 +812,7 @@ void RegisterMessageTypes() {
|
|||||||
MessageRegistry::instance()->Register<Ipc_TextDocumentDidSave>();
|
MessageRegistry::instance()->Register<Ipc_TextDocumentDidSave>();
|
||||||
MessageRegistry::instance()->Register<Ipc_TextDocumentRename>();
|
MessageRegistry::instance()->Register<Ipc_TextDocumentRename>();
|
||||||
MessageRegistry::instance()->Register<Ipc_TextDocumentComplete>();
|
MessageRegistry::instance()->Register<Ipc_TextDocumentComplete>();
|
||||||
|
MessageRegistry::instance()->Register<Ipc_TextDocumentSignatureHelp>();
|
||||||
MessageRegistry::instance()->Register<Ipc_TextDocumentDefinition>();
|
MessageRegistry::instance()->Register<Ipc_TextDocumentDefinition>();
|
||||||
MessageRegistry::instance()->Register<Ipc_TextDocumentDocumentHighlight>();
|
MessageRegistry::instance()->Register<Ipc_TextDocumentDocumentHighlight>();
|
||||||
MessageRegistry::instance()->Register<Ipc_TextDocumentHover>();
|
MessageRegistry::instance()->Register<Ipc_TextDocumentHover>();
|
||||||
@ -1342,6 +1343,11 @@ bool QueryDbMainLoop(
|
|||||||
// See https://github.com/Microsoft/language-server-protocol/issues/138.
|
// See https://github.com/Microsoft/language-server-protocol/issues/138.
|
||||||
response.result.capabilities.completionProvider->triggerCharacters = { ".", ":", ">" };
|
response.result.capabilities.completionProvider->triggerCharacters = { ".", ":", ">" };
|
||||||
|
|
||||||
|
response.result.capabilities.signatureHelpProvider = lsSignatureHelpOptions();
|
||||||
|
// NOTE: If updating signature help tokens make sure to also update
|
||||||
|
// WorkingFile::FindClosestCallNameInBuffer.
|
||||||
|
response.result.capabilities.signatureHelpProvider->triggerCharacters = { "(", "," };
|
||||||
|
|
||||||
response.result.capabilities.codeLensProvider = lsCodeLensOptions();
|
response.result.capabilities.codeLensProvider = lsCodeLensOptions();
|
||||||
response.result.capabilities.codeLensProvider->resolveProvider = false;
|
response.result.capabilities.codeLensProvider->resolveProvider = false;
|
||||||
|
|
||||||
@ -1599,6 +1605,76 @@ bool QueryDbMainLoop(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case IpcId::TextDocumentSignatureHelp: {
|
||||||
|
auto msg = static_cast<Ipc_TextDocumentSignatureHelp*>(message.get());
|
||||||
|
lsTextDocumentPositionParams params = msg->params;
|
||||||
|
//std::cerr << "!! SignatureHelp @ " << msg->params.position.line << ":" << msg->params.position.character << std::endl;
|
||||||
|
|
||||||
|
WorkingFile* file = working_files->GetFileByFilename(params.textDocument.uri.GetPath());
|
||||||
|
std::string search;
|
||||||
|
int active_param = 0;
|
||||||
|
if (file) {
|
||||||
|
lsPosition completion_position;
|
||||||
|
search = file->FindClosestCallNameInBuffer(params.position, &active_param, &completion_position);
|
||||||
|
// Move completion position back by one; completer will automatically increment it by one to deal with -> and . accesses.
|
||||||
|
if (completion_position.character > 0)
|
||||||
|
completion_position.character -= 1;
|
||||||
|
std::cerr << "[completion] Changing completion position from " << params.position.ToString() << " to " << completion_position.ToString() << std::endl;
|
||||||
|
params.position = completion_position;
|
||||||
|
}
|
||||||
|
std::cerr << "[completion] Returning signatures for " << search << std::endl;
|
||||||
|
if (search.empty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
CompletionManager::OnComplete callback = std::bind([](BaseIpcMessage* message, std::string search, int active_param, const NonElidedVector<lsCompletionItem>& results) {
|
||||||
|
auto msg = static_cast<Ipc_TextDocumentSignatureHelp*>(message);
|
||||||
|
auto ipc = IpcManager::instance();
|
||||||
|
|
||||||
|
Out_TextDocumentSignatureHelp response;
|
||||||
|
response.id = msg->id;
|
||||||
|
|
||||||
|
for (auto& result : results) {
|
||||||
|
if (result.label != search)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
lsSignatureInformation signature;
|
||||||
|
signature.label = result.detail;
|
||||||
|
for (auto& parameter : result.parameters_) {
|
||||||
|
lsParameterInformation ls_param;
|
||||||
|
ls_param.label = parameter;
|
||||||
|
signature.parameters.push_back(ls_param);
|
||||||
|
}
|
||||||
|
response.result.signatures.push_back(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guess the signature the user wants based on available parameter
|
||||||
|
// count.
|
||||||
|
response.result.activeSignature = 0;
|
||||||
|
for (size_t i = 0; i < response.result.signatures.size(); ++i) {
|
||||||
|
if (active_param < response.result.signatures.size()) {
|
||||||
|
response.result.activeSignature = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set signature to what we parsed from the working file.
|
||||||
|
response.result.activeParameter = active_param;
|
||||||
|
|
||||||
|
response.Write(std::cerr);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
|
||||||
|
Timer timer;
|
||||||
|
ipc->SendOutMessageToClient(IpcId::TextDocumentSignatureHelp, response);
|
||||||
|
timer.ResetAndPrint("[complete] Writing signature help results");
|
||||||
|
|
||||||
|
delete message;
|
||||||
|
}, message.release(), search, active_param, std::placeholders::_1);
|
||||||
|
|
||||||
|
completion_manager->CodeComplete(params, std::move(callback));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case IpcId::TextDocumentDefinition: {
|
case IpcId::TextDocumentDefinition: {
|
||||||
auto msg = static_cast<Ipc_TextDocumentDefinition*>(message.get());
|
auto msg = static_cast<Ipc_TextDocumentDefinition*>(message.get());
|
||||||
|
|
||||||
@ -2144,6 +2220,7 @@ void LanguageServerStdinLoop(IndexerConfig* config, std::unordered_map<IpcId, Ti
|
|||||||
case IpcId::TextDocumentDidSave:
|
case IpcId::TextDocumentDidSave:
|
||||||
case IpcId::TextDocumentRename:
|
case IpcId::TextDocumentRename:
|
||||||
case IpcId::TextDocumentCompletion:
|
case IpcId::TextDocumentCompletion:
|
||||||
|
case IpcId::TextDocumentSignatureHelp:
|
||||||
case IpcId::TextDocumentDefinition:
|
case IpcId::TextDocumentDefinition:
|
||||||
case IpcId::TextDocumentDocumentHighlight:
|
case IpcId::TextDocumentDocumentHighlight:
|
||||||
case IpcId::TextDocumentHover:
|
case IpcId::TextDocumentHover:
|
||||||
|
@ -26,6 +26,8 @@ const char* IpcIdToString(IpcId id) {
|
|||||||
return "textDocument/rename";
|
return "textDocument/rename";
|
||||||
case IpcId::TextDocumentCompletion:
|
case IpcId::TextDocumentCompletion:
|
||||||
return "textDocument/completion";
|
return "textDocument/completion";
|
||||||
|
case IpcId::TextDocumentSignatureHelp:
|
||||||
|
return "textDocument/signatureHelp";
|
||||||
case IpcId::TextDocumentDefinition:
|
case IpcId::TextDocumentDefinition:
|
||||||
return "textDocument/definition";
|
return "textDocument/definition";
|
||||||
case IpcId::TextDocumentDocumentHighlight:
|
case IpcId::TextDocumentDocumentHighlight:
|
||||||
|
@ -18,6 +18,7 @@ enum class IpcId : int {
|
|||||||
TextDocumentPublishDiagnostics,
|
TextDocumentPublishDiagnostics,
|
||||||
TextDocumentRename,
|
TextDocumentRename,
|
||||||
TextDocumentCompletion,
|
TextDocumentCompletion,
|
||||||
|
TextDocumentSignatureHelp,
|
||||||
TextDocumentDefinition,
|
TextDocumentDefinition,
|
||||||
TextDocumentDocumentHighlight,
|
TextDocumentDocumentHighlight,
|
||||||
TextDocumentHover,
|
TextDocumentHover,
|
||||||
|
@ -189,6 +189,10 @@ bool lsPosition::operator==(const lsPosition& other) const {
|
|||||||
return line == other.line && character == other.character;
|
return line == other.line && character == other.character;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string lsPosition::ToString() const {
|
||||||
|
return std::to_string(line) + ":" + std::to_string(character);
|
||||||
|
}
|
||||||
|
|
||||||
lsRange::lsRange() {}
|
lsRange::lsRange() {}
|
||||||
lsRange::lsRange(lsPosition start, lsPosition end) : start(start), end(end) {}
|
lsRange::lsRange(lsPosition start, lsPosition end) : start(start), end(end) {}
|
||||||
|
|
||||||
|
@ -260,6 +260,8 @@ struct lsPosition {
|
|||||||
|
|
||||||
bool operator==(const lsPosition& other) const;
|
bool operator==(const lsPosition& other) const;
|
||||||
|
|
||||||
|
std::string ToString() const;
|
||||||
|
|
||||||
// Note: these are 0-based.
|
// Note: these are 0-based.
|
||||||
int line = 0;
|
int line = 0;
|
||||||
int character = 0;
|
int character = 0;
|
||||||
@ -421,6 +423,9 @@ enum class lsCompletionItemKind {
|
|||||||
MAKE_REFLECT_TYPE_PROXY(lsCompletionItemKind, int);
|
MAKE_REFLECT_TYPE_PROXY(lsCompletionItemKind, int);
|
||||||
|
|
||||||
struct lsCompletionItem {
|
struct lsCompletionItem {
|
||||||
|
// A set of function parameters. Used internally for signature help. Not sent to vscode.
|
||||||
|
std::vector<std::string> parameters_;
|
||||||
|
|
||||||
// The label of this completion item. By default
|
// The label of this completion item. By default
|
||||||
// also the text that is inserted when selecting
|
// also the text that is inserted when selecting
|
||||||
// this completion.
|
// this completion.
|
||||||
@ -447,11 +452,11 @@ struct lsCompletionItem {
|
|||||||
|
|
||||||
// A string that should be inserted a document when selecting
|
// A string that should be inserted a document when selecting
|
||||||
// this completion. When `falsy` the label is used.
|
// this completion. When `falsy` the label is used.
|
||||||
//std::string insertText;
|
std::string insertText;
|
||||||
|
|
||||||
// The format of the insert text. The format applies to both the `insertText` property
|
// The format of the insert text. The format applies to both the `insertText` property
|
||||||
// and the `newText` property of a provided `textEdit`.
|
// and the `newText` property of a provided `textEdit`.
|
||||||
//lsInsertTextFormat insertTextFormat;
|
lsInsertTextFormat insertTextFormat = lsInsertTextFormat::Snippet;
|
||||||
|
|
||||||
// An edit which is applied to a document when selecting this completion. When an edit is provided the value of
|
// An edit which is applied to a document when selecting this completion. When an edit is provided the value of
|
||||||
// `insertText` is ignored.
|
// `insertText` is ignored.
|
||||||
@ -479,7 +484,9 @@ MAKE_REFLECT_STRUCT(lsCompletionItem,
|
|||||||
kind,
|
kind,
|
||||||
detail,
|
detail,
|
||||||
documentation,
|
documentation,
|
||||||
sortText);
|
sortText,
|
||||||
|
insertText,
|
||||||
|
insertTextFormat);
|
||||||
|
|
||||||
|
|
||||||
struct lsTextDocumentItem {
|
struct lsTextDocumentItem {
|
||||||
@ -937,7 +944,7 @@ MAKE_REFLECT_STRUCT(lsCompletionOptions, resolveProvider, triggerCharacters);
|
|||||||
// Signature help options.
|
// Signature help options.
|
||||||
struct lsSignatureHelpOptions {
|
struct lsSignatureHelpOptions {
|
||||||
// The characters that trigger signature help automatically.
|
// The characters that trigger signature help automatically.
|
||||||
NonElidedVector<std::string> triggerCharacters;
|
std::vector<std::string> triggerCharacters;
|
||||||
};
|
};
|
||||||
MAKE_REFLECT_STRUCT(lsSignatureHelpOptions, triggerCharacters);
|
MAKE_REFLECT_STRUCT(lsSignatureHelpOptions, triggerCharacters);
|
||||||
|
|
||||||
@ -1273,6 +1280,74 @@ struct Out_TextDocumentComplete : public lsOutMessage<Out_TextDocumentComplete>
|
|||||||
};
|
};
|
||||||
MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result);
|
MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result);
|
||||||
|
|
||||||
|
// Signature help.
|
||||||
|
struct Ipc_TextDocumentSignatureHelp : public IpcMessage<Ipc_TextDocumentSignatureHelp> {
|
||||||
|
const static IpcId kIpcId = IpcId::TextDocumentSignatureHelp;
|
||||||
|
|
||||||
|
lsRequestId id;
|
||||||
|
lsTextDocumentPositionParams params;
|
||||||
|
};
|
||||||
|
MAKE_REFLECT_STRUCT(Ipc_TextDocumentSignatureHelp, id, params);
|
||||||
|
// Represents a parameter of a callable-signature. A parameter can
|
||||||
|
// have a label and a doc-comment.
|
||||||
|
struct lsParameterInformation {
|
||||||
|
// 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.
|
||||||
|
optional<std::string> documentation;
|
||||||
|
};
|
||||||
|
MAKE_REFLECT_STRUCT(lsParameterInformation, label, documentation);
|
||||||
|
// 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 lsSignatureInformation {
|
||||||
|
// 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.
|
||||||
|
optional<std::string> documentation;
|
||||||
|
|
||||||
|
// The parameters of this signature.
|
||||||
|
std::vector<lsParameterInformation> parameters;
|
||||||
|
};
|
||||||
|
MAKE_REFLECT_STRUCT(lsSignatureInformation, label, documentation, parameters);
|
||||||
|
// Signature help represents the signature of something
|
||||||
|
// callable. There can be multiple signature but only one
|
||||||
|
// active and only one active parameter.
|
||||||
|
struct lsSignatureHelp {
|
||||||
|
// One or more signatures.
|
||||||
|
NonElidedVector<lsSignatureInformation> 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.
|
||||||
|
optional<int> activeSignature;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
optional<int> activeParameter;
|
||||||
|
};
|
||||||
|
MAKE_REFLECT_STRUCT(lsSignatureHelp, signatures, activeSignature, activeParameter);
|
||||||
|
struct Out_TextDocumentSignatureHelp : public lsOutMessage<Out_TextDocumentSignatureHelp> {
|
||||||
|
lsRequestId id;
|
||||||
|
lsSignatureHelp result;
|
||||||
|
};
|
||||||
|
MAKE_REFLECT_STRUCT(Out_TextDocumentSignatureHelp, jsonrpc, id, result);
|
||||||
|
|
||||||
// Goto definition
|
// Goto definition
|
||||||
struct Ipc_TextDocumentDefinition : public IpcMessage<Ipc_TextDocumentDefinition> {
|
struct Ipc_TextDocumentDefinition : public IpcMessage<Ipc_TextDocumentDefinition> {
|
||||||
const static IpcId kIpcId = IpcId::TextDocumentDefinition;
|
const static IpcId kIpcId = IpcId::TextDocumentDefinition;
|
||||||
|
@ -2,10 +2,32 @@
|
|||||||
|
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
|
|
||||||
|
#include <doctest/doctest.h>
|
||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
lsPosition GetPositionForOffset(const std::string& content, int offset) {
|
||||||
|
if (offset >= content.size())
|
||||||
|
offset = content.size() - 1;
|
||||||
|
|
||||||
|
lsPosition result;
|
||||||
|
int i = 0;
|
||||||
|
while (i < offset) {
|
||||||
|
if (content[i] == '\n') {
|
||||||
|
result.line += 1;
|
||||||
|
result.character = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.character += 1;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
int GetOffsetForPosition(lsPosition position, const std::string& content) {
|
int GetOffsetForPosition(lsPosition position, const std::string& content) {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
@ -142,6 +164,50 @@ optional<int> WorkingFile::GetIndexLineFromBufferLine(int buffer_line) const {
|
|||||||
return closest_index_line;
|
return closest_index_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string WorkingFile::FindClosestCallNameInBuffer(lsPosition position, int* active_parameter, lsPosition* completion_position) const {
|
||||||
|
*active_parameter = 0;
|
||||||
|
|
||||||
|
int offset = GetOffsetForPosition(position, buffer_content);
|
||||||
|
|
||||||
|
// If vscode auto-inserts closing ')' we will begin on ')' token in foo()
|
||||||
|
// which will make the below algorithm think it's a nested call.
|
||||||
|
if (offset > 0 && buffer_content[offset] == ')')
|
||||||
|
--offset;
|
||||||
|
|
||||||
|
// Scan back out of call context.
|
||||||
|
int balance = 0;
|
||||||
|
while (offset > 0) {
|
||||||
|
char c = buffer_content[offset];
|
||||||
|
if (c == ')') ++balance;
|
||||||
|
else if (c == '(') --balance;
|
||||||
|
|
||||||
|
if (balance == 0 && c == ',')
|
||||||
|
*active_parameter += 1;
|
||||||
|
|
||||||
|
--offset;
|
||||||
|
|
||||||
|
if (balance == -1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// Scan back entire identifier.
|
||||||
|
int start_offset = offset;
|
||||||
|
while (offset > 0) {
|
||||||
|
char c = buffer_content[offset - 1];
|
||||||
|
if (isalnum(c) == false && c != '_')
|
||||||
|
break;
|
||||||
|
--offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completion_position)
|
||||||
|
*completion_position = GetPositionForOffset(buffer_content, offset);
|
||||||
|
|
||||||
|
return buffer_content.substr(offset, start_offset - offset + 1);
|
||||||
|
}
|
||||||
|
|
||||||
CXUnsavedFile WorkingFile::AsUnsavedFile() const {
|
CXUnsavedFile WorkingFile::AsUnsavedFile() const {
|
||||||
CXUnsavedFile result;
|
CXUnsavedFile result;
|
||||||
result.Filename = filename.c_str();
|
result.Filename = filename.c_str();
|
||||||
@ -237,3 +303,73 @@ std::vector<CXUnsavedFile> WorkingFiles::AsUnsavedFiles() {
|
|||||||
result.push_back(file->AsUnsavedFile());
|
result.push_back(file->AsUnsavedFile());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_SUITE("WorkingFile");
|
||||||
|
|
||||||
|
lsPosition CharPos(const WorkingFile& file, char character, int character_offset = 0) {
|
||||||
|
const std::string& search = file.buffer_content;
|
||||||
|
|
||||||
|
lsPosition result;
|
||||||
|
int index = 0;
|
||||||
|
while (index < search.size()) {
|
||||||
|
char c = search[index];
|
||||||
|
if (c == character)
|
||||||
|
break;
|
||||||
|
if (c == '\n') {
|
||||||
|
result.line += 1;
|
||||||
|
result.character = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.character += 1;
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
REQUIRE(index < search.size());
|
||||||
|
result.character += character_offset;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("simple call") {
|
||||||
|
WorkingFile f("foo.cc", "abcd(1, 2");
|
||||||
|
int active_param = 0;
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '1'), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ','), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 1);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ' '), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 1);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '2'), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("nested call") {
|
||||||
|
WorkingFile f("foo.cc", "abcd(efg(), 2");
|
||||||
|
int active_param = 0;
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'e'), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'f'), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g'), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g', 1), &active_param) == "efg");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g', 2), &active_param) == "efg");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ','), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 1);
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ' '), &active_param) == "abcd");
|
||||||
|
REQUIRE(active_param == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("auto-insert )") {
|
||||||
|
WorkingFile f("foo.cc", "abc()");
|
||||||
|
int active_param = 0;
|
||||||
|
REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ')'), &active_param) == "abc");
|
||||||
|
REQUIRE(active_param == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
@ -49,6 +49,13 @@ struct WorkingFile {
|
|||||||
// accepts and returns 1-based lines.
|
// accepts and returns 1-based lines.
|
||||||
optional<int> GetIndexLineFromBufferLine(int buffer_line) const;
|
optional<int> GetIndexLineFromBufferLine(int buffer_line) const;
|
||||||
|
|
||||||
|
// Finds the closest 'callable' name prior to position. This is used for
|
||||||
|
// signature help to filter code completion results.
|
||||||
|
//
|
||||||
|
// |completion_position| will be point to a good code completion location to
|
||||||
|
// for fetching signatures.
|
||||||
|
std::string FindClosestCallNameInBuffer(lsPosition position, int* active_parameter, lsPosition* completion_position = nullptr) const;
|
||||||
|
|
||||||
CXUnsavedFile AsUnsavedFile() const;
|
CXUnsavedFile AsUnsavedFile() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user