Use Sema/CodeCompleteConsumer

This commit is contained in:
Fangrui Song 2018-07-14 10:00:04 -07:00
parent d3536831c3
commit a4dd5d0c44
2 changed files with 158 additions and 227 deletions

View File

@ -5,8 +5,10 @@
#include "log.hh"
#include "platform.h"
#include <clang/Sema/CodeCompleteConsumer.h>
#include <llvm/ADT/Twine.h>
#include <llvm/Support/Threading.h>
using namespace clang;
using namespace llvm;
#include <algorithm>
@ -30,23 +32,16 @@ std::string StripFileType(const std::string& path) {
return Ret.str();
}
unsigned GetCompletionPriority(const CXCompletionString& str,
unsigned GetCompletionPriority(const CodeCompletionString& CCS,
CXCursorKind result_kind,
const std::optional<std::string>& typedText) {
unsigned priority = clang_getCompletionPriority(str);
// XXX: What happens if priority overflows?
if (result_kind == CXCursor_Destructor) {
priority *= 100;
}
if (result_kind == CXCursor_ConversionFunction ||
unsigned priority = CCS.getPriority();
if (CCS.getAvailability() != CXAvailability_Available ||
result_kind == CXCursor_Destructor ||
result_kind == CXCursor_ConversionFunction ||
(result_kind == CXCursor_CXXMethod && typedText &&
StartsWith(*typedText, "operator"))) {
StartsWith(*typedText, "operator")))
priority *= 100;
}
if (clang_getCompletionAvailability(str) != CXAvailability_Available) {
priority *= 100;
}
return priority;
}
@ -142,85 +137,76 @@ lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) {
}
}
void BuildCompletionItemTexts(std::vector<lsCompletionItem>& out,
CXCompletionString completion_string,
void BuildCompletionItemTexts(std::vector<lsCompletionItem> &out,
CodeCompletionString &CCS,
bool include_snippets) {
assert(!out.empty());
auto out_first = out.size() - 1;
std::string result_type;
int num_chunks = clang_getNumCompletionChunks(completion_string);
for (int i = 0; i < num_chunks; ++i) {
CXCompletionChunkKind kind =
clang_getCompletionChunkKind(completion_string, i);
for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) {
const CodeCompletionString::Chunk &Chunk = CCS[i];
CodeCompletionString::ChunkKind Kind = Chunk.Kind;
std::string text;
switch (kind) {
// clang-format off
case CXCompletionChunk_LeftParen: text = '('; break;
case CXCompletionChunk_RightParen: text = ')'; break;
case CXCompletionChunk_LeftBracket: text = '['; break;
case CXCompletionChunk_RightBracket: text = ']'; break;
case CXCompletionChunk_LeftBrace: text = '{'; break;
case CXCompletionChunk_RightBrace: text = '}'; break;
case CXCompletionChunk_LeftAngle: text = '<'; break;
case CXCompletionChunk_RightAngle: text = '>'; break;
case CXCompletionChunk_Comma: text = ", "; break;
case CXCompletionChunk_Colon: text = ':'; break;
case CXCompletionChunk_SemiColon: text = ';'; break;
case CXCompletionChunk_Equal: text = '='; break;
case CXCompletionChunk_HorizontalSpace: text = ' '; break;
case CXCompletionChunk_VerticalSpace: text = ' '; break;
// clang-format on
case CXCompletionChunk_ResultType:
result_type =
ToString(clang_getCompletionChunkText(completion_string, i));
continue;
case CXCompletionChunk_TypedText:
case CXCompletionChunk_Placeholder:
case CXCompletionChunk_Text:
case CXCompletionChunk_Informative:
text = ToString(clang_getCompletionChunkText(completion_string, i));
for (auto i = out_first; i < out.size(); ++i) {
// first typed text is used for filtering
if (kind == CXCompletionChunk_TypedText && !out[i].filterText)
switch (Kind) {
case CodeCompletionString::CK_TypedText:
case CodeCompletionString::CK_Text:
case CodeCompletionString::CK_Placeholder:
case CodeCompletionString::CK_Informative:
if (Chunk.Text)
text = Chunk.Text;
for (auto i = out_first; i < out.size(); i++) {
// first TypedText is used for filtering
if (Kind == CodeCompletionString::CK_TypedText && !out[i].filterText)
out[i].filterText = text;
if (kind == CXCompletionChunk_Placeholder)
if (Kind == CodeCompletionString::CK_Placeholder)
out[i].parameters_.push_back(text);
}
break;
case CXCompletionChunk_CurrentParameter:
case CodeCompletionString::CK_ResultType:
if (Chunk.Text)
result_type = Chunk.Text;
continue;
case CodeCompletionString::CK_CurrentParameter:
// We have our own parsing logic for active parameter. This doesn't seem
// to be very reliable.
continue;
case CXCompletionChunk_Optional: {
CXCompletionString nested =
clang_getCompletionChunkCompletionString(completion_string, i);
case CodeCompletionString::CK_Optional: {
// duplicate last element, the recursive call will complete it
out.push_back(out.back());
BuildCompletionItemTexts(out, nested, include_snippets);
BuildCompletionItemTexts(out, *Chunk.Optional, include_snippets);
continue;
}
// clang-format off
case CodeCompletionString::CK_LeftParen: text = '('; break;
case CodeCompletionString::CK_RightParen: text = ')'; break;
case CodeCompletionString::CK_LeftBracket: text = '['; break;
case CodeCompletionString::CK_RightBracket: text = ']'; break;
case CodeCompletionString::CK_LeftBrace: text = '{'; break;
case CodeCompletionString::CK_RightBrace: text = '}'; break;
case CodeCompletionString::CK_LeftAngle: text = '<'; break;
case CodeCompletionString::CK_RightAngle: text = '>'; break;
case CodeCompletionString::CK_Comma: text = ", "; break;
case CodeCompletionString::CK_Colon: text = ':'; break;
case CodeCompletionString::CK_SemiColon: text = ';'; break;
case CodeCompletionString::CK_Equal: text = '='; break;
case CodeCompletionString::CK_HorizontalSpace: text = ' '; break;
case CodeCompletionString::CK_VerticalSpace: text = ' '; break;
// clang-format on
}
for (auto i = out_first; i < out.size(); ++i)
out[i].label += text;
if (kind == CXCompletionChunk_Informative)
if (Kind == CodeCompletionString::CK_Informative)
continue;
for (auto i = out_first; i < out.size(); ++i) {
if (!include_snippets && !out[i].parameters_.empty())
continue;
if (kind == CXCompletionChunk_Placeholder) {
if (Kind == CodeCompletionString::CK_Placeholder) {
out[i].insertText +=
"${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}";
out[i].insertTextFormat = lsInsertTextFormat::Snippet;
@ -243,106 +229,71 @@ void BuildCompletionItemTexts(std::vector<lsCompletionItem>& out,
// |do_insert|: if |!do_insert|, do not append strings to |insert| after
// a placeholder.
void BuildDetailString(CXCompletionString completion_string,
std::string& label,
std::string& detail,
std::string& insert,
bool& do_insert,
lsInsertTextFormat& format,
std::vector<std::string>* parameters,
bool include_snippets,
int& angle_stack) {
int num_chunks = clang_getNumCompletionChunks(completion_string);
auto append = [&](const char* text) {
detail += text;
if (do_insert && include_snippets)
insert += text;
};
for (int i = 0; i < num_chunks; ++i) {
CXCompletionChunkKind kind =
clang_getCompletionChunkKind(completion_string, i);
switch (kind) {
case CXCompletionChunk_Optional: {
CXCompletionString nested =
clang_getCompletionChunkCompletionString(completion_string, i);
// Do not add text to insert string if we're in angle brackets.
bool should_insert = do_insert && angle_stack == 0;
BuildDetailString(nested, label, detail, insert,
should_insert, format, parameters,
include_snippets, angle_stack);
void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item,
bool &do_insert, std::vector<std::string> *parameters,
bool include_snippets, int &angle_stack) {
for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) {
const CodeCompletionString::Chunk &Chunk = CCS[i];
CodeCompletionString::ChunkKind Kind = Chunk.Kind;
const char* text = nullptr;
switch (Kind) {
case CodeCompletionString::CK_TypedText:
item.label = Chunk.Text;
[[fallthrough]];
case CodeCompletionString::CK_Text:
item.detail += Chunk.Text;
if (do_insert)
item.insertText += Chunk.Text;
break;
}
case CXCompletionChunk_Placeholder: {
std::string text =
ToString(clang_getCompletionChunkText(completion_string, i));
parameters->push_back(text);
detail += text;
case CodeCompletionString::CK_Placeholder: {
parameters->push_back(Chunk.Text);
item.detail += Chunk.Text;
// Add parameter declarations as snippets if enabled
if (include_snippets) {
insert +=
"${" + std::to_string(parameters->size()) + ":" + text + "}";
format = lsInsertTextFormat::Snippet;
item.insertText += "${" + std::to_string(parameters->size()) + ":" + Chunk.Text + "}";
item.insertTextFormat = lsInsertTextFormat::Snippet;
} else
do_insert = false;
break;
}
case CXCompletionChunk_CurrentParameter:
case CodeCompletionString::CK_Informative:
item.detail += Chunk.Text;
break;
case CodeCompletionString::CK_Optional: {
// Do not add text to insert string if we're in angle brackets.
bool should_insert = do_insert && angle_stack == 0;
BuildDetailString(*Chunk.Optional, item, should_insert,
parameters, include_snippets, angle_stack);
break;
}
case CodeCompletionString::CK_ResultType:
item.detail = Chunk.Text + item.detail + " ";
break;
case CodeCompletionString::CK_CurrentParameter:
// We have our own parsing logic for active parameter. This doesn't seem
// to be very reliable.
break;
case CXCompletionChunk_TypedText: {
std::string text =
ToString(clang_getCompletionChunkText(completion_string, i));
label = text;
detail += text;
if (do_insert)
insert += text;
break;
}
case CXCompletionChunk_Text: {
std::string text =
ToString(clang_getCompletionChunkText(completion_string, i));
detail += text;
if (do_insert)
insert += text;
break;
}
case CXCompletionChunk_Informative: {
detail += ToString(clang_getCompletionChunkText(completion_string, i));
break;
}
case CXCompletionChunk_ResultType: {
CXString text = clang_getCompletionChunkText(completion_string, i);
std::string new_detail = ToString(text) + detail + " ";
detail = new_detail;
break;
}
// clang-format off
case CXCompletionChunk_LeftParen: append("("); break;
case CXCompletionChunk_RightParen: append(")"); break;
case CXCompletionChunk_LeftBracket: append("["); break;
case CXCompletionChunk_RightBracket: append("]"); break;
case CXCompletionChunk_LeftBrace: append("{"); break;
case CXCompletionChunk_RightBrace: append("}"); break;
case CXCompletionChunk_LeftAngle: append("<"); angle_stack++; break;
case CXCompletionChunk_RightAngle: append(">"); angle_stack--; break;
case CXCompletionChunk_Comma: append(", "); break;
case CXCompletionChunk_Colon: append(":"); break;
case CXCompletionChunk_SemiColon: append(";"); break;
case CXCompletionChunk_Equal: append("="); break;
case CodeCompletionString::CK_LeftParen: text = "("; break;
case CodeCompletionString::CK_RightParen: text = ")"; break;
case CodeCompletionString::CK_LeftBracket: text = "["; break;
case CodeCompletionString::CK_RightBracket: text = "]"; break;
case CodeCompletionString::CK_LeftBrace: text = "{"; break;
case CodeCompletionString::CK_RightBrace: text = "}"; break;
case CodeCompletionString::CK_LeftAngle: text = "<"; angle_stack++; break;
case CodeCompletionString::CK_RightAngle: text = ">"; angle_stack--; break;
case CodeCompletionString::CK_Comma: text = ", "; break;
case CodeCompletionString::CK_Colon: text = ":"; break;
case CodeCompletionString::CK_SemiColon: text = ";"; break;
case CodeCompletionString::CK_Equal: text = "="; break;
case CodeCompletionString::CK_HorizontalSpace:
case CodeCompletionString::CK_VerticalSpace: text = " "; break;
// clang-format on
case CXCompletionChunk_HorizontalSpace:
case CXCompletionChunk_VerticalSpace:
append(" ");
break;
}
if (text) {
item.detail += text;
if (do_insert && include_snippets)
item.insertText += text;
}
}
}
@ -483,35 +434,24 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
for (unsigned i = 0; i < cx_results->NumResults; ++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)
auto CCS = (CodeCompletionString *)result.CompletionString;
if (CCS->getAvailability() == CXAvailability_NotAvailable)
continue;
// TODO: fill in more data
lsCompletionItem ls_completion_item;
ls_completion_item.kind = GetCompletionKind(result.CursorKind);
ls_completion_item.documentation =
ToString(clang_getCompletionBriefComment(result.CompletionString));
lsCompletionItem ls_item;
ls_item.kind = GetCompletionKind(result.CursorKind);
if (const char* brief = CCS->getBriefComment())
ls_item.documentation = brief;
// label/detail/filterText/insertText/priority
if (g_config->completion.detailedLabel) {
ls_completion_item.detail = ToString(
clang_getCompletionParent(result.CompletionString, nullptr));
ls_item.detail = CCS->getParentContextName().str();
auto first_idx = ls_result.size();
ls_result.push_back(ls_completion_item);
ls_result.push_back(ls_item);
// label/filterText/insertText
BuildCompletionItemTexts(ls_result, result.CompletionString,
BuildCompletionItemTexts(ls_result, *CCS,
g_config->client.snippetSupport);
for (auto i = first_idx; i < ls_result.size(); ++i) {
@ -520,28 +460,21 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
ls_result[i].insertText += "$0";
}
ls_result[i].priority_ =
GetCompletionPriority(result.CompletionString, result.CursorKind,
ls_result[i].filterText);
ls_result[i].priority_ = GetCompletionPriority(
*CCS, result.CursorKind, ls_result[i].filterText);
}
} else {
bool do_insert = true;
int angle_stack = 0;
BuildDetailString(result.CompletionString, ls_completion_item.label,
ls_completion_item.detail,
ls_completion_item.insertText, do_insert,
ls_completion_item.insertTextFormat,
&ls_completion_item.parameters_,
BuildDetailString(*CCS, ls_item, do_insert,
&ls_item.parameters_,
g_config->client.snippetSupport, angle_stack);
if (g_config->client.snippetSupport &&
ls_completion_item.insertTextFormat ==
lsInsertTextFormat::Snippet) {
ls_completion_item.insertText += "$0";
}
ls_completion_item.priority_ =
GetCompletionPriority(result.CompletionString, result.CursorKind,
ls_completion_item.label);
ls_result.push_back(ls_completion_item);
ls_item.insertTextFormat == lsInsertTextFormat::Snippet)
ls_item.insertText += "$0";
ls_item.priority_ =
GetCompletionPriority(*CCS, result.CursorKind, ls_item.label);
ls_result.push_back(ls_item);
}
}

View File

@ -19,10 +19,11 @@ enum class lsCompletionTriggerKind {
// Completion was triggered by typing an identifier (24x7 code
// complete), manual invocation (e.g Ctrl+Space) or via API.
Invoked = 1,
// Completion was triggered by a trigger character specified by
// the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
TriggerCharacter = 2
TriggerCharacter = 2,
// Completion was re-triggered as the current completion list is incomplete.
TriggerForIncompleteCompletions = 3,
};
MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind);
@ -30,7 +31,7 @@ MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind);
// request is triggered.
struct lsCompletionContext {
// How the completion was triggered.
lsCompletionTriggerKind triggerKind;
lsCompletionTriggerKind triggerKind = lsCompletionTriggerKind::Invoked;
// The trigger character (a single character) that has trigger code complete.
// Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
@ -42,7 +43,7 @@ struct lsCompletionParams : lsTextDocumentPositionParams {
// The completion context. This is only available it the client specifies to
// send this using
// `ClientCapabilities.textDocument.completion.contextSupport === true`
std::optional<lsCompletionContext> context;
lsCompletionContext context;
};
MAKE_REFLECT_STRUCT(lsCompletionParams, textDocument, position, context);
@ -254,6 +255,7 @@ struct Handler_TextDocumentCompletion : MessageHandler {
void Run(std::unique_ptr<InMessage> message) override {
auto request = std::shared_ptr<In_TextDocumentComplete>(
static_cast<In_TextDocumentComplete*>(message.release()));
auto& params = request->params;
auto write_empty_result = [request]() {
Out_TextDocumentComplete out;
@ -261,7 +263,7 @@ struct Handler_TextDocumentCompletion : MessageHandler {
pipeline::WriteStdout(kMethodType, out);
};
std::string path = request->params.textDocument.uri.GetPath();
std::string path = params.textDocument.uri.GetPath();
WorkingFile* file = working_files->GetFileByFilename(path);
if (!file) {
write_empty_result();
@ -271,21 +273,19 @@ struct Handler_TextDocumentCompletion : MessageHandler {
// It shouldn't be possible, but sometimes vscode will send queries out
// of order, ie, we get completion request before buffer content update.
std::string buffer_line;
if (request->params.position.line >= 0 &&
request->params.position.line < file->buffer_lines.size()) {
buffer_line = file->buffer_lines[request->params.position.line];
}
if (params.position.line >= 0 &&
params.position.line < file->buffer_lines.size())
buffer_line = file->buffer_lines[params.position.line];
// Check for - and : before completing -> or ::, since vscode does not
// support multi-character trigger characters.
if (request->params.context &&
request->params.context->triggerKind ==
if (params.context.triggerKind ==
lsCompletionTriggerKind::TriggerCharacter &&
request->params.context->triggerCharacter) {
params.context.triggerCharacter) {
bool did_fail_check = false;
std::string character = *request->params.context->triggerCharacter;
int preceding_index = request->params.position.character - 2;
std::string character = *params.context.triggerCharacter;
int preceding_index = params.position.character - 2;
// If the character is '"', '<' or '/', make sure that the line starts
// with '#'.
@ -318,11 +318,11 @@ struct Handler_TextDocumentCompletion : MessageHandler {
bool is_global_completion = false;
std::string existing_completion;
lsPosition end_pos = request->params.position;
lsPosition end_pos = params.position;
if (file) {
request->params.position = file->FindStableCompletionSource(
request->params.position, &is_global_completion,
&existing_completion, &end_pos);
params.position = file->FindStableCompletionSource(
request->params.position, &is_global_completion, &existing_completion,
&end_pos);
}
ParseIncludeLineResult result = ParseIncludeLine(buffer_line);
@ -359,16 +359,16 @@ struct Handler_TextDocumentCompletion : MessageHandler {
}
for (lsCompletionItem& item : out.result.items) {
item.textEdit->range.start.line = request->params.position.line;
item.textEdit->range.start.line = params.position.line;
item.textEdit->range.start.character = 0;
item.textEdit->range.end.line = request->params.position.line;
item.textEdit->range.end.line = params.position.line;
item.textEdit->range.end.character = (int)buffer_line.size();
}
pipeline::WriteStdout(kMethodType, out);
} else {
ClangCompleteManager::OnComplete callback = std::bind(
[this, request, is_global_completion, existing_completion,
[this, request, params, is_global_completion, existing_completion,
has_open_paren](const std::vector<lsCompletionItem>& results,
bool is_cached_result) {
Out_TextDocumentComplete out;
@ -381,7 +381,7 @@ struct Handler_TextDocumentCompletion : MessageHandler {
// Cache completion results.
if (!is_cached_result) {
std::string path = request->params.textDocument.uri.GetPath();
std::string path = params.textDocument.uri.GetPath();
if (is_global_completion) {
global_code_complete_cache->WithLock([&]() {
global_code_complete_cache->cached_path_ = path;
@ -391,7 +391,7 @@ struct Handler_TextDocumentCompletion : MessageHandler {
non_global_code_complete_cache->WithLock([&]() {
non_global_code_complete_cache->cached_path_ = path;
non_global_code_complete_cache->cached_completion_position_ =
request->params.position;
params.position;
non_global_code_complete_cache->cached_results_ = results;
});
}
@ -421,16 +421,14 @@ struct Handler_TextDocumentCompletion : MessageHandler {
callback(global_code_complete_cache->cached_results_,
true /*is_cached_result*/);
});
clang_complete->CodeComplete(request->id, request->params,
freshen_global);
} else if (non_global_code_complete_cache->IsCacheValid(
request->params)) {
clang_complete->CodeComplete(request->id, params, freshen_global);
} else if (non_global_code_complete_cache->IsCacheValid(params)) {
non_global_code_complete_cache->WithLock([&]() {
callback(non_global_code_complete_cache->cached_results_,
true /*is_cached_result*/);
});
} else {
clang_complete->CodeComplete(request->id, request->params, callback);
clang_complete->CodeComplete(request->id, params, callback);
}
}
}