Pre-filter completion results before sending to vscode.

This results in a less laggy completion experience. Before, vscode would
drop frames if handed too many completions (ie, by typing #include in a
very large project).
This commit is contained in:
Jacob Dufault 2017-06-15 19:28:49 -07:00
parent 1ade2e5ca1
commit 9d1369786f
5 changed files with 142 additions and 10 deletions

View File

@ -435,11 +435,53 @@ void EmitDiagnostics(WorkingFiles* working_files, std::string path, NonElidedVec
});
}
// 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
const size_t kMaxResultSize = 100u;
if (complete_response->result.items.size() > kMaxResultSize) {
//std::cerr << "!!! Filtering " << complete_response->result.items.size() << " results using " << complete_text << std::endl;
complete_response->result.isIncomplete = true;
if (complete_text.empty()) {
complete_response->result.items.resize(kMaxResultSize);
}
else {
NonElidedVector<lsCompletionItem> filtered_result;
filtered_result.reserve(kMaxResultSize);
for (const lsCompletionItem& item : complete_response->result.items) {
if (SubstringMatch(complete_text, item.label)) {
//std::cerr << "!! emitting " << item.label << std::endl;
filtered_result.push_back(item);
if (filtered_result.size() >= kMaxResultSize)
break;
}
}
complete_response->result.items = filtered_result;
}
//std::cerr << "!! Filtering resulted in " << complete_response->result.items.size() << " entries" << std::endl;
}
}
@ -1547,15 +1589,20 @@ bool QueryDbMainLoop(
}
std::cerr << "[complete] Returning " << complete_response.result.items.size() << " include completions" << std::endl;
FilterCompletionResponse(&complete_response, buffer_line);
ipc->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
}
else {
bool is_global_completion = false;
if (file)
msg->params.position = file->FindStableCompletionSource(msg->params.position, &is_global_completion);
std::string existing_completion;
if (file) {
msg->params.position = file->FindStableCompletionSource(msg->params.position, &is_global_completion, &existing_completion);
}
std::cerr << "[complete] Got existing completion " << existing_completion;
ClangCompleteManager::OnComplete callback = std::bind(
[working_files, global_code_complete_cache, non_global_code_complete_cache, is_global_completion]
[working_files, global_code_complete_cache, non_global_code_complete_cache, is_global_completion, existing_completion]
(Ipc_TextDocumentComplete* msg, NonElidedVector<lsCompletionItem> results) {
Out_TextDocumentComplete complete_response;
@ -1564,6 +1611,7 @@ bool QueryDbMainLoop(
complete_response.result.items = results;
// Emit completion results.
FilterCompletionResponse(&complete_response, existing_completion);
IpcManager::instance()->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
// Cache completion results.

View File

@ -1,5 +1,7 @@
#include "lex_utils.h"
#include <doctest/doctest.h>
int GetOffsetForPosition(lsPosition position, const std::string& content) {
int offset = 0;
@ -173,4 +175,65 @@ std::string LexWordAroundPos(lsPosition position, const std::string& content) {
}
return content.substr(start, end - start + 1);
}
}
bool SubstringMatch(const std::string& search, const std::string& content) {
if (search.empty())
return true;
size_t search_index = 0;
char search_char = tolower(search[search_index]);
size_t content_index = 0;
while (true) {
char content_char = tolower(content[content_index]);
if (content_char == search_char) {
search_index += 1;
if (search_index >= search.size())
return true;
search_char = tolower(search[search_index]);
}
content_index += 1;
if (content_index >= content.size())
return false;
}
return false;
}
TEST_SUITE("Substring");
TEST_CASE("match") {
// Sanity.
REQUIRE(SubstringMatch("a", "aa"));
REQUIRE(SubstringMatch("aa", "aa"));
// Empty string matches anything.
REQUIRE(SubstringMatch("", ""));
REQUIRE(SubstringMatch("", "aa"));
// Match in start/middle/end.
REQUIRE(SubstringMatch("a", "abbbb"));
REQUIRE(SubstringMatch("a", "bbabb"));
REQUIRE(SubstringMatch("a", "bbbba"));
REQUIRE(SubstringMatch("aa", "aabbb"));
REQUIRE(SubstringMatch("aa", "bbaab"));
REQUIRE(SubstringMatch("aa", "bbbaa"));
// Capitalization.
REQUIRE(SubstringMatch("aa", "aA"));
REQUIRE(SubstringMatch("aa", "Aa"));
REQUIRE(SubstringMatch("aa", "AA"));
// Token skipping.
REQUIRE(SubstringMatch("ad", "abcd"));
REQUIRE(SubstringMatch("ad", "ABCD"));
// Ordering.
REQUIRE(!SubstringMatch("ad", "dcba"));
}
TEST_SUITE_END();

View File

@ -17,4 +17,6 @@ optional<lsRange> ExtractQuotedRange(int line_number, const std::string& line);
void LexFunctionDeclaration(const std::string& buffer_content, lsPosition declaration_spelling, optional<std::string> type_name, std::string* insert_text, int* newlines_after_name);
std::string LexWordAroundPos(lsPosition position, const std::string& content);
std::string LexWordAroundPos(lsPosition position, const std::string& content);
bool SubstringMatch(const std::string& search, const std::string& content);

View File

@ -214,12 +214,11 @@ std::string WorkingFile::FindClosestCallNameInBuffer(lsPosition position, int* a
return buffer_content.substr(offset, start_offset - offset + 1);
}
// Returns a position which contains the most recent ., ->, :, or ( for code
// completion purposes.
lsPosition WorkingFile::FindStableCompletionSource(lsPosition position, bool* is_global_completion) const {
lsPosition WorkingFile::FindStableCompletionSource(lsPosition position, bool* is_global_completion, std::string* existing_completion) const {
*is_global_completion = true;
int offset = GetOffsetForPosition(position, buffer_content);
int start_offset = GetOffsetForPosition(position, buffer_content);
int offset = start_offset;
while (offset > 0) {
char c = buffer_content[offset - 1];
@ -241,6 +240,7 @@ lsPosition WorkingFile::FindStableCompletionSource(lsPosition position, bool* is
--offset;
}
*existing_completion = buffer_content.substr(offset, start_offset - offset);
return GetPositionForOffset(buffer_content, offset);
}
@ -403,4 +403,21 @@ TEST_CASE("auto-insert )") {
REQUIRE(active_param == 0);
}
TEST_CASE("existing completion") {
WorkingFile f("foo.cc", "zzz.asdf");
bool is_global_completion;
std::string existing_completion;
f.FindStableCompletionSource(CharPos(f, '.'), &is_global_completion, &existing_completion);
REQUIRE(existing_completion == "zzz");
f.FindStableCompletionSource(CharPos(f, 'a', 1), &is_global_completion, &existing_completion);
REQUIRE(existing_completion == "a");
f.FindStableCompletionSource(CharPos(f, 's', 1), &is_global_completion, &existing_completion);
REQUIRE(existing_completion == "as");
f.FindStableCompletionSource(CharPos(f, 'd', 1), &is_global_completion, &existing_completion);
REQUIRE(existing_completion == "asd");
f.FindStableCompletionSource(CharPos(f, 'f', 1), &is_global_completion, &existing_completion);
REQUIRE(existing_completion == "asdf");
}
TEST_SUITE_END();

View File

@ -61,7 +61,9 @@ struct WorkingFile {
//
// The out param |is_global_completion| is set to true if this looks like a
// global completion.
lsPosition FindStableCompletionSource(lsPosition position, bool* is_global_completion) const;
// The out param |existing_completion| is set to any existing completion
// content the user has entered.
lsPosition FindStableCompletionSource(lsPosition position, bool* is_global_completion, std::string* existing_completion) const;
CXUnsavedFile AsUnsavedFile() const;
};