Merge pull request #112 from theClueless/fuzzyMatchUpdates

Fuzzy match logic update
This commit is contained in:
Jeremy Wu 2020-01-14 08:26:45 +11:00 committed by GitHub
commit 6cad4bc986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 323 additions and 194 deletions

View File

@ -47,8 +47,53 @@ namespace Wox.Infrastructure.Logger
return valid;
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Error(string message)
[MethodImpl(MethodImplOptions.Synchronized)]
public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "")
{
if (string.IsNullOrWhiteSpace(className))
{
LogFaultyFormat($"Fail to specify a class name during logging of message: {message ?? "no message entered"}");
}
if (string.IsNullOrWhiteSpace(message))
{ // todo: not sure we really need that
LogFaultyFormat($"Fail to specify a message during logging");
}
if (!string.IsNullOrWhiteSpace(methodName))
{
className += "." + methodName;
}
ExceptionInternal(className, message, exception);
}
private static void ExceptionInternal(string classAndMethod, string message, System.Exception e)
{
var logger = LogManager.GetLogger(classAndMethod);
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
logger.Error("-------------------------- Begin exception --------------------------");
logger.Error(message);
do
{
logger.Error($"Exception full name:\n <{e.GetType().FullName}>");
logger.Error($"Exception message:\n <{e.Message}>");
logger.Error($"Exception stack trace:\n <{e.StackTrace}>");
logger.Error($"Exception source:\n <{e.Source}>");
logger.Error($"Exception target site:\n <{e.TargetSite}>");
logger.Error($"Exception HResult:\n <{e.HResult}>");
e = e.InnerException;
} while (e != null);
logger.Error("-------------------------- End exception --------------------------");
}
private static void LogInternal(string message, LogLevel level)
{
if (FormatValid(message))
{
@ -57,8 +102,8 @@ namespace Wox.Infrastructure.Logger
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
logger.Error(unprefixed);
System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}");
logger.Log(level, unprefixed);
}
else
{
@ -78,25 +123,7 @@ namespace Wox.Infrastructure.Logger
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
logger.Error("-------------------------- Begin exception --------------------------");
logger.Error(unprefixed);
do
{
logger.Error($"Exception full name:\n <{e.GetType().FullName}>");
logger.Error($"Exception message:\n <{e.Message}>");
logger.Error($"Exception stack trace:\n <{e.StackTrace}>");
logger.Error($"Exception source:\n <{e.Source}>");
logger.Error($"Exception target site:\n <{e.TargetSite}>");
logger.Error($"Exception HResult:\n <{e.HResult}>");
e = e.InnerException;
} while (e != null);
logger.Error("-------------------------- End exception --------------------------");
ExceptionInternal(prefix, unprefixed, e);
}
else
{
@ -104,62 +131,29 @@ namespace Wox.Infrastructure.Logger
}
#endif
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Error(string message)
{
LogInternal(message, LogLevel.Error);
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Debug(string message)
{
if (FormatValid(message))
{
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"DEBUG|{message}");
logger.Debug(unprefixed);
}
else
{
LogFaultyFormat(message);
}
LogInternal(message, LogLevel.Debug);
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Info(string message)
{
if (FormatValid(message))
{
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"INFO|{message}");
logger.Info(unprefixed);
}
else
{
LogFaultyFormat(message);
}
LogInternal(message, LogLevel.Info);
}
/// <param name="message">example: "|prefix|unprefixed" </param>
public static void Warn(string message)
{
if (FormatValid(message))
{
var parts = message.Split('|');
var prefix = parts[1];
var unprefixed = parts[2];
var logger = LogManager.GetLogger(prefix);
System.Diagnostics.Debug.WriteLine($"WARN|{message}");
logger.Warn(unprefixed);
}
else
{
LogFaultyFormat(message);
}
LogInternal(message, LogLevel.Warn);
}
}
}

View File

