2017-12-06 03:32:33 +00:00
|
|
|
#include "message_handler.h"
|
|
|
|
|
|
|
|
#include "lex_utils.h"
|
|
|
|
|
2017-12-06 04:39:44 +00:00
|
|
|
#include <loguru.hpp>
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
struct Ipc_TextDocumentComplete : public IpcMessage<Ipc_TextDocumentComplete> {
|
|
|
|
const static IpcId kIpcId = IpcId::TextDocumentCompletion;
|
|
|
|
|
|
|
|
lsRequestId id;
|
|
|
|
lsTextDocumentPositionParams params;
|
|
|
|
};
|
|
|
|
MAKE_REFLECT_STRUCT(Ipc_TextDocumentComplete, id, params);
|
|
|
|
REGISTER_IPC_MESSAGE(Ipc_TextDocumentComplete);
|
|
|
|
|
|
|
|
struct lsTextDocumentCompleteResult {
|
|
|
|
// This list it not complete. Further typing should result in recomputing
|
|
|
|
// this list.
|
|
|
|
bool isIncomplete = false;
|
|
|
|
// The completion items.
|
2017-12-12 05:20:29 +00:00
|
|
|
std::vector<lsCompletionItem> items;
|
2017-12-06 04:39:44 +00:00
|
|
|
};
|
|
|
|
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);
|
|
|
|
|
|
|
|
// 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 FilterCompletionResponse(Out_TextDocumentComplete* complete_response,
|
|
|
|
const std::string& complete_text) {
|
|
|
|
// Used to inject more completions.
|
|
|
|
#if false
|
|
|
|
const size_t kNumIterations = 250;
|
|
|
|
size_t size = complete_response->result.items.size();
|
|
|
|
complete_response->result.items.reserve(size * (kNumIterations + 1));
|
|
|
|
for (size_t iteration = 0; iteration < kNumIterations; ++iteration) {
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
|
|
auto item = complete_response->result.items[i];
|
|
|
|
item.label += "#" + std::to_string(iteration);
|
|
|
|
complete_response->result.items.push_back(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-12-27 15:53:35 +00:00
|
|
|
auto& items = complete_response->result.items;
|
2017-12-26 08:44:27 +00:00
|
|
|
|
|
|
|
// If the text doesn't start with underscore,
|
|
|
|
// remove all candidates that start with underscore.
|
|
|
|
if (!complete_text.empty() && complete_text[0] != '_') {
|
|
|
|
items.erase(std::remove_if(items.begin(), items.end(),
|
|
|
|
[](const lsCompletionItem& item) {
|
|
|
|
return item.label[0] == '_';
|
2017-12-27 15:53:35 +00:00
|
|
|
}),
|
|
|
|
items.end());
|
2017-12-26 08:44:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// find the exact text
|
|
|
|
const bool found = !complete_text.empty() &&
|
|
|
|
std::find_if(items.begin(), items.end(),
|
|
|
|
[&](const lsCompletionItem& item) {
|
|
|
|
return item.label == complete_text;
|
|
|
|
}) != items.end();
|
|
|
|
// If found, remove all candidates that do not start with it.
|
|
|
|
if (found) {
|
|
|
|
items.erase(std::remove_if(items.begin(), items.end(),
|
|
|
|
[&](const lsCompletionItem& item) {
|
|
|
|
return item.label.find(complete_text) != 0;
|
2017-12-27 15:53:35 +00:00
|
|
|
}),
|
|
|
|
items.end());
|
2017-12-26 08:44:27 +00:00
|
|
|
}
|
|
|
|
|
2017-12-06 04:39:44 +00:00
|
|
|
const size_t kMaxResultSize = 100u;
|
2017-12-26 08:44:27 +00:00
|
|
|
if (items.size() > kMaxResultSize) {
|
2017-12-06 04:39:44 +00:00
|
|
|
if (complete_text.empty()) {
|
2017-12-26 08:44:27 +00:00
|
|
|
items.resize(kMaxResultSize);
|
2017-12-06 04:39:44 +00:00
|
|
|
} else {
|
2017-12-12 05:20:29 +00:00
|
|
|
std::vector<lsCompletionItem> filtered_result;
|
2017-12-06 04:39:44 +00:00
|
|
|
filtered_result.reserve(kMaxResultSize);
|
|
|
|
|
|
|
|
std::unordered_set<std::string> inserted;
|
|
|
|
inserted.reserve(kMaxResultSize);
|
|
|
|
|
|
|
|
// Find literal matches first.
|
2017-12-26 08:44:27 +00:00
|
|
|
for (const auto& item : items) {
|
2017-12-06 04:39:44 +00:00
|
|
|
if (item.label.find(complete_text) != std::string::npos) {
|
|
|
|
// Don't insert the same completion entry.
|
|
|
|
if (!inserted.insert(item.InsertedContent()).second)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
filtered_result.push_back(item);
|
|
|
|
if (filtered_result.size() >= kMaxResultSize)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find fuzzy matches if we haven't found all of the literal matches.
|
|
|
|
if (filtered_result.size() < kMaxResultSize) {
|
2017-12-26 08:44:27 +00:00
|
|
|
for (const auto& item : items) {
|
2017-12-06 04:39:44 +00:00
|
|
|
if (SubstringMatch(complete_text, item.label)) {
|
|
|
|
// Don't insert the same completion entry.
|
|
|
|
if (!inserted.insert(item.InsertedContent()).second)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
filtered_result.push_back(item);
|
|
|
|
if (filtered_result.size() >= kMaxResultSize)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-26 08:44:27 +00:00
|
|
|
items = filtered_result;
|
2017-12-06 04:39:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assuming the client does not support out-of-order completion (ie, ao
|
|
|
|
// matches against oa), our filtering is guaranteed to contain any
|
|
|
|
// potential matches, so the completion is only incomplete if we have the
|
|
|
|
// max number of emitted matches.
|
2017-12-26 08:44:27 +00:00
|
|
|
if (items.size() >= kMaxResultSize) {
|
2017-12-06 04:39:44 +00:00
|
|
|
LOG_S(INFO) << "Marking completion results as incomplete";
|
|
|
|
complete_response->result.isIncomplete = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-06 03:32:33 +00:00
|
|
|
struct TextDocumentCompletionHandler : MessageHandler {
|
|
|
|
IpcId GetId() const override { return IpcId::TextDocumentCompletion; }
|
|
|
|
|
|
|
|
void Run(std::unique_ptr<BaseIpcMessage> message) override {
|
|
|
|
auto request = std::shared_ptr<Ipc_TextDocumentComplete>(
|
|
|
|
static_cast<Ipc_TextDocumentComplete*>(message.release()));
|
|
|
|
|
|
|
|
std::string path = request->params.textDocument.uri.GetPath();
|
|
|
|
WorkingFile* file = working_files->GetFileByFilename(path);
|
|
|
|
|
|
|
|
// 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->all_buffer_lines.size()) {
|
|
|
|
buffer_line = file->all_buffer_lines[request->params.position.line];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ShouldRunIncludeCompletion(buffer_line)) {
|
|
|
|
Out_TextDocumentComplete out;
|
|
|
|
out.id = request->id;
|
|
|
|
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(
|
|
|
|
include_complete->completion_items_mutex, std::defer_lock);
|
|
|
|
if (include_complete->is_scanning)
|
|
|
|
lock.lock();
|
|
|
|
out.result.items.assign(include_complete->completion_items.begin(),
|
|
|
|
include_complete->completion_items.end());
|
|
|
|
if (lock)
|
|
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update textEdit params.
|
|
|
|
for (lsCompletionItem& item : out.result.items) {
|
|
|
|
item.textEdit->range.start.line = request->params.position.line;
|
|
|
|
item.textEdit->range.start.character = 0;
|
|
|
|
item.textEdit->range.end.line = request->params.position.line;
|
|
|
|
item.textEdit->range.end.character = (int)buffer_line.size();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FilterCompletionResponse(&out, buffer_line);
|
2017-12-24 00:25:18 +00:00
|
|
|
QueueManager::WriteStdout(IpcId::TextDocumentCompletion, out);
|
2017-12-06 03:32:33 +00:00
|
|
|
} else {
|
|
|
|
bool is_global_completion = false;
|
|
|
|
std::string existing_completion;
|
|
|
|
if (file) {
|
|
|
|
request->params.position = file->FindStableCompletionSource(
|
|
|
|
request->params.position, &is_global_completion,
|
|
|
|
&existing_completion);
|
|
|
|
}
|
|
|
|
|
|
|
|
ClangCompleteManager::OnComplete callback = std::bind(
|
|
|
|
[this, is_global_completion, existing_completion, request](
|
2017-12-12 05:20:29 +00:00
|
|
|
const std::vector<lsCompletionItem>& results,
|
2017-12-06 03:32:33 +00:00
|
|
|
bool is_cached_result) {
|
|
|
|
Out_TextDocumentComplete out;
|
|
|
|
out.id = request->id;
|
|
|
|
out.result.items = results;
|
|
|
|
|
|
|
|
// Emit completion results.
|
|
|
|
FilterCompletionResponse(&out, existing_completion);
|
2017-12-24 00:25:18 +00:00
|
|
|
QueueManager::WriteStdout(IpcId::TextDocumentCompletion, out);
|
2017-12-06 03:32:33 +00:00
|
|
|
|
|
|
|
// Cache completion results.
|
|
|
|
if (!is_cached_result) {
|
|
|
|
std::string path = request->params.textDocument.uri.GetPath();
|
|
|
|
if (is_global_completion) {
|
|
|
|
global_code_complete_cache->WithLock([&]() {
|
|
|
|
global_code_complete_cache->cached_path_ = path;
|
|
|
|
global_code_complete_cache->cached_results_ = results;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
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;
|
|
|
|
non_global_code_complete_cache->cached_results_ = results;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
std::placeholders::_1, std::placeholders::_2);
|
|
|
|
|
|
|
|
bool is_cache_match = false;
|
|
|
|
global_code_complete_cache->WithLock([&]() {
|
|
|
|
is_cache_match = is_global_completion &&
|
|
|
|
global_code_complete_cache->cached_path_ == path &&
|
|
|
|
!global_code_complete_cache->cached_results_.empty();
|
|
|
|
});
|
|
|
|
if (is_cache_match) {
|
2017-12-23 16:01:43 +00:00
|
|
|
ClangCompleteManager::OnComplete freshen_global = [this](
|
|
|
|
std::vector<lsCompletionItem> results, bool is_cached_result) {
|
|
|
|
assert(!is_cached_result);
|
|
|
|
|
|
|
|
// note: path is updated in the normal completion handler.
|
|
|
|
global_code_complete_cache->WithLock(
|
|
|
|
[&]() { global_code_complete_cache->cached_results_ = results; });
|
|
|
|
};
|
2017-12-06 03:32:33 +00:00
|
|
|
|
|
|
|
global_code_complete_cache->WithLock([&]() {
|
|
|
|
callback(global_code_complete_cache->cached_results_,
|
|
|
|
true /*is_cached_result*/);
|
|
|
|
});
|
|
|
|
clang_complete->CodeComplete(request->params, freshen_global);
|
|
|
|
} else if (non_global_code_complete_cache->IsCacheValid(
|
|
|
|
request->params)) {
|
|
|
|
non_global_code_complete_cache->WithLock([&]() {
|
|
|
|
callback(non_global_code_complete_cache->cached_results_,
|
|
|
|
true /*is_cached_result*/);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
clang_complete->CodeComplete(request->params, callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
REGISTER_MESSAGE_HANDLER(TextDocumentCompletionHandler);
|
2017-12-06 04:39:44 +00:00
|
|
|
|
2017-12-26 08:44:27 +00:00
|
|
|
} // namespace
|