From bb6a91124a8901e0d431a68d7a4d0681bbf01580 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Feb 2021 06:50:53 +1100 Subject: [PATCH 01/10] add loop to check remaining acronyms if previous matched --- Flow.Launcher.Infrastructure/StringMatcher.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index c8f22cf7c92..2042b73f788 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -68,6 +68,8 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption // preset acronymScore int acronymScore = 100; + int acronymsRemainingNotMatched = 0; + int acronymsMatched = 0; var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; @@ -91,6 +93,15 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) { + if (currentAcronymQueryIndex >= queryWithoutCase.Length && acronymsMatched > 0) + { + if (char.IsUpper(stringToCompare[compareStringIndex]) || + char.IsNumber(stringToCompare[compareStringIndex]) || + char.IsWhiteSpace(stringToCompare[compareStringIndex])) + acronymsRemainingNotMatched++; + continue; + } + if (currentAcronymQueryIndex >= queryWithoutCase.Length || allQuerySubstringsMatched && acronymScore < (int) UserSettingSearchPrecision) break; @@ -118,11 +129,13 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption char.IsDigit(currentCompareChar)) { acronymMatchData.Add(compareStringIndex); + acronymsMatched++; } } else if (!(spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]))) { acronymMatchData.Add(compareStringIndex); + acronymsMatched++; } currentAcronymQueryIndex++; From 99da6f207ae32da3533284fe89b8806089b457db Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Feb 2021 21:34:35 +1100 Subject: [PATCH 02/10] move acronym check to method and update scoring to percentage based --- Flow.Launcher.Infrastructure/StringMatcher.cs | 85 +++++++++---------- Flow.Launcher.Test/FuzzyMatcherTest.cs | 46 +++++----- 2 files changed, 61 insertions(+), 70 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 2042b73f788..cdb5c4e6896 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -66,10 +66,8 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption 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 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption 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 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption 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 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption } } - // 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 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption 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 78c918b6150..32d2e8fdbf4 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -151,13 +151,17 @@ public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring( [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 @@ public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( [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 @@ public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( [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 @@ public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore( $"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) { From 8fb2dad65c55d5b6704f85635fc2ae96b72acfe8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 1 Feb 2021 22:22:48 +1100 Subject: [PATCH 03/10] add tests --- Flow.Launcher.Test/FuzzyMatcherTest.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 32d2e8fdbf4..61dacf38d67 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -131,16 +131,17 @@ public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreat [TestCase(Chrome, Chrome, 157)] [TestCase(Chrome, LastIsChrome, 147)] - [TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 90)] + [TestCase("chro", HelpCureHopeRaiseOnMindEntityChrome, 50)] + [TestCase("chr", HelpCureHopeRaiseOnMindEntityChrome, 30)] [TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)] [TestCase(Chrome, CandyCrushSagaFromKing, 0)] - [TestCase("sql", MicrosoftSqlServerManagementStudio, 90)] + [TestCase("sql", MicrosoftSqlServerManagementStudio, 110)] [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)] //double spacing intended public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring( string queryString, string compareString, int expectedScore) { // When, Given - var matcher = new StringMatcher(); + var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular}; var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore; // Should @@ -162,6 +163,7 @@ public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring( [TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)] [TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)] [TestCase("vts", VisualStudioCode, SearchPrecisionScore.Regular, false)] + [TestCase("wt", "Windows Terminal From Microsoft Store", SearchPrecisionScore.Regular, false)] public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( string queryString, string compareString, @@ -184,8 +186,8 @@ public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( // Should Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), - $"Query:{queryString}{Environment.NewLine} " + - $"Compare:{compareString}{Environment.NewLine}" + + $"Query: {queryString}{Environment.NewLine} " + + $"Compare: {compareString}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + $"Precision Score: {(int)expectedPrecisionScore}"); } @@ -309,6 +311,7 @@ public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore( [TestCase("psc", "Postman super Canary", 100)] [TestCase("vsp", "Visual Studio", 0)] [TestCase("vps", "Visual Studio", 0)] + [TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 75)] public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString, int desiredScore) { From f32cbaf331dbb606f8e86c35d03bb790c0f148c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 1 Feb 2021 19:57:08 +0800 Subject: [PATCH 04/10] Split name matching and desciption matching due to fuzzy change --- .../Programs/UWP.cs | 54 ++++++++++++------- .../Programs/Win32.cs | 50 +++++++++++------ 2 files changed, 71 insertions(+), 33 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 3ea78156d77..ed81606fc68 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -206,12 +206,12 @@ private static IEnumerable CurrentUserPackages() } catch (Exception e) { - ProgramLogger.LogException("UWP" ,"CurrentUserPackages", $"id","An unexpected error occured and " + ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occured and " + $"unable to verify if package is valid", e); return false; } - - + + return valid; }); return ps; @@ -263,24 +263,42 @@ public class Application : IProgram public string LogoPath { get; set; } public UWP Package { get; set; } - public Application(){} + public Application() { } public Result Result(string query, IPublicAPI api) { - var title = (Name, Description) switch - { - (var n, null) => n, - (var n, var d) when d.StartsWith(n) => d, - (var n, var d) when n.StartsWith(d) => n, - (var n, var d) when !string.IsNullOrEmpty(d) => $"{n}: {d}", - _ => Name - }; + string title; + MatchResult matchResult; - var matchResult = StringMatcher.FuzzySearch(query, title); + // We suppose Name won't be null + if (Description == null || Name.StartsWith(Description)) + { + title = Name; + matchResult = StringMatcher.FuzzySearch(query, title); + } + else if (Description.StartsWith(Name)) + { + title = Description; + matchResult = StringMatcher.FuzzySearch(query, Description); + } + else + { + title = $"{Name}: {Description}"; + var nameMatch = StringMatcher.FuzzySearch(query, Name); + var desciptionMatch = StringMatcher.FuzzySearch(query, Description); + if (desciptionMatch.Score > nameMatch.Score) + { + for (int i = 0; i < desciptionMatch.MatchData.Count; i++) + { + desciptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": " + } + matchResult = desciptionMatch; + } + else matchResult = nameMatch; + } - if (!matchResult.Success) - return null; + if (!matchResult.Success) return null; var result = new Result { @@ -311,7 +329,7 @@ public List ContextMenus(IPublicAPI api) Action = _ => { - Main.StartProcess(Process.Start, + Main.StartProcess(Process.Start, new ProcessStartInfo( !string.IsNullOrEmpty(Main._settings.CustomizedExplorer) ? Main._settings.CustomizedExplorer @@ -403,14 +421,14 @@ internal string ResourceFromPri(string packageFullName, string packageName, stri public string FormattedPriReferenceValue(string packageName, string rawPriReferenceValue) { const string prefix = "ms-resource:"; - + if (string.IsNullOrWhiteSpace(rawPriReferenceValue) || !rawPriReferenceValue.StartsWith(prefix)) return rawPriReferenceValue; string key = rawPriReferenceValue.Substring(prefix.Length); if (key.StartsWith("//")) return $"{prefix}{key}"; - + if (!key.StartsWith("/")) { key = $"/{key}"; diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 77278330a47..fd994aeb347 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -12,6 +12,7 @@ using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.Program.Programs { @@ -36,19 +37,38 @@ public class Win32 : IProgram public Result Result(string query, IPublicAPI api) { - var title = (Name, Description) switch + string title; + MatchResult matchResult; + + // We suppose Name won't be null + if (Description == null || Name.StartsWith(Description)) { - (var n, null) => n, - (var n, var d) when d.StartsWith(n) => d, - (var n, var d) when n.StartsWith(d) => n, - (var n, var d) when !string.IsNullOrEmpty(d) => $"{n}: {d}", - _ => Name - }; + title = Name; + matchResult = StringMatcher.FuzzySearch(query, title); + } + else if (Description.StartsWith(Name)) + { + title = Description; + matchResult = StringMatcher.FuzzySearch(query, Description); + } + else + { + title = $"{Name}: {Description}"; + var nameMatch = StringMatcher.FuzzySearch(query, Name); + var desciptionMatch = StringMatcher.FuzzySearch(query, Description); + if (desciptionMatch.Score > nameMatch.Score) + { + for (int i = 0; i < desciptionMatch.MatchData.Count; i++) + { + desciptionMatch.MatchData[i] += Name.Length + 2; // 2 is ": " + } + matchResult = desciptionMatch; + } + else matchResult = nameMatch; + } - var matchResult = StringMatcher.FuzzySearch(query, title); + if (!matchResult.Success) return null; - if (!matchResult.Success) - return null; var result = new Result { @@ -58,7 +78,7 @@ public Result Result(string query, IPublicAPI api) Score = matchResult.Score, TitleHighlightData = matchResult.MatchData, ContextData = this, - Action = e => + Action = _ => { var info = new ProcessStartInfo { @@ -268,10 +288,10 @@ private static IEnumerable ProgramPaths(string directory, string[] suffi try { var paths = Directory.EnumerateFiles(directory, "*", new EnumerationOptions - { - IgnoreInaccessible = true, - RecurseSubdirectories = true - }) + { + IgnoreInaccessible = true, + RecurseSubdirectories = true + }) .Where(x => suffixes.Contains(Extension(x))); return paths; From 19918244ad1dc0f91b8df7ee24de93ea9f2fdffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Mon, 1 Feb 2021 19:59:56 +0800 Subject: [PATCH 05/10] fix a using --- Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index ed81606fc68..a1f8e52284a 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -18,6 +18,7 @@ using Flow.Launcher.Plugin.Program.Logger; using IStream = AppxPackaing.IStream; using Rect = System.Windows.Rect; +using Flow.Launcher.Plugin.SharedModels; namespace Flow.Launcher.Plugin.Program.Programs { From 838d0ec583af2690d485b6af126bd66b3cea6611 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Feb 2021 05:45:24 +1100 Subject: [PATCH 06/10] combine condition --- Flow.Launcher.Infrastructure/StringMatcher.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index cdb5c4e6896..decfdb27ce4 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -217,10 +217,8 @@ 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) + char.IsDigit(stringToCompare[compareStringIndex]) || + compareStringIndex == 0) //0 index means char is the start of the compare string, which is an acronym return true; if (compareStringIndex != 0 && char.IsWhiteSpace(stringToCompare[compareStringIndex - 1])) From d9a4836118d4da3d7a12f20cc72a472ea6c7f6cc Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Feb 2021 05:50:27 +1100 Subject: [PATCH 07/10] remove spacing --- Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index a1f8e52284a..159c3a9a0ad 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -212,7 +212,6 @@ private static IEnumerable CurrentUserPackages() return false; } - return valid; }); return ps; From 2bdf69b431475530e2b60a7c5258f1b0f08cedcc Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 2 Feb 2021 07:41:36 +1100 Subject: [PATCH 08/10] separate acronym count and check --- Flow.Launcher.Infrastructure/StringMatcher.cs | 48 ++++++++++++++++--- Flow.Launcher.Test/FuzzyMatcherTest.cs | 4 ++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index decfdb27ce4..7a5b09e1011 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -72,7 +72,7 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; - var querySubstrings = queryWithoutCase.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); + var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); int currentQuerySubstringIndex = 0; var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; var currentQuerySubstringCharacterIndex = 0; @@ -92,7 +92,7 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption // If acronyms matching successfully finished, this gets the remaining not matched acronyms for score calculation if (currentAcronymQueryIndex >= query.Length && acronymsMatched == query.Length) { - if (IsAcronym(stringToCompare, compareStringIndex)) + if (IsAcronymCount(stringToCompare, compareStringIndex)) acronymsTotalCount++; continue; } @@ -117,9 +117,11 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption currentAcronymQueryIndex++; } + } + if (IsAcronymCount(stringToCompare, compareStringIndex)) acronymsTotalCount++; - } + // Acronym end if (allQuerySubstringsMatched || fullStringToCompareWithoutCase[compareStringIndex] != @@ -214,14 +216,46 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption } private bool IsAcronym(string stringToCompare, int compareStringIndex) + { + if (IsAcronymChar(stringToCompare, compareStringIndex) || IsAcronymNumber(stringToCompare, compareStringIndex)) + return true; + + return false; + } + + // When counting acronyms, treat a set of numbers as one acronym ie. Visual 2019 as 2 acronyms instead of 5 + private bool IsAcronymCount(string stringToCompare, int compareStringIndex) + { + if (IsAcronymChar(stringToCompare, compareStringIndex)) + return true; + + if (char.IsNumber(stringToCompare[compareStringIndex]) || char.IsDigit(stringToCompare[compareStringIndex])) + { + return compareStringIndex switch + { + int i when i == 0 => true, + int i when char.IsWhiteSpace(stringToCompare[i - 1]) => true, + _ => false, + }; + } + + return false; + } + + private bool IsAcronymChar(string stringToCompare, int compareStringIndex) { if (char.IsUpper(stringToCompare[compareStringIndex]) || - char.IsNumber(stringToCompare[compareStringIndex]) || - char.IsDigit(stringToCompare[compareStringIndex]) || - compareStringIndex == 0) //0 index means char is the start of the compare string, which is an acronym + compareStringIndex == 0 || //0 index means char is the start of the compare string, which is an acronym + compareStringIndex != 0 && char.IsWhiteSpace(stringToCompare[compareStringIndex - 1])) return true; - if (compareStringIndex != 0 && char.IsWhiteSpace(stringToCompare[compareStringIndex - 1])) + return false; + } + + private bool IsAcronymNumber(string stringToCompare, int compareStringIndex) + { + if (char.IsNumber(stringToCompare[compareStringIndex]) || + char.IsDigit(stringToCompare[compareStringIndex])) return true; return false; diff --git a/Flow.Launcher.Test/FuzzyMatcherTest.cs b/Flow.Launcher.Test/FuzzyMatcherTest.cs index 61dacf38d67..bbddcbd2ad4 100644 --- a/Flow.Launcher.Test/FuzzyMatcherTest.cs +++ b/Flow.Launcher.Test/FuzzyMatcherTest.cs @@ -163,7 +163,11 @@ public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring( [TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)] [TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)] [TestCase("vts", VisualStudioCode, SearchPrecisionScore.Regular, false)] + [TestCase("vcs", VisualStudioCode, SearchPrecisionScore.Regular, false)] [TestCase("wt", "Windows Terminal From Microsoft Store", SearchPrecisionScore.Regular, false)] + [TestCase("vsp", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)] + [TestCase("vsp", "2019 Visual Studio Preview", SearchPrecisionScore.Regular, true)] + [TestCase("2019p", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)] public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual( string queryString, string compareString, From f9b7294b9341736fb91a509e5d86ac84e796f74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 2 Feb 2021 12:38:41 +0800 Subject: [PATCH 09/10] Optimize code --- Flow.Launcher.Infrastructure/StringMatcher.cs | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 7a5b09e1011..48d89b86207 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -229,52 +229,35 @@ private bool IsAcronymCount(string stringToCompare, int compareStringIndex) if (IsAcronymChar(stringToCompare, compareStringIndex)) return true; - if (char.IsNumber(stringToCompare[compareStringIndex]) || char.IsDigit(stringToCompare[compareStringIndex])) - { - return compareStringIndex switch - { - int i when i == 0 => true, - int i when char.IsWhiteSpace(stringToCompare[i - 1]) => true, - _ => false, - }; - } - - return false; - } + if (IsAcronymNumber(stringToCompare, compareStringIndex)) + return compareStringIndex == 0 || char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]); - private bool IsAcronymChar(string stringToCompare, int compareStringIndex) - { - if (char.IsUpper(stringToCompare[compareStringIndex]) || - compareStringIndex == 0 || //0 index means char is the start of the compare string, which is an acronym - compareStringIndex != 0 && char.IsWhiteSpace(stringToCompare[compareStringIndex - 1])) - return true; return false; } - private bool IsAcronymNumber(string stringToCompare, int compareStringIndex) - { - if (char.IsNumber(stringToCompare[compareStringIndex]) || - char.IsDigit(stringToCompare[compareStringIndex])) - return true; + private bool IsAcronymChar(string stringToCompare, int compareStringIndex) + => char.IsUpper(stringToCompare[compareStringIndex]) || + compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym + char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]); - return false; - } + private bool IsAcronymNumber(string stringToCompare, int compareStringIndex) => stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9; // 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 + var closestSpaceIndex = -1; + + // spaceIndices should be ordered asc + foreach (var index in spaceIndices) { - int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)) - .FirstOrDefault(item => firstMatchIndex > item); - int closestSpaceIndex = ind ?? -1; - return closestSpaceIndex; + if (index < firstMatchIndex) + closestSpaceIndex = index; + else + break; } + + return closestSpaceIndex; } private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, From 65a6548c56a23c043bb95afb75bef58c51ad2d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=98=E9=9F=AC=20=E5=BC=A0?= Date: Tue, 2 Feb 2021 12:42:55 +0800 Subject: [PATCH 10/10] Use int instead of decimal --- Flow.Launcher.Infrastructure/StringMatcher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/StringMatcher.cs b/Flow.Launcher.Infrastructure/StringMatcher.cs index 48d89b86207..6d53cc72204 100644 --- a/Flow.Launcher.Infrastructure/StringMatcher.cs +++ b/Flow.Launcher.Infrastructure/StringMatcher.cs @@ -66,8 +66,8 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption var currentAcronymQueryIndex = 0; var acronymMatchData = new List(); - decimal acronymsTotalCount = 0; - decimal acronymsMatched = 0; + int acronymsTotalCount = 0; + int acronymsMatched = 0; var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; @@ -192,7 +192,7 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption // return acronym match if all query char matched if (acronymsMatched > 0 && acronymsMatched == query.Length) { - int acronymScore = (int)(acronymsMatched / acronymsTotalCount * 100); + int acronymScore = acronymsMatched * 100 / acronymsTotalCount; if (acronymScore >= (int)UserSettingSearchPrecision) {