diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index ff72dff1c3..cc1408b533 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -47,8 +47,53 @@ namespace Wox.Infrastructure.Logger return valid; } - /// example: "|prefix|unprefixed" - 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 } - + + /// example: "|prefix|unprefixed" + public static void Error(string message) + { + LogInternal(message, LogLevel.Error); + } + /// example: "|prefix|unprefixed" 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); } /// example: "|prefix|unprefixed" 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); } /// example: "|prefix|unprefixed" 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); } } } \ No newline at end of file diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 58ffa336fc..d1cc1fdecb 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -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 } /// - /// 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 /// 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(); - 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 GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List indexList) + { + var updatedList = new List(); + + 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. /// 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; - } - /// /// prefix of match char, use for hightlight /// - public string Prefix { get; set; } + [Obsolete("this is never used")] + public string Prefix { get; set; } = ""; + /// /// suffix of match char, use for hightlight /// - 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; } -} +} \ No newline at end of file diff --git a/Wox.Infrastructure/UserSettings/Settings.cs b/Wox.Infrastructure/UserSettings/Settings.cs index de5a2e6624..b11ec069ca 100644 --- a/Wox.Infrastructure/UserSettings/Settings.cs +++ b/Wox.Infrastructure/UserSettings/Settings.cs @@ -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; + } } } diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index 21563f91f9..f2de39a289 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -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 GetSearchStrings() => new List { - "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 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(); - + 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 - { - "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(); - 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}"); } } -} +} \ No newline at end of file diff --git a/Wox/SettingWindow.xaml b/Wox/SettingWindow.xaml index 9a5146c7ee..a6f23814ff 100644 --- a/Wox/SettingWindow.xaml +++ b/Wox/SettingWindow.xaml @@ -62,7 +62,7 @@ + SelectedItem="{Binding Settings.QuerySearchPrecisionString}" /> diff --git a/Wox/ViewModel/SettingWindowViewModel.cs b/Wox/ViewModel/SettingWindowViewModel.cs index 67b8d7af06..19a31be58c 100644 --- a/Wox/ViewModel/SettingWindowViewModel.cs +++ b/Wox/ViewModel/SettingWindowViewModel.cs @@ -73,7 +73,7 @@ namespace Wox.ViewModel public List QuerySearchPrecisionStrings { get - { + { var precisionStrings = new List(); var enumList = Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)).Cast().ToList();