Merge branch 'ranked-word-completion'
This commit is contained in:
commit
6a4e9d1a7d
|
@ -72,7 +72,7 @@ WordDB& get_word_db(const Buffer& buffer)
|
||||||
return cache_val.as<WordDB>();
|
return cache_val.as<WordDB>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool other_buffers, bool subseq>
|
template<bool other_buffers>
|
||||||
InsertCompletion complete_word(const Buffer& buffer, ByteCoord cursor_pos)
|
InsertCompletion complete_word(const Buffer& buffer, ByteCoord cursor_pos)
|
||||||
{
|
{
|
||||||
auto pos = buffer.iterator_at(cursor_pos);
|
auto pos = buffer.iterator_at(cursor_pos);
|
||||||
|
@ -93,21 +93,22 @@ InsertCompletion complete_word(const Buffer& buffer, ByteCoord cursor_pos)
|
||||||
|
|
||||||
String current_word{begin, end};
|
String current_word{begin, end};
|
||||||
|
|
||||||
struct MatchAndBuffer {
|
struct RankedMatchAndBuffer : RankedMatch
|
||||||
MatchAndBuffer(StringView m, const Buffer* b = nullptr) : match(m), buffer(b) {}
|
{
|
||||||
|
RankedMatchAndBuffer(const RankedMatch& m, const Buffer* b = nullptr)
|
||||||
|
: RankedMatch{m}, buffer{b} {}
|
||||||
|
|
||||||
bool operator==(const MatchAndBuffer& other) const { return match == other.match; }
|
using RankedMatch::operator==;
|
||||||
bool operator<(const MatchAndBuffer& other) const { return match < other.match; }
|
using RankedMatch::operator<;
|
||||||
|
bool operator==(StringView other) const { return candidate() == other; }
|
||||||
|
|
||||||
StringView match;
|
|
||||||
const Buffer* buffer;
|
const Buffer* buffer;
|
||||||
};
|
};
|
||||||
Vector<MatchAndBuffer> matches;
|
Vector<RankedMatchAndBuffer> matches;
|
||||||
|
|
||||||
auto add_matches = [&](const Buffer& buf) {
|
auto add_matches = [&](const Buffer& buf) {
|
||||||
auto& word_db = get_word_db(buf);
|
auto& word_db = get_word_db(buf);
|
||||||
auto bufmatches = word_db.find_matching(
|
auto bufmatches = word_db.find_matching(prefix);
|
||||||
prefix, subseq ? subsequence_match : prefix_match);
|
|
||||||
for (auto& m : bufmatches)
|
for (auto& m : bufmatches)
|
||||||
matches.push_back({ m, &buf });
|
matches.push_back({ m, &buf });
|
||||||
};
|
};
|
||||||
|
@ -131,8 +132,8 @@ InsertCompletion complete_word(const Buffer& buffer, ByteCoord cursor_pos)
|
||||||
matches.erase(std::unique(matches.begin(), matches.end()), matches.end());
|
matches.erase(std::unique(matches.begin(), matches.end()), matches.end());
|
||||||
|
|
||||||
const auto longest = std::accumulate(matches.begin(), matches.end(), 0_char,
|
const auto longest = std::accumulate(matches.begin(), matches.end(), 0_char,
|
||||||
[](const CharCount& lhs, const MatchAndBuffer& rhs)
|
[](const CharCount& lhs, const RankedMatchAndBuffer& rhs)
|
||||||
{ return std::max(lhs, rhs.match.char_length()); });
|
{ return std::max(lhs, rhs.candidate().char_length()); });
|
||||||
|
|
||||||
InsertCompletion::CandidateList candidates;
|
InsertCompletion::CandidateList candidates;
|
||||||
candidates.reserve(matches.size());
|
candidates.reserve(matches.size());
|
||||||
|
@ -141,15 +142,15 @@ InsertCompletion complete_word(const Buffer& buffer, ByteCoord cursor_pos)
|
||||||
DisplayLine menu_entry;
|
DisplayLine menu_entry;
|
||||||
if (m.buffer)
|
if (m.buffer)
|
||||||
{
|
{
|
||||||
const auto pad_len = longest + 1 - m.match.char_length();
|
const auto pad_len = longest + 1 - m.candidate().char_length();
|
||||||
menu_entry.push_back(m.match.str());
|
menu_entry.push_back(m.candidate().str());
|
||||||
menu_entry.push_back(String{' ', pad_len});
|
menu_entry.push_back(String{' ', pad_len});
|
||||||
menu_entry.push_back({ m.buffer->display_name(), get_face("MenuInfo") });
|
menu_entry.push_back({ m.buffer->display_name(), get_face("MenuInfo") });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
menu_entry.push_back(m.match.str());
|
menu_entry.push_back(m.candidate().str());
|
||||||
|
|
||||||
candidates.push_back({m.match.str(), "", std::move(menu_entry)});
|
candidates.push_back({m.candidate().str(), "", std::move(menu_entry)});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { begin.coord(), cursor_pos, std::move(candidates), buffer.timestamp() };
|
return { begin.coord(), cursor_pos, std::move(candidates), buffer.timestamp() };
|
||||||
|
@ -216,7 +217,7 @@ InsertCompletion complete_option(const Buffer& buffer, ByteCoord cursor_pos,
|
||||||
str_to_int({match[2].first, match[2].second}) - 1 };
|
str_to_int({match[2].first, match[2].second}) - 1 };
|
||||||
if (not buffer.is_valid(coord))
|
if (not buffer.is_valid(coord))
|
||||||
return {};
|
return {};
|
||||||
auto end = coord;
|
auto end = cursor_pos;
|
||||||
if (match[3].matched)
|
if (match[3].matched)
|
||||||
{
|
{
|
||||||
ByteCount len = str_to_int({match[3].first, match[3].second});
|
ByteCount len = str_to_int({match[3].first, match[3].second});
|
||||||
|
@ -231,29 +232,46 @@ InsertCompletion complete_option(const Buffer& buffer, ByteCoord cursor_pos,
|
||||||
|
|
||||||
if (cursor_pos.line == coord.line and cursor_pos.column >= coord.column)
|
if (cursor_pos.line == coord.line and cursor_pos.column >= coord.column)
|
||||||
{
|
{
|
||||||
StringView prefix = buffer[coord.line].substr(
|
StringView query = buffer[coord.line].substr(
|
||||||
coord.column, cursor_pos.column - coord.column);
|
coord.column, cursor_pos.column - coord.column);
|
||||||
|
|
||||||
|
|
||||||
const CharCount tabstop = options["tabstop"].get<int>();
|
const CharCount tabstop = options["tabstop"].get<int>();
|
||||||
const CharCount column = get_column(buffer, tabstop, cursor_pos);
|
const CharCount column = get_column(buffer, tabstop, cursor_pos);
|
||||||
|
|
||||||
InsertCompletion::CandidateList candidates;
|
struct RankedMatchAndInfo : RankedMatch
|
||||||
|
{
|
||||||
|
using RankedMatch::RankedMatch;
|
||||||
|
using RankedMatch::operator==;
|
||||||
|
using RankedMatch::operator<;
|
||||||
|
|
||||||
|
StringView docstring;
|
||||||
|
DisplayLine menu_entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<RankedMatchAndInfo> matches;
|
||||||
|
|
||||||
for (auto it = opt.begin() + 1; it != opt.end(); ++it)
|
for (auto it = opt.begin() + 1; it != opt.end(); ++it)
|
||||||
{
|
{
|
||||||
auto splitted = split(*it, '@');
|
auto splitted = split(*it, '@');
|
||||||
if (not splitted.empty() and prefix_match(splitted[0], prefix))
|
if (splitted.empty())
|
||||||
|
continue;
|
||||||
|
if (RankedMatchAndInfo match{splitted[0], query})
|
||||||
{
|
{
|
||||||
StringView completion = splitted[0];
|
match.docstring = splitted.size() > 1 ? splitted[1] : StringView{};
|
||||||
StringView docstring = splitted.size() > 1 ? splitted[1] : StringView{};
|
match.menu_entry = splitted.size() > 2 ?
|
||||||
|
|
||||||
DisplayLine menu_entry = splitted.size() > 2 ?
|
|
||||||
parse_display_line(expand_tabs(splitted[2], tabstop, column))
|
parse_display_line(expand_tabs(splitted[2], tabstop, column))
|
||||||
: DisplayLine{ expand_tabs(splitted[0], tabstop, column) };
|
: DisplayLine{ expand_tabs(splitted[0], tabstop, column) };
|
||||||
|
|
||||||
candidates.push_back({completion.str(), docstring.str(), std::move(menu_entry)});
|
matches.push_back(std::move(match));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::sort(matches.begin(), matches.end());
|
||||||
|
InsertCompletion::CandidateList candidates;
|
||||||
|
candidates.reserve(matches.size());
|
||||||
|
for (auto& match : matches)
|
||||||
|
candidates.push_back({ match.candidate().str(), match.docstring.str(),
|
||||||
|
std::move(match.menu_entry) });
|
||||||
|
|
||||||
return { coord, end, std::move(candidates), timestamp };
|
return { coord, end, std::move(candidates), timestamp };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,41 +366,9 @@ void InsertCompleter::select(int offset, Vector<Key>& keystrokes)
|
||||||
|
|
||||||
void InsertCompleter::update()
|
void InsertCompleter::update()
|
||||||
{
|
{
|
||||||
if (m_completions.is_valid())
|
if (m_explicit_completer and try_complete(m_explicit_completer))
|
||||||
{
|
|
||||||
ByteCount longest_completion = 0;
|
|
||||||
for (auto& candidate : m_completions.candidates)
|
|
||||||
longest_completion = std::max(longest_completion, candidate.completion.length());
|
|
||||||
|
|
||||||
ByteCoord cursor = m_context.selections().main().cursor();
|
|
||||||
ByteCoord compl_beg = m_completions.begin;
|
|
||||||
if (cursor.line == compl_beg.line and
|
|
||||||
compl_beg.column <= cursor.column and
|
|
||||||
cursor.column < compl_beg.column + longest_completion)
|
|
||||||
{
|
|
||||||
String prefix = m_context.buffer().string(compl_beg, cursor);
|
|
||||||
|
|
||||||
if (m_context.buffer().timestamp() == m_completions.timestamp)
|
|
||||||
m_matching_candidates = m_completions.candidates;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_matching_candidates.clear();
|
|
||||||
for (auto& candidate : m_completions.candidates)
|
|
||||||
{
|
|
||||||
if (candidate.completion.substr(0, prefix.length()) == prefix)
|
|
||||||
m_matching_candidates.push_back(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (not m_matching_candidates.empty())
|
|
||||||
{
|
|
||||||
m_current_candidate = m_matching_candidates.size();
|
|
||||||
m_completions.end = cursor;
|
|
||||||
menu_show();
|
|
||||||
m_matching_candidates.push_back({prefix, ""});
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reset();
|
reset();
|
||||||
setup_ifn();
|
setup_ifn();
|
||||||
}
|
}
|
||||||
|
@ -390,6 +376,7 @@ void InsertCompleter::update()
|
||||||
void InsertCompleter::reset()
|
void InsertCompleter::reset()
|
||||||
{
|
{
|
||||||
m_completions = InsertCompletion{};
|
m_completions = InsertCompletion{};
|
||||||
|
m_explicit_completer = nullptr;
|
||||||
if (m_context.has_ui())
|
if (m_context.has_ui())
|
||||||
{
|
{
|
||||||
m_context.ui().menu_hide();
|
m_context.ui().menu_hide();
|
||||||
|
@ -419,13 +406,11 @@ bool InsertCompleter::setup_ifn()
|
||||||
return true;
|
return true;
|
||||||
if (completer.mode == InsertCompleterDesc::Word and
|
if (completer.mode == InsertCompleterDesc::Word and
|
||||||
*completer.param == "buffer" and
|
*completer.param == "buffer" and
|
||||||
(try_complete(complete_word<false, false>) or
|
try_complete(complete_word<false>))
|
||||||
try_complete(complete_word<false, true>)))
|
|
||||||
return true;
|
return true;
|
||||||
if (completer.mode == InsertCompleterDesc::Word and
|
if (completer.mode == InsertCompleterDesc::Word and
|
||||||
*completer.param == "all" and
|
*completer.param == "all" and
|
||||||
(try_complete(complete_word<true, false>) or
|
try_complete(complete_word<true>))
|
||||||
try_complete(complete_word<true, true>)))
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -497,21 +482,26 @@ bool InsertCompleter::try_complete(CompleteFunc complete_func)
|
||||||
|
|
||||||
void InsertCompleter::explicit_file_complete()
|
void InsertCompleter::explicit_file_complete()
|
||||||
{
|
{
|
||||||
try_complete([this](const Buffer& buffer, ByteCoord cursor_pos) {
|
auto func = [this](const Buffer& buffer, ByteCoord cursor_pos) {
|
||||||
return complete_filename<false>(buffer, cursor_pos, m_options);
|
return complete_filename<false>(buffer, cursor_pos, m_options);
|
||||||
});
|
};
|
||||||
|
try_complete(func);
|
||||||
|
m_explicit_completer = func;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InsertCompleter::explicit_word_complete()
|
void InsertCompleter::explicit_word_complete()
|
||||||
{
|
{
|
||||||
try_complete(complete_word<true, true>);
|
try_complete(complete_word<true>);
|
||||||
|
m_explicit_completer = complete_word<true>;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InsertCompleter::explicit_line_complete()
|
void InsertCompleter::explicit_line_complete()
|
||||||
{
|
{
|
||||||
try_complete([this](const Buffer& buffer, ByteCoord cursor_pos) {
|
auto func = [this](const Buffer& buffer, ByteCoord cursor_pos) {
|
||||||
return complete_line(buffer, m_options, cursor_pos);
|
return complete_line(buffer, m_options, cursor_pos);
|
||||||
});
|
};
|
||||||
|
try_complete(func);
|
||||||
|
m_explicit_completer = func;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,8 @@ private:
|
||||||
InsertCompletion m_completions;
|
InsertCompletion m_completions;
|
||||||
CandidateList m_matching_candidates;
|
CandidateList m_matching_candidates;
|
||||||
int m_current_candidate = -1;
|
int m_current_candidate = -1;
|
||||||
|
|
||||||
|
std::function<InsertCompletion (const Buffer&, ByteCoord)> m_explicit_completer;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
113
src/ranked_match.cc
Normal file
113
src/ranked_match.cc
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#include "ranked_match.hh"
|
||||||
|
|
||||||
|
#include "unit_tests.hh"
|
||||||
|
|
||||||
|
namespace Kakoune
|
||||||
|
{
|
||||||
|
|
||||||
|
static int count_word_boundaries_match(StringView candidate, StringView query)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
auto it = query.begin();
|
||||||
|
char prev = 0;
|
||||||
|
for (auto c : candidate)
|
||||||
|
{
|
||||||
|
const bool is_word_boundary = prev == 0 or
|
||||||
|
(ispunct(prev) and is_word(c)) or
|
||||||
|
(islower(prev) and isupper(c));
|
||||||
|
prev = c;
|
||||||
|
|
||||||
|
if (not is_word_boundary)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const char lc = tolower(c);
|
||||||
|
for (; it != query.end(); ++it)
|
||||||
|
{
|
||||||
|
const char qc = *it;
|
||||||
|
if (qc == (islower(qc) ? lc : c))
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
++it;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (it == query.end())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool smartcase_eq(char query, char candidate)
|
||||||
|
{
|
||||||
|
return query == (islower(query) ? tolower(candidate) : candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool subsequence_match_smart_case(StringView str, StringView subseq)
|
||||||
|
{
|
||||||
|
auto it = str.begin();
|
||||||
|
for (auto& c : subseq)
|
||||||
|
{
|
||||||
|
if (it == str.end())
|
||||||
|
return false;
|
||||||
|
while (not smartcase_eq(c, *it))
|
||||||
|
{
|
||||||
|
if (++it == str.end())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RankedMatch::RankedMatch(StringView candidate, StringView query)
|
||||||
|
{
|
||||||
|
if (candidate.empty() or query.length() > candidate.length())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (query.empty())
|
||||||
|
{
|
||||||
|
m_candidate = candidate;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not subsequence_match_smart_case(candidate, query))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_candidate = candidate;
|
||||||
|
|
||||||
|
m_first_char_match = smartcase_eq(query[0], candidate[0]);
|
||||||
|
m_word_boundary_match_count = count_word_boundaries_match(candidate, query);
|
||||||
|
m_only_word_boundary = m_word_boundary_match_count == query.length();
|
||||||
|
m_prefix = std::equal(query.begin(), query.end(), candidate.begin(), smartcase_eq);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RankedMatch::operator<(const RankedMatch& other) const
|
||||||
|
{
|
||||||
|
if (m_only_word_boundary or other.m_only_word_boundary)
|
||||||
|
return m_only_word_boundary and other.m_only_word_boundary ?
|
||||||
|
m_word_boundary_match_count > other.m_word_boundary_match_count
|
||||||
|
: m_only_word_boundary;
|
||||||
|
|
||||||
|
if (m_prefix != other.m_prefix)
|
||||||
|
return m_prefix;
|
||||||
|
|
||||||
|
if (m_word_boundary_match_count != other.m_word_boundary_match_count)
|
||||||
|
return m_word_boundary_match_count > other.m_word_boundary_match_count;
|
||||||
|
|
||||||
|
if (m_first_char_match != other.m_first_char_match)
|
||||||
|
return m_first_char_match;
|
||||||
|
|
||||||
|
return std::lexicographical_compare(
|
||||||
|
m_candidate.begin(), m_candidate.end(),
|
||||||
|
other.m_candidate.begin(), other.m_candidate.end(),
|
||||||
|
[](char a, char b) {
|
||||||
|
const bool low_a = islower(a), low_b = islower(b);
|
||||||
|
return low_a == low_b ? a < b : low_a;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitTest test_ranked_match{[] {
|
||||||
|
kak_assert(count_word_boundaries_match("run_all_tests", "rat") == 3);
|
||||||
|
}};
|
||||||
|
|
||||||
|
}
|
32
src/ranked_match.hh
Normal file
32
src/ranked_match.hh
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef ranked_match_hh_INCLUDED
|
||||||
|
#define ranked_match_hh_INCLUDED
|
||||||
|
|
||||||
|
#include "string.hh"
|
||||||
|
#include "vector.hh"
|
||||||
|
|
||||||
|
namespace Kakoune
|
||||||
|
{
|
||||||
|
|
||||||
|
struct RankedMatch
|
||||||
|
{
|
||||||
|
RankedMatch(StringView candidate, StringView query);
|
||||||
|
|
||||||
|
const StringView& candidate() const { return m_candidate; }
|
||||||
|
bool operator<(const RankedMatch& other) const;
|
||||||
|
bool operator==(const RankedMatch& other) const { return m_candidate == other.m_candidate; }
|
||||||
|
|
||||||
|
explicit operator bool() const { return not m_candidate.empty(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
StringView m_candidate;
|
||||||
|
bool m_first_char_match = false;
|
||||||
|
bool m_prefix = false;
|
||||||
|
int m_word_boundary_match_count = 0;
|
||||||
|
bool m_only_word_boundary = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
using RankedMatchList = Vector<RankedMatch>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ranked_match_hh_INCLUDED
|
|
@ -27,9 +27,19 @@ UsedLetters used_letters(StringView str)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static WordDB::WordList get_words(const SharedString& content)
|
constexpr UsedLetters upper_mask = 0xFFFFFFC000000;
|
||||||
|
|
||||||
|
UsedLetters to_lower(UsedLetters letters)
|
||||||
{
|
{
|
||||||
WordDB::WordList res;
|
return ((letters & upper_mask) >> 26) | (letters & (~upper_mask));
|
||||||
|
}
|
||||||
|
|
||||||
|
using WordList = Vector<StringView>;
|
||||||
|
|
||||||
|
|
||||||
|
static WordList get_words(const SharedString& content)
|
||||||
|
{
|
||||||
|
WordList res;
|
||||||
using Utf8It = utf8::iterator<const char*, utf8::InvalidPolicy::Pass>;
|
using Utf8It = utf8::iterator<const char*, utf8::InvalidPolicy::Pass>;
|
||||||
const char* word_start = content.begin();
|
const char* word_start = content.begin();
|
||||||
bool in_word = false;
|
bool in_word = false;
|
||||||
|
@ -136,8 +146,50 @@ int WordDB::get_word_occurences(StringView word) const
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RankedMatchList WordDB::find_matching(StringView query)
|
||||||
|
{
|
||||||
|
auto matches = [](UsedLetters query, UsedLetters letters)
|
||||||
|
{
|
||||||
|
return (query & letters) == query;
|
||||||
|
};
|
||||||
|
|
||||||
|
update_db();
|
||||||
|
const UsedLetters letters = used_letters(query);
|
||||||
|
RankedMatchList res;
|
||||||
|
for (auto&& word : m_words)
|
||||||
|
{
|
||||||
|
if (query.empty())
|
||||||
|
{
|
||||||
|
res.push_back(RankedMatch{word.first, query});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UsedLetters word_letters = word.second.letters;
|
||||||
|
if (not matches(to_lower(letters), to_lower(word_letters)) or
|
||||||
|
not matches(letters & upper_mask, word_letters & upper_mask))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (RankedMatch match{word.first, query})
|
||||||
|
res.push_back(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
UnitTest test_word_db{[]()
|
UnitTest test_word_db{[]()
|
||||||
{
|
{
|
||||||
|
auto cmp_words = [](const RankedMatch& lhs, const RankedMatch& rhs) {
|
||||||
|
return lhs.candidate() < rhs.candidate();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto eq = [](ArrayView<const RankedMatch> lhs, const WordList& rhs) {
|
||||||
|
return lhs.size() == rhs.size() and
|
||||||
|
std::equal(lhs.begin(), lhs.end(), rhs.begin(),
|
||||||
|
[](const RankedMatch& lhs, const StringView& rhs) {
|
||||||
|
return lhs.candidate() == rhs;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
Buffer buffer("test", Buffer::Flags::None,
|
Buffer buffer("test", Buffer::Flags::None,
|
||||||
"tchou mutch\n"
|
"tchou mutch\n"
|
||||||
"tchou kanaky tchou\n"
|
"tchou kanaky tchou\n"
|
||||||
|
@ -145,19 +197,24 @@ UnitTest test_word_db{[]()
|
||||||
"tchaa tchaa\n"
|
"tchaa tchaa\n"
|
||||||
"allo\n");
|
"allo\n");
|
||||||
WordDB word_db(buffer);
|
WordDB word_db(buffer);
|
||||||
auto res = word_db.find_matching("", prefix_match);
|
auto res = word_db.find_matching("");
|
||||||
std::sort(res.begin(), res.end());
|
std::sort(res.begin(), res.end(), cmp_words);
|
||||||
kak_assert(res == WordDB::WordList{ "allo" COMMA "kanaky" COMMA "mutch" COMMA "tchaa" COMMA "tchou" });
|
kak_assert(eq(res, WordList{ "allo" COMMA "kanaky" COMMA "mutch" COMMA "tchaa" COMMA "tchou" }));
|
||||||
kak_assert(word_db.get_word_occurences("tchou") == 3);
|
kak_assert(word_db.get_word_occurences("tchou") == 3);
|
||||||
kak_assert(word_db.get_word_occurences("allo") == 1);
|
kak_assert(word_db.get_word_occurences("allo") == 1);
|
||||||
buffer.erase(buffer.iterator_at({1, 6}), buffer.iterator_at({4, 0}));
|
buffer.erase(buffer.iterator_at({1, 6}), buffer.iterator_at({4, 0}));
|
||||||
res = word_db.find_matching("", prefix_match);
|
res = word_db.find_matching("");
|
||||||
std::sort(res.begin(), res.end());
|
std::sort(res.begin(), res.end(), cmp_words);
|
||||||
kak_assert(res == WordDB::WordList{ "allo" COMMA "mutch" COMMA "tchou" });
|
kak_assert(eq(res, WordList{ "allo" COMMA "mutch" COMMA "tchou" }));
|
||||||
buffer.insert(buffer.iterator_at({1, 0}), "re");
|
buffer.insert(buffer.iterator_at({1, 0}), "re");
|
||||||
res = word_db.find_matching("", subsequence_match);
|
res = word_db.find_matching("");
|
||||||
std::sort(res.begin(), res.end());
|
std::sort(res.begin(), res.end(), cmp_words);
|
||||||
kak_assert(res == WordDB::WordList{ "allo" COMMA "mutch" COMMA "retchou" COMMA "tchou" });
|
kak_assert(eq(res, WordList{ "allo" COMMA "mutch" COMMA "retchou" COMMA "tchou" }));
|
||||||
|
}};
|
||||||
|
|
||||||
|
UnitTest test_used_letters{[]()
|
||||||
|
{
|
||||||
|
kak_assert(used_letters("abcd") == to_lower(used_letters("abcdABCD")));
|
||||||
}};
|
}};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "shared_string.hh"
|
#include "shared_string.hh"
|
||||||
#include "unordered_map.hh"
|
#include "unordered_map.hh"
|
||||||
#include "vector.hh"
|
#include "vector.hh"
|
||||||
|
#include "ranked_match.hh"
|
||||||
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
|
||||||
|
@ -22,21 +23,7 @@ public:
|
||||||
WordDB(const WordDB&) = delete;
|
WordDB(const WordDB&) = delete;
|
||||||
WordDB(WordDB&&) = default;
|
WordDB(WordDB&&) = default;
|
||||||
|
|
||||||
using WordList = Vector<StringView>;
|
RankedMatchList find_matching(StringView str);
|
||||||
template<typename MatchFunc>
|
|
||||||
WordList find_matching(StringView str, MatchFunc match)
|
|
||||||
{
|
|
||||||
update_db();
|
|
||||||
const UsedLetters letters = used_letters(str);
|
|
||||||
WordList res;
|
|
||||||
for (auto&& word : m_words)
|
|
||||||
{
|
|
||||||
if ((letters & word.second.letters) == letters and
|
|
||||||
match(word.first, str))
|
|
||||||
res.push_back(word.first);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_word_occurences(StringView word) const;
|
int get_word_occurences(StringView word) const;
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user