@ -6,13 +6,14 @@ using Wox.Infrastructure.Logger;
using Wox.Infrastructure.UserSettings;
using static Wox.Infrastructure.StringMatcher;
namespace Wox.Infrastructure
namespace Wox.Infrastructure
{
public static class StringMatcher
{
public static MatchOption DefaultMatchOption = new MatchOption();
public static string UserSettingSearchPrecision { get; set; }
public static SearchPrecisionScore UserSettingSearchPrecision { get; set; }
public static bool ShouldUsePinyin { get; set; }
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
@ -40,56 +41,104 @@ namespace Wox.Infrastructure
}
/// <summary>
/// refer to https://github.com/mattyork/fuzzy
/// Current method:
/// Character matching + substring matching;
/// 1. Query search string is split into substrings, separator is whitespace.
/// 2. Check each query substring's characters against full compare string,
/// 3. if a character in the substring is matched, loop back to verify the previous character.
/// 4. If previous character also matches, and is the start of the substring, update list.
/// 5. Once the previous character is verified, move on to the next character in the query substring.
/// 6. Move onto the next substring's characters until all substrings are checked.
/// 7. Consider success and move onto scoring if every char or substring without whitespaces matched
/// </summary>
public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt)
{
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult { Success = false };
query = query.Trim();
var len = stringToCompare.Length;
var compareString = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
var pattern = opt.IgnoreCase ? query.ToLower() : query;
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int currentQuerySubstringIndex = 0;
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
var currentQuerySubstringCharacterIndex = 0;
var sb = new StringBuilder(stringToCompare.Length + (query.Length * (opt.Prefix.Length + opt.Suffix.Length)));
var patternIdx = 0;
var firstMatchIndex = -1;
var firstMatchIndexInWord = -1;
var lastMatchIndex = 0;
char ch;
bool allQuerySubstringsMatched = false;
bool matchFoundInPreviousLoop = false;
bool allSubstringsContainedInCompareString = true;
var indexList = new List<int>();
for (var idx = 0; idx < len; idx++)
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
{
ch = stringToCompare[idx];
if (compareString[idx] == pattern[patternIdx])
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
{
if (firstMatchIndex < 0)
firstMatchIndex = idx;
lastMatchIndex = idx + 1;
indexList.Add(idx);
sb.Append(opt.Prefix + ch + opt.Suffix);
patternIdx += 1;
}
else
{
sb.Append(ch);
matchFoundInPreviousLoop = false;
continue;
}
// match success, append remain char
if (patternIdx == pattern.Length && (idx + 1) != compareString.Length)
if (firstMatchIndex < 0)
{
sb.Append(stringToCompare.Substring(idx + 1));
break;
// first matched char will become the start of the compared string
firstMatchIndex = compareStringIndex;
}
if (currentQuerySubstringCharacterIndex == 0)
{
// first letter of current word
matchFoundInPreviousLoop = true;
firstMatchIndexInWord = compareStringIndex;
}
else if (!matchFoundInPreviousLoop)
{
// we want to verify that there is not a better match if this is not a full word
// in order to do so we need to verify all previous chars are part of the pattern
var startIndexToVerify = compareStringIndex - currentQuerySubstringCharacterIndex;
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex, fullStringToCompareWithoutCase, currentQuerySubstring))
{
matchFoundInPreviousLoop = true;
// if it's the beginning character of the first query substring that is matched then we need to update start index
firstMatchIndex = currentQuerySubstringIndex == 0 ? startIndexToVerify : firstMatchIndex;
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, firstMatchIndexInWord, indexList);
}
}
lastMatchIndex = compareStringIndex + 1;
indexList.Add(compareStringIndex);
currentQuerySubstringCharacterIndex++;
// if finished looping through every character in the current substring
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
{
// if any of the substrings was not matched then consider as all are not matched
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
currentQuerySubstringIndex++;
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
if (allQuerySubstringsMatched)
break;
// otherwise move to the next query substring
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
currentQuerySubstringCharacterIndex = 0;
}
}
// return rendered string if we have a match for every char
if (patternIdx == pattern.Length)
// proceed to calculate score if every char or substring without whitespaces matched
if (allQuerySubstringsMatched)
{
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex);
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
var pinyinScore = ScoreForPinyin(stringToCompare, query);
var result = new MatchResult
@ -105,7 +154,44 @@ namespace Wox.Infrastructure
return new MatchResult { Success = false };
}
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen)
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
string fullStringToCompareWithoutCase, string currentQuerySubstring)
{
var allMatch = true;
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
{
if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] !=
currentQuerySubstring[indexToCheck])
{
allMatch = false;
}
}
return allMatch;
}
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
{
var updatedList = new List<int>();
indexList.RemoveAll(x => x >= firstMatchIndexInWord);
updatedList.AddRange(indexList);
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
{
updatedList.Add(startIndexToVerify + indexToCheck);
}
return updatedList;
}
private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength)
{
return currentQuerySubstringIndex >= querySubstringsLength;
}
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString)
{
// A match found near the beginning of a string is scored more than a match found near the end
// A match is scored more if the characters in the patterns are closer to each other,
@ -122,6 +208,13 @@ namespace Wox.Infrastructure
score += 10;
}
if (allSubstringsContainedInCompareString)
{
int count = query.Count(c => !char.IsWhiteSpace(c));
int factor = count < 4 ? 10 : 5;
score += factor * count;
}
return score;
}
@ -143,11 +236,11 @@ namespace Wox.Infrastructure
{
if (Alphabet.ContainsChinese(source))
{
var combination = Alphabet.PinyinComination(source);
var combination = Alphabet.PinyinComination(source);
var pinyinScore = combination
.Select(pinyin => FuzzySearch(target, string.Join("", pinyin)).Score)
.Max();
var acronymScore = combination.Select(Alphabet.Acronym)
var acronymScore = combination.Select(Alphabet.Acronym)
.Select(pinyin => FuzzySearch(target, pinyin).Score)
.Max();
var score = Math.Max(pinyinScore, acronymScore);
@ -162,7 +255,7 @@ namespace Wox.Infrastructure
{
return 0;
}
}
}
}
public class MatchResult
@ -178,6 +271,7 @@ namespace Wox.Infrastructure
/// The raw calculated search score without any search precision filtering applied.
/// </summary>
private int _rawScore;
public int RawScore
{
get { return _rawScore; }
@ -200,10 +294,7 @@ namespace Wox.Infrastructure
private bool IsSearchPrecisionScoreMet(int score)
{
var precisionScore = (SearchPrecisionScore)Enum.Parse(
typeof(SearchPrecisionScore),
UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString());
return score >= (int)precisionScore;
return score >= (int)UserSettingSearchPrecision;
}
private int ApplySearchPrecisionFilter(int score)
@ -214,22 +305,18 @@ namespace Wox.Infrastructure
public class MatchOption
{
public MatchOption()
{
Prefix = "";
Suffix = "";
IgnoreCase = true;
}
/// <summary>
/// prefix of match char, use for hightlight
/// </summary>
public string Prefix { get; set; }
[Obsolete("this is never used")]
public string Prefix { get; set; } = "";
/// <summary>
/// suffix of match char, use for hightlight
/// </summary>
public string Suffix { get; set; }
[Obsolete("this is never used")]
public string Suffix { get; set; } = "";
public bool IgnoreCase { get; set; }
public bool IgnoreCase { get; set; } = true;
}
}
}

View File

@ -36,14 +36,30 @@ namespace Wox.Infrastructure.UserSettings
}
private string _querySearchPrecision { get; set; } = StringMatcher.SearchPrecisionScore.Regular.ToString();
public string QuerySearchPrecision
internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular;
public string QuerySearchPrecisionString
{
get { return _querySearchPrecision; }
get { return QuerySearchPrecision.ToString(); }
set
{
_querySearchPrecision = value;
StringMatcher.UserSettingSearchPrecision = value;
try
{
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
.Parse(typeof(StringMatcher.SearchPrecisionScore), value);
QuerySearchPrecision = precisionScore;
StringMatcher.UserSettingSearchPrecision = precisionScore;
}
catch (ArgumentException e)
{
Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e);
QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
StringMatcher.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
throw;
}
}
}

View File

@ -4,7 +4,6 @@ using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using Wox.Infrastructure;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
namespace Wox.Test
@ -12,17 +11,25 @@ namespace Wox.Test
[TestFixture]
public class FuzzyMatcherTest
{
private const string Chrome = "Chrome";
private const string CandyCrushSagaFromKing = "Candy Crush Saga from King";
private const string HelpCureHopeRaiseOnMindEntityChrome = "Help cure hope raise on mind entity Chrome";
private const string UninstallOrChangeProgramsOnYourComputer = "Uninstall or change programs on your computer";
private const string LastIsChrome = "Last is chrome";
private const string OneOneOneOne = "1111";
private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio";
public List<string> GetSearchStrings()
=> new List<string>
{
"Chrome",
Chrome,
"Choose which programs you want Windows to use for activities like web browsing, editing photos, sending e-mail, and playing music.",
"Help cure hope raise on mind entity Chrome ",
"Candy Crush Saga from King",
"Uninstall or change programs on your computer",
HelpCureHopeRaiseOnMindEntityChrome,
CandyCrushSagaFromKing,
UninstallOrChangeProgramsOnYourComputer,
"Add, change, and manage fonts on your computer",
"Last is chrome",
"1111"
LastIsChrome,
OneOneOneOne
};
public List<int> GetPrecisionScores()
@ -76,17 +83,17 @@ namespace Wox.Test
Assert.True(scoreResult == 0);
}
[TestCase("chr")]
[TestCase("chrom")]
[TestCase("chrome")]
[TestCase("chrome")]
[TestCase("cand")]
[TestCase("cpywa")]
[TestCase("ccs")]
public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
{
var results = new List<Result>();
foreach (var str in GetSearchStrings())
{
results.Add(new Result
@ -94,7 +101,7 @@ namespace Wox.Test
Title = str,
Score = StringMatcher.FuzzySearch(searchTerm, str).Score
});
}
}
foreach (var precisionScore in GetPrecisionScores())
{
@ -114,78 +121,103 @@ namespace Wox.Test
}
}
[TestCase("chrome")]
public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring(string searchTerm)
[TestCase(Chrome, Chrome, 137)]
[TestCase(Chrome, LastIsChrome, 83)]
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 21)]
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 15)]
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, 56)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 79)]//double spacing intended
public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore)
{
var searchStrings = new List<string>
{
"Chrome",//SCORE: 107
"Last is chrome",//SCORE: 53
"Help cure hope raise on mind entity Chrome",//SCORE: 21
"Uninstall or change programs on your computer", //SCORE: 15
"Candy Crush Saga from King"//SCORE: 0
}
.OrderByDescending(x => x)
.ToList();
// When, Given
var rawScore = StringMatcher.FuzzySearch(queryString, compareString).RawScore;
var results = new List<Result>();
foreach (var str in searchStrings)
{
results.Add(new Result
{
Title = str,
Score = StringMatcher.FuzzySearch(searchTerm, str).RawScore
});
}
var orderedResults = results.OrderByDescending(x => x.Title).ToList();
Debug.WriteLine("");
Debug.WriteLine("###############################################");
Debug.WriteLine("SEARCHTERM: " + searchTerm);
foreach (var item in orderedResults)
{
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
}
Debug.WriteLine("###############################################");
Debug.WriteLine("");
Assert.IsTrue(orderedResults[0].Score == 15 && orderedResults[0].Title == searchStrings[0]);
Assert.IsTrue(orderedResults[1].Score == 53 && orderedResults[1].Title == searchStrings[1]);
Assert.IsTrue(orderedResults[2].Score == 21 && orderedResults[2].Title == searchStrings[2]);
Assert.IsTrue(orderedResults[3].Score == 107 && orderedResults[3].Title == searchStrings[3]);
Assert.IsTrue(orderedResults[4].Score == 0 && orderedResults[4].Title == searchStrings[4]);
// Should
Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
}
[TestCase("goo", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.None, true)]
[TestCase("ccs", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("cand", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("cand", "Help cure hope raise on mind entity Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Google Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.None, true)]
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)]
[TestCase("cand", "Candy Crush Saga from King",StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(
string queryString,
string compareString,
int expectedPrecisionScore,
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
bool expectedPrecisionResult)
{
var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore;
StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString();
// When
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore;
// Given
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
Debug.WriteLine("");
Debug.WriteLine("###############################################");
Debug.WriteLine($"SearchTerm: {queryString} PrecisionLevelSetAt: {expectedPrecisionString} ({expectedPrecisionScore})");
Debug.WriteLine($"SCORE: {matchResult.Score.ToString()}, ComparedString: {compareString}");
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
Debug.WriteLine("###############################################");
Debug.WriteLine("");
var matchPrecisionResult = matchResult.IsSearchPrecisionScoreMet();
Assert.IsTrue(matchPrecisionResult == expectedPrecisionResult);
// Should
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
$"Query:{queryString}{Environment.NewLine} " +
$"Compare:{compareString}{Environment.NewLine}" +
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
$"Precision Score: {(int)expectedPrecisionScore}");
}
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("term", "Windows Terminal (Preview)", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sqlserv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql servman", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql serv man", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Shutdown", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("a test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)]
public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings(
string queryString,
string compareString,
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
bool expectedPrecisionResult)
{
// When
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore;
// Given
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
Debug.WriteLine("");
Debug.WriteLine("###############################################");
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
Debug.WriteLine("###############################################");
Debug.WriteLine("");
// Should
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
$"Query:{queryString}{Environment.NewLine} " +
$"Compare:{compareString}{Environment.NewLine}" +
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
$"Precision Score: {(int)expectedPrecisionScore}");
}
}
}
}

View File

@ -62,7 +62,7 @@
<TextBlock Text="{DynamicResource querySearchPrecision}" />
<ComboBox Margin="10 0 0 0" Width="120"
ItemsSource="{Binding QuerySearchPrecisionStrings}"
SelectedItem="{Binding Settings.QuerySearchPrecision}" />
SelectedItem="{Binding Settings.QuerySearchPrecisionString}" />
</StackPanel>
<StackPanel Margin="10" Orientation="Horizontal">
<TextBlock Text="{DynamicResource lastQueryMode}" />

View File

@ -73,7 +73,7 @@ namespace Wox.ViewModel
public List<string> QuerySearchPrecisionStrings
{
get
{
{
var precisionStrings = new List<string>();
var enumList = Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)).Cast<StringMatcher.SearchPrecisionScore>().ToList();