mirror of
https://github.com/MaskRay/ccls.git
synced 2025-02-07 17:32:14 +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;
|
std::cerr << "[complete] Returning " << complete_response.result.items.size() << " include completions" << std::endl;
|
||||||
|
FilterCompletionResponse(&complete_response, buffer_line);
|
||||||
ipc->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
|
ipc->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bool is_global_completion = false;
|
bool is_global_completion = false;
|
||||||
if (file)
|
std::string existing_completion;
|
||||||
msg->params.position = file->FindStableCompletionSource(msg->params.position, &is_global_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(
|
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) {
|
(Ipc_TextDocumentComplete* msg, NonElidedVector<lsCompletionItem> results) {
|
||||||
|
|
||||||
Out_TextDocumentComplete complete_response;
|
Out_TextDocumentComplete complete_response;
|
||||||
@ -1564,6 +1611,7 @@ bool QueryDbMainLoop(
|
|||||||
complete_response.result.items = results;
|
complete_response.result.items = results;
|
||||||
|
|
||||||
// Emit completion results.
|
// Emit completion results.
|
||||||
|
FilterCompletionResponse(&complete_response, existing_completion);
|
||||||
IpcManager::instance()->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
|
IpcManager::instance()->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
|
||||||
|
|
||||||
// Cache completion results.
|
// Cache completion results.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "lex_utils.h"
|
#include "lex_utils.h"
|
||||||
|
|
||||||
|
#include <doctest/doctest.h>
|
||||||
|
|
||||||
int GetOffsetForPosition(lsPosition position, const std::string& content) {
|
int GetOffsetForPosition(lsPosition position, const std::string& content) {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
@ -173,4 +175,65 @@ std::string LexWordAroundPos(lsPosition position, const std::string& content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return content.substr(start, end - start + 1);
|
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);
|
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);
|
return buffer_content.substr(offset, start_offset - offset + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a position which contains the most recent ., ->, :, or ( for code
|
lsPosition WorkingFile::FindStableCompletionSource(lsPosition position, bool* is_global_completion, std::string* existing_completion) const {
|
||||||
// completion purposes.
|
|
||||||
lsPosition WorkingFile::FindStableCompletionSource(lsPosition position, bool* is_global_completion) const {
|
|
||||||
*is_global_completion = true;
|
*is_global_completion = true;
|
||||||
|
|
||||||
int offset = GetOffsetForPosition(position, buffer_content);
|
int start_offset = GetOffsetForPosition(position, buffer_content);
|
||||||
|
int offset = start_offset;
|
||||||
|
|
||||||
while (offset > 0) {
|
while (offset > 0) {
|
||||||
char c = buffer_content[offset - 1];
|
char c = buffer_content[offset - 1];
|
||||||
@ -241,6 +240,7 @@ lsPosition WorkingFile::FindStableCompletionSource(lsPosition position, bool* is
|
|||||||
--offset;
|
--offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*existing_completion = buffer_content.substr(offset, start_offset - offset);
|
||||||
return GetPositionForOffset(buffer_content, offset);
|
return GetPositionForOffset(buffer_content, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,4 +403,21 @@ TEST_CASE("auto-insert )") {
|
|||||||
REQUIRE(active_param == 0);
|
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();
|
TEST_SUITE_END();
|
@ -61,7 +61,9 @@ struct WorkingFile {
|
|||||||
//
|
//
|
||||||
// The out param |is_global_completion| is set to true if this looks like a
|
// The out param |is_global_completion| is set to true if this looks like a
|
||||||
// global completion.
|
// 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;
|
CXUnsavedFile AsUnsavedFile() const;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user