WIP Fuzzy search rework.

1) fts_fuzzy_match has been extended to support wchar_t for a char
   type and uint16_t for an index type for the match indices.
2) fts_fuzzy_match has been extended to place a proper stopper character
   into the match buffer.
3) Slicer integration now uses the fuzzy match indices for highlighting.
4) Slicer integration now correctly highlights the matched word.
5) Slicer search dialog now sorts based on match AND category.

Further modifications are planned:
1) Matching in local language vs. English: Just show the English variant
   if matched in English. Don't mix the two together.
2) Matching the group or category: Continue matching the label.
3) For matches with equal match quality and category sort alphanumerically.
This commit is contained in:
bubnikv 2020-05-01 08:57:24 +02:00
parent f479b77e01
commit 07ab5c31e6
4 changed files with 160 additions and 159 deletions

View file

@ -12,6 +12,7 @@
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h> #include <wx/stattext.h>
@ -1098,7 +1099,7 @@ void Sidebar::search()
void Sidebar::jump_to_option(size_t selected) void Sidebar::jump_to_option(size_t selected)
{ {
const Search::Option& opt = p->searcher.get_option(selected); const Search::Option& opt = p->searcher.get_option(selected);
wxGetApp().get_tab(opt.type)->activate_option(opt.opt_key, opt.category); wxGetApp().get_tab(opt.type)->activate_option(boost::nowide::narrow(opt.opt_key), boost::nowide::narrow(opt.category));
// Switch to the Settings NotePad, if plater is shown // Switch to the Settings NotePad, if plater is shown
if (p->plater->IsShown()) if (p->plater->IsShown())

View file

@ -2,7 +2,9 @@
#include <cstddef> #include <cstddef>
#include <string> #include <string>
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/nowide/convert.hpp>
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
@ -31,64 +33,35 @@ static std::map<Preset::Type, std::string> NameByType = {
{ Preset::TYPE_PRINTER, L("Printer") } { Preset::TYPE_PRINTER, L("Printer") }
}; };
FMFlag Option::fuzzy_match_simple(char const * search_pattern) const FMFlag Option::fuzzy_match(wchar_t const* search_pattern, int& outScore, std::vector<uint16_t> &out_matches) const
{
return fts::fuzzy_match_simple(search_pattern, label_local.utf8_str()) ? fmLabelLocal :
fts::fuzzy_match_simple(search_pattern, group_local.utf8_str()) ? fmGroupLocal :
fts::fuzzy_match_simple(search_pattern, category_local.utf8_str()) ? fmCategoryLocal :
fts::fuzzy_match_simple(search_pattern, opt_key.c_str()) ? fmOptKey :
fts::fuzzy_match_simple(search_pattern, label.utf8_str()) ? fmLabel :
fts::fuzzy_match_simple(search_pattern, group.utf8_str()) ? fmGroup :
fts::fuzzy_match_simple(search_pattern, category.utf8_str()) ? fmCategory : fmUndef ;
}
FMFlag Option::fuzzy_match_simple(const wxString& search) const
{
char const* search_pattern = search.utf8_str();
return fuzzy_match_simple(search_pattern);
}
FMFlag Option::fuzzy_match_simple(const std::string& search) const
{
char const* search_pattern = search.c_str();
return fuzzy_match_simple(search_pattern);
}
FMFlag Option::fuzzy_match(char const* search_pattern, int& outScore) const
{ {
FMFlag flag = fmUndef; FMFlag flag = fmUndef;
int score; int score;
if (fts::fuzzy_match(search_pattern, label_local.utf8_str(), score) && outScore < score) { uint16_t matches[fts::max_matches + 1]; // +1 for the stopper
outScore = score; flag = fmLabelLocal ; } auto save_matches = [&matches, &out_matches]() {
if (fts::fuzzy_match(search_pattern, group_local.utf8_str(), score) && outScore < score) { size_t cnt = 0;
outScore = score; flag = fmGroupLocal ; } for (; matches[cnt] != fts::stopper; ++cnt);
if (fts::fuzzy_match(search_pattern, category_local.utf8_str(), score) && outScore < score) { out_matches.assign(matches, matches + cnt);
outScore = score; flag = fmCategoryLocal; } };
if (fts::fuzzy_match(search_pattern, opt_key.c_str(), score) && outScore < score) { if (fts::fuzzy_match(search_pattern, label_local.c_str(), score, matches) && outScore < score) {
outScore = score; flag = fmOptKey ; } outScore = score; flag = fmLabelLocal ; save_matches(); }
if (fts::fuzzy_match(search_pattern, label.utf8_str(), score) && outScore < score) { if (fts::fuzzy_match(search_pattern, group_local.c_str(), score, matches) && outScore < score) {
outScore = score; flag = fmLabel ; } outScore = score; flag = fmGroupLocal ; save_matches(); }
if (fts::fuzzy_match(search_pattern, group.utf8_str(), score) && outScore < score) { if (fts::fuzzy_match(search_pattern, category_local.c_str(), score, matches) && outScore < score) {
outScore = score; flag = fmGroup ; } outScore = score; flag = fmCategoryLocal; save_matches(); }
if (fts::fuzzy_match(search_pattern, category.utf8_str(), score) && outScore < score) { if (fts::fuzzy_match(search_pattern, opt_key.c_str(), score, matches) && outScore < score) {
outScore = score; flag = fmCategory ; } outScore = score; flag = fmOptKey ; save_matches(); }
if (fts::fuzzy_match(search_pattern, label.c_str(), score, matches) && outScore < score) {
outScore = score; flag = fmLabel ; save_matches(); }
if (fts::fuzzy_match(search_pattern, group.c_str(), score, matches) && outScore < score) {
outScore = score; flag = fmGroup ; save_matches(); }
if (fts::fuzzy_match(search_pattern, category.c_str(), score, matches) && outScore < score) {
outScore = score; flag = fmCategory ; save_matches(); }
return flag; return flag;
} }
FMFlag Option::fuzzy_match(const wxString& search, int& outScore) const
{
char const* search_pattern = search.utf8_str();
return fuzzy_match(search_pattern, outScore);
}
FMFlag Option::fuzzy_match(const std::string& search, int& outScore) const
{
char const* search_pattern = search.c_str();
return fuzzy_match(search_pattern, outScore);
}
void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const
{ {
*label_ = marked_label.c_str(); *label_ = marked_label.c_str();
@ -120,10 +93,10 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty
suffix = opt_key.back()=='1' ? L("Stealth") : L("Normal"); suffix = opt_key.back()=='1' ? L("Stealth") : L("Normal");
if (!label.IsEmpty()) if (!label.IsEmpty())
options.emplace_back(Option{ opt_key, type, options.emplace_back(Option{ boost::nowide::widen(opt_key), type,
label+ " " + suffix, _(label)+ " " + _(suffix), (label+ " " + suffix).ToStdWstring(), (_(label)+ " " + _(suffix)).ToStdWstring(),
gc.group, _(gc.group), gc.group.ToStdWstring(), _(gc.group).ToStdWstring(),
gc.category, _(gc.category) }); gc.category.ToStdWstring(), _(gc.category).ToStdWstring() });
}; };
for (std::string opt_key : config->keys()) for (std::string opt_key : config->keys())
@ -173,41 +146,32 @@ static wxString wrap_string(const wxString& str)
} }
// Mark a string using ColorMarkerStart and ColorMarkerEnd symbols // Mark a string using ColorMarkerStart and ColorMarkerEnd symbols
static void mark_string(wxString& str, const wxString& search_str) static std::wstring mark_string(const std::wstring &str, const std::vector<uint16_t> &matches)
{ {
// Try to find whole search string std::wstring out;
if (str.Replace(search_str, wrap_string(search_str), false) != 0) if (matches.empty())
return; out = str;
else {
// Try to find whole capitalized search string out.reserve(str.size() * 2);
wxString search_str_capitalized = search_str.Capitalize(); if (matches.front() > 0)
if (str.Replace(search_str_capitalized, wrap_string(search_str_capitalized), false) != 0) out += str.substr(0, matches.front());
return; for (size_t i = 0;;) {
// Find the longest string of successive indices.
// if search string is just a one letter now, there is no reason to continue size_t j = i + 1;
if (search_str.Len()==1) while (j < matches.size() && matches[j] == matches[j - 1] + 1)
return; ++ j;
out += ImGui::ColorMarkerStart;
// Split a search string for two strings (string without last letter and last letter) out += str.substr(matches[i], matches[j - 1] - matches[i] + 1);
// and repeat a function with new search strings out += ImGui::ColorMarkerEnd;
mark_string(str, search_str.SubString(0, search_str.Len() - 2)); if (j == matches.size()) {
mark_string(str, search_str.Last()); out += str.substr(matches[j - 1] + 1);
} break;
}
// clear marked string from a redundant use of ColorMarkers out += str.substr(matches[j - 1] + 1, matches[j] - matches[j - 1] - 1);
static void clear_marked_string(wxString& str) i = j;
{ }
// Check if the string has a several ColorMarkerStart in a row and replace them to only one, if any }
wxString delete_string = wxString::Format("%c%c", ImGui::ColorMarkerStart, ImGui::ColorMarkerStart); return out;
str.Replace(delete_string, ImGui::ColorMarkerStart, true);
// If there were several ColorMarkerStart in a row, it means there should be a several ColorMarkerStop in a row,
// replace them to only one
delete_string = wxString::Format("%c%c", ImGui::ColorMarkerEnd, ImGui::ColorMarkerEnd);
str.Replace(delete_string, ImGui::ColorMarkerEnd, true);
// And we should to remove redundant ColorMarkers, if they are in "End, Start" sequence in a row
delete_string = wxString::Format("%c%c", ImGui::ColorMarkerEnd, ImGui::ColorMarkerStart);
str.Replace(delete_string, wxEmptyString, true);
} }
bool OptionsSearcher::search() bool OptionsSearcher::search()
@ -245,32 +209,50 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/)
opt.group_local + sep + opt.label_local; opt.group_local + sep + opt.label_local;
}; };
std::vector<uint16_t> matches;
for (size_t i=0; i < options.size(); i++) for (size_t i=0; i < options.size(); i++)
{ {
const Option &opt = options[i]; const Option &opt = options[i];
if (full_list) { if (full_list) {
std::string label = into_u8(get_label(opt)); std::string label = into_u8(get_label(opt));
found.emplace_back(FoundOption{ label, label, into_u8(get_tooltip(opt)), i, 0 }); found.emplace_back(FoundOption{ label, label, into_u8(get_tooltip(opt)), i, fmUndef, 0 });
continue; continue;
} }
int score = 0; int score = 0;
FMFlag fuzzy_match_flag = opt.fuzzy_match(boost::nowide::widen(search).c_str(), score, matches);
FMFlag fuzzy_match_flag = opt.fuzzy_match(search, score);
if (fuzzy_match_flag != fmUndef) if (fuzzy_match_flag != fmUndef)
{ {
wxString label = get_label(opt); wxString label;
if ( fuzzy_match_flag == fmLabel ) label += "(" + opt.label + ")"; if (view_params.type)
else if (fuzzy_match_flag == fmGroup ) label += "(" + opt.group + ")"; label += _(NameByType[opt.type]) + sep;
else if (fuzzy_match_flag == fmCategory) label += "(" + opt.category + ")"; if (fuzzy_match_flag == fmCategoryLocal)
else if (fuzzy_match_flag == fmOptKey ) label += "(" + opt.opt_key + ")"; label += mark_string(opt.category_local, matches) + sep;
else if (view_params.category)
label += opt.category_local + sep;
if (fuzzy_match_flag == fmGroupLocal)
label += mark_string(opt.group_local, matches) + sep;
else if (view_params.group)
label += opt.group_local + sep;
label += ((fuzzy_match_flag == fmLabelLocal) ? mark_string(opt.label_local, matches) : opt.label_local) + sep;
wxString marked_label = label; switch (fuzzy_match_flag) {
mark_string(marked_label, from_u8(search)); case fmLabelLocal:
clear_marked_string(marked_label); case fmGroupLocal:
case fmCategoryLocal:
break;
case fmLabel: label = get_label(opt) + "(" + mark_string(opt.label, matches) + ")"; break;
case fmGroup: label = get_label(opt) + "(" + mark_string(opt.group, matches) + ")"; break;
case fmCategory: label = get_label(opt) + "(" + mark_string(opt.category, matches) + ")"; break;
case fmOptKey: label = get_label(opt) + "(" + mark_string(opt.opt_key, matches) + ")"; break;
case fmUndef: assert(false); break;
}
found.emplace_back(FoundOption{ into_u8(label), into_u8(marked_label), into_u8(get_tooltip(opt)), i, score }); std::string label_plain = into_u8(label);
boost::erase_all(label_plain, std::wstring(1, wchar_t(ImGui::ColorMarkerStart)));
boost::erase_all(label_plain, std::wstring(1, wchar_t(ImGui::ColorMarkerEnd)));
found.emplace_back(FoundOption{ label_plain, into_u8(label), into_u8(get_tooltip(opt)), i, fuzzy_match_flag, score });
} }
} }

