mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-22 07:35:08 +00:00
Simplify and optimize completion.
This commit is contained in:
parent
02542b1e69
commit
ad88f707f7
@ -156,7 +156,7 @@ TEST_SUITE("fuzzy_match") {
|
||||
Ranks("ma", {"map", "many", "maximum"});
|
||||
Ranks("print", {"printf", "sprintf"});
|
||||
// score(PRINT) = kMinScore
|
||||
Ranks("int", {"int", "INT", "PRINT"});
|
||||
Ranks("ast", {"ast", "AST", "INT_FAST16_MAX"});
|
||||
// score(PRINT) > kMinScore
|
||||
Ranks("Int", {"int", "INT", "PRINT"});
|
||||
}
|
||||
|
@ -180,23 +180,10 @@ std::string_view LexIdentifierAroundPos(lsPosition position,
|
||||
return content.substr(start, end - start);
|
||||
}
|
||||
|
||||
bool SubsequenceMatchIgnoreCase(std::string_view search, std::string_view content) {
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < search.size(); i++) {
|
||||
char search_char = tolower(search[i]);
|
||||
while (j < content.size() && tolower(content[j]) != search_char)
|
||||
j++;
|
||||
if (j == content.size())
|
||||
return false;
|
||||
j++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find discontinous |search| in |content|.
|
||||
// Return |found| and the count of skipped chars before found.
|
||||
std::tuple<bool, int> SubsequenceCountSkip(std::string_view search,
|
||||
std::string_view content) {
|
||||
std::pair<bool, int> CaseFoldingSubsequenceMatch(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;
|
||||
@ -206,10 +193,10 @@ std::tuple<bool, int> SubsequenceCountSkip(std::string_view search,
|
||||
: tolower(content[j]) != tolower(c)))
|
||||
++j, ++skip;
|
||||
if (j == content.size())
|
||||
return std::make_tuple(false, skip);
|
||||
return {false, skip};
|
||||
++j;
|
||||
}
|
||||
return std::make_tuple(true, skip);
|
||||
return {true, skip};
|
||||
}
|
||||
|
||||
TEST_SUITE("Offset") {
|
||||
@ -234,43 +221,20 @@ TEST_SUITE("Offset") {
|
||||
}
|
||||
|
||||
TEST_SUITE("Substring") {
|
||||
TEST_CASE("match") {
|
||||
// Empty string matches anything.
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("", ""));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("", "aa"));
|
||||
|
||||
// Match in start/middle/end.
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("a", "abbbb"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("a", "bbabb"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("a", "bbbba"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("aa", "aabbb"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("aa", "bbaab"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("aa", "bbbaa"));
|
||||
|
||||
// Capitalization.
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("aa", "aA"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("aa", "Aa"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("aa", "AA"));
|
||||
|
||||
// Token skipping.
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("ad", "abcd"));
|
||||
REQUIRE(SubsequenceMatchIgnoreCase("ad", "ABCD"));
|
||||
|
||||
// Ordering.
|
||||
REQUIRE(!SubsequenceMatchIgnoreCase("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));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("a", "a") == std::make_pair(true, 0));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("b", "a") == std::make_pair(false, 1));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("", "") == std::make_pair(true, 0));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("a", "ba") == std::make_pair(true, 1));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("aa", "aba") ==
|
||||
std::make_pair(true, 1));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("aa", "baa") ==
|
||||
std::make_pair(true, 1));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("aA", "aA") == std::make_pair(true, 0));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("aA", "aa") ==
|
||||
std::make_pair(false, 1));
|
||||
REQUIRE(CaseFoldingSubsequenceMatch("incstdioh", "include <stdio.h>") ==
|
||||
std::make_pair(true, 7));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,5 @@ void LexFunctionDeclaration(const std::string& buffer_content,
|
||||
std::string_view LexIdentifierAroundPos(lsPosition position,
|
||||
std::string_view content);
|
||||
|
||||
// Case-insensitive subsequence matching.
|
||||
bool SubsequenceMatchIgnoreCase(std::string_view search, std::string_view content);
|
||||
|
||||
std::tuple<bool, int> SubsequenceCountSkip(std::string_view search,
|
||||
std::string_view content);
|
||||
std::pair<bool, int> CaseFoldingSubsequenceMatch(std::string_view search,
|
||||
std::string_view content);
|
||||
|
@ -72,8 +72,7 @@ struct lsCompletionItem {
|
||||
optional<std::string> documentation;
|
||||
|
||||
// Internal information to order candidates.
|
||||
bool found_;
|
||||
std::string::size_type skip_;
|
||||
int score_;
|
||||
unsigned priority_;
|
||||
|
||||
// Use <> or "" by default as include path.
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "clang_complete.h"
|
||||
#include "code_complete_cache.h"
|
||||
#include "fuzzy_match.h"
|
||||
#include "include_complete.h"
|
||||
#include "message_handler.h"
|
||||
#include "queue_manager.h"
|
||||
@ -70,19 +71,6 @@ struct Out_TextDocumentComplete
|
||||
};
|
||||
MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result);
|
||||
|
||||
bool CompareLsCompletionItem(const lsCompletionItem& lhs,
|
||||
const lsCompletionItem& rhs) {
|
||||
if (lhs.found_ != rhs.found_)
|
||||
return !lhs.found_ < !rhs.found_;
|
||||
if (lhs.skip_ != rhs.skip_)
|
||||
return lhs.skip_ < rhs.skip_;
|
||||
if (lhs.priority_ != rhs.priority_)
|
||||
return lhs.priority_ < rhs.priority_;
|
||||
if (lhs.filterText->length() != rhs.filterText->length())
|
||||
return lhs.filterText->length() < rhs.filterText->length();
|
||||
return *lhs.filterText < *rhs.filterText;
|
||||
}
|
||||
|
||||
void DecorateIncludePaths(const std::smatch& match,
|
||||
std::vector<lsCompletionItem>* items) {
|
||||
std::string spaces_after_include = " ";
|
||||
@ -200,41 +188,29 @@ void FilterAndSortCompletionResponse(
|
||||
item.filterText = item.label;
|
||||
}
|
||||
|
||||
// If the text doesn't start with underscore, remove all candidates that
|
||||
// start with underscore.
|
||||
if (complete_text[0] != '_') {
|
||||
auto filter = [](const lsCompletionItem& item) {
|
||||
return (*item.filterText)[0] == '_';
|
||||
};
|
||||
items.erase(std::remove_if(items.begin(), items.end(), filter),
|
||||
items.end());
|
||||
}
|
||||
|
||||
// Fuzzy match. Remove any candidates that do not match.
|
||||
bool found = false;
|
||||
// Fuzzy match and remove awful candidates.
|
||||
FuzzyMatcher fuzzy(complete_text);
|
||||
for (auto& item : items) {
|
||||
std::tie(item.found_, item.skip_) =
|
||||
SubsequenceCountSkip(complete_text, *item.filterText);
|
||||
found = found || item.found_;
|
||||
}
|
||||
if (found) {
|
||||
auto filter = [](const lsCompletionItem& item) { return !item.found_; };
|
||||
items.erase(std::remove_if(items.begin(), items.end(), filter),
|
||||
items.end());
|
||||
|
||||
// Order all items and set |sortText|.
|
||||
const size_t kMaxSortSize = 200u;
|
||||
if (items.size() <= kMaxSortSize) {
|
||||
std::sort(items.begin(), items.end(), CompareLsCompletionItem);
|
||||
} else {
|
||||
// Just place items that found the text before those not.
|
||||
std::vector<lsCompletionItem> items_found, items_notfound;
|
||||
for (auto& item : items)
|
||||
(item.found_ ? items_found : items_notfound).push_back(item);
|
||||
items = items_found;
|
||||
items.insert(items.end(), items_notfound.begin(), items_notfound.end());
|
||||
}
|
||||
item.score_ =
|
||||
CaseFoldingSubsequenceMatch(complete_text, *item.filterText).first
|
||||
? fuzzy.Match(*item.filterText)
|
||||
: FuzzyMatcher::kMinScore;
|
||||
}
|
||||
items.erase(std::remove_if(items.begin(), items.end(),
|
||||
[](const lsCompletionItem& item) {
|
||||
return item.score_ <= FuzzyMatcher::kMinScore;
|
||||
}),
|
||||
items.end());
|
||||
std::sort(items.begin(), items.end(),
|
||||
[](const lsCompletionItem& lhs, const lsCompletionItem& rhs) {
|
||||
if (lhs.score_ != rhs.score_)
|
||||
return lhs.score_ > rhs.score_;
|
||||
if (lhs.priority_ != rhs.priority_)
|
||||
return lhs.priority_ < rhs.priority_;
|
||||
if (lhs.filterText->size() != rhs.filterText->size())
|
||||
return lhs.filterText->size() < rhs.filterText->size();
|
||||
return *lhs.filterText < *rhs.filterText;
|
||||
});
|
||||
|
||||
// Trim result.
|
||||
finalize();
|
||||
|
@ -107,7 +107,7 @@ struct WorkspaceSymbolHandler : BaseMessageHandler<Ipc_WorkspaceSymbol> {
|
||||
|
||||
for (int i = 0; i < (int)db->symbols.size(); ++i) {
|
||||
std::string_view detailed_name = db->GetSymbolDetailedName(i);
|
||||
if (SubsequenceMatchIgnoreCase(query_without_space, detailed_name)) {
|
||||
if (CaseFoldingSubsequenceMatch(query_without_space, detailed_name).first) {
|
||||
// Do not show the same entry twice.
|
||||
if (!inserted_results.insert(std::string(detailed_name)).second)
|
||||
continue;
|
||||
|
Loading…
Reference in New Issue
Block a user