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