View file

@ -37,6 +37,7 @@ struct GroupAndCategory {
}; };
// fuzzy_match flag // fuzzy_match flag
// Sorted by the order of importance. The outputs will be sorted by the importance if the match value given by fuzzy_match is equal.
enum FMFlag enum FMFlag
{ {
fmUndef = 0, // didn't find fmUndef = 0, // didn't find
@ -53,28 +54,27 @@ struct Option {
bool operator<(const Option& other) const { return other.label > this->label; } bool operator<(const Option& other) const { return other.label > this->label; }
bool operator>(const Option& other) const { return other.label < this->label; } bool operator>(const Option& other) const { return other.label < this->label; }
std::string opt_key; // Fuzzy matching works at a character level. Thus matching with wide characters is a safer bet than with short characters,
// though for some languages (Chinese?) it may not work correctly.
std::wstring opt_key;
Preset::Type type {Preset::TYPE_INVALID}; Preset::Type type {Preset::TYPE_INVALID};
wxString label; std::wstring label;
wxString label_local; std::wstring label_local;
wxString group; std::wstring group;
wxString group_local; std::wstring group_local;
wxString category; std::wstring category;
wxString category_local; std::wstring category_local;
FMFlag fuzzy_match_simple(char const *search_pattern) const; FMFlag fuzzy_match(wchar_t const *search_pattern, int &outScore, std::vector<uint16_t> &out_matches) const;
FMFlag fuzzy_match_simple(const wxString& search) const;
FMFlag fuzzy_match_simple(const std::string &search) const;
FMFlag fuzzy_match(char const *search_pattern, int &outScore) const;
FMFlag fuzzy_match(const wxString &search, int &outScore) const ;
FMFlag fuzzy_match(const std::string &search, int &outScore) const ;
}; };
struct FoundOption { struct FoundOption {
// UTF8 encoding, to be consumed by ImGUI by reference.
std::string label; std::string label;
std::string marked_label; std::string marked_label;
std::string tooltip; std::string tooltip;
size_t option_idx {0}; size_t option_idx {0};
FMFlag category {fmUndef};
int outScore {0}; int outScore {0};
// Returning pointers to contents of std::string members, to be used by ImGUI for rendering. // Returning pointers to contents of std::string members, to be used by ImGUI for rendering.
@ -106,7 +106,7 @@ class OptionsSearcher
} }
void sort_found() { void sort_found() {
std::sort(found.begin(), found.end(), [](const FoundOption& f1, const FoundOption& f2) { std::sort(found.begin(), found.end(), [](const FoundOption& f1, const FoundOption& f2) {
return f1.outScore > f2.outScore; }); return f1.outScore > f2.outScore || (f1.outScore == f2.outScore && int(f1.category) < int(f2.category)); });
}; };
size_t options_size() const { return options.size(); } size_t options_size() const { return options.size(); }

View file

@ -1,4 +1,4 @@
// LICENSE // LICENSE
// //
// This software is dual-licensed to the public domain and under the following // This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify, // license: you are granted a perpetual, irrevocable license to copy, modify,
@ -23,7 +23,7 @@
// Performs exhaustive search via recursion to find all possible matches and match with highest score. // Performs exhaustive search via recursion to find all possible matches and match with highest score.
// Scores values have no intrinsic meaning. Possible score range is not normalized and varies with pattern. // Scores values have no intrinsic meaning. Possible score range is not normalized and varies with pattern.
// Recursion is limited internally (default=10) to prevent degenerate cases (pattern="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") // Recursion is limited internally (default=10) to prevent degenerate cases (pattern="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
// Uses uint8_t for match indices. Therefore patterns are limited to 256 characters. // Uses uint8_t for match indices. Therefore patterns are limited to max_matches characters.
// Score system should be tuned for YOUR use case. Words, sentences, file names, or method names all prefer different tuning. // Score system should be tuned for YOUR use case. Words, sentences, file names, or method names all prefer different tuning.
@ -39,54 +39,61 @@
// Public interface // Public interface
namespace fts { namespace fts {
static bool fuzzy_match_simple(char const * pattern, char const * str); using char_type = wchar_t;
static bool fuzzy_match(char const * pattern, char const * str, int & outScore); using pos_type = uint16_t;
static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches); static constexpr pos_type stopper = pos_type(-1);
} static constexpr int max_matches = 255;
static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore);
static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore, pos_type * matches);
}
#ifdef FTS_FUZZY_MATCH_IMPLEMENTATION #ifdef FTS_FUZZY_MATCH_IMPLEMENTATION
namespace fts { namespace fts {
// Forward declarations for "private" implementation // Forward declarations for "private" implementation
namespace fuzzy_internal { namespace fuzzy_internal {
static bool fuzzy_match_recursive(const char * pattern, const char * str, int & outScore, const char * strBegin, static bool fuzzy_match_recursive(const char_type * pattern, const char_type * str, int & outScore, const char_type * strBegin,
uint8_t const * srcMatches, uint8_t * newMatches, int maxMatches, int nextMatch, pos_type const * srcMatches, pos_type * newMatches, int nextMatch,
int & recursionCount, int recursionLimit); int & recursionCount, int recursionLimit);
static void copy_matches(pos_type * dst, pos_type const* src);
} }
// Public interface // Public interface
static bool fuzzy_match_simple(char const * pattern, char const * str) { static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore) {
while (*pattern != '\0' && *str != '\0') {
if (tolower(*pattern) == tolower(*str))
++pattern;
++str;
}
return *pattern == '\0' ? true : false;
}
static bool fuzzy_match(char const * pattern, char const * str, int & outScore) {
uint8_t matches[256]; pos_type matches[max_matches + 1]; // with the room for the stopper
return fuzzy_match(pattern, str, outScore, matches, sizeof(matches)); matches[0] = stopper;
return fuzzy_match(pattern, str, outScore, matches);
} }
static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches) { static bool fuzzy_match(char_type const * pattern, char_type const * str, int & outScore, pos_type * matches) {
int recursionCount = 0; int recursionCount = 0;
int recursionLimit = 10; int recursionLimit = 10;
return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, 0, recursionCount, recursionLimit);
return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit);
} }
// Private implementation // Private implementation
static bool fuzzy_internal::fuzzy_match_recursive(const char * pattern, const char * str, int & outScore, static bool fuzzy_internal::fuzzy_match_recursive(
const char * strBegin, uint8_t const * srcMatches, uint8_t * matches, int maxMatches, // Pattern to match over str.
int nextMatch, int & recursionCount, int recursionLimit) const char_type * pattern,
// Text to match the pattern over.
const char_type * str,
// Score of the pattern matching str. Output variable.
int & outScore,
const char_type * strBegin,
// Matches when entering this function.
pos_type const * srcMatches,
// Output matches.
pos_type * matches,
// Number of matched characters stored in srcMatches when entering this function, also tracking the successive matches.
int nextMatch,
// Recursion count is input / output to track the maximum depth reached.
int & recursionCount,
int recursionLimit)
{ {
// Count recursions // Count recursions
++recursionCount; if (++ recursionCount >= recursionLimit)
if (recursionCount >= recursionLimit)
return false; return false;
// Detect end of strings // Detect end of strings
@ -95,7 +102,7 @@ namespace fts {
// Recursion params // Recursion params
bool recursiveMatch = false; bool recursiveMatch = false;
uint8_t bestRecursiveMatches[256]; pos_type bestRecursiveMatches[max_matches + 1]; // with the room for the stopper
int bestRecursiveScore = 0; int bestRecursiveScore = 0;
// Loop through pattern and str looking for a match // Loop through pattern and str looking for a match
@ -106,30 +113,32 @@ namespace fts {
if (tolower(*pattern) == tolower(*str)) { if (tolower(*pattern) == tolower(*str)) {
// Supplied matches buffer was too short // Supplied matches buffer was too short
if (nextMatch >= maxMatches) if (nextMatch >= max_matches)
return false; return false;
// "Copy-on-Write" srcMatches into matches // "Copy-on-Write" srcMatches into matches
if (first_match && srcMatches) { if (first_match && srcMatches) {
memcpy(matches, srcMatches, nextMatch); memcpy(matches, srcMatches, sizeof(pos_type) * (nextMatch + 1)); // including the stopper
first_match = false; first_match = false;
} }
// Recursive call that "skips" this match // Recursive call that "skips" this match
uint8_t recursiveMatches[256]; pos_type recursiveMatches[max_matches];
int recursiveScore; int recursiveScore;
if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) { if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, nextMatch, recursionCount, recursionLimit)) {
// Pick best recursive score // Pick best recursive score
if (!recursiveMatch || recursiveScore > bestRecursiveScore) { if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
memcpy(bestRecursiveMatches, recursiveMatches, 256); copy_matches(bestRecursiveMatches, recursiveMatches);
bestRecursiveScore = recursiveScore; bestRecursiveScore = recursiveScore;
} }
recursiveMatch = true; recursiveMatch = true;
} }
// Advance // Advance
matches[nextMatch++] = (uint8_t)(str - strBegin); matches[nextMatch++] = (pos_type)(str - strBegin);
// Write a stopper sign.
matches[nextMatch] = stopper;
++pattern; ++pattern;
} }
++str; ++str;
@ -168,10 +177,10 @@ namespace fts {
// Apply ordering bonuses // Apply ordering bonuses
for (int i = 0; i < nextMatch; ++i) { for (int i = 0; i < nextMatch; ++i) {
uint8_t currIdx = matches[i]; pos_type currIdx = matches[i];
if (i > 0) { if (i > 0) {
uint8_t prevIdx = matches[i - 1]; pos_type prevIdx = matches[i - 1];
// Sequential // Sequential
if (currIdx == (prevIdx + 1)) if (currIdx == (prevIdx + 1))
@ -182,13 +191,13 @@ namespace fts {
if (currIdx > 0) { if (currIdx > 0) {
// Camel case // Camel case
// ::islower() expects an unsigned char in range of 0 to 255. // ::islower() expects an unsigned char in range of 0 to 255.
unsigned char uneighbor = ((unsigned char *)strBegin)[currIdx - 1]; char_type uneighbor = strBegin[currIdx - 1];
unsigned char ucurr = ((unsigned char*)strBegin)[currIdx]; char_type ucurr = strBegin[currIdx];
if (::islower(uneighbor) && ::isupper(ucurr)) if (std::islower(uneighbor) && std::isupper(ucurr))
outScore += camel_bonus; outScore += camel_bonus;
// Separator // Separator
char neighbor = strBegin[currIdx - 1]; char_type neighbor = strBegin[currIdx - 1];
bool neighborSeparator = neighbor == '_' || neighbor == ' '; bool neighborSeparator = neighbor == '_' || neighbor == ' ';
if (neighborSeparator) if (neighborSeparator)
outScore += separator_bonus; outScore += separator_bonus;
@ -203,7 +212,7 @@ namespace fts {
// Return best result // Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) { if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
// Recursive score is better than "this" // Recursive score is better than "this"
memcpy(matches, bestRecursiveMatches, maxMatches); copy_matches(matches, bestRecursiveMatches);
outScore = bestRecursiveScore; outScore = bestRecursiveScore;
return true; return true;
} }
@ -216,6 +225,15 @@ namespace fts {
return false; return false;
} }
} }
// Copy matches up to a stopper.
static void fuzzy_internal::copy_matches(pos_type * dst, pos_type const* src)
{
while (*src != stopper)
*dst++ = *src++;
*dst = stopper;
}
} // namespace fts } // namespace fts
#endif // FTS_FUZZY_MATCH_IMPLEMENTATION #endif // FTS_FUZZY_MATCH_IMPLEMENTATION