fixes to string matcher alg and some logging stuff

This commit is contained in:
AT
2019-12-30 01:13:33 +02:00
parent c63e8d32c9
commit 42edb20b07
6 changed files with 278 additions and 165 deletions

View File

@@ -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
{ {
@@ -104,62 +131,29 @@ 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);
}
} }
} }
} }

View File

@@ -6,13 +6,14 @@ using Wox.Infrastructure.Logger;
using Wox.Infrastructure.UserSettings; using Wox.Infrastructure.UserSettings;
using static Wox.Infrastructure.StringMatcher; using static Wox.Infrastructure.StringMatcher;
namespace Wox.Infrastructure namespace Wox.Infrastructure
{ {
public static class StringMatcher public static class StringMatcher
{ {
public static MatchOption DefaultMatchOption = new MatchOption(); public static MatchOption DefaultMatchOption = new MatchOption();
public static string UserSettingSearchPrecision { get; set; } public static int 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")]
@@ -45,51 +46,106 @@ namespace Wox.Infrastructure
public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt) public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt)
{ {
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult { Success = false }; if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult { Success = false };
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 sb = new StringBuilder(stringToCompare.Length + (query.Length * (opt.Prefix.Length + opt.Suffix.Length))); var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
var patternIdx = 0;
int currentQueryToCompareIndex = 0;
var queryToCompareSeparated = queryWithoutCase.Split(' ');
var currentQueryToCompare = queryToCompareSeparated[currentQueryToCompareIndex];
var patternIndex = 0;
var firstMatchIndex = -1; var firstMatchIndex = -1;
var firstMatchIndexInWord = -1;
var lastMatchIndex = 0; var lastMatchIndex = 0;
char ch; bool allMatched = false;
bool isFullWordMatched = false;
bool allWordsFullyMatched = true;
var indexList = new List<int>(); var indexList = new List<int>();
for (var idx = 0; idx < len; idx++) for (var index = 0; index < fullStringToCompareWithoutCase.Length; index++)
{ {
ch = stringToCompare[idx]; var ch = stringToCompare[index];
if (compareString[idx] == pattern[patternIdx]) if (fullStringToCompareWithoutCase[index] == currentQueryToCompare[patternIndex])
{ {
if (firstMatchIndex < 0) if (firstMatchIndex < 0)
firstMatchIndex = idx; { // first matched char will become the start of the compared string
lastMatchIndex = idx + 1; firstMatchIndex = index;
}
indexList.Add(idx); if (patternIndex == 0)
sb.Append(opt.Prefix + ch + opt.Suffix); { // first letter of current word
patternIdx += 1; isFullWordMatched = true;
firstMatchIndexInWord = index;
}
else if (!isFullWordMatched)
{ // 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
int startIndexToVerify = index - patternIndex;
bool allMatch = true;
for (int indexToCheck = 0; indexToCheck < patternIndex; indexToCheck++)
{
if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] !=
currentQueryToCompare[indexToCheck])
{
allMatch = false;
}
}
if (allMatch)
{ // update to this as a full word
isFullWordMatched = true;
if (currentQueryToCompareIndex == 0)
{ // first word so we need to update start index
firstMatchIndex = startIndexToVerify;
}
indexList.RemoveAll(x => x >= firstMatchIndexInWord);
for (int indexToCheck = 0; indexToCheck < patternIndex; indexToCheck++)
{ // update the index list
indexList.Add(startIndexToVerify + indexToCheck);
}
}
}
lastMatchIndex = index + 1;
indexList.Add(index);
// increase the pattern matched index and check if everything was matched
if (++patternIndex == currentQueryToCompare.Length)
{
if (++currentQueryToCompareIndex >= queryToCompareSeparated.Length)
{ // moved over all the words
allMatched = true;
break;
}
// otherwise move to the next word
currentQueryToCompare = queryToCompareSeparated[currentQueryToCompareIndex];
patternIndex = 0;
if (!isFullWordMatched)
{ // if any of the words was not fully matched all are not fully matched
allWordsFullyMatched = false;
}
}
} }
else else
{ {
sb.Append(ch); isFullWordMatched = false;
}
// match success, append remain char
if (patternIdx == pattern.Length && (idx + 1) != compareString.Length)
{
sb.Append(stringToCompare.Substring(idx + 1));
break;
} }
} }
// return rendered string if we have a match for every char
if (patternIdx == pattern.Length) // return rendered string if we have a match for every char or all substring without whitespaces matched
if (allMatched)
{ {
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex); // check if all query string was contained in string to compare
bool containedFully = lastMatchIndex - firstMatchIndex == queryWithoutCase.Length;
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, containedFully, allWordsFullyMatched);
var pinyinScore = ScoreForPinyin(stringToCompare, query); var pinyinScore = ScoreForPinyin(stringToCompare, query);
var result = new MatchResult var result = new MatchResult
@@ -105,7 +161,8 @@ 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 int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen,
bool isFullyContained, bool allWordsFullyMatched)
{ {
// 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 +179,16 @@ namespace Wox.Infrastructure
score += 10; score += 10;
} }
if (isFullyContained)
{
score += 20; // honestly I'm not sure what would be a good number here or should it factor the size of the pattern
}
if (allWordsFullyMatched)
{
score += 20;
}
return score; return score;
} }
@@ -143,11 +210,11 @@ namespace Wox.Infrastructure
{ {
if (Alphabet.ContainsChinese(source)) if (Alphabet.ContainsChinese(source))
{ {
var combination = Alphabet.PinyinComination(source); var combination = Alphabet.PinyinComination(source);
var pinyinScore = combination var pinyinScore = combination
.Select(pinyin => FuzzySearch(target, string.Join("", pinyin)).Score) .Select(pinyin => FuzzySearch(target, string.Join("", pinyin)).Score)
.Max(); .Max();
var acronymScore = combination.Select(Alphabet.Acronym) var acronymScore = combination.Select(Alphabet.Acronym)
.Select(pinyin => FuzzySearch(target, pinyin).Score) .Select(pinyin => FuzzySearch(target, pinyin).Score)
.Max(); .Max();
var score = Math.Max(pinyinScore, acronymScore); var score = Math.Max(pinyinScore, acronymScore);
@@ -162,7 +229,7 @@ namespace Wox.Infrastructure
{ {
return 0; return 0;
} }
} }
} }
public class MatchResult public class MatchResult
@@ -178,6 +245,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 +268,7 @@ namespace Wox.Infrastructure
private bool IsSearchPrecisionScoreMet(int score) private bool IsSearchPrecisionScoreMet(int score)
{ {
var precisionScore = (SearchPrecisionScore)Enum.Parse( return score >= UserSettingSearchPrecision;
typeof(SearchPrecisionScore),
UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString());
return score >= (int)precisionScore;
} }
private int ApplySearchPrecisionFilter(int score) private int ApplySearchPrecisionFilter(int score)
@@ -214,22 +279,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;
} }
} }

View File

@@ -36,14 +36,27 @@ 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 = (int)precisionScore;
}
catch (System.Exception e)
{
// what do we do here?!
Logger.Log.Exception(nameof(Settings), "Fail to set QuerySearchPrecision", e);
throw;
}
} }
} }

View File

@@ -12,17 +12,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()
@@ -76,17 +84,17 @@ namespace Wox.Test
Assert.True(scoreResult == 0); Assert.True(scoreResult == 0);
} }
[TestCase("chr")] [TestCase("chr")]
[TestCase("chrom")] [TestCase("chrom")]
[TestCase("chrome")] [TestCase("chrome")]
[TestCase("cand")] [TestCase("cand")]
[TestCase("cpywa")] [TestCase("cpywa")]
[TestCase("ccs")] [TestCase("ccs")]
public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm) public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm)
{ {
var results = new List<Result>(); var results = new List<Result>();
foreach (var str in GetSearchStrings()) foreach (var str in GetSearchStrings())
{ {
results.Add(new Result results.Add(new Result
@@ -94,7 +102,7 @@ namespace Wox.Test
Title = str, Title = str,
Score = StringMatcher.FuzzySearch(searchTerm, str).Score Score = StringMatcher.FuzzySearch(searchTerm, str).Score
}); });
} }
foreach (var precisionScore in GetPrecisionScores()) foreach (var precisionScore in GetPrecisionScores())
{ {
@@ -114,20 +122,23 @@ namespace Wox.Test
} }
} }
[TestCase("chrome")] [TestCase]
public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring(string searchTerm) public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring()
{ {
// Arrange
string searchTerm = "chrome"; // since this looks for specific results it will always be one case
var searchStrings = new List<string> var searchStrings = new List<string>
{ {
"Chrome",//SCORE: 107 Chrome,//SCORE: 107
"Last is chrome",//SCORE: 53 LastIsChrome,//SCORE: 53
"Help cure hope raise on mind entity Chrome",//SCORE: 21 HelpCureHopeRaiseOnMindEntityChrome,//SCORE: 21
"Uninstall or change programs on your computer", //SCORE: 15 UninstallOrChangeProgramsOnYourComputer, //SCORE: 15
"Candy Crush Saga from King"//SCORE: 0 CandyCrushSagaFromKing//SCORE: 0
} }
.OrderByDescending(x => x) .OrderByDescending(x => x)
.ToList(); .ToList();
// Act
var results = new List<Result>(); var results = new List<Result>();
foreach (var str in searchStrings) foreach (var str in searchStrings)
{ {
@@ -138,23 +149,23 @@ namespace Wox.Test
}); });
} }
var orderedResults = results.OrderByDescending(x => x.Title).ToList(); // Assert
VerifyResult(147, Chrome);
VerifyResult(93, LastIsChrome);
VerifyResult(41, HelpCureHopeRaiseOnMindEntityChrome);
VerifyResult(35, UninstallOrChangeProgramsOnYourComputer);
VerifyResult(0, CandyCrushSagaFromKing);
Debug.WriteLine(""); void VerifyResult(int expectedScore, string expectedTitle)
Debug.WriteLine("###############################################");
Debug.WriteLine("SEARCHTERM: " + searchTerm);
foreach (var item in orderedResults)
{ {
Debug.WriteLine("SCORE: " + item.Score.ToString() + ", FoundString: " + item.Title); var result = results.FirstOrDefault(x => x.Title == expectedTitle);
if (result == null)
{
Assert.Fail($"Fail to find result: {expectedTitle} in result list");
}
Assert.AreEqual(expectedScore, result.Score, $"Expected score for {expectedTitle}: {expectedScore}, Actual: {result.Score}");
} }
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]);
} }
[TestCase("goo", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)] [TestCase("goo", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
@@ -168,24 +179,58 @@ namespace Wox.Test
[TestCase("cand", "Candy Crush Saga from King", (int)StringMatcher.SearchPrecisionScore.Regular, 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("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, int expectedPrecisionScore,
bool expectedPrecisionResult) bool expectedPrecisionResult)
{ {
var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore; // Arrange
StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString(); var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore;
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore; // this is why static state is evil...
// Act
var matchResult = StringMatcher.FuzzySearch(queryString, compareString); var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
Debug.WriteLine(""); // Assert
Debug.WriteLine("###############################################"); Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
Debug.WriteLine($"SearchTerm: {queryString} PrecisionLevelSetAt: {expectedPrecisionString} ({expectedPrecisionScore})"); $"Query:{queryString}{Environment.NewLine} " +
Debug.WriteLine($"SCORE: {matchResult.Score.ToString()}, ComparedString: {compareString}"); $"Compare:{compareString}{Environment.NewLine}" +
Debug.WriteLine("###############################################"); $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
Debug.WriteLine(""); $"Precision Level: {expectedPrecisionString}={expectedPrecisionScore}");
}
var matchPrecisionResult = matchResult.IsSearchPrecisionScoreMet(); [TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
Assert.IsTrue(matchPrecisionResult == expectedPrecisionResult); [TestCase("term", "Windows Terminal (Preview)", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql", MicrosoftSqlServerManagementStudio, (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("sql serv", MicrosoftSqlServerManagementStudio, (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("mic", MicrosoftSqlServerManagementStudio, (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("chr", "Shutdown", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", (int)StringMatcher.SearchPrecisionScore.Regular, false)]
[TestCase("a test", "This is a test", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
[TestCase("test", "This is a test", (int)StringMatcher.SearchPrecisionScore.Regular, true)]
public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings(
string queryString,
string compareString,
int expectedPrecisionScore,
bool expectedPrecisionResult)
{
// Arrange
var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore;
StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore; // this is why static state is evil...
// Act
var matchResult = StringMatcher.FuzzySearch(queryString, compareString);
// Assert
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
$"Query:{queryString}{Environment.NewLine} " +
$"Compare:{compareString}{Environment.NewLine}" +
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
$"Precision Level: {expectedPrecisionString}={expectedPrecisionScore}");
} }
} }
} }

View File

@@ -55,7 +55,7 @@ namespace Wox
Alphabet.Initialize(_settings); Alphabet.Initialize(_settings);
StringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision; StringMatcher.UserSettingSearchPrecision = (int)_settings.QuerySearchPrecision;
StringMatcher.ShouldUsePinyin = _settings.ShouldUsePinyin; StringMatcher.ShouldUsePinyin = _settings.ShouldUsePinyin;
PluginManager.LoadPlugins(_settings.PluginSettings); PluginManager.LoadPlugins(_settings.PluginSettings);

View File

@@ -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}" />