mirror of
https://github.com/MaskRay/ccls.git
synced 2025-03-24 17:47:00 +00:00
649 lines
23 KiB
C++
649 lines
23 KiB
C++
/* Copyright 2017-2018 ccls Authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
==============================================================================*/
|
|
|
|
#include "clang_complete.hh"
|
|
#include "fuzzy_match.h"
|
|
#include "include_complete.h"
|
|
#include "log.hh"
|
|
#include "message_handler.h"
|
|
#include "pipeline.hh"
|
|
#include "working_files.h"
|
|
using namespace ccls;
|
|
|
|
#include <clang/Sema/CodeCompleteConsumer.h>
|
|
#include <clang/Sema/Sema.h>
|
|
#include <llvm/Support/Timer.h>
|
|
using namespace clang;
|
|
using namespace llvm;
|
|
|
|
#include <regex>
|
|
|
|
namespace {
|
|
MethodType kMethodType = "textDocument/completion";
|
|
|
|
// How a completion was triggered
|
|
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,
|
|
// Completion was re-triggered as the current completion list is incomplete.
|
|
TriggerForIncompleteCompletions = 3,
|
|
};
|
|
MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind);
|
|
|
|
// Contains additional information about the context in which a completion
|
|
// request is triggered.
|
|
struct lsCompletionContext {
|
|
// How the completion was triggered.
|
|
lsCompletionTriggerKind triggerKind = lsCompletionTriggerKind::Invoked;
|
|
|
|
// The trigger character (a single character) that has trigger code complete.
|
|
// Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
|
|
std::optional<std::string> triggerCharacter;
|
|
};
|
|
MAKE_REFLECT_STRUCT(lsCompletionContext, triggerKind, triggerCharacter);
|
|
|
|
struct lsCompletionParams : lsTextDocumentPositionParams {
|
|
// The completion context. This is only available it the client specifies to
|
|
// send this using
|
|
// `ClientCapabilities.textDocument.completion.contextSupport === true`
|
|
lsCompletionContext context;
|
|
};
|
|
MAKE_REFLECT_STRUCT(lsCompletionParams, textDocument, position, context);
|
|
|
|
struct In_TextDocumentComplete : public RequestInMessage {
|
|
MethodType GetMethodType() const override { return kMethodType; }
|
|
lsCompletionParams params;
|
|
};
|
|
MAKE_REFLECT_STRUCT(In_TextDocumentComplete, id, params);
|
|
REGISTER_IN_MESSAGE(In_TextDocumentComplete);
|
|
|
|
struct lsTextDocumentCompleteResult {
|
|
// This list it not complete. Further typing should result in recomputing
|
|
// this list.
|
|
bool isIncomplete = false;
|
|
// The completion items.
|
|
std::vector<lsCompletionItem> items;
|
|
};
|
|
MAKE_REFLECT_STRUCT(lsTextDocumentCompleteResult, isIncomplete, items);
|
|
|
|
struct Out_TextDocumentComplete
|
|
: public lsOutMessage<Out_TextDocumentComplete> {
|
|
lsRequestId id;
|
|
lsTextDocumentCompleteResult result;
|
|
};
|
|
MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result);
|
|
|
|
void DecorateIncludePaths(const std::smatch &match,
|
|
std::vector<lsCompletionItem> *items) {
|
|
std::string spaces_after_include = " ";
|
|
if (match[3].compare("include") == 0 && match[5].length())
|
|
spaces_after_include = match[4].str();
|
|
|
|
std::string prefix =
|
|
match[1].str() + '#' + match[2].str() + "include" + spaces_after_include;
|
|
std::string suffix = match[7].str();
|
|
|
|
for (lsCompletionItem &item : *items) {
|
|
char quote0, quote1;
|
|
if (match[5].compare("<") == 0 ||
|
|
(match[5].length() == 0 && item.use_angle_brackets_))
|
|
quote0 = '<', quote1 = '>';
|
|
else
|
|
quote0 = quote1 = '"';
|
|
|
|
item.textEdit->newText =
|
|
prefix + quote0 + item.textEdit->newText + quote1 + suffix;
|
|
item.label = prefix + quote0 + item.label + quote1 + suffix;
|
|
item.filterText = std::nullopt;
|
|
}
|
|
}
|
|
|
|
struct ParseIncludeLineResult {
|
|
bool ok;
|
|
std::string keyword;
|
|
std::string quote;
|
|
std::string pattern;
|
|
std::smatch match;
|
|
};
|
|
|
|
ParseIncludeLineResult ParseIncludeLine(const std::string &line) {
|
|
static const std::regex pattern("(\\s*)" // [1]: spaces before '#'
|
|
"#" //
|
|
"(\\s*)" // [2]: spaces after '#'
|
|
"([^\\s\"<]*)" // [3]: "include"
|
|
"(\\s*)" // [4]: spaces before quote
|
|
"([\"<])?" // [5]: the first quote char
|
|
"([^\\s\">]*)" // [6]: path of file
|
|
"[\">]?" //
|
|
"(.*)"); // [7]: suffix after quote char
|
|
std::smatch match;
|
|
bool ok = std::regex_match(line, match, pattern);
|
|
return {ok, match[3], match[5], match[6], match};
|
|
}
|
|
|
|
static const std::vector<std::string> preprocessorKeywords = {
|
|
"define", "undef", "include", "if", "ifdef", "ifndef",
|
|
"else", "elif", "endif", "line", "error", "pragma"};
|
|
|
|
std::vector<lsCompletionItem>
|
|
PreprocessorKeywordCompletionItems(const std::smatch &match) {
|
|
std::vector<lsCompletionItem> items;
|
|
for (auto &keyword : preprocessorKeywords) {
|
|
lsCompletionItem item;
|
|
item.label = keyword;
|
|
item.priority_ = (keyword == "include" ? 2 : 1);
|
|
item.textEdit = lsTextEdit();
|
|
std::string space = (keyword == "else" || keyword == "endif") ? "" : " ";
|
|
item.textEdit->newText = match[1].str() + "#" + match[2].str() + keyword +
|
|
space + match[6].str();
|
|
item.insertTextFormat = lsInsertTextFormat::PlainText;
|
|
items.push_back(item);
|
|
}
|
|
return items;
|
|
}
|
|
|
|
template <typename T> char *tofixedbase64(T input, char *out) {
|
|
const char *digits = "./0123456789"
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz";
|
|
int len = (sizeof(T) * 8 - 1) / 6 + 1;
|
|
for (int i = len - 1; i >= 0; i--) {
|
|
out[i] = digits[input % 64];
|
|
input /= 64;
|
|
}
|
|
out[len] = '\0';
|
|
return out;
|
|
}
|
|
|
|
// Pre-filters completion responses before sending to vscode. This results in a
|
|
// significantly snappier completion experience as vscode is easily overloaded
|
|
// when given 1000+ completion items.
|
|
void FilterAndSortCompletionResponse(
|
|
Out_TextDocumentComplete *complete_response,
|
|
const std::string &complete_text, bool has_open_paren) {
|
|
if (!g_config->completion.filterAndSort)
|
|
return;
|
|
|
|
static Timer timer("FilterAndSortCompletionResponse", "");
|
|
TimeRegion region(timer);
|
|
|
|
auto &items = complete_response->result.items;
|
|
|
|
auto finalize = [&]() {
|
|
const size_t kMaxResultSize = 100u;
|
|
if (items.size() > kMaxResultSize) {
|
|
items.resize(kMaxResultSize);
|
|
complete_response->result.isIncomplete = true;
|
|
}
|
|
|
|
if (has_open_paren)
|
|
for (auto &item : items)
|
|
item.insertText = item.label;
|
|
|
|
// Set sortText. Note that this happens after resizing - we could do it
|
|
// before, but then we should also sort by priority.
|
|
char buf[16];
|
|
for (size_t i = 0; i < items.size(); ++i)
|
|
items[i].sortText = tofixedbase64(i, buf);
|
|
};
|
|
|
|
// No complete text; don't run any filtering logic except to trim the items.
|
|
if (complete_text.empty()) {
|
|
finalize();
|
|
return;
|
|
}
|
|
|
|
// Make sure all items have |filterText| set, code that follow needs it.
|
|
for (auto &item : items) {
|
|
if (!item.filterText)
|
|
item.filterText = item.label;
|
|
}
|
|
|
|
// Fuzzy match and remove awful candidates.
|
|
bool sensitive = g_config->completion.caseSensitivity;
|
|
FuzzyMatcher fuzzy(complete_text, sensitive);
|
|
for (auto &item : items) {
|
|
item.score_ =
|
|
ReverseSubseqMatch(complete_text, *item.filterText, sensitive) >= 0
|
|
? fuzzy.Match(*item.filterText)
|
|
: FuzzyMatcher::kMinScore;
|
|
}
|
|
items.erase(std::remove_if(items.begin(), items.end(),
|
|
[](const lsCompletionItem &item) {
|
|
return item.score_ <= FuzzyMatcher::kMinScore;
|
|
}),
|
|
items.end());
|
|
std::sort(items.begin(), items.end(),
|
|
[](const lsCompletionItem &lhs, const lsCompletionItem &rhs) {
|
|
if (lhs.score_ != rhs.score_)
|
|
return lhs.score_ > rhs.score_;
|
|
if (lhs.priority_ != rhs.priority_)
|
|
return lhs.priority_ < rhs.priority_;
|
|
if (lhs.filterText->size() != rhs.filterText->size())
|
|
return lhs.filterText->size() < rhs.filterText->size();
|
|
return *lhs.filterText < *rhs.filterText;
|
|
});
|
|
|
|
// Trim result.
|
|
finalize();
|
|
}
|
|
|
|
// Returns true if position is an points to a '(' character in |lines|. Skips
|
|
// whitespace.
|
|
bool IsOpenParenOrAngle(const std::vector<std::string> &lines,
|
|
const lsPosition &position) {
|
|
auto [c, l] = position;
|
|
while (l < lines.size()) {
|
|
const auto &line = lines[l];
|
|
if (c >= line.size())
|
|
return false;
|
|
if (line[c] == '(' || line[c] == '<')
|
|
return true;
|
|
if (!isspace(line[c]))
|
|
break;
|
|
if (++c >= line.size()) {
|
|
c = 0;
|
|
l++;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
unsigned GetCompletionPriority(const CodeCompletionString &CCS,
|
|
CXCursorKind result_kind,
|
|
const std::optional<std::string> &typedText) {
|
|
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")))
|
|
priority *= 100;
|
|
return priority;
|
|
}
|
|
|
|
lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) {
|
|
switch (cursor_kind) {
|
|
case CXCursor_UnexposedDecl:
|
|
return lsCompletionItemKind::Text;
|
|
|
|
case CXCursor_StructDecl:
|
|
case CXCursor_UnionDecl:
|
|
return lsCompletionItemKind::Struct;
|
|
case CXCursor_ClassDecl:
|
|
return lsCompletionItemKind::Class;
|
|
case CXCursor_EnumDecl:
|
|
return lsCompletionItemKind::Enum;
|
|
case CXCursor_FieldDecl:
|
|
return lsCompletionItemKind::Field;
|
|
case CXCursor_EnumConstantDecl:
|
|
return lsCompletionItemKind::EnumMember;
|
|
case CXCursor_FunctionDecl:
|
|
return lsCompletionItemKind::Function;
|
|
case CXCursor_VarDecl:
|
|
case CXCursor_ParmDecl:
|
|
return lsCompletionItemKind::Variable;
|
|
case CXCursor_ObjCInterfaceDecl:
|
|
return lsCompletionItemKind::Interface;
|
|
|
|
case CXCursor_ObjCInstanceMethodDecl:
|
|
case CXCursor_CXXMethod:
|
|
case CXCursor_ObjCClassMethodDecl:
|
|
return lsCompletionItemKind::Method;
|
|
|
|
case CXCursor_FunctionTemplate:
|
|
return lsCompletionItemKind::Function;
|
|
|
|
case CXCursor_Constructor:
|
|
case CXCursor_Destructor:
|
|
case CXCursor_ConversionFunction:
|
|
return lsCompletionItemKind::Constructor;
|
|
|
|
case CXCursor_ObjCIvarDecl:
|
|
return lsCompletionItemKind::Variable;
|
|
|
|
case CXCursor_ClassTemplate:
|
|
case CXCursor_ClassTemplatePartialSpecialization:
|
|
case CXCursor_UsingDeclaration:
|
|
case CXCursor_TypedefDecl:
|
|
case CXCursor_TypeAliasDecl:
|
|
case CXCursor_TypeAliasTemplateDecl:
|
|
case CXCursor_ObjCCategoryDecl:
|
|
case CXCursor_ObjCProtocolDecl:
|
|
case CXCursor_ObjCImplementationDecl:
|
|
case CXCursor_ObjCCategoryImplDecl:
|
|
return lsCompletionItemKind::Class;
|
|
|
|
case CXCursor_ObjCPropertyDecl:
|
|
return lsCompletionItemKind::Property;
|
|
|
|
case CXCursor_MacroInstantiation:
|
|
case CXCursor_MacroDefinition:
|
|
return lsCompletionItemKind::Interface;
|
|
|
|
case CXCursor_Namespace:
|
|
case CXCursor_NamespaceAlias:
|
|
case CXCursor_NamespaceRef:
|
|
return lsCompletionItemKind::Module;
|
|
|
|
case CXCursor_MemberRef:
|
|
case CXCursor_TypeRef:
|
|
case CXCursor_ObjCSuperClassRef:
|
|
case CXCursor_ObjCProtocolRef:
|
|
case CXCursor_ObjCClassRef:
|
|
return lsCompletionItemKind::Reference;
|
|
|
|
// return lsCompletionItemKind::Unit;
|
|
// return lsCompletionItemKind::Value;
|
|
// return lsCompletionItemKind::Keyword;
|
|
// return lsCompletionItemKind::Snippet;
|
|
// return lsCompletionItemKind::Color;
|
|
// return lsCompletionItemKind::File;
|
|
|
|
case CXCursor_NotImplemented:
|
|
case CXCursor_OverloadCandidate:
|
|
return lsCompletionItemKind::Text;
|
|
|
|
case CXCursor_TemplateTypeParameter:
|
|
case CXCursor_TemplateTemplateParameter:
|
|
return lsCompletionItemKind::TypeParameter;
|
|
|
|
default:
|
|
LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind;
|
|
return lsCompletionItemKind::Text;
|
|
}
|
|
}
|
|
|
|
void BuildItem(std::vector<lsCompletionItem> &out,
|
|
const CodeCompletionString &CCS) {
|
|
assert(!out.empty());
|
|
auto first = out.size() - 1;
|
|
|
|
std::string result_type;
|
|
|
|
for (const auto &Chunk : CCS) {
|
|
CodeCompletionString::ChunkKind Kind = Chunk.Kind;
|
|
std::string text;
|
|
switch (Kind) {
|
|
case CodeCompletionString::CK_TypedText:
|
|
text = Chunk.Text;
|
|
for (auto i = first; i < out.size(); i++)
|
|
if (Kind == CodeCompletionString::CK_TypedText && !out[i].filterText)
|
|
out[i].filterText = text;
|
|
break;
|
|
case CodeCompletionString::CK_Placeholder:
|
|
text = Chunk.Text;
|
|
for (auto i = first; i < out.size(); i++)
|
|
out[i].parameters_.push_back(text);
|
|
break;
|
|
case CodeCompletionString::CK_ResultType:
|
|
result_type = Chunk.Text;
|
|
continue;
|
|
case CodeCompletionString::CK_CurrentParameter:
|
|
// This should never be present while collecting completion items.
|
|
llvm_unreachable("unexpected CK_CurrentParameter");
|
|
continue;
|
|
case CodeCompletionString::CK_Optional: {
|
|
// Duplicate last element, the recursive call will complete it.
|
|
if (g_config->completion.duplicateOptional) {
|
|
out.push_back(out.back());
|
|
BuildItem(out, *Chunk.Optional);
|
|
}
|
|
continue;
|
|
}
|
|
default:
|
|
text = Chunk.Text;
|
|
break;
|
|
}
|
|
|
|
for (auto i = first; i < out.size(); ++i) {
|
|
out[i].label += text;
|
|
if (!g_config->client.snippetSupport && !out[i].parameters_.empty())
|
|
continue;
|
|
|
|
if (Kind == CodeCompletionString::CK_Placeholder) {
|
|
out[i].insertText +=
|
|
"${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}";
|
|
out[i].insertTextFormat = lsInsertTextFormat::Snippet;
|
|
} else {
|
|
out[i].insertText += text;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result_type.size())
|
|
for (auto i = first; i < out.size(); ++i) {
|
|
// ' : ' for variables,
|
|
// ' -> ' (trailing return type-like) for functions
|
|
out[i].label += (out[i].label == out[i].filterText ? " : " : " -> ");
|
|
out[i].label += result_type;
|
|
}
|
|
}
|
|
|
|
class CompletionConsumer : public CodeCompleteConsumer {
|
|
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Alloc;
|
|
CodeCompletionTUInfo CCTUInfo;
|
|
|
|
public:
|
|
bool from_cache;
|
|
std::vector<lsCompletionItem> ls_items;
|
|
|
|
CompletionConsumer(const CodeCompleteOptions &Opts, bool from_cache)
|
|
: CodeCompleteConsumer(Opts, false),
|
|
Alloc(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
|
|
CCTUInfo(Alloc), from_cache(from_cache) {}
|
|
|
|
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
|
|
CodeCompletionResult *Results,
|
|
unsigned NumResults) override {
|
|
ls_items.reserve(NumResults);
|
|
for (unsigned i = 0; i != NumResults; i++) {
|
|
auto &R = Results[i];
|
|
if (R.Availability == CXAvailability_NotAccessible ||
|
|
R.Availability == CXAvailability_NotAvailable)
|
|
continue;
|
|
CodeCompletionString *CCS = R.CreateCodeCompletionString(
|
|
S, Context, getAllocator(), getCodeCompletionTUInfo(),
|
|
includeBriefComments());
|
|
lsCompletionItem ls_item;
|
|
ls_item.kind = GetCompletionKind(R.CursorKind);
|
|
if (const char *brief = CCS->getBriefComment())
|
|
ls_item.documentation = brief;
|
|
ls_item.detail = CCS->getParentContextName().str();
|
|
|
|
size_t first_idx = ls_items.size();
|
|
ls_items.push_back(ls_item);
|
|
BuildItem(ls_items, *CCS);
|
|
|
|
for (size_t j = first_idx; j < ls_items.size(); j++) {
|
|
if (g_config->client.snippetSupport &&
|
|
ls_items[j].insertTextFormat == lsInsertTextFormat::Snippet)
|
|
ls_items[j].insertText += "$0";
|
|
ls_items[j].priority_ = GetCompletionPriority(
|
|
*CCS, Results[i].CursorKind, ls_items[j].filterText);
|
|
if (!g_config->completion.detailedLabel) {
|
|
ls_items[j].detail = ls_items[j].label;
|
|
ls_items[j].label = ls_items[j].filterText.value_or("");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CodeCompletionAllocator &getAllocator() override { return *Alloc; }
|
|
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
|
|
};
|
|
|
|
struct Handler_TextDocumentCompletion
|
|
: BaseMessageHandler<In_TextDocumentComplete> {
|
|
MethodType GetMethodType() const override { return kMethodType; }
|
|
|
|
void Run(In_TextDocumentComplete *request) override {
|
|
static CompleteConsumerCache<std::vector<lsCompletionItem>> cache;
|
|
|
|
auto ¶ms = request->params;
|
|
Out_TextDocumentComplete out;
|
|
out.id = request->id;
|
|
|
|
std::string path = params.textDocument.uri.GetPath();
|
|
WorkingFile *file = working_files->GetFileByFilename(path);
|
|
if (!file) {
|
|
pipeline::WriteStdout(kMethodType, out);
|
|
return;
|
|
}
|
|
|
|
// 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 (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 (params.context.triggerKind ==
|
|
lsCompletionTriggerKind::TriggerCharacter &&
|
|
params.context.triggerCharacter) {
|
|
bool did_fail_check = false;
|
|
|
|
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 '#'.
|
|
if (character == "\"" || character == "<" || character == "/") {
|
|
size_t i = 0;
|
|
while (i < buffer_line.size() && isspace(buffer_line[i]))
|
|
++i;
|
|
if (i >= buffer_line.size() || buffer_line[i] != '#')
|
|
did_fail_check = true;
|
|
}
|
|
// If the character is > or : and we are at the start of the line, do not
|
|
// show completion results.
|
|
else if ((character == ">" || character == ":") && preceding_index < 0) {
|
|
did_fail_check = true;
|
|
}
|
|
// If the character is > but - does not preced it, or if it is : and :
|
|
// does not preced it, do not show completion results.
|
|
else if (preceding_index >= 0 &&
|
|
preceding_index < (int)buffer_line.size()) {
|
|
char preceding = buffer_line[preceding_index];
|
|
did_fail_check = (preceding != '-' && character == ">") ||
|
|
(preceding != ':' && character == ":");
|
|
}
|
|
|
|
if (did_fail_check) {
|
|
pipeline::WriteStdout(kMethodType, out);
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::string completion_text;
|
|
lsPosition end_pos = params.position;
|
|
params.position = file->FindStableCompletionSource(
|
|
request->params.position, &completion_text, &end_pos);
|
|
|
|
ParseIncludeLineResult result = ParseIncludeLine(buffer_line);
|
|
bool has_open_paren = IsOpenParenOrAngle(file->buffer_lines, end_pos);
|
|
|
|
if (result.ok) {
|
|
Out_TextDocumentComplete out;
|
|
out.id = request->id;
|
|
|
|
if (result.quote.empty() && result.pattern.empty()) {
|
|
// no quote or path of file, do preprocessor keyword completion
|
|
if (!std::any_of(preprocessorKeywords.begin(),
|
|
preprocessorKeywords.end(),
|
|
[&result](std::string_view k) {
|
|
return k == result.keyword;
|
|
})) {
|
|
out.result.items = PreprocessorKeywordCompletionItems(result.match);
|
|
FilterAndSortCompletionResponse(&out, result.keyword, has_open_paren);
|
|
}
|
|
} else if (result.keyword.compare("include") == 0) {
|
|
{
|
|
// do include completion
|
|
std::unique_lock<std::mutex> lock(
|
|
include_complete->completion_items_mutex, std::defer_lock);
|
|
if (include_complete->is_scanning)
|
|
lock.lock();
|
|
std::string quote = result.match[5];
|
|
for (auto &item : include_complete->completion_items)
|
|
if (quote.empty() ||
|
|
quote == (item.use_angle_brackets_ ? "<" : "\""))
|
|
out.result.items.push_back(item);
|
|
}
|
|
FilterAndSortCompletionResponse(&out, result.pattern, has_open_paren);
|
|
DecorateIncludePaths(result.match, &out.result.items);
|
|
}
|
|
|
|
for (lsCompletionItem &item : out.result.items) {
|
|
item.textEdit->range.start.line = params.position.line;
|
|
item.textEdit->range.start.character = 0;
|
|
item.textEdit->range.end.line = params.position.line;
|
|
item.textEdit->range.end.character = (int)buffer_line.size();
|
|
}
|
|
|
|
pipeline::WriteStdout(kMethodType, out);
|
|
} else {
|
|
CompletionManager::OnComplete callback =
|
|
[completion_text, has_open_paren, id = request->id,
|
|
params = request->params](CodeCompleteConsumer *OptConsumer) {
|
|
if (!OptConsumer)
|
|
return;
|
|
auto *Consumer = static_cast<CompletionConsumer *>(OptConsumer);
|
|
Out_TextDocumentComplete out;
|
|
out.id = id;
|
|
out.result.items = Consumer->ls_items;
|
|
|
|
FilterAndSortCompletionResponse(&out, completion_text,
|
|
has_open_paren);
|
|
pipeline::WriteStdout(kMethodType, out);
|
|
if (!Consumer->from_cache) {
|
|
std::string path = params.textDocument.uri.GetPath();
|
|
cache.WithLock([&]() {
|
|
cache.path = path;
|
|
cache.position = params.position;
|
|
cache.result = Consumer->ls_items;
|
|
});
|
|
}
|
|
};
|
|
|
|
clang::CodeCompleteOptions CCOpts;
|
|
CCOpts.IncludeBriefComments = true;
|
|
#if LLVM_VERSION_MAJOR >= 7
|
|
CCOpts.IncludeFixIts = true;
|
|
#endif
|
|
if (cache.IsCacheValid(params)) {
|
|
CompletionConsumer Consumer(CCOpts, true);
|
|
cache.WithLock([&]() { Consumer.ls_items = cache.result; });
|
|
callback(&Consumer);
|
|
} else {
|
|
clang_complete->completion_request_.PushBack(
|
|
std::make_unique<CompletionManager::CompletionRequest>(
|
|
request->id, params.textDocument, params.position,
|
|
std::make_unique<CompletionConsumer>(CCOpts, false), CCOpts,
|
|
callback));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentCompletion);
|
|
|
|
} // namespace
|