mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-01-22 17:13:07 +08:00
Merge pull request #112 from theClueless/fuzzyMatchUpdates
Fuzzy match logic update
This commit is contained in:
commit
6cad4bc986
@ -47,8 +47,53 @@ namespace Wox.Infrastructure.Logger
|
|||||||
return valid;
|
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))
|
if (FormatValid(message))
|
||||||
{
|
{
|
||||||
@ -57,8 +102,8 @@ namespace Wox.Infrastructure.Logger
|
|||||||
var unprefixed = parts[2];
|
var unprefixed = parts[2];
|
||||||
var logger = LogManager.GetLogger(prefix);
|
var logger = LogManager.GetLogger(prefix);
|
||||||
|
|
||||||
System.Diagnostics.Debug.WriteLine($"ERROR|{message}");
|
System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}");
|
||||||
logger.Error(unprefixed);
|
logger.Log(level, unprefixed);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -78,25 +123,7 @@ namespace Wox.Infrastructure.Logger
|
|||||||
var parts = message.Split('|');
|
var parts = message.Split('|');
|
||||||
var prefix = parts[1];
|
var prefix = parts[1];
|
||||||
var unprefixed = parts[2];
|
var unprefixed = parts[2];
|
||||||
var logger = LogManager.GetLogger(prefix);
|
ExceptionInternal(prefix, unprefixed, e);
|
||||||
|
|
||||||
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 --------------------------");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -105,61 +132,28 @@ namespace Wox.Infrastructure.Logger
|
|||||||
#endif
|
#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>
|
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||||
public static void Debug(string message)
|
public static void Debug(string message)
|
||||||
{
|
{
|
||||||
if (FormatValid(message))
|
LogInternal(message, LogLevel.Debug);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||||
public static void Info(string message)
|
public static void Info(string message)
|
||||||
{
|
{
|
||||||
if (FormatValid(message))
|
LogInternal(message, LogLevel.Info);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <param name="message">example: "|prefix|unprefixed" </param>
|
/// <param name="message">example: "|prefix|unprefixed" </param>
|
||||||
public static void Warn(string message)
|
public static void Warn(string message)
|
||||||
{
|
{
|
||||||
if (FormatValid(message))
|
LogInternal(message, LogLevel.Warn);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,7 +12,8 @@ namespace Wox.Infrastructure
|
|||||||
{
|
{
|
||||||
public static MatchOption DefaultMatchOption = new MatchOption();
|
public static MatchOption DefaultMatchOption = new MatchOption();
|
||||||
|
|
||||||
public static string UserSettingSearchPrecision { get; set; }
|
public static SearchPrecisionScore UserSettingSearchPrecision { get; set; }
|
||||||
|
|
||||||
public static bool ShouldUsePinyin { 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")]
|
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
|
||||||
@ -40,7 +41,15 @@ namespace Wox.Infrastructure
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt)
|
public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt)
|
||||||
{
|
{
|
||||||
@ -48,48 +57,88 @@ namespace Wox.Infrastructure
|
|||||||
|
|
||||||
query = query.Trim();
|
query = query.Trim();
|
||||||
|
|
||||||
var len = stringToCompare.Length;
|
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
||||||
var compareString = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
|
||||||
var pattern = opt.IgnoreCase ? query.ToLower() : query;
|
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 firstMatchIndex = -1;
|
||||||
|
var firstMatchIndexInWord = -1;
|
||||||
var lastMatchIndex = 0;
|
var lastMatchIndex = 0;
|
||||||
char ch;
|
bool allQuerySubstringsMatched = false;
|
||||||
|
bool matchFoundInPreviousLoop = false;
|
||||||
|
bool allSubstringsContainedInCompareString = true;
|
||||||
|
|
||||||
var indexList = new List<int>();
|
var indexList = new List<int>();
|
||||||
|
|
||||||
for (var idx = 0; idx < len; idx++)
|
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
|
||||||
{
|
{
|
||||||
ch = stringToCompare[idx];
|
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||||
if (compareString[idx] == pattern[patternIdx])
|
|
||||||
{
|
{
|
||||||
|
matchFoundInPreviousLoop = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (firstMatchIndex < 0)
|
if (firstMatchIndex < 0)
|
||||||
firstMatchIndex = idx;
|
|
||||||
lastMatchIndex = idx + 1;
|
|
||||||
|
|
||||||
indexList.Add(idx);
|
|
||||||
sb.Append(opt.Prefix + ch + opt.Suffix);
|
|
||||||
patternIdx += 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
sb.Append(ch);
|
// first matched char will become the start of the compared string
|
||||||
|
firstMatchIndex = compareStringIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// match success, append remain char
|
if (currentQuerySubstringCharacterIndex == 0)
|
||||||
if (patternIdx == pattern.Length && (idx + 1) != compareString.Length)
|
|
||||||
{
|
{
|
||||||
sb.Append(stringToCompare.Substring(idx + 1));
|
// 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;
|
break;
|
||||||
|
|
||||||
|
// otherwise move to the next query substring
|
||||||
|
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||||
|
currentQuerySubstringCharacterIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return rendered string if we have a match for every char
|
// proceed to calculate score if every char or substring without whitespaces matched
|
||||||
if (patternIdx == pattern.Length)
|
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 pinyinScore = ScoreForPinyin(stringToCompare, query);
|
||||||
|
|
||||||
var result = new MatchResult
|
var result = new MatchResult
|
||||||
@ -105,7 +154,44 @@ namespace Wox.Infrastructure
|
|||||||
return new MatchResult { Success = false };
|
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 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,
|
// 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;
|
score += 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allSubstringsContainedInCompareString)
|
||||||
|
{
|
||||||
|
int count = query.Count(c => !char.IsWhiteSpace(c));
|
||||||
|
int factor = count < 4 ? 10 : 5;
|
||||||
|
score += factor * count;
|
||||||
|
}
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +271,7 @@ namespace Wox.Infrastructure
|
|||||||
/// The raw calculated search score without any search precision filtering applied.
|
/// The raw calculated search score without any search precision filtering applied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int _rawScore;
|
private int _rawScore;
|
||||||
|
|
||||||
public int RawScore
|
public int RawScore
|
||||||
{
|
{
|
||||||
get { return _rawScore; }
|
get { return _rawScore; }
|
||||||
@ -200,10 +294,7 @@ namespace Wox.Infrastructure
|
|||||||
|
|
||||||
private bool IsSearchPrecisionScoreMet(int score)
|
private bool IsSearchPrecisionScoreMet(int score)
|
||||||
{
|
{
|
||||||
var precisionScore = (SearchPrecisionScore)Enum.Parse(
|
return score >= (int)UserSettingSearchPrecision;
|
||||||
typeof(SearchPrecisionScore),
|
|
||||||
UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString());
|
|
||||||
return score >= (int)precisionScore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ApplySearchPrecisionFilter(int score)
|
private int ApplySearchPrecisionFilter(int score)
|
||||||
@ -214,22 +305,18 @@ namespace Wox.Infrastructure
|
|||||||
|
|
||||||
public class MatchOption
|
public class MatchOption
|
||||||
{
|
{
|
||||||
public MatchOption()
|
|
||||||
{
|
|
||||||
Prefix = "";
|
|
||||||
Suffix = "";
|
|
||||||
IgnoreCase = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// prefix of match char, use for hightlight
|
/// prefix of match char, use for hightlight
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Prefix { get; set; }
|
[Obsolete("this is never used")]
|
||||||
|
public string Prefix { get; set; } = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// suffix of match char, use for hightlight
|
/// suffix of match char, use for hightlight
|
||||||
/// </summary>
|
/// </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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -36,14 +36,30 @@ namespace Wox.Infrastructure.UserSettings
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private string _querySearchPrecision { get; set; } = StringMatcher.SearchPrecisionScore.Regular.ToString();
|
internal StringMatcher.SearchPrecisionScore QuerySearchPrecision { get; private set; } = StringMatcher.SearchPrecisionScore.Regular;
|
||||||
public string QuerySearchPrecision
|
|
||||||
|
public string QuerySearchPrecisionString
|
||||||
{
|
{
|
||||||
get { return _querySearchPrecision; }
|
get { return QuerySearchPrecision.ToString(); }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_querySearchPrecision = value;
|
try
|
||||||
StringMatcher.UserSettingSearchPrecision = value;
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Wox.Infrastructure;
|
using Wox.Infrastructure;
|
||||||
using Wox.Infrastructure.UserSettings;
|
|
||||||
using Wox.Plugin;
|
using Wox.Plugin;
|
||||||
|
|
||||||
namespace Wox.Test
|
namespace Wox.Test
|
||||||
@ -12,17 +11,25 @@ namespace Wox.Test
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class FuzzyMatcherTest
|
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()
|
public List<string> GetSearchStrings()
|
||||||
=> new List<string>
|
=> 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.",
|
"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 ",
|
HelpCureHopeRaiseOnMindEntityChrome,
|
||||||
"Candy Crush Saga from King",
|
CandyCrushSagaFromKing,
|
||||||
"Uninstall or change programs on your computer",
|
UninstallOrChangeProgramsOnYourComputer,
|
||||||
"Add, change, and manage fonts on your computer",
|
"Add, change, and manage fonts on your computer",
|
||||||
"Last is chrome",
|
LastIsChrome,
|
||||||
"1111"
|
OneOneOneOne
|
||||||
};
|
};
|
||||||
|
|
||||||
public List<int> GetPrecisionScores()
|
public List<int> GetPrecisionScores()
|
||||||
@ -114,78 +121,103 @@ namespace Wox.Test
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("chrome")]
|
[TestCase(Chrome, Chrome, 137)]
|
||||||
public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring(string searchTerm)
|
[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>
|
// When, Given
|
||||||
{
|
var rawScore = StringMatcher.FuzzySearch(queryString, compareString).RawScore;
|
||||||
"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();
|
|
||||||
|
|
||||||
var results = new List<Result>();
|
// Should
|
||||||
foreach (var str in searchStrings)
|
Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
|
||||||
{
|
|
||||||
results.Add(new Result
|
|
||||||
{
|
|
||||||
Title = str,
|
|
||||||
Score = StringMatcher.FuzzySearch(searchTerm, str).RawScore
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var orderedResults = results.OrderByDescending(x => x.Title).ToList();
|
[TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||||
|
[TestCase("chr", "Google Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||||
Debug.WriteLine("");
|
[TestCase("chr", "Chrome", StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||||
Debug.WriteLine("###############################################");
|
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||||
Debug.WriteLine("SEARCHTERM: " + searchTerm);
|
[TestCase("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||||
foreach (var item in orderedResults)
|
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||||
{
|
[TestCase("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.None, true)]
|
||||||
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title);
|
[TestCase("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)]
|
||||||
}
|
[TestCase("cand", "Candy Crush Saga from King",StringMatcher.SearchPrecisionScore.Regular, true)]
|
||||||
Debug.WriteLine("###############################################");
|
[TestCase("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)]
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[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)]
|
|
||||||
public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(
|
public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(
|
||||||
string queryString,
|
string queryString,
|
||||||
string compareString,
|
string compareString,
|
||||||
int expectedPrecisionScore,
|
StringMatcher.SearchPrecisionScore expectedPrecisionScore,
|
||||||
bool expectedPrecisionResult)
|
bool expectedPrecisionResult)
|
||||||
{
|
{
|
||||||
var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore;
|
// When
|
||||||
StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString();
|
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore;
|
||||||
|
|
||||||
|
// Given
|
||||||
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
|
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
|
||||||
|
|
||||||
Debug.WriteLine("");
|
Debug.WriteLine("");
|
||||||
Debug.WriteLine("###############################################");
|
Debug.WriteLine("###############################################");
|
||||||
Debug.WriteLine($"SearchTerm: {queryString} PrecisionLevelSetAt: {expectedPrecisionString} ({expectedPrecisionScore})");
|
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
|
||||||
Debug.WriteLine($"SCORE: {matchResult.Score.ToString()}, ComparedString: {compareString}");
|
Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})");
|
||||||
Debug.WriteLine("###############################################");
|
Debug.WriteLine("###############################################");
|
||||||
Debug.WriteLine("");
|
Debug.WriteLine("");
|
||||||
|
|
||||||
var matchPrecisionResult = matchResult.IsSearchPrecisionScoreMet();
|
// Should
|
||||||
Assert.IsTrue(matchPrecisionResult == expectedPrecisionResult);
|
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}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -62,7 +62,7 @@
|
|||||||
<TextBlock Text="{DynamicResource querySearchPrecision}" />
|
<TextBlock Text="{DynamicResource querySearchPrecision}" />
|
||||||
<ComboBox Margin="10 0 0 0" Width="120"
|
<ComboBox Margin="10 0 0 0" Width="120"
|
||||||
ItemsSource="{Binding QuerySearchPrecisionStrings}"
|
ItemsSource="{Binding QuerySearchPrecisionStrings}"
|
||||||
SelectedItem="{Binding Settings.QuerySearchPrecision}" />
|
SelectedItem="{Binding Settings.QuerySearchPrecisionString}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="10" Orientation="Horizontal">
|
<StackPanel Margin="10" Orientation="Horizontal">
|
||||||
<TextBlock Text="{DynamicResource lastQueryMode}" />
|
<TextBlock Text="{DynamicResource lastQueryMode}" />
|
||||||
|
Loading…
Reference in New Issue
Block a user