From 30c37f06ae9142a172cedbbb980bbd7dcbfdf6d1 Mon Sep 17 00:00:00 2001 From: Alekhya Date: Mon, 1 Jun 2020 15:25:14 -0700 Subject: [PATCH 1/7] Take space into consideration while calculating the first matched index (#3874) --- Flow.Launcher.Infrastructure/StringMatcher.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index aef8bf7ed..94e119bcc 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -83,9 +83,18 @@ namespace Flow.Launcher.Infrastructure bool allSubstringsContainedInCompareString = true; var indexList = new List(); + List spaceIndices = new List(); for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) { + + // To maintain a list of indices which correspond to spaces in the string to compare + // To populate the list only for the first query substring + if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0) + { + spaceIndices.Add(compareStringIndex); + } + if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex]) { matchFoundInPreviousLoop = false; @@ -147,7 +156,8 @@ namespace Flow.Launcher.Infrastructure // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) { - var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); + var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex); + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); return new MatchResult(true, UserSettingSearchPrecision, indexList, score); } @@ -155,6 +165,21 @@ namespace Flow.Launcher.Infrastructure return new MatchResult (false, UserSettingSearchPrecision); } + // To get the index of the closest space which preceeds the first matching index + private int CalculateClosestSpaceIndex(List spaceIndices, int firstMatchIndex) + { + if(spaceIndices.Count == 0) + { + return -1; + } + else + { + int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault(); + int closestSpaceIndex = ind ?? -1; + return closestSpaceIndex; + } + } + private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, string fullStringToCompareWithoutCase, string currentQuerySubstring) { From d35b5169b584973ebd02b7670f0654199702a53d Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 22 Jun 2020 21:09:08 +1000 Subject: [PATCH 2/7] Add unit test more weight to start of new word --- Flow.Launcher.Test/FuzzyMatcherTest.cs | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 68d5d43ac..e65846bd8 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -225,5 +225,33 @@ namespace Flow.Launcher.Test $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + $"Precision Score: {(int)expectedPrecisionScore}"); } + + [TestCase("man", "Task Manager", "eManual")] + [TestCase("term", "Windows Terminal", "Character Map")] + [TestCase("winterm", "Windows Terminal", "Cygwin64 Terminal")] + public void WhenGivenAQueryResultsShouldGiveMoreScoreWeightToStartOfNewWord(string queryString, string compareString1, string compareString2) + { + // When + var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular }; + + // Given + var compareString1Result = matcher.FuzzyMatch(queryString, compareString1); + var compareString2Result = matcher.FuzzyMatch(queryString, compareString2); + + Debug.WriteLine(""); + Debug.WriteLine("###############################################"); + Debug.WriteLine($"QueryString: \"{queryString}\"{Environment.NewLine}"); + Debug.WriteLine($"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}"); + Debug.WriteLine($"CompareString2: \"{compareString2}\", Score: {compareString2Result.Score}{Environment.NewLine}"); + Debug.WriteLine("###############################################"); + Debug.WriteLine(""); + + // Should + Assert.True(compareString1Result.Score > compareString2Result.Score, + $"Query: \"{queryString}\"{Environment.NewLine} " + + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" + + $"Should be greater than{ Environment.NewLine}" + + $"CompareString2: \"{compareString2}\", Score: {compareString1Result.Score}{Environment.NewLine}"); + } } } \ No newline at end of file From 11c4aeae67c51e3f828330c3eabedc6378715428 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 22 Jun 2020 22:36:16 +1000 Subject: [PATCH 3/7] fix testing to be more reflective of testing intent --- Flow.Launcher.Test/FuzzyMatcherTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index e65846bd8..8a023e152 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -185,8 +185,8 @@ namespace Flow.Launcher.Test [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("servez", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)] + [TestCase("sql servz", 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)] From b0ec1375e76ab24ce2fd021e3f98921755ee7034 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 23 Jun 2020 18:50:47 +1000 Subject: [PATCH 4/7] add test to safe guard exact matches --- Flow.Launcher.Test/FuzzyMatcherTest.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 8a023e152..8fec5a168 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -253,5 +253,31 @@ namespace Flow.Launcher.Test $"Should be greater than{ Environment.NewLine}" + $"CompareString2: \"{compareString2}\", Score: {compareString1Result.Score}{Environment.NewLine}"); } + + [TestCase("vim", "Vim", "ignoreDescription", "ignore.exe", "Vim Diff", "ignoreDescription", "ignore.exe")] + public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore( + string queryString, string firstName, string firstDescription, string firstExecutableName, + string secondName, string secondDescription, string secondExecutableName) + { + // Act + var matcher = new StringMatcher(); + var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore; + var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore; + var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore; + + var secondNameMatch = matcher.FuzzyMatch(queryString, secondName).RawScore; + var secondDescriptionMatch = matcher.FuzzyMatch(queryString, secondDescription).RawScore; + var secondExecutableNameMatch = matcher.FuzzyMatch(queryString, secondExecutableName).RawScore; + + var firstScore = new[] { firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch }.Max(); + var secondScore = new[] { secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch }.Max(); + + // Assert + Assert.IsTrue(firstScore > secondScore, + $"Query: \"{queryString}\"{Environment.NewLine} " + + $"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" + + $"Should be greater than{ Environment.NewLine}" + + $"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}"); + } } } \ No newline at end of file From c363b9a5c0ebea75ddb1e17a6d4a63f4f8bc5bfb Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 23 Jun 2020 18:54:39 +1000 Subject: [PATCH 5/7] update tests for string matcher we expect the score to change since we updated the logic for calculating the distance from the first match --- Flow.Launcher.Test/FuzzyMatcherTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 8fec5a168..5ebd4e450 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -124,12 +124,12 @@ namespace Flow.Launcher.Test } [TestCase(Chrome, Chrome, 157)] - [TestCase(Chrome, LastIsChrome, 103)] - [TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 21)] - [TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 15)] + [TestCase(Chrome, LastIsChrome, 147)] + [TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 25)] + [TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)] [TestCase(Chrome, CandyCrushSagaFromKing, 0)] - [TestCase("sql", MicrosoftSqlServerManagementStudio, 56)] - [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 99)]//double spacing intended + [TestCase("sql", MicrosoftSqlServerManagementStudio, 110)] + [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)]//double spacing intended public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore) { // When, Given From c142a50272c166c78825319dde57153e3d01a852 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 23 Jun 2020 19:02:04 +1000 Subject: [PATCH 6/7] update unit test naming, no logic changes --- Flow.Launcher.Test/FuzzyMatcherTest.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 5ebd4e450..468b94457 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -77,7 +77,7 @@ namespace Flow.Launcher.Test } [TestCase("Chrome")] - public void WhenGivenNotAllCharactersFoundInSearchStringThenShouldReturnZeroScore(string searchString) + public void WhenNotAllCharactersFoundInSearchString_ThenShouldReturnZeroScore(string searchString) { var compareString = "Can have rum only in my glass"; var matcher = new StringMatcher(); @@ -92,7 +92,7 @@ namespace Flow.Launcher.Test [TestCase("cand")] [TestCase("cpywa")] [TestCase("ccs")] - public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm) + public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm) { var results = new List(); var matcher = new StringMatcher(); @@ -107,7 +107,10 @@ namespace Flow.Launcher.Test foreach (var precisionScore in GetPrecisionScores()) { - var filteredResult = results.Where(result => result.Score >= precisionScore).Select(result => result).OrderByDescending(x => x.Score).ToList(); + var filteredResult = results.Where(result => result.Score >= precisionScore) + .Select(result => result) + .OrderByDescending(x => x.Score) + .ToList(); Debug.WriteLine(""); Debug.WriteLine("###############################################"); @@ -130,14 +133,16 @@ namespace Flow.Launcher.Test [TestCase(Chrome, CandyCrushSagaFromKing, 0)] [TestCase("sql", MicrosoftSqlServerManagementStudio, 110)] [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)]//double spacing intended - public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore) + public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring( + string queryString, string compareString, int expectedScore) { // When, Given var matcher = new StringMatcher(); var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore; // Should - Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}"); + Assert.AreEqual(expectedScore, rawScore, + $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}"); } [TestCase("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)] @@ -150,7 +155,7 @@ namespace Flow.Launcher.Test [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( + public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( string queryString, string compareString, StringMatcher.SearchPrecisionScore expectedPrecisionScore, @@ -199,7 +204,7 @@ namespace Flow.Launcher.Test [TestCase("cod", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] [TestCase("code", VisualStudioCode, StringMatcher.SearchPrecisionScore.Regular, true)] [TestCase("codes", "Visual Studio Codes", StringMatcher.SearchPrecisionScore.Regular, true)] - public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings( + public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings( string queryString, string compareString, StringMatcher.SearchPrecisionScore expectedPrecisionScore, @@ -229,7 +234,8 @@ namespace Flow.Launcher.Test [TestCase("man", "Task Manager", "eManual")] [TestCase("term", "Windows Terminal", "Character Map")] [TestCase("winterm", "Windows Terminal", "Cygwin64 Terminal")] - public void WhenGivenAQueryResultsShouldGiveMoreScoreWeightToStartOfNewWord(string queryString, string compareString1, string compareString2) + public void WhenGivenAQuery_Scoring_ShouldGiveMoreWeightToStartOfNewWord( + string queryString, string compareString1, string compareString2) { // When var matcher = new StringMatcher { UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular }; From 3abd2d0b07b0ea18b031f2518fe440fdd5c6ae4f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 23 Jun 2020 20:46:36 +1000 Subject: [PATCH 7/7] fix formatting --- Flow.Launcher.Infrastructure/StringMatcher.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 94e119bcc..79ccfd7af 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -162,13 +162,13 @@ namespace Flow.Launcher.Infrastructure return new MatchResult(true, UserSettingSearchPrecision, indexList, score); } - return new MatchResult (false, UserSettingSearchPrecision); + return new MatchResult(false, UserSettingSearchPrecision); } // To get the index of the closest space which preceeds the first matching index private int CalculateClosestSpaceIndex(List spaceIndices, int firstMatchIndex) { - if(spaceIndices.Count == 0) + if (spaceIndices.Count == 0) { return -1; } @@ -180,7 +180,7 @@ namespace Flow.Launcher.Infrastructure } } - private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, + private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, string fullStringToCompareWithoutCase, string currentQuerySubstring) { var allMatch = true; @@ -324,13 +324,13 @@ namespace Flow.Launcher.Infrastructure public class MatchOption { /// - /// prefix of match char, use for hightlight + /// prefix of match char, use for highlight /// [Obsolete("this is never used")] public string Prefix { get; set; } = ""; /// - /// suffix of match char, use for hightlight + /// suffix of match char, use for highlight /// [Obsolete("this is never used")] public string Suffix { get; set; } = "";