mirror of
https://github.com/MaskRay/ccls.git
synced 2025-01-31 09:50:26 +00:00
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:
parent
1ade2e5ca1
commit
9d1369786f
@ -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.
|
||||
|
@ -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();
|
@ -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);
|
@ -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();
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user