mirror of
https://github.com/MaskRay/ccls.git
synced 2025-03-31 05:52:09 +00:00
Improve completion.
This commit is contained in:
parent
e5128d3db9
commit
e16753d261
@ -355,7 +355,8 @@ struct lsCompletionItem {
|
|||||||
std::string documentation;
|
std::string documentation;
|
||||||
|
|
||||||
// Internal information to order candidates.
|
// Internal information to order candidates.
|
||||||
std::string::size_type pos_;
|
bool found_;
|
||||||
|
std::string::size_type skip_;
|
||||||
unsigned priority_;
|
unsigned priority_;
|
||||||
|
|
||||||
// A string that shoud be used when comparing this item
|
// A string that shoud be used when comparing this item
|
||||||
|
@ -229,6 +229,23 @@ bool SubsequenceMatch(std::string_view search, std::string_view content) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::tuple<bool, int> SubsequenceCountSkip(std::string_view search,
|
||||||
|
std::string_view content) {
|
||||||
|
bool hasUppercaseLetter = std::any_of(search.begin(), search.end(), isupper);
|
||||||
|
int skip = 0;
|
||||||
|
size_t j = 0;
|
||||||
|
for (char c : search) {
|
||||||
|
while (j < content.size() &&
|
||||||
|
(hasUppercaseLetter ? content[j] != c
|
||||||
|
: tolower(content[j]) != tolower(c)))
|
||||||
|
++j, ++skip;
|
||||||
|
if (j == content.size())
|
||||||
|
return std::make_tuple(false, skip);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
return std::make_tuple(true, skip);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE("Offset") {
|
TEST_SUITE("Offset") {
|
||||||
TEST_CASE("past end") {
|
TEST_CASE("past end") {
|
||||||
std::string content = "foo";
|
std::string content = "foo";
|
||||||
@ -280,6 +297,18 @@ TEST_SUITE("Substring") {
|
|||||||
// Ordering.
|
// Ordering.
|
||||||
REQUIRE(!SubsequenceMatch("ad", "dcba"));
|
REQUIRE(!SubsequenceMatch("ad", "dcba"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("skip") {
|
||||||
|
REQUIRE(SubsequenceCountSkip("a", "a") == std::make_tuple(true, 0));
|
||||||
|
REQUIRE(SubsequenceCountSkip("b", "a") == std::make_tuple(false, 1));
|
||||||
|
REQUIRE(SubsequenceCountSkip("", "") == std::make_tuple(true, 0));
|
||||||
|
REQUIRE(SubsequenceCountSkip("a", "ba") == std::make_tuple(true, 1));
|
||||||
|
REQUIRE(SubsequenceCountSkip("aa", "aba") == std::make_tuple(true, 1));
|
||||||
|
REQUIRE(SubsequenceCountSkip("aa", "baa") == std::make_tuple(true, 1));
|
||||||
|
REQUIRE(SubsequenceCountSkip("aA", "aA") == std::make_tuple(true, 0));
|
||||||
|
REQUIRE(SubsequenceCountSkip("aA", "aa") == std::make_tuple(false, 1));
|
||||||
|
REQUIRE(SubsequenceCountSkip("incstdioh", "include <stdio.h>") == std::make_tuple(true, 7));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE("LexFunctionDeclaration") {
|
TEST_SUITE("LexFunctionDeclaration") {
|
||||||
|
@ -30,3 +30,6 @@ std::string LexWordAroundPos(lsPosition position, const std::string& content);
|
|||||||
|
|
||||||
// Case-insensitive subsequence matching.
|
// Case-insensitive subsequence matching.
|
||||||
bool SubsequenceMatch(std::string_view search, std::string_view content);
|
bool SubsequenceMatch(std::string_view search, std::string_view content);
|
||||||
|
|
||||||
|
std::tuple<bool, int> SubsequenceCountSkip(std::string_view search,
|
||||||
|
std::string_view content);
|
||||||
|
@ -70,8 +70,10 @@ MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result);
|
|||||||
|
|
||||||
bool CompareLsCompletionItem(const lsCompletionItem& item1,
|
bool CompareLsCompletionItem(const lsCompletionItem& item1,
|
||||||
const lsCompletionItem& item2) {
|
const lsCompletionItem& item2) {
|
||||||
if (item1.pos_ != item2.pos_)
|
if (item1.found_ != item2.found_)
|
||||||
return item1.pos_ < item2.pos_;
|
return item1.found_ > item2.found_;
|
||||||
|
if (item1.skip_ != item2.skip_)
|
||||||
|
return item1.skip_ < item2.skip_;
|
||||||
if (item1.priority_ != item2.priority_)
|
if (item1.priority_ != item2.priority_)
|
||||||
return item1.priority_ < item2.priority_;
|
return item1.priority_ < item2.priority_;
|
||||||
if (item1.label.length() != item2.label.length())
|
if (item1.label.length() != item2.label.length())
|
||||||
@ -127,28 +129,6 @@ void FilterAndSortCompletionResponse(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
items.erase(std::remove_if(items.begin(), items.end(),
|
|
||||||
[&](const lsCompletionItem& item) {
|
|
||||||
return !SubsequenceMatch(complete_text,
|
|
||||||
item.label);
|
|
||||||
}),
|
|
||||||
items.end());
|
|
||||||
|
|
||||||
// Find the appearance of |complete_text| in all candidates.
|
|
||||||
bool found = false;
|
|
||||||
for (auto& item : items) {
|
|
||||||
item.pos_ = item.label.find(complete_text);
|
|
||||||
if (item.pos_ == 0 && item.label.length() == complete_text.length())
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If found, remove all candidates that do not start with it.
|
|
||||||
if (!complete_text.empty() && found) {
|
|
||||||
auto filter = [](const lsCompletionItem& item) { return item.pos_ != 0; };
|
|
||||||
items.erase(std::remove_if(items.begin(), items.end(), filter),
|
|
||||||
items.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the text doesn't start with underscore,
|
// If the text doesn't start with underscore,
|
||||||
// remove all candidates that start with underscore.
|
// remove all candidates that start with underscore.
|
||||||
if (!complete_text.empty() && complete_text[0] != '_') {
|
if (!complete_text.empty() && complete_text[0] != '_') {
|
||||||
@ -159,6 +139,11 @@ void FilterAndSortCompletionResponse(
|
|||||||
items.end());
|
items.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fuzzy match.
|
||||||
|
for (auto& item : items)
|
||||||
|
std::tie(item.found_, item.skip_) =
|
||||||
|
SubsequenceCountSkip(complete_text, item.label);
|
||||||
|
|
||||||
// Order all items and set |sortText|.
|
// Order all items and set |sortText|.
|
||||||
std::sort(items.begin(), items.end(), CompareLsCompletionItem);
|
std::sort(items.begin(), items.end(), CompareLsCompletionItem);
|
||||||
char buf[16];
|
char buf[16];
|
||||||
@ -170,49 +155,6 @@ void FilterAndSortCompletionResponse(
|
|||||||
if (items.size() > kMaxResultSize) {
|
if (items.size() > kMaxResultSize) {
|
||||||
if (complete_text.empty()) {
|
if (complete_text.empty()) {
|
||||||
items.resize(kMaxResultSize);
|
items.resize(kMaxResultSize);
|
||||||
} else {
|
|
||||||
std::vector<lsCompletionItem> filtered_result;
|
|
||||||
filtered_result.reserve(kMaxResultSize);
|
|
||||||
|
|
||||||
std::unordered_set<std::string> inserted;
|
|
||||||
inserted.reserve(kMaxResultSize);
|
|
||||||
|
|
||||||
// Find literal matches first.
|
|
||||||
for (const auto& item : items) {
|
|
||||||
if (item.pos_ != 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) {
|
|
||||||
for (const auto& item : items) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
items = filtered_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
if (items.size() >= kMaxResultSize) {
|
|
||||||
LOG_S(INFO) << "Marking completion results as incomplete";
|
|
||||||
complete_response->result.isIncomplete = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user