diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 2042b73f7..cdb5c4e68 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -66,10 +66,8 @@ namespace Flow.Launcher.Infrastructure var currentAcronymQueryIndex = 0; var acronymMatchData = new List(); - // preset acronymScore - int acronymScore = 100; - int acronymsRemainingNotMatched = 0; - int acronymsMatched = 0; + decimal acronymsTotalCount = 0; + decimal acronymsMatched = 0; var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; @@ -89,21 +87,18 @@ namespace Flow.Launcher.Infrastructure var indexList = new List(); List spaceIndices = new List(); - bool spaceMet = false; - for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) { - if (currentAcronymQueryIndex >= queryWithoutCase.Length && acronymsMatched > 0) + // If acronyms matching successfully finished, this gets the remaining not matched acronyms for score calculation + if (currentAcronymQueryIndex >= query.Length && acronymsMatched == query.Length) { - if (char.IsUpper(stringToCompare[compareStringIndex]) || - char.IsNumber(stringToCompare[compareStringIndex]) || - char.IsWhiteSpace(stringToCompare[compareStringIndex])) - acronymsRemainingNotMatched++; + if (IsAcronym(stringToCompare, compareStringIndex)) + acronymsTotalCount++; continue; } - if (currentAcronymQueryIndex >= queryWithoutCase.Length - || allQuerySubstringsMatched && acronymScore < (int) UserSettingSearchPrecision) + if (currentAcronymQueryIndex >= query.Length || + currentAcronymQueryIndex >= query.Length && allQuerySubstringsMatched) break; // To maintain a list of indices which correspond to spaces in the string to compare @@ -112,43 +107,18 @@ namespace Flow.Launcher.Infrastructure spaceIndices.Add(compareStringIndex); // Acronym check - if (char.IsUpper(stringToCompare[compareStringIndex]) || - char.IsNumber(stringToCompare[compareStringIndex]) || - char.IsWhiteSpace(stringToCompare[compareStringIndex]) || - spaceMet) + if (IsAcronym(stringToCompare, compareStringIndex)) { if (fullStringToCompareWithoutCase[compareStringIndex] == queryWithoutCase[currentAcronymQueryIndex]) { - if (!spaceMet) - { - char currentCompareChar = stringToCompare[compareStringIndex]; - spaceMet = char.IsWhiteSpace(currentCompareChar); - // if is space, no need to check whether upper or digit, though insignificant - if (!spaceMet && compareStringIndex == 0 || char.IsUpper(currentCompareChar) || - char.IsDigit(currentCompareChar)) - { - acronymMatchData.Add(compareStringIndex); - acronymsMatched++; - } - } - else if (!(spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]))) - { - acronymMatchData.Add(compareStringIndex); - acronymsMatched++; - } + acronymMatchData.Add(compareStringIndex); + acronymsMatched++; currentAcronymQueryIndex++; } - else - { - spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]); - // Acronym Penalty - if (!spaceMet) - { - acronymScore -= 10; - } - } + + acronymsTotalCount++; } // Acronym end @@ -217,11 +187,16 @@ namespace Flow.Launcher.Infrastructure } } - // return acronym Match if possible - if (acronymMatchData.Count == query.Length && acronymScore >= (int) UserSettingSearchPrecision) + // return acronym match if all query char matched + if (acronymsMatched > 0 && acronymsMatched == query.Length) { - acronymMatchData = acronymMatchData.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); - return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); + int acronymScore = (int)(acronymsMatched / acronymsTotalCount * 100); + + if (acronymScore >= (int)UserSettingSearchPrecision) + { + acronymMatchData = acronymMatchData.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList(); + return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore); + } } // proceed to calculate score if every char or substring without whitespaces matched @@ -238,6 +213,22 @@ namespace Flow.Launcher.Infrastructure return new MatchResult(false, UserSettingSearchPrecision); } + private bool IsAcronym(string stringToCompare, int compareStringIndex) + { + if (char.IsUpper(stringToCompare[compareStringIndex]) || + char.IsNumber(stringToCompare[compareStringIndex]) || + char.IsDigit(stringToCompare[compareStringIndex])) + return true; + + if (compareStringIndex == 0) + return true; + + if (compareStringIndex != 0 && char.IsWhiteSpace(stringToCompare[compareStringIndex - 1])) + return true; + + return false; + } + // To get the index of the closest space which preceeds the first matching index private int CalculateClosestSpaceIndex(List spaceIndices, int firstMatchIndex) { diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 78c918b61..32d2e8fdb 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -151,13 +151,17 @@ namespace Flow.Launcher.Test [TestCase("goo", "Google Chrome", SearchPrecisionScore.Regular, true)] [TestCase("chr", "Google Chrome", SearchPrecisionScore.Low, true)] [TestCase("chr", "Chrome", SearchPrecisionScore.Regular, true)] + [TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)] [TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Low, true)] [TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.Regular, false)] [TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.None, true)] - [TestCase("ccs", "Candy Crush Saga from King", SearchPrecisionScore.Regular, true)] + [TestCase("ccs", "Candy Crush Saga from King", SearchPrecisionScore.Low, true)] [TestCase("cand", "Candy Crush Saga from King", SearchPrecisionScore.Regular, true)] - [TestCase("cand", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, - false)] + [TestCase("cand", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)] + [TestCase("vsc", VisualStudioCode, SearchPrecisionScore.Regular, true)] + [TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)] + [TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)] + [TestCase("vts", VisualStudioCode, SearchPrecisionScore.Regular, false)] public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( string queryString, string compareString, @@ -188,10 +192,8 @@ namespace Flow.Launcher.Test [TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", SearchPrecisionScore.Regular, false)] [TestCase("term", "Windows Terminal (Preview)", SearchPrecisionScore.Regular, true)] - [TestCase("sql s managa", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, - false)] - [TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, - false)] + [TestCase("sql s managa", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)] + [TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)] [TestCase("sql s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] [TestCase("sql manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] [TestCase("sql", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] @@ -204,18 +206,13 @@ namespace Flow.Launcher.Test [TestCase("mssms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] [TestCase("msms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)] [TestCase("chr", "Shutdown", SearchPrecisionScore.Regular, false)] - [TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", - SearchPrecisionScore.Regular, false)] - [TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", - SearchPrecisionScore.Regular, true)] + [TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, false)] + [TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, true)] [TestCase("a test", "This is a test", SearchPrecisionScore.Regular, true)] [TestCase("test", "This is a test", SearchPrecisionScore.Regular, true)] [TestCase("cod", VisualStudioCode, SearchPrecisionScore.Regular, true)] [TestCase("code", VisualStudioCode, SearchPrecisionScore.Regular, true)] [TestCase("codes", "Visual Studio Codes", SearchPrecisionScore.Regular, true)] - [TestCase("vsc", VisualStudioCode, SearchPrecisionScore.Regular, true)] - [TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)] - [TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)] public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings( string queryString, string compareString, @@ -300,15 +297,18 @@ namespace Flow.Launcher.Test $"Should be greater than{Environment.NewLine}" + $"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}"); } - - [TestCase("vsc","Visual Studio Code", 100)] - [TestCase("jbr","JetBrain Rider",100)] - [TestCase("jr","JetBrain Rider",90)] - [TestCase("vs","Visual Studio",100)] - [TestCase("vs","Visual Studio Preview",100)] - [TestCase("vsp","Visual Studio Preview",100)] - [TestCase("vsp","Visual Studio",0)] - [TestCase("pc","Postman Canary",100)] + + [TestCase("vsc", "Visual Studio Code", 100)] + [TestCase("jbr", "JetBrain Rider", 100)] + [TestCase("jr", "JetBrain Rider", 66)] + [TestCase("vs", "Visual Studio", 100)] + [TestCase("vs", "Visual Studio Preview", 66)] + [TestCase("vsp", "Visual Studio Preview", 100)] + [TestCase("pc", "postman canary", 100)] + [TestCase("psc", "Postman super canary", 100)] + [TestCase("psc", "Postman super Canary", 100)] + [TestCase("vsp", "Visual Studio", 0)] + [TestCase("vps", "Visual Studio", 0)] public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString, int desiredScore) {