From dde03eca7cf633c1f0a1f6df4dc5a2318b9ffe30 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 13 Dec 2019 01:48:05 +0200 Subject: [PATCH 01/42] started update with cancellation token --- Wox/ViewModel/MainViewModel.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Wox/ViewModel/MainViewModel.cs b/Wox/ViewModel/MainViewModel.cs index a7e17d9a7..78ccc54d9 100644 --- a/Wox/ViewModel/MainViewModel.cs +++ b/Wox/ViewModel/MainViewModel.cs @@ -372,7 +372,7 @@ namespace Wox.ViewModel { _updateSource?.Cancel(); _updateSource = new CancellationTokenSource(); - _updateToken = _updateSource.Token; + var updateToken = _updateSource.Token; ProgressBarVisibility = Visibility.Hidden; _queryHasReturn = false; @@ -402,18 +402,19 @@ namespace Wox.ViewModel } _lastQuery = query; - Task.Delay(200, _updateToken).ContinueWith(_ => + Task.Delay(200, updateToken).ContinueWith(_ => { if (query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) { ProgressBarVisibility = Visibility.Visible; } - }, _updateToken); + }, updateToken); var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(() => { - Parallel.ForEach(plugins, plugin => + var parallelOptions = new ParallelOptions {CancellationToken = updateToken}; // so looping will stop once it was cancelled + Parallel.ForEach(plugins, parallelOptions, plugin => { var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; if (!config.Disabled) @@ -421,13 +422,13 @@ namespace Wox.ViewModel var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); } - }); + });// TODO add cancel code. // this should happen once after all queries are done so progress bar should continue // until the end of all querying _queryHasReturn = true; ProgressBarVisibility = Visibility.Hidden; - }, _updateToken); + }, updateToken); } } else From ced0faf9164a11012b5f3276d3acf3416986589c Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Sat, 14 Dec 2019 00:06:13 +0200 Subject: [PATCH 02/42] results and query update fixes --- Wox.Infrastructure/Image/ImageLoader.cs | 2 +- Wox/MainWindow.xaml | 2 +- Wox/ViewModel/MainViewModel.cs | 77 ++++++++++++++----------- Wox/ViewModel/ResultsViewModel.cs | 6 +- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 3498e4f3b..184f78cad 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -133,7 +133,7 @@ namespace Wox.Infrastructure.Image } catch (System.Exception e) { - Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); + // Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); image = ImageCache[Constant.ErrorIcon]; ImageCache[path] = image; diff --git a/Wox/MainWindow.xaml b/Wox/MainWindow.xaml index 7dfe8cd1e..cc50e76dc 100644 --- a/Wox/MainWindow.xaml +++ b/Wox/MainWindow.xaml @@ -56,7 +56,7 @@ - { - if (query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) + Task.Delay(200, currentCancellationToken).ContinueWith(_ => + { // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet + if (currentUpdateSource == _updateSource && _isQueryRunning) { ProgressBarVisibility = Visibility.Visible; } - }, updateToken); + }, currentCancellationToken); var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(() => { - var parallelOptions = new ParallelOptions {CancellationToken = updateToken}; // so looping will stop once it was cancelled + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; Parallel.ForEach(plugins, parallelOptions, plugin => { var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; @@ -422,13 +404,16 @@ namespace Wox.ViewModel var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); } - });// TODO add cancel code. + }); // this should happen once after all queries are done so progress bar should continue // until the end of all querying - _queryHasReturn = true; - ProgressBarVisibility = Visibility.Hidden; - }, updateToken); + _isQueryRunning = false; + if (currentUpdateSource == _updateSource) + { // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; + } + }, currentCancellationToken); } } else @@ -438,6 +423,30 @@ namespace Wox.ViewModel } } + private void RemoveOldQueryResults(Query query) + { + string lastKeyword = _lastQuery.ActionKeyword; + string keyword = query.ActionKeyword; + if (string.IsNullOrEmpty(lastKeyword)) + { + if (!string.IsNullOrEmpty(keyword)) + { + Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + } + } + else + { + if (string.IsNullOrEmpty(keyword)) + { + Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); + } + else if (lastKeyword != keyword) + { + Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + } + } + } + private Result ContextMenuTopMost(Result result) { diff --git a/Wox/ViewModel/ResultsViewModel.cs b/Wox/ViewModel/ResultsViewModel.cs index e36ddc94d..45eb4bec2 100644 --- a/Wox/ViewModel/ResultsViewModel.cs +++ b/Wox/ViewModel/ResultsViewModel.cs @@ -248,6 +248,10 @@ namespace Wox.ViewModel } } + /// + /// Update the results collection with new results, try to keep identical results + /// + /// public void Update(List newItems) { int newCount = newItems.Count; @@ -259,7 +263,7 @@ namespace Wox.ViewModel ResultViewModel oldResult = this[i]; ResultViewModel newResult = newItems[i]; if (!oldResult.Equals(newResult)) - { + { // result is not the same update it in the current index this[i] = newResult; } else if (oldResult.Result.Score != newResult.Result.Score) From 3dfccea5229dc9943cca16760ce9b5ed011860c7 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Sat, 14 Dec 2019 00:07:27 +0200 Subject: [PATCH 03/42] Revert "results and query update fixes" This reverts commit ced0faf9164a11012b5f3276d3acf3416986589c. --- Wox.Infrastructure/Image/ImageLoader.cs | 2 +- Wox/MainWindow.xaml | 2 +- Wox/ViewModel/MainViewModel.cs | 77 +++++++++++-------------- Wox/ViewModel/ResultsViewModel.cs | 6 +- 4 files changed, 37 insertions(+), 50 deletions(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 184f78cad..3498e4f3b 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -133,7 +133,7 @@ namespace Wox.Infrastructure.Image } catch (System.Exception e) { - // Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); + Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); image = ImageCache[Constant.ErrorIcon]; ImageCache[path] = image; diff --git a/Wox/MainWindow.xaml b/Wox/MainWindow.xaml index cc50e76dc..7dfe8cd1e 100644 --- a/Wox/MainWindow.xaml +++ b/Wox/MainWindow.xaml @@ -56,7 +56,7 @@ - { // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet - if (currentUpdateSource == _updateSource && _isQueryRunning) + Task.Delay(200, updateToken).ContinueWith(_ => + { + if (query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) { ProgressBarVisibility = Visibility.Visible; } - }, currentCancellationToken); + }, updateToken); var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(() => { - // so looping will stop once it was cancelled - var parallelOptions = new ParallelOptions {CancellationToken = currentCancellationToken}; + var parallelOptions = new ParallelOptions {CancellationToken = updateToken}; // so looping will stop once it was cancelled Parallel.ForEach(plugins, parallelOptions, plugin => { var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; @@ -404,16 +422,13 @@ namespace Wox.ViewModel var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); } - }); + });// TODO add cancel code. // this should happen once after all queries are done so progress bar should continue // until the end of all querying - _isQueryRunning = false; - if (currentUpdateSource == _updateSource) - { // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; - } - }, currentCancellationToken); + _queryHasReturn = true; + ProgressBarVisibility = Visibility.Hidden; + }, updateToken); } } else @@ -423,30 +438,6 @@ namespace Wox.ViewModel } } - private void RemoveOldQueryResults(Query query) - { - string lastKeyword = _lastQuery.ActionKeyword; - string keyword = query.ActionKeyword; - if (string.IsNullOrEmpty(lastKeyword)) - { - if (!string.IsNullOrEmpty(keyword)) - { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); - } - } - else - { - if (string.IsNullOrEmpty(keyword)) - { - Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); - } - else if (lastKeyword != keyword) - { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); - } - } - } - private Result ContextMenuTopMost(Result result) { diff --git a/Wox/ViewModel/ResultsViewModel.cs b/Wox/ViewModel/ResultsViewModel.cs index 45eb4bec2..e36ddc94d 100644 --- a/Wox/ViewModel/ResultsViewModel.cs +++ b/Wox/ViewModel/ResultsViewModel.cs @@ -248,10 +248,6 @@ namespace Wox.ViewModel } } - /// - /// Update the results collection with new results, try to keep identical results - /// - /// public void Update(List newItems) { int newCount = newItems.Count; @@ -263,7 +259,7 @@ namespace Wox.ViewModel ResultViewModel oldResult = this[i]; ResultViewModel newResult = newItems[i]; if (!oldResult.Equals(newResult)) - { // result is not the same update it in the current index + { this[i] = newResult; } else if (oldResult.Result.Score != newResult.Result.Score) From e6e1aab0984827f7ac92eda24ad3bdfd3b15287f Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Sat, 14 Dec 2019 00:17:05 +0200 Subject: [PATCH 04/42] updates --- Wox/MainWindow.xaml | 2 +- Wox/ViewModel/MainViewModel.cs | 84 +++++++++++++++++-------------- Wox/ViewModel/ResultsViewModel.cs | 12 +++-- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/Wox/MainWindow.xaml b/Wox/MainWindow.xaml index 7dfe8cd1e..cc50e76dc 100644 --- a/Wox/MainWindow.xaml +++ b/Wox/MainWindow.xaml @@ -56,7 +56,7 @@ StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() + r => StringMatcher.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || StringMatcher.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() ).ToList(); ContextMenu.AddResults(filtered, id); @@ -371,49 +370,33 @@ namespace Wox.ViewModel if (!string.IsNullOrEmpty(QueryText)) { _updateSource?.Cancel(); - _updateSource = new CancellationTokenSource(); - var updateToken = _updateSource.Token; + var currentUpdateSource = new CancellationTokenSource(); + _updateSource = currentUpdateSource; + var currentCancellationToken = _updateSource.Token; + _updateToken = currentCancellationToken; ProgressBarVisibility = Visibility.Hidden; - _queryHasReturn = false; + _isQueryRunning = true; var query = PluginManager.QueryInit(QueryText.Trim()); if (query != null) { // handle the exclusiveness of plugin using action keyword - string lastKeyword = _lastQuery.ActionKeyword; - string keyword = query.ActionKeyword; - if (string.IsNullOrEmpty(lastKeyword)) - { - if (!string.IsNullOrEmpty(keyword)) - { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); - } - } - else - { - if (string.IsNullOrEmpty(keyword)) - { - Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); - } - else if (lastKeyword != keyword) - { - Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); - } - } + RemoveOldQueryResults(query); _lastQuery = query; - Task.Delay(200, updateToken).ContinueWith(_ => - { - if (query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) + Task.Delay(200, currentCancellationToken).ContinueWith(_ => + { // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet + if (currentUpdateSource == _updateSource && _isQueryRunning) { ProgressBarVisibility = Visibility.Visible; } - }, updateToken); + }, currentCancellationToken); var plugins = PluginManager.ValidPluginsForQuery(query); Task.Run(() => { - var parallelOptions = new ParallelOptions {CancellationToken = updateToken}; // so looping will stop once it was cancelled + // so looping will stop once it was cancelled + var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; Parallel.ForEach(plugins, parallelOptions, plugin => { var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; @@ -422,13 +405,16 @@ namespace Wox.ViewModel var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); } - });// TODO add cancel code. + }); // this should happen once after all queries are done so progress bar should continue // until the end of all querying - _queryHasReturn = true; - ProgressBarVisibility = Visibility.Hidden; - }, updateToken); + _isQueryRunning = false; + if (currentUpdateSource == _updateSource) + { // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; + } + }, currentCancellationToken); } } else @@ -438,6 +424,30 @@ namespace Wox.ViewModel } } + private void RemoveOldQueryResults(Query query) + { + string lastKeyword = _lastQuery.ActionKeyword; + string keyword = query.ActionKeyword; + if (string.IsNullOrEmpty(lastKeyword)) + { + if (!string.IsNullOrEmpty(keyword)) + { + Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + } + } + else + { + if (string.IsNullOrEmpty(keyword)) + { + Results.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); + } + else if (lastKeyword != keyword) + { + Results.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + } + } + } + private Result ContextMenuTopMost(Result result) { @@ -661,4 +671,4 @@ namespace Wox.ViewModel #endregion } -} +} \ No newline at end of file diff --git a/Wox/ViewModel/ResultsViewModel.cs b/Wox/ViewModel/ResultsViewModel.cs index e36ddc94d..76a7ee75b 100644 --- a/Wox/ViewModel/ResultsViewModel.cs +++ b/Wox/ViewModel/ResultsViewModel.cs @@ -156,14 +156,14 @@ namespace Wox.ViewModel private List NewResults(List newRawResults, string resultId) { var results = Results.ToList(); - var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList(); + var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList(); var oldResults = results.Where(r => r.Result.PluginID == resultId).ToList(); // Find the same results in A (old results) and B (new newResults) var sameResults = oldResults .Where(t1 => newResults.Any(x => x.Result.Equals(t1.Result))) .ToList(); - + // remove result of relative complement of B in A foreach (var result in oldResults.Except(sameResults)) { @@ -248,6 +248,10 @@ namespace Wox.ViewModel } } + /// + /// Update the results collection with new results, try to keep identical results + /// + /// public void Update(List newItems) { int newCount = newItems.Count; @@ -259,7 +263,7 @@ namespace Wox.ViewModel ResultViewModel oldResult = this[i]; ResultViewModel newResult = newItems[i]; if (!oldResult.Equals(newResult)) - { + { // result is not the same update it in the current index this[i] = newResult; } else if (oldResult.Result.Score != newResult.Result.Score) @@ -286,4 +290,4 @@ namespace Wox.ViewModel } } } -} +} \ No newline at end of file From 4c2a09369d36d0df526c66d27acb0edd4243856f Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Sat, 14 Dec 2019 00:41:29 +0200 Subject: [PATCH 05/42] added catch --- Wox/ViewModel/MainViewModel.cs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Wox/ViewModel/MainViewModel.cs b/Wox/ViewModel/MainViewModel.cs index bf8530df7..bd1314a27 100644 --- a/Wox/ViewModel/MainViewModel.cs +++ b/Wox/ViewModel/MainViewModel.cs @@ -397,15 +397,23 @@ namespace Wox.ViewModel { // so looping will stop once it was cancelled var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken }; - Parallel.ForEach(plugins, parallelOptions, plugin => + try { - var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; - if (!config.Disabled) + Parallel.ForEach(plugins, parallelOptions, plugin => { - var results = PluginManager.QueryForPlugin(plugin, query); - UpdateResultView(results, plugin.Metadata, query); - } - }); + var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; + if (!config.Disabled) + { + var results = PluginManager.QueryForPlugin(plugin, query); + UpdateResultView(results, plugin.Metadata, query); + } + }); + } + catch (OperationCanceledException) + { + // nothing to do here + } + // this should happen once after all queries are done so progress bar should continue // until the end of all querying From 42edb20b07fb5f44fe18bf72ad144ec95d8996cc Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Mon, 30 Dec 2019 01:13:33 +0200 Subject: [PATCH 06/42] fixes to string matcher alg and some logging stuff --- Wox.Infrastructure/Logger/Log.cs | 126 ++++++++-------- Wox.Infrastructure/StringMatcher.cs | 155 ++++++++++++++------ Wox.Infrastructure/UserSettings/Settings.cs | 23 ++- Wox.Test/FuzzyMatcherTest.cs | 135 +++++++++++------ Wox/App.xaml.cs | 2 +- Wox/SettingWindow.xaml | 2 +- 6 files changed, 278 insertions(+), 165 deletions(-) diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index ff72dff1c..cc1408b53 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 58ffa336f..deff9ff7b 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 int 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")] @@ -45,51 +46,106 @@ namespace Wox.Infrastructure 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 sb = new StringBuilder(stringToCompare.Length + (query.Length * (opt.Prefix.Length + opt.Suffix.Length))); - var patternIdx = 0; + var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; + + int currentQueryToCompareIndex = 0; + var queryToCompareSeparated = queryWithoutCase.Split(' '); + var currentQueryToCompare = queryToCompareSeparated[currentQueryToCompareIndex]; + + var patternIndex = 0; var firstMatchIndex = -1; + var firstMatchIndexInWord = -1; var lastMatchIndex = 0; - char ch; + bool allMatched = false; + bool isFullWordMatched = false; + bool allWordsFullyMatched = true; var indexList = new List(); - for (var idx = 0; idx < len; idx++) + for (var index = 0; index < fullStringToCompareWithoutCase.Length; index++) { - ch = stringToCompare[idx]; - if (compareString[idx] == pattern[patternIdx]) + var ch = stringToCompare[index]; + if (fullStringToCompareWithoutCase[index] == currentQueryToCompare[patternIndex]) { if (firstMatchIndex < 0) - firstMatchIndex = idx; - lastMatchIndex = idx + 1; + { // first matched char will become the start of the compared string + firstMatchIndex = index; + } - indexList.Add(idx); - sb.Append(opt.Prefix + ch + opt.Suffix); - patternIdx += 1; + if (patternIndex == 0) + { // first letter of current word + 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 { - sb.Append(ch); - } - - // match success, append remain char - if (patternIdx == pattern.Length && (idx + 1) != compareString.Length) - { - sb.Append(stringToCompare.Substring(idx + 1)); - break; + isFullWordMatched = false; } } - // 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 result = new MatchResult @@ -105,7 +161,8 @@ namespace Wox.Infrastructure 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 is scored more if the characters in the patterns are closer to each other, @@ -122,6 +179,16 @@ namespace Wox.Infrastructure 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; } @@ -143,11 +210,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 +229,7 @@ namespace Wox.Infrastructure { return 0; } - } + } } public class MatchResult @@ -178,6 +245,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 +268,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 >= UserSettingSearchPrecision; } private int ApplySearchPrecisionFilter(int score) @@ -214,22 +279,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 de5a2e662..5a129832a 100644 --- a/Wox.Infrastructure/UserSettings/Settings.cs +++ b/Wox.Infrastructure/UserSettings/Settings.cs @@ -36,14 +36,27 @@ 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 = (int)precisionScore; + } + catch (System.Exception e) + { + // what do we do here?! + Logger.Log.Exception(nameof(Settings), "Fail to set QuerySearchPrecision", e); + throw; + } } } diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index 21563f91f..b5fe58cac 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -12,17 +12,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 +84,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 +102,7 @@ namespace Wox.Test Title = str, Score = StringMatcher.FuzzySearch(searchTerm, str).Score }); - } + } foreach (var precisionScore in GetPrecisionScores()) { @@ -114,20 +122,23 @@ namespace Wox.Test } } - [TestCase("chrome")] - public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring(string searchTerm) + [TestCase] + public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring() { + // Arrange + string searchTerm = "chrome"; // since this looks for specific results it will always be one case 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 + Chrome,//SCORE: 107 + LastIsChrome,//SCORE: 53 + HelpCureHopeRaiseOnMindEntityChrome,//SCORE: 21 + UninstallOrChangeProgramsOnYourComputer, //SCORE: 15 + CandyCrushSagaFromKing//SCORE: 0 } .OrderByDescending(x => x) .ToList(); + // Act var results = new List(); 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(""); - Debug.WriteLine("###############################################"); - Debug.WriteLine("SEARCHTERM: " + searchTerm); - foreach (var item in orderedResults) + void VerifyResult(int expectedScore, string expectedTitle) { - 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)] @@ -168,24 +179,58 @@ namespace Wox.Test [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)] public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual( - string queryString, - string compareString, - int expectedPrecisionScore, + string queryString, + string compareString, + int expectedPrecisionScore, bool expectedPrecisionResult) { - var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore; - StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString(); + // Arrange + var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore; + StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore; // this is why static state is evil... + + // Act 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("###############################################"); - Debug.WriteLine(""); + // Assert + Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), + $"Query:{queryString}{Environment.NewLine} " + + $"Compare:{compareString}{Environment.NewLine}" + + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + + $"Precision Level: {expectedPrecisionString}={expectedPrecisionScore}"); + } - var matchPrecisionResult = matchResult.IsSearchPrecisionScoreMet(); - Assert.IsTrue(matchPrecisionResult == expectedPrecisionResult); + [TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", (int)StringMatcher.SearchPrecisionScore.Regular, false)] + [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}"); } } -} +} \ No newline at end of file diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index 9436df475..aa5426d06 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -55,7 +55,7 @@ namespace Wox Alphabet.Initialize(_settings); - StringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision; + StringMatcher.UserSettingSearchPrecision = (int)_settings.QuerySearchPrecision; StringMatcher.ShouldUsePinyin = _settings.ShouldUsePinyin; PluginManager.LoadPlugins(_settings.PluginSettings); diff --git a/Wox/SettingWindow.xaml b/Wox/SettingWindow.xaml index 9a5146c7e..a6f23814f 100644 --- a/Wox/SettingWindow.xaml +++ b/Wox/SettingWindow.xaml @@ -62,7 +62,7 @@ + SelectedItem="{Binding Settings.QuerySearchPrecisionString}" /> From 34342599b937be250a4791ce229a66e83cb817f2 Mon Sep 17 00:00:00 2001 From: theClueless <14300910+theClueless@users.noreply.github.com> Date: Mon, 30 Dec 2019 01:28:10 +0200 Subject: [PATCH 07/42] Update MainWindow.xaml removed delay in binding --- Wox/MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Wox/MainWindow.xaml b/Wox/MainWindow.xaml index cc50e76dc..d50411b83 100644 --- a/Wox/MainWindow.xaml +++ b/Wox/MainWindow.xaml @@ -56,7 +56,7 @@ - \ No newline at end of file + From 52615c6f52d1d483afeab31c57fa8f8895db745e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 2 Jan 2020 08:02:23 +1100 Subject: [PATCH 08/42] WIP variables --- Wox.Infrastructure/StringMatcher.cs | 36 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index deff9ff7b..91ac09f01 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -52,12 +52,12 @@ namespace Wox.Infrastructure var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; + + var separatedqueryStrings = queryWithoutCase.Split(' '); + int currentSeparatedQueryStringIndex = 0; + var currentSeparatedQueryString = separatedqueryStrings[currentSeparatedQueryStringIndex]; - int currentQueryToCompareIndex = 0; - var queryToCompareSeparated = queryWithoutCase.Split(' '); - var currentQueryToCompare = queryToCompareSeparated[currentQueryToCompareIndex]; - - var patternIndex = 0; + var queryIndex = 0; var firstMatchIndex = -1; var firstMatchIndexInWord = -1; var lastMatchIndex = 0; @@ -70,14 +70,14 @@ namespace Wox.Infrastructure for (var index = 0; index < fullStringToCompareWithoutCase.Length; index++) { var ch = stringToCompare[index]; - if (fullStringToCompareWithoutCase[index] == currentQueryToCompare[patternIndex]) + if (fullStringToCompareWithoutCase[index] == currentSeparatedQueryString[queryIndex]) { if (firstMatchIndex < 0) { // first matched char will become the start of the compared string firstMatchIndex = index; } - if (patternIndex == 0) + if (queryIndex == 0) { // first letter of current word isFullWordMatched = true; firstMatchIndexInWord = index; @@ -85,12 +85,12 @@ namespace Wox.Infrastructure 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; + int startIndexToVerify = index - queryIndex; bool allMatch = true; - for (int indexToCheck = 0; indexToCheck < patternIndex; indexToCheck++) + for (int indexToCheck = 0; indexToCheck < queryIndex; indexToCheck++) { if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] != - currentQueryToCompare[indexToCheck]) + currentSeparatedQueryString[indexToCheck]) { allMatch = false; } @@ -99,13 +99,13 @@ namespace Wox.Infrastructure if (allMatch) { // update to this as a full word isFullWordMatched = true; - if (currentQueryToCompareIndex == 0) + if (currentSeparatedQueryStringIndex == 0) { // first word so we need to update start index firstMatchIndex = startIndexToVerify; } indexList.RemoveAll(x => x >= firstMatchIndexInWord); - for (int indexToCheck = 0; indexToCheck < patternIndex; indexToCheck++) + for (int indexToCheck = 0; indexToCheck < queryIndex; indexToCheck++) { // update the index list indexList.Add(startIndexToVerify + indexToCheck); } @@ -115,18 +115,22 @@ namespace Wox.Infrastructure lastMatchIndex = index + 1; indexList.Add(index); + queryIndex++; + // increase the pattern matched index and check if everything was matched - if (++patternIndex == currentQueryToCompare.Length) + if (queryIndex == currentSeparatedQueryString.Length) { - if (++currentQueryToCompareIndex >= queryToCompareSeparated.Length) + currentSeparatedQueryStringIndex++; + + if (currentSeparatedQueryStringIndex >= separatedqueryStrings.Length) { // moved over all the words allMatched = true; break; } // otherwise move to the next word - currentQueryToCompare = queryToCompareSeparated[currentQueryToCompareIndex]; - patternIndex = 0; + currentSeparatedQueryString = separatedqueryStrings[currentSeparatedQueryStringIndex]; + queryIndex = 0; if (!isFullWordMatched) { // if any of the words was not fully matched all are not fully matched allWordsFullyMatched = false; From f6d0738c79636918d141930956ecf4ebdfcbee9f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 2 Jan 2020 08:04:16 +1100 Subject: [PATCH 09/42] debug logging --- Wox.Test/FuzzyMatcherTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index b5fe58cac..3091102c7 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -191,6 +191,13 @@ namespace Wox.Test // Act var matchResult = StringMatcher.FuzzySearch(queryString, compareString); + Debug.WriteLine(""); + Debug.WriteLine("###############################################"); + Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); + Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionString} ({expectedPrecisionScore})"); + Debug.WriteLine("###############################################"); + Debug.WriteLine(""); + // Assert Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), $"Query:{queryString}{Environment.NewLine} " + @@ -225,6 +232,13 @@ namespace Wox.Test // Act var matchResult = StringMatcher.FuzzySearch(queryString, compareString); + Debug.WriteLine(""); + Debug.WriteLine("###############################################"); + Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); + Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionString} ({expectedPrecisionScore})"); + Debug.WriteLine("###############################################"); + Debug.WriteLine(""); + // Assert Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), $"Query:{queryString}{Environment.NewLine} " + From 84d6fc2787cdd6ebddbd80febc42e9e1d61e3e77 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 3 Jan 2020 07:58:20 +1100 Subject: [PATCH 10/42] Update variable names Make variables more descriptive of the state they represent --- Wox.Infrastructure/StringMatcher.cs | 128 +++++++++++++--------------- 1 file changed, 59 insertions(+), 69 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 91ac09f01..db84d302e 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -52,100 +52,90 @@ namespace Wox.Infrastructure var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare; var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; - - var separatedqueryStrings = queryWithoutCase.Split(' '); - int currentSeparatedQueryStringIndex = 0; - var currentSeparatedQueryString = separatedqueryStrings[currentSeparatedQueryStringIndex]; + + var querySubstrings = queryWithoutCase.Split(' '); + int currentQuerySubstringIndex = 0; + var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; + var currentQuerySubstringCharacterIndex = 0; - var queryIndex = 0; var firstMatchIndex = -1; var firstMatchIndexInWord = -1; var lastMatchIndex = 0; - bool allMatched = false; - bool isFullWordMatched = false; + bool allQuerySubstringsMatched = false; + bool matchFoundInPreviousLoop = false; bool allWordsFullyMatched = true; var indexList = new List(); - for (var index = 0; index < fullStringToCompareWithoutCase.Length; index++) + for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) { - var ch = stringToCompare[index]; - if (fullStringToCompareWithoutCase[index] == currentSeparatedQueryString[queryIndex]) + if (fullStringToCompareWithoutCase[compareStringIndex] == currentQuerySubstring[currentQuerySubstringCharacterIndex]) { if (firstMatchIndex < 0) - { // first matched char will become the start of the compared string - firstMatchIndex = index; - } - - if (queryIndex == 0) - { // first letter of current word - 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 - queryIndex; - bool allMatch = true; - for (int indexToCheck = 0; indexToCheck < queryIndex; indexToCheck++) - { - if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] != - currentSeparatedQueryString[indexToCheck]) - { - allMatch = false; - } - } - - if (allMatch) - { // update to this as a full word - isFullWordMatched = true; - if (currentSeparatedQueryStringIndex == 0) - { // first word so we need to update start index - firstMatchIndex = startIndexToVerify; - } - - indexList.RemoveAll(x => x >= firstMatchIndexInWord); - for (int indexToCheck = 0; indexToCheck < queryIndex; indexToCheck++) - { // update the index list - indexList.Add(startIndexToVerify + indexToCheck); - } - } - } - - lastMatchIndex = index + 1; - indexList.Add(index); - - queryIndex++; - - // increase the pattern matched index and check if everything was matched - if (queryIndex == currentSeparatedQueryString.Length) { - currentSeparatedQueryStringIndex++; + // first matched char will become the start of the compared string + firstMatchIndex = compareStringIndex; + } - if (currentSeparatedQueryStringIndex >= separatedqueryStrings.Length) - { // moved over all the words - allMatched = true; + 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 begining 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 substring + if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length) + { + currentQuerySubstringIndex++; + + // if all query substrings are matched + if (currentQuerySubstringIndex >= querySubstrings.Length) + { + allQuerySubstringsMatched = true; break; } - // otherwise move to the next word - currentSeparatedQueryString = separatedqueryStrings[currentSeparatedQueryStringIndex]; - queryIndex = 0; - if (!isFullWordMatched) - { // if any of the words was not fully matched all are not fully matched + // otherwise move to the next query substring + currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; + currentQuerySubstringCharacterIndex = 0; + + if (!matchFoundInPreviousLoop) + { + // if any of the words was not fully matched all are not fully matched allWordsFullyMatched = false; } } } else { - isFullWordMatched = false; + matchFoundInPreviousLoop = false; } } - - + // return rendered string if we have a match for every char or all substring without whitespaces matched - if (allMatched) + if (allQuerySubstringsMatched) { // check if all query string was contained in string to compare bool containedFully = lastMatchIndex - firstMatchIndex == queryWithoutCase.Length; From 220dbd7e304ef21e44f5d7c03ec7b01392c2f2eb Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 3 Jan 2020 08:02:02 +1100 Subject: [PATCH 11/42] Move some logic into functions - Move checking if there is a prev compare string char match into function - Move updating of index list when a better match is found for the first substring logic into function --- Wox.Infrastructure/StringMatcher.cs | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index db84d302e..e361c0c18 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -155,6 +155,38 @@ namespace Wox.Infrastructure return new MatchResult { Success = false }; } + 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 int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool isFullyContained, bool allWordsFullyMatched) { From b14d6c9216209db195530e3b0aee67a4657dac13 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 21:16:17 +0200 Subject: [PATCH 12/42] adding hash ability to image loader (reducing the load on memory) --- Wox.Infrastructure/Image/ImageCache.cs | 13 +++ .../Image/ImageHashGenerator.cs | 47 +++++++++ Wox.Infrastructure/Image/ImageLoader.cs | 99 +++++++++++++++---- Wox.Infrastructure/Image/ThumbnailReader.cs | 1 - Wox.Infrastructure/Wox.Infrastructure.csproj | 1 + 5 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 Wox.Infrastructure/Image/ImageHashGenerator.cs diff --git a/Wox.Infrastructure/Image/ImageCache.cs b/Wox.Infrastructure/Image/ImageCache.cs index 599b69066..5e74a2a38 100644 --- a/Wox.Infrastructure/Image/ImageCache.cs +++ b/Wox.Infrastructure/Image/ImageCache.cs @@ -39,6 +39,19 @@ namespace Wox.Infrastructure.Image var contains = _data.ContainsKey(key); return contains; } + + public int CacheSize() + { + return _data.Count; + } + + /// + /// return the number of unique images in the cache (by reference not by checking images content) + /// + public int UniqueImagesInCache() + { + return _data.Values.Distinct().Count(); + } } } diff --git a/Wox.Infrastructure/Image/ImageHashGenerator.cs b/Wox.Infrastructure/Image/ImageHashGenerator.cs new file mode 100644 index 000000000..96361d815 --- /dev/null +++ b/Wox.Infrastructure/Image/ImageHashGenerator.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace Wox.Infrastructure.Image +{ + public interface IImageHashGenerator + { + string GetHashFromImage(ImageSource image); + } + public class ImageHashGenerator : IImageHashGenerator + { + public string GetHashFromImage(ImageSource imageSource) + { + if (!(imageSource is BitmapSource image)) + { + return null; + } + + try + { + using (var outStream = new MemoryStream()) + { + // PngBitmapEncoder enc2 = new PngBitmapEncoder(); + // enc2.Frames.Add(BitmapFrame.Create(tt)); + + var enc = new JpegBitmapEncoder(); + enc.Frames.Add(BitmapFrame.Create(image)); + enc.Save(outStream); + var byteArray = outStream.GetBuffer(); + using (var sha1 = new SHA1CryptoServiceProvider()) + { + var hash = Convert.ToBase64String(sha1.ComputeHash(byteArray)); + return hash; + } + } + } + catch + { + return null; + } + + } + } +} \ No newline at end of file diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 3498e4f3b..8479ae94a 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -14,6 +15,8 @@ namespace Wox.Infrastructure.Image { private static readonly ImageCache ImageCache = new ImageCache(); private static BinaryStorage> _storage; + private static readonly ConcurrentDictionary GuidToKey = new ConcurrentDictionary(); + private static IImageHashGenerator _hashGenerator; private static readonly string[] ImageExtensions = @@ -30,7 +33,8 @@ namespace Wox.Infrastructure.Image public static void Initialize() { - _storage = new BinaryStorage> ("Image"); + _storage = new BinaryStorage>("Image"); + _hashGenerator = new ImageHashGenerator(); ImageCache.Usage = _storage.TryLoad(new ConcurrentDictionary()); foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon }) @@ -43,16 +47,12 @@ namespace Wox.Infrastructure.Image { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(i => + ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(x => { - var img = Load(i.Key); - if (img != null) - { - ImageCache[i.Key] = img; - } + Load(x.Key); }); }); - Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>"); + Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); }); } @@ -61,31 +61,54 @@ namespace Wox.Infrastructure.Image ImageCache.Cleanup(); _storage.Save(ImageCache.Usage); } - - public static ImageSource Load(string path, bool loadFullImage = false) + + private class ImageResult + { + public ImageResult(ImageSource imageSource, ImageType imageType) + { + ImageSource = imageSource; + ImageType = imageType; + } + + public ImageType ImageType { get; } + public ImageSource ImageSource { get; } + } + + private enum ImageType + { + File, + Folder, + Data, + ImageFile, + Error, + Cache + } + + private static ImageResult LoadInternal(string path, bool loadFullImage = false) { ImageSource image; + ImageType type = ImageType.Error; try { if (string.IsNullOrEmpty(path)) { - return ImageCache[Constant.ErrorIcon]; + return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error); } if (ImageCache.ContainsKey(path)) { - return ImageCache[path]; + return new ImageResult(ImageCache[path], ImageType.Cache); } - + if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) { - return new BitmapImage(new Uri(path)); + return new ImageResult(new BitmapImage(new Uri(path)), ImageType.Data); } if (!Path.IsPathRooted(path)) { path = Path.Combine(Constant.ProgramDirectory, "Images", Path.GetFileName(path)); } - + if (Directory.Exists(path)) { /* Directories can also have thumbnails instead of shell icons. @@ -94,14 +117,17 @@ namespace Wox.Infrastructure.Image * Wox responsibility. * - Solution: just load the icon */ + type = ImageType.Folder; image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.IconOnly); + } else if (File.Exists(path)) { var extension = Path.GetExtension(path).ToLower(); if (ImageExtensions.Contains(extension)) { + type = ImageType.ImageFile; if (loadFullImage) { image = LoadFullImage(path); @@ -119,6 +145,7 @@ namespace Wox.Infrastructure.Image } else { + type = ImageType.File; image = WindowsThumbnailProvider.GetThumbnail(path, Constant.ThumbnailSize, Constant.ThumbnailSize, ThumbnailOptions.None); } @@ -128,17 +155,51 @@ namespace Wox.Infrastructure.Image image = ImageCache[Constant.ErrorIcon]; path = Constant.ErrorIcon; } - ImageCache[path] = image; - image.Freeze(); + + if (type != ImageType.Error) + { + image.Freeze(); + } } catch (System.Exception e) { Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); - + type = ImageType.Error; image = ImageCache[Constant.ErrorIcon]; ImageCache[path] = image; } - return image; + return new ImageResult(image, type); + } + + private static bool EnableImageHash = true; + + public static ImageSource Load(string path, bool loadFullImage = false) + { + // return LoadInternal(path, loadFullImage).ImageSource; + var imageResult = LoadInternal(path, loadFullImage); + + var img = imageResult.ImageSource; + if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) + { // we need to get image hash + string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; + if (hash != null) + { + if (GuidToKey.TryGetValue(hash, out string key)) + { // image already exists + img = ImageCache[key]; + } + else + { // new guid + GuidToKey[hash] = path; + } + } + + // update cache + ImageCache[path] = img; + } + + + return img; } private static BitmapImage LoadFullImage(string path) diff --git a/Wox.Infrastructure/Image/ThumbnailReader.cs b/Wox.Infrastructure/Image/ThumbnailReader.cs index e0ea9bba3..bd65fc700 100644 --- a/Wox.Infrastructure/Image/ThumbnailReader.cs +++ b/Wox.Infrastructure/Image/ThumbnailReader.cs @@ -110,7 +110,6 @@ namespace Wox.Infrastructure.Image try { - return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } finally diff --git a/Wox.Infrastructure/Wox.Infrastructure.csproj b/Wox.Infrastructure/Wox.Infrastructure.csproj index af76894ed..bd14c5603 100644 --- a/Wox.Infrastructure/Wox.Infrastructure.csproj +++ b/Wox.Infrastructure/Wox.Infrastructure.csproj @@ -71,6 +71,7 @@ + From 72e1a19ea5df36a09012ce8236423f5ada33bb91 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 21:16:38 +0200 Subject: [PATCH 13/42] remove using --- Wox.Infrastructure/Image/ImageLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 8479ae94a..e4ff51cd5 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; From e80147d24e2e47af4f5d22fd31c26faa06b56f7f Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 21:17:58 +0200 Subject: [PATCH 14/42] removed a duplicate check --- Wox.Infrastructure/Image/ImageLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index e4ff51cd5..761613bc4 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -46,7 +46,7 @@ namespace Wox.Infrastructure.Image { Stopwatch.Normal("|ImageLoader.Initialize|Preload images cost", () => { - ImageCache.Usage.AsParallel().Where(i => !ImageCache.ContainsKey(i.Key)).ForAll(x => + ImageCache.Usage.AsParallel().ForAll(x => { Load(x.Key); }); From 05bd32f750c4bb9f8b4f670cf661bb8b235dfe56 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 22:01:15 +0200 Subject: [PATCH 15/42] remove comment --- Wox.Infrastructure/Image/ImageLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 761613bc4..59a7a24fb 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -174,7 +174,6 @@ namespace Wox.Infrastructure.Image public static ImageSource Load(string path, bool loadFullImage = false) { - // return LoadInternal(path, loadFullImage).ImageSource; var imageResult = LoadInternal(path, loadFullImage); var img = imageResult.ImageSource; From fd59088528481d25615579a8a32968b057c6f6c2 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 3 Jan 2020 22:33:00 +0200 Subject: [PATCH 16/42] made data images freeze as well --- Wox.Infrastructure/Image/ImageLoader.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 59a7a24fb..528900ce7 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -100,7 +100,9 @@ namespace Wox.Infrastructure.Image if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) { - return new ImageResult(new BitmapImage(new Uri(path)), ImageType.Data); + var imageSource = new BitmapImage(new Uri(path)); + imageSource.Freeze(); + return new ImageResult(imageSource, ImageType.Data); } if (!Path.IsPathRooted(path)) @@ -181,7 +183,7 @@ namespace Wox.Infrastructure.Image { // we need to get image hash string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; if (hash != null) - { + { if (GuidToKey.TryGetValue(hash, out string key)) { // image already exists img = ImageCache[key]; @@ -195,7 +197,7 @@ namespace Wox.Infrastructure.Image // update cache ImageCache[path] = img; } - + return img; } From 28b098cfb7361ad1ab72f2bcae98ee7a53dc0fec Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Sat, 4 Jan 2020 00:40:37 +0200 Subject: [PATCH 17/42] make image created in hash freeze --- Wox.Infrastructure/Image/ImageHashGenerator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageHashGenerator.cs b/Wox.Infrastructure/Image/ImageHashGenerator.cs index 96361d815..9ace8b74f 100644 --- a/Wox.Infrastructure/Image/ImageHashGenerator.cs +++ b/Wox.Infrastructure/Image/ImageHashGenerator.cs @@ -27,7 +27,9 @@ namespace Wox.Infrastructure.Image // enc2.Frames.Add(BitmapFrame.Create(tt)); var enc = new JpegBitmapEncoder(); - enc.Frames.Add(BitmapFrame.Create(image)); + var bitmapFrame = BitmapFrame.Create(image); + bitmapFrame.Freeze(); + enc.Frames.Add(bitmapFrame); enc.Save(outStream); var byteArray = outStream.GetBuffer(); using (var sha1 = new SHA1CryptoServiceProvider()) From 42a938b50b6382ba6248a346436f0bbcf99462e6 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 6 Jan 2020 19:15:05 +1100 Subject: [PATCH 18/42] Simplify IfElse --- Wox.Infrastructure/StringMatcher.cs | 119 ++++++++++++++-------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index e361c0c18..0b0767f58 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -69,68 +69,67 @@ namespace Wox.Infrastructure for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++) { - if (fullStringToCompareWithoutCase[compareStringIndex] == currentQuerySubstring[currentQuerySubstringCharacterIndex]) - { - if (firstMatchIndex < 0) - { - // 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 begining 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 substring - if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length) - { - currentQuerySubstringIndex++; - - // if all query substrings are matched - if (currentQuerySubstringIndex >= querySubstrings.Length) - { - allQuerySubstringsMatched = true; - break; - } - - // otherwise move to the next query substring - currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; - currentQuerySubstringCharacterIndex = 0; - - if (!matchFoundInPreviousLoop) - { - // if any of the words was not fully matched all are not fully matched - allWordsFullyMatched = false; - } - } - } - else + if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex]) { matchFoundInPreviousLoop = false; + continue; + } + + if (firstMatchIndex < 0) + { + // 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 begining 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 substring + if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length) + { + currentQuerySubstringIndex++; + + // if all query substrings are matched + if (currentQuerySubstringIndex >= querySubstrings.Length) + { + allQuerySubstringsMatched = true; + break; + } + + // otherwise move to the next query substring + currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; + currentQuerySubstringCharacterIndex = 0; + + if (!matchFoundInPreviousLoop) + { + // if any of the words was not fully matched all are not fully matched + allWordsFullyMatched = false; + } } } From e453dceacdb2be2db06c01bfeed505722640fdb8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 6 Jan 2020 20:51:27 +1100 Subject: [PATCH 19/42] Move condition checking into functions - Moved if statement that checks if all query substrings are matched into a funciton - convert into shorthand expression the if statement that checks if all words are fully matched --- Wox.Infrastructure/StringMatcher.cs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 0b0767f58..2d74c2f12 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -109,34 +109,28 @@ namespace Wox.Infrastructure currentQuerySubstringCharacterIndex++; - // if finished looping through every character in the substring + // if finished looping through every character in the current substring if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length) { currentQuerySubstringIndex++; - // if all query substrings are matched - if (currentQuerySubstringIndex >= querySubstrings.Length) - { - allQuerySubstringsMatched = true; + allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); + if (allQuerySubstringsMatched) break; - } // otherwise move to the next query substring currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; currentQuerySubstringCharacterIndex = 0; - if (!matchFoundInPreviousLoop) - { - // if any of the words was not fully matched all are not fully matched - allWordsFullyMatched = false; - } + // if any of the substrings was not matched then consider as all are not matched + allWordsFullyMatched = !matchFoundInPreviousLoop ? false : allWordsFullyMatched; } } // return rendered string if we have a match for every char or all substring without whitespaces matched if (allQuerySubstringsMatched) { - // check if all query string was contained in string to compare + // check if all query substrings were contained in the string to compare bool containedFully = lastMatchIndex - firstMatchIndex == queryWithoutCase.Length; var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, containedFully, allWordsFullyMatched); var pinyinScore = ScoreForPinyin(stringToCompare, query); @@ -186,6 +180,11 @@ namespace Wox.Infrastructure 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 isFullyContained, bool allWordsFullyMatched) { From 04b0f8b2a4cbfb427a9b8067ebacf11e03204511 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 6 Jan 2020 21:06:41 +1100 Subject: [PATCH 20/42] Remove fuzzy match github repo reference + add logic context in summary 1. Remove the github repo reference as we have mixed in substring matching 2. Added context on how the logic is run --- Wox.Infrastructure/StringMatcher.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 2d74c2f12..d71dddb23 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -41,7 +41,13 @@ namespace Wox.Infrastructure } /// - /// refer to https://github.com/mattyork/fuzzy + /// Current method: + /// Character matching + substring matching; + /// 1. Check query substring's character against full compare string, + /// 2. if matched, loop back to verify the previous character. + /// 3. If previous character also matches, and is the start of the substring, update list. + /// 4. Once the previous character is verified, move on to the next character in the query substring. + /// 5. Consider success and move onto scoring if every char or substring without whitespaces matched /// public static MatchResult FuzzySearch(string query, string stringToCompare, MatchOption opt) { @@ -127,7 +133,7 @@ namespace Wox.Infrastructure } } - // return rendered string if we have a match for every char or all substring without whitespaces matched + // return rendered string if every char or substring without whitespaces matched if (allQuerySubstringsMatched) { // check if all query substrings were contained in the string to compare From 19911d9f1f0d3d5cc9f5d370f46105f93589f162 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 6 Jan 2020 21:19:15 +1100 Subject: [PATCH 21/42] Update comment only --- Wox.Infrastructure/StringMatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index d71dddb23..902490e2a 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -133,7 +133,7 @@ namespace Wox.Infrastructure } } - // return rendered string if every char or substring without whitespaces matched + // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) { // check if all query substrings were contained in the string to compare From 5040f09f0c149db2be2210241cec10c0aede2f56 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 6 Jan 2020 21:38:07 +1100 Subject: [PATCH 22/42] Update method summary only --- Wox.Infrastructure/StringMatcher.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 902490e2a..cfdb0880a 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -41,13 +41,15 @@ namespace Wox.Infrastructure } /// - /// Current method: + /// Current method: /// Character matching + substring matching; - /// 1. Check query substring's character against full compare string, - /// 2. if matched, loop back to verify the previous character. - /// 3. If previous character also matches, and is the start of the substring, update list. - /// 4. Once the previous character is verified, move on to the next character in the query substring. - /// 5. Consider success and move onto scoring if every char or substring without whitespaces matched + /// 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) { From e4b017b3040444f11d6af27b613e56b1acbd9999 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 05:59:47 +1100 Subject: [PATCH 23/42] fix index out of range exception occurs when query contains more than one whitespace eg. 'sql manag' --- Wox.Infrastructure/StringMatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index cfdb0880a..d8c6ae215 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -61,7 +61,7 @@ namespace Wox.Infrastructure var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query; - var querySubstrings = queryWithoutCase.Split(' '); + var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); int currentQuerySubstringIndex = 0; var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; var currentQuerySubstringCharacterIndex = 0; From 13996740e032d8568aebbc64a4e91e9220609b06 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 07:12:34 +1100 Subject: [PATCH 24/42] Add additional test which should pass for regular precision --- Wox.Test/FuzzyMatcherTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index 3091102c7..7eb16c8a0 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -214,6 +214,7 @@ namespace Wox.Test [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("sql studio", 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)] From dde658a514eb504c0d0aa5ec99ba6e26295869de Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 07:22:00 +1100 Subject: [PATCH 25/42] rename variable state allWordsFullyMatched --- Wox.Infrastructure/StringMatcher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index d8c6ae215..45fbe5808 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -71,7 +71,7 @@ namespace Wox.Infrastructure var lastMatchIndex = 0; bool allQuerySubstringsMatched = false; bool matchFoundInPreviousLoop = false; - bool allWordsFullyMatched = true; + bool allSubstringsContainedInCompareString = true; var indexList = new List(); @@ -131,7 +131,7 @@ namespace Wox.Infrastructure currentQuerySubstringCharacterIndex = 0; // if any of the substrings was not matched then consider as all are not matched - allWordsFullyMatched = !matchFoundInPreviousLoop ? false : allWordsFullyMatched; + allSubstringsContainedInCompareString = !matchFoundInPreviousLoop ? false : allSubstringsContainedInCompareString; } } @@ -140,7 +140,7 @@ namespace Wox.Infrastructure { // check if all query substrings were contained in the string to compare bool containedFully = lastMatchIndex - firstMatchIndex == queryWithoutCase.Length; - var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, containedFully, allWordsFullyMatched); + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, containedFully, allSubstringsContainedInCompareString); var pinyinScore = ScoreForPinyin(stringToCompare, query); var result = new MatchResult From 0093838a7535b92f996170e3dc090fd284b0207e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 07:25:13 +1100 Subject: [PATCH 26/42] fix variable state which failed to represent correctly Failed if query text is 'sql servman'- returns true when should be false - moved it up so evaluation is included in the final substring check --- Wox.Infrastructure/StringMatcher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 45fbe5808..1868eee6e 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -120,6 +120,9 @@ namespace Wox.Infrastructure // 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 ? false : allSubstringsContainedInCompareString; + currentQuerySubstringIndex++; allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length); @@ -129,9 +132,6 @@ namespace Wox.Infrastructure // otherwise move to the next query substring currentQuerySubstring = querySubstrings[currentQuerySubstringIndex]; currentQuerySubstringCharacterIndex = 0; - - // if any of the substrings was not matched then consider as all are not matched - allSubstringsContainedInCompareString = !matchFoundInPreviousLoop ? false : allSubstringsContainedInCompareString; } } From 24cc5dbaa0930e4ea6176c99410175745e566c57 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 07:55:02 +1100 Subject: [PATCH 27/42] Add unit tests for checking substrings checking if all substrings contained in compareString --- Wox.Infrastructure/StringMatcher.cs | 8 +++++++- Wox.Test/FuzzyMatcherTest.cs | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 1868eee6e..a5162c281 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -147,7 +147,8 @@ namespace Wox.Infrastructure { Success = true, MatchData = indexList, - RawScore = Math.Max(score, pinyinScore) + RawScore = Math.Max(score, pinyinScore), + AllSubstringsContainedInCompareString = allSubstringsContainedInCompareString }; return result; @@ -288,6 +289,11 @@ namespace Wox.Infrastructure } } + /// + /// Indicates if all query's substrings are contained in the string to compare + /// + public bool AllSubstringsContainedInCompareString { get; set; } + /// /// Matched data to highlight. /// diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index 7eb16c8a0..660a8ff96 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -247,5 +247,21 @@ namespace Wox.Test $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + $"Precision Level: {expectedPrecisionString}={expectedPrecisionScore}"); } + + [TestCase("sql servman", MicrosoftSqlServerManagementStudio, false)] + [TestCase("sql serv man", MicrosoftSqlServerManagementStudio, true)] + [TestCase("sql", MicrosoftSqlServerManagementStudio, true)] + [TestCase("sqlserv", MicrosoftSqlServerManagementStudio, false)] + [TestCase("mssms", MicrosoftSqlServerManagementStudio, false)] + [TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", false)] + [TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", true)] + public void WhenGivenQueryShouldEvaluateTrueFalseIfCompareStringContainsAllSubstrings(string queryString, string compareString, bool expectedResult) + { + // When, Given + var matchResult = StringMatcher.FuzzySearch(queryString, compareString).AllSubstringsContainedInCompareString; + + // Should + Assert.AreEqual(matchResult, expectedResult); + } } } \ No newline at end of file From 78a20865350e6994105e1185b496e211d289a49f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 08:04:56 +1100 Subject: [PATCH 28/42] Remove containedFully variable state Not necessary to have and not needed to add another dimension to the scoring --- Wox.Infrastructure/StringMatcher.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index a5162c281..27f99b2a6 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -138,9 +138,7 @@ namespace Wox.Infrastructure // proceed to calculate score if every char or substring without whitespaces matched if (allQuerySubstringsMatched) { - // check if all query substrings were contained in the string to compare - bool containedFully = lastMatchIndex - firstMatchIndex == queryWithoutCase.Length; - var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, containedFully, allSubstringsContainedInCompareString); + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString); var pinyinScore = ScoreForPinyin(stringToCompare, query); var result = new MatchResult @@ -194,8 +192,7 @@ namespace Wox.Infrastructure return currentQuerySubstringIndex >= querySubstringsLength; } - private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, - bool isFullyContained, bool allWordsFullyMatched) + private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allWordsFullyMatched) { // 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, @@ -212,11 +209,6 @@ namespace Wox.Infrastructure 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; From b54241a5b27d4802e976f2f1b9571a4c4d6f2d36 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 08:28:27 +1100 Subject: [PATCH 29/42] Update scoring for all substrings contained in compare string --- Wox.Infrastructure/StringMatcher.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 27f99b2a6..45d65549a 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -192,7 +192,7 @@ namespace Wox.Infrastructure return currentQuerySubstringIndex >= querySubstringsLength; } - private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allWordsFullyMatched) + 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, @@ -209,10 +209,8 @@ namespace Wox.Infrastructure score += 10; } - if (allWordsFullyMatched) - { - score += 20; - } + if (allSubstringsContainedInCompareString) + score += 10 * string.Concat(query.Where(c => !char.IsWhiteSpace(c))).Count(); return score; } From 49b85d150c2fb0d084667c7d6be37a53ea722f53 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Tue, 7 Jan 2020 02:34:46 +0200 Subject: [PATCH 30/42] initial work, added github to setting, change update manage from static created log folder prop for log class --- Wox.Core/Updater.cs | 21 +++++---- Wox.Infrastructure/Http/Http.cs | 10 +++++ Wox.Infrastructure/Image/ImageLoader.cs | 2 +- Wox.Infrastructure/Wox.cs | 1 - Wox/App.config | 12 +++++ Wox/App.xaml.cs | 7 +-- Wox/Properties/Settings.Designer.cs | 11 ++++- Wox/Properties/Settings.settings | 12 ++--- Wox/ReportWindow.xaml.cs | 2 +- Wox/SettingWindow.xaml.cs | 45 ++----------------- Wox/Settings.cs | 28 ++++++++++++ Wox/ViewModel/SettingWindowViewModel.cs | 59 +++++++++++++++++++++++-- Wox/Wox.csproj | 1 + 13 files changed, 146 insertions(+), 65 deletions(-) create mode 100644 Wox/Settings.cs diff --git a/Wox.Core/Updater.cs b/Wox.Core/Updater.cs index d2461baf4..971995f5e 100644 --- a/Wox.Core/Updater.cs +++ b/Wox.Core/Updater.cs @@ -16,18 +16,23 @@ using Wox.Infrastructure.Logger; namespace Wox.Core { - public static class Updater + public class Updater { - private static readonly Internationalization Translater = InternationalizationManager.Instance; + public string GitHubRepository { get; } - public static async Task UpdateApp() + public Updater(string gitHubRepository) + { + GitHubRepository = gitHubRepository; + } + + public async Task UpdateApp() { UpdateManager m; UpdateInfo u; try { - m = await GitHubUpdateManager(Constant.Repository); + m = await GitHubUpdateManager(GitHubRepository); } catch (Exception e) when (e is HttpRequestException || e is WebException || e is SocketException) { @@ -66,8 +71,8 @@ namespace Wox.Core await m.ApplyReleases(u); await m.CreateUninstallerRegistryEntry(); - var newVersionTips = Translater.GetTranslation("newVersionTips"); - newVersionTips = string.Format(newVersionTips, fr.Version); + var newVersionTips = this.NewVersinoTips(fr.Version.ToString()); + MessageBox.Show(newVersionTips); Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); } @@ -90,7 +95,7 @@ namespace Wox.Core } /// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs - private static async Task GitHubUpdateManager(string repository) + private async Task GitHubUpdateManager(string repository) { var uri = new Uri(repository); var api = $"https://api.github.com/repos{uri.AbsolutePath}/releases"; @@ -109,7 +114,7 @@ namespace Wox.Core return manager; } - public static string NewVersinoTips(string version) + public string NewVersinoTips(string version) { var translater = InternationalizationManager.Instance; var tips = string.Format(translater.GetTranslation("newVersionTips"), version); diff --git a/Wox.Infrastructure/Http/Http.cs b/Wox.Infrastructure/Http/Http.cs index d79f3481f..0c8597a5d 100644 --- a/Wox.Infrastructure/Http/Http.cs +++ b/Wox.Infrastructure/Http/Http.cs @@ -13,6 +13,16 @@ namespace Wox.Infrastructure.Http { private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko"; + static Http() + { + // need to be added so it would work on a win10 machine + ServicePointManager.Expect100Continue = true; + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls + | SecurityProtocolType.Tls11 + | SecurityProtocolType.Tls12 + | SecurityProtocolType.Ssl3; + } + public static HttpProxy Proxy { private get; set; } public static IWebProxy WebProxy() { diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index 528900ce7..d1a0a74fa 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -164,7 +164,7 @@ namespace Wox.Infrastructure.Image } catch (System.Exception e) { - Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); + // Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); type = ImageType.Error; image = ImageCache[Constant.ErrorIcon]; ImageCache[path] = image; diff --git a/Wox.Infrastructure/Wox.cs b/Wox.Infrastructure/Wox.cs index fbab671ed..396ee0bb1 100644 --- a/Wox.Infrastructure/Wox.cs +++ b/Wox.Infrastructure/Wox.cs @@ -29,7 +29,6 @@ namespace Wox.Infrastructure public static readonly string DataDirectory = DetermineDataDirectory(); public static readonly string PluginsDirectory = Path.Combine(DataDirectory, Plugins); public static readonly string PreinstalledDirectory = Path.Combine(ProgramDirectory, Plugins); - public const string Repository = "https://github.com/Wox-launcher/Wox"; public const string Issue = "https://github.com/Wox-launcher/Wox/issues/new"; public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion; diff --git a/Wox/App.config b/Wox/App.config index 0fa0fe8bd..aef034f76 100644 --- a/Wox/App.config +++ b/Wox/App.config @@ -1,7 +1,19 @@  + + +
+ + + + + + https://github.com/Wox-launcher/Wox + + + \ No newline at end of file diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index 9436df475..6d6716a86 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -25,6 +25,7 @@ namespace Wox private Settings _settings; private MainViewModel _mainVM; private SettingWindowViewModel _settingsVM; + private readonly Updater _updater = new Updater(Wox.Properties.Settings.Default.GithubRepo); [STAThread] public static void Main() @@ -50,7 +51,7 @@ namespace Wox ImageLoader.Initialize(); - _settingsVM = new SettingWindowViewModel(); + _settingsVM = new SettingWindowViewModel(_updater); _settings = _settingsVM.Settings; Alphabet.Initialize(_settings); @@ -111,12 +112,12 @@ namespace Wox var timer = new Timer(1000 * 60 * 60 * 5); timer.Elapsed += async (s, e) => { - await Updater.UpdateApp(); + await _updater.UpdateApp(); }; timer.Start(); // check updates on startup - await Updater.UpdateApp(); + await _updater.UpdateApp(); } }); } diff --git a/Wox/Properties/Settings.Designer.cs b/Wox/Properties/Settings.Designer.cs index 7a4226349..a61339f5e 100644 --- a/Wox/Properties/Settings.Designer.cs +++ b/Wox/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace Wox.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -22,5 +22,14 @@ namespace Wox.Properties { return defaultInstance; } } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("https://github.com/Wox-launcher/Wox")] + public string GithubRepo { + get { + return ((string)(this["GithubRepo"])); + } + } } } diff --git a/Wox/Properties/Settings.settings b/Wox/Properties/Settings.settings index a585a6308..1fc52390b 100644 --- a/Wox/Properties/Settings.settings +++ b/Wox/Properties/Settings.settings @@ -1,7 +1,9 @@  - - - - - + + + + + https://github.com/Wox-launcher/Wox + + \ No newline at end of file diff --git a/Wox/ReportWindow.xaml.cs b/Wox/ReportWindow.xaml.cs index 38509a728..ac7e986fd 100644 --- a/Wox/ReportWindow.xaml.cs +++ b/Wox/ReportWindow.xaml.cs @@ -23,7 +23,7 @@ namespace Wox private void SetException(Exception exception) { - string path = Path.Combine(Constant.DataDirectory, Log.DirectoryName, Constant.Version); + string path = Log.CurrentLogDirectory; var directory = new DirectoryInfo(path); var log = directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First(); diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index c041d7ddd..67d67351e 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -260,53 +260,16 @@ namespace Wox #region Proxy private void OnTestProxyClick(object sender, RoutedEventArgs e) - { - if (string.IsNullOrEmpty(_settings.Proxy.Server)) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty")); - return; - } - if (_settings.Proxy.Port <= 0) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("portCantBeEmpty")); - return; - } - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Infrastructure.Constant.Repository); - if (string.IsNullOrEmpty(_settings.Proxy.UserName) || string.IsNullOrEmpty(_settings.Proxy.Password)) - { - request.Proxy = new WebProxy(_settings.Proxy.Server, _settings.Proxy.Port); - } - else - { - request.Proxy = new WebProxy(_settings.Proxy.Server, _settings.Proxy.Port) - { - Credentials = new NetworkCredential(_settings.Proxy.UserName, _settings.Proxy.Password) - }; - } - try - { - var response = (HttpWebResponse)request.GetResponse(); - if (response.StatusCode == HttpStatusCode.OK) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyIsCorrect")); - } - else - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyConnectFailed")); - } - } - catch - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("proxyConnectFailed")); - } + { // TODO: change to command + var msg = _viewModel.TestProxy(); + MessageBox.Show(msg); // TODO: add message box service } #endregion private async void OnCheckUpdates(object sender, RoutedEventArgs e) { - await Updater.UpdateApp(); + _viewModel.UpdateApp(); // TODO: change to command } private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) diff --git a/Wox/Settings.cs b/Wox/Settings.cs new file mode 100644 index 000000000..b45d4a5a1 --- /dev/null +++ b/Wox/Settings.cs @@ -0,0 +1,28 @@ +namespace Wox.Properties { + + + // This class allows you to handle specific events on the settings class: + // The SettingChanging event is raised before a setting's value is changed. + // The PropertyChanged event is raised after a setting's value is changed. + // The SettingsLoaded event is raised after the setting values are loaded. + // The SettingsSaving event is raised before the setting values are saved. + internal sealed partial class Settings { + + public Settings() { + // // To add event handlers for saving and changing settings, uncomment the lines below: + // + // this.SettingChanging += this.SettingChangingEventHandler; + // + // this.SettingsSaving += this.SettingsSavingEventHandler; + // + } + + private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { + // Add code to handle the SettingChangingEvent event here. + } + + private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { + // Add code to handle the SettingsSaving event here. + } + } +} diff --git a/Wox/ViewModel/SettingWindowViewModel.cs b/Wox/ViewModel/SettingWindowViewModel.cs index 67b8d7af0..5c418a8bd 100644 --- a/Wox/ViewModel/SettingWindowViewModel.cs +++ b/Wox/ViewModel/SettingWindowViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -20,10 +21,12 @@ namespace Wox.ViewModel { public class SettingWindowViewModel : BaseModel { + private readonly Updater _updater; private readonly WoxJsonStorage _storage; - public SettingWindowViewModel() + public SettingWindowViewModel(Updater updater) { + _updater = updater; _storage = new WoxJsonStorage(); Settings = _storage.Load(); Settings.PropertyChanged += (s, e) => @@ -39,6 +42,10 @@ namespace Wox.ViewModel public Settings Settings { get; set; } + public async void UpdateApp() + { + await _updater.UpdateApp(); + } public void Save() { @@ -88,6 +95,50 @@ namespace Wox.ViewModel public List Languages => _translater.LoadAvailableLanguages(); public IEnumerable MaxResultsRange => Enumerable.Range(2, 16); + public string TestProxy() + { + var proxyServer = Settings.Proxy.Server; + var proxyUserName = Settings.Proxy.UserName; + if (string.IsNullOrEmpty(proxyServer)) + { + return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty"); + } + if (Settings.Proxy.Port <= 0) + { + return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty"); + } + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository); + + if (string.IsNullOrEmpty(proxyUserName) || string.IsNullOrEmpty(Settings.Proxy.Password)) + { + request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port); + } + else + { + request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port) + { + Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password) + }; + } + try + { + var response = (HttpWebResponse)request.GetResponse(); + if (response.StatusCode == HttpStatusCode.OK) + { + return InternationalizationManager.Instance.GetTranslation("proxyIsCorrect"); + } + else + { + return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"); + } + } + catch + { + return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"); + } + } + #endregion #region plugin @@ -220,7 +271,7 @@ namespace Wox.ViewModel }, new Result { - Title = $"Open Source: {Constant.Repository}", + Title = $"Open Source: {_updater.GitHubRepository}", SubTitle = "Please star it!" } }; @@ -330,8 +381,8 @@ namespace Wox.ViewModel #region about - public static string Github => Constant.Repository; - public static string ReleaseNotes => @"https://github.com/Wox-launcher/Wox/releases/latest"; + public string Github => _updater.GitHubRepository; + public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest"; public static string Version => Constant.Version; public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes); #endregion diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index 4a4dc62bf..77b7e16d0 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -165,6 +165,7 @@ ResultListBox.xaml + From 2a49b3899aa9f62af3484ca86055a99f35ded274 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 20:26:26 +1100 Subject: [PATCH 31/42] Update tests Two scoring changes only as a result of substring matching. --- Wox.Test/FuzzyMatcherTest.cs | 80 +++++++++++------------------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index 660a8ff96..1d3d16c95 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -122,50 +122,20 @@ namespace Wox.Test } } - [TestCase] - public void WhenGivenStringsForCalScoreMethodThenShouldReturnCurrentScoring() + [TestCase(Chrome, Chrome, 167)] + [TestCase(Chrome, LastIsChrome, 113)] + [TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 21)] + [TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 15)] + [TestCase(Chrome, CandyCrushSagaFromKing, 0)] + [TestCase("sql", MicrosoftSqlServerManagementStudio, 56)] + [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 119)]//double spacing intended + public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore) { - // Arrange - string searchTerm = "chrome"; // since this looks for specific results it will always be one case - var searchStrings = new List - { - Chrome,//SCORE: 107 - LastIsChrome,//SCORE: 53 - HelpCureHopeRaiseOnMindEntityChrome,//SCORE: 21 - UninstallOrChangeProgramsOnYourComputer, //SCORE: 15 - CandyCrushSagaFromKing//SCORE: 0 - } - .OrderByDescending(x => x) - .ToList(); + // When, Given + var rawScore = StringMatcher.FuzzySearch(queryString, compareString).RawScore; - // Act - var results = new List(); - foreach (var str in searchStrings) - { - results.Add(new Result - { - Title = str, - Score = StringMatcher.FuzzySearch(searchTerm, str).RawScore - }); - } - - // Assert - VerifyResult(147, Chrome); - VerifyResult(93, LastIsChrome); - VerifyResult(41, HelpCureHopeRaiseOnMindEntityChrome); - VerifyResult(35, UninstallOrChangeProgramsOnYourComputer); - VerifyResult(0, CandyCrushSagaFromKing); - - void VerifyResult(int expectedScore, string expectedTitle) - { - 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}"); - } + // Should + Assert.AreEqual(expectedScore, rawScore, $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}"); } [TestCase("goo", "Google Chrome", (int)StringMatcher.SearchPrecisionScore.Regular, true)] @@ -184,26 +154,25 @@ namespace Wox.Test int expectedPrecisionScore, bool expectedPrecisionResult) { - // Arrange - var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore; - StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore; // this is why static state is evil... + // When + StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore; - // Act + // 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: {expectedPrecisionString} ({expectedPrecisionScore})"); + Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore} ({expectedPrecisionScore})"); Debug.WriteLine("###############################################"); Debug.WriteLine(""); - // Assert + // Should Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), $"Query:{queryString}{Environment.NewLine} " + $"Compare:{compareString}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + - $"Precision Level: {expectedPrecisionString}={expectedPrecisionScore}"); + $"Precision Level: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore}={expectedPrecisionScore}"); } [TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", (int)StringMatcher.SearchPrecisionScore.Regular, false)] @@ -226,26 +195,25 @@ namespace Wox.Test int expectedPrecisionScore, bool expectedPrecisionResult) { - // Arrange - var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore; - StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore; // this is why static state is evil... + // When + StringMatcher.UserSettingSearchPrecision = expectedPrecisionScore; - // Act + // 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: {expectedPrecisionString} ({expectedPrecisionScore})"); + Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore} ({expectedPrecisionScore})"); Debug.WriteLine("###############################################"); Debug.WriteLine(""); - // Assert + // Should Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), $"Query:{queryString}{Environment.NewLine} " + $"Compare:{compareString}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + - $"Precision Level: {expectedPrecisionString}={expectedPrecisionScore}"); + $"Precision Level: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore}={expectedPrecisionScore}"); } [TestCase("sql servman", MicrosoftSqlServerManagementStudio, false)] From 76727d09bf618ad086b6d2f133b532347819e086 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 7 Jan 2020 22:30:36 +1100 Subject: [PATCH 32/42] Update StringMatcher's UserSettingSearchPrecision property type makes more sense and less conversion to int for actual precision score --- Wox.Infrastructure/StringMatcher.cs | 4 +- Wox.Infrastructure/UserSettings/Settings.cs | 17 +++--- Wox.Test/FuzzyMatcherTest.cs | 61 ++++++++++----------- Wox/App.xaml.cs | 2 +- Wox/ViewModel/SettingWindowViewModel.cs | 2 +- 5 files changed, 44 insertions(+), 42 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 45d65549a..9c667ced0 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -12,7 +12,7 @@ namespace Wox.Infrastructure { public static MatchOption DefaultMatchOption = new MatchOption(); - public static int UserSettingSearchPrecision { get; set; } + public static SearchPrecisionScore UserSettingSearchPrecision { get; set; } public static bool ShouldUsePinyin { get; set; } @@ -296,7 +296,7 @@ namespace Wox.Infrastructure private bool IsSearchPrecisionScoreMet(int score) { - return score >= UserSettingSearchPrecision; + return score >= (int)UserSettingSearchPrecision; } private int ApplySearchPrecisionFilter(int score) diff --git a/Wox.Infrastructure/UserSettings/Settings.cs b/Wox.Infrastructure/UserSettings/Settings.cs index 5a129832a..b11ec069c 100644 --- a/Wox.Infrastructure/UserSettings/Settings.cs +++ b/Wox.Infrastructure/UserSettings/Settings.cs @@ -45,16 +45,19 @@ namespace Wox.Infrastructure.UserSettings { try { - var precisionScore = (StringMatcher.SearchPrecisionScore)Enum.Parse( - typeof(StringMatcher.SearchPrecisionScore), - value); + var precisionScore = (StringMatcher.SearchPrecisionScore)Enum + .Parse(typeof(StringMatcher.SearchPrecisionScore), value); + QuerySearchPrecision = precisionScore; - StringMatcher.UserSettingSearchPrecision = (int)precisionScore; + StringMatcher.UserSettingSearchPrecision = precisionScore; } - catch (System.Exception e) + catch (ArgumentException e) { - // what do we do here?! - Logger.Log.Exception(nameof(Settings), "Fail to set QuerySearchPrecision", 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 1d3d16c95..1a8255987 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 @@ -138,20 +137,20 @@ namespace Wox.Test 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, + StringMatcher.SearchPrecisionScore expectedPrecisionScore, bool expectedPrecisionResult) { // When @@ -163,7 +162,7 @@ namespace Wox.Test Debug.WriteLine(""); Debug.WriteLine("###############################################"); Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); - Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore} ({expectedPrecisionScore})"); + Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})"); Debug.WriteLine("###############################################"); Debug.WriteLine(""); @@ -172,27 +171,27 @@ namespace Wox.Test $"Query:{queryString}{Environment.NewLine} " + $"Compare:{compareString}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + - $"Precision Level: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore}={expectedPrecisionScore}"); + $"Precision Score: {(int)expectedPrecisionScore}"); } - [TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", (int)StringMatcher.SearchPrecisionScore.Regular, false)] - [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("sql studio", 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)] + [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("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] + [TestCase("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] + [TestCase("chr", "Shutdown", StringMatcher.SearchPrecisionScore.Regular, false)] + [TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, false)] + [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, - int expectedPrecisionScore, + StringMatcher.SearchPrecisionScore expectedPrecisionScore, bool expectedPrecisionResult) { // When @@ -204,7 +203,7 @@ namespace Wox.Test Debug.WriteLine(""); Debug.WriteLine("###############################################"); Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); - Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore} ({expectedPrecisionScore})"); + Debug.WriteLine($"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})"); Debug.WriteLine("###############################################"); Debug.WriteLine(""); @@ -213,7 +212,7 @@ namespace Wox.Test $"Query:{queryString}{Environment.NewLine} " + $"Compare:{compareString}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + - $"Precision Level: {(StringMatcher.SearchPrecisionScore)expectedPrecisionScore}={expectedPrecisionScore}"); + $"Precision Score: {(int)expectedPrecisionScore}"); } [TestCase("sql servman", MicrosoftSqlServerManagementStudio, false)] diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index aa5426d06..9436df475 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -55,7 +55,7 @@ namespace Wox Alphabet.Initialize(_settings); - StringMatcher.UserSettingSearchPrecision = (int)_settings.QuerySearchPrecision; + StringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision; StringMatcher.ShouldUsePinyin = _settings.ShouldUsePinyin; PluginManager.LoadPlugins(_settings.PluginSettings); diff --git a/Wox/ViewModel/SettingWindowViewModel.cs b/Wox/ViewModel/SettingWindowViewModel.cs index 67b8d7af0..19a31be58 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(); From c509c02546b52b6edfb50ca9bed4f7ab18eb14e8 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 8 Jan 2020 20:39:34 +1100 Subject: [PATCH 33/42] Update search wild card when retrieving folders and files based search term, use wildcard to match everything before and after search term --- Plugins/Wox.Plugin.Folder/Main.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index d6b4a8665..fa622f16d 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -191,7 +191,9 @@ namespace Wox.Plugin.Folder if (incompleteName.StartsWith(">")) { searchOption = SearchOption.AllDirectories; - incompleteName = incompleteName.Substring(1); + + // match everything before and after search term using supported wildcard '*', ie. *searchterm* + incompleteName = "*" + incompleteName.Substring(1); } try From ac6ee28c5f6e4dc6665ea58bc0508791313a02e0 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Wed, 8 Jan 2020 21:50:29 +1100 Subject: [PATCH 34/42] Add sorting order by result title asc, then type being folder first. --- Plugins/Wox.Plugin.Folder/Main.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index fa622f16d..170799029 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -195,7 +195,10 @@ namespace Wox.Plugin.Folder // match everything before and after search term using supported wildcard '*', ie. *searchterm* incompleteName = "*" + incompleteName.Substring(1); } - + + var folderList = new List(); + var fileList = new List(); + try { // search folder and add results @@ -206,11 +209,14 @@ namespace Wox.Plugin.Folder { if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue; - var result = - fileSystemInfo is DirectoryInfo - ? CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query) - : CreateFileResult(fileSystemInfo.FullName, query); - results.Add(result); + if(fileSystemInfo is DirectoryInfo) + { + folderList.Add(CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, query)); + } + else + { + fileList.Add(CreateFileResult(fileSystemInfo.FullName, query)); + } } } catch (Exception e) @@ -225,7 +231,8 @@ namespace Wox.Plugin.Folder throw; } - return results; + // Intial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. + return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); } private static Result CreateFileResult(string filePath, Query query) From 0f19010e524f380bd253212ed1d4b19282a832f5 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Thu, 9 Jan 2020 01:10:13 +0200 Subject: [PATCH 35/42] oops --- Wox.Infrastructure/Image/ImageLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index d1a0a74fa..528900ce7 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -164,7 +164,7 @@ namespace Wox.Infrastructure.Image } catch (System.Exception e) { - // Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); + Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); type = ImageType.Error; image = ImageCache[Constant.ErrorIcon]; ImageCache[path] = image; From d74b5c8764b013f878aa8eed00062b6222e110a0 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Thu, 9 Jan 2020 01:13:56 +0200 Subject: [PATCH 36/42] another --- Wox.Infrastructure/Logger/Log.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index ff72dff1c..ae4ea095b 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -11,18 +11,20 @@ namespace Wox.Infrastructure.Logger { public const string DirectoryName = "Logs"; + public static string CurrentLogDirectory { get; private set; } + static Log() { - var path = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version); - if (!Directory.Exists(path)) + CurrentLogDirectory = Path.Combine(Constant.DataDirectory, DirectoryName, Constant.Version); + if (!Directory.Exists(CurrentLogDirectory)) { - Directory.CreateDirectory(path); + Directory.CreateDirectory(CurrentLogDirectory); } var configuration = new LoggingConfiguration(); var target = new FileTarget(); configuration.AddTarget("file", target); - target.FileName = path.Replace(@"\", "/") + "/${shortdate}.txt"; + target.FileName = CurrentLogDirectory.Replace(@"\", "/") + "/${shortdate}.txt"; #if DEBUG var rule = new LoggingRule("*", LogLevel.Debug, target); #else From ed01a46a3f21f59e8d6e6c64de2f721812d15d65 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 13 Jan 2020 07:45:43 +1100 Subject: [PATCH 37/42] Update maintenance and all releases badge --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d0659d0fa..2b9da637f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ WoX === -![Maintenance](https://img.shields.io/maintenance/yes/2019) +![Maintenance](https://img.shields.io/maintenance/yes/2020) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/jjw24/wox)](https://github.com/jjw24/Wox/releases/latest) ![GitHub Release Date](https://img.shields.io/github/release-date/jjw24/wox) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/jjw24/wox/v1.3.524) [![Build Status](https://dev.azure.com/Wox-Launcher/Wox/_apis/build/status/jjw24.Wox?branchName=master)](https://dev.azure.com/Wox-Launcher/Wox/_build/latest?definitionId=1&branchName=master) -[![Github All Releases](https://img.shields.io/github/downloads/Wox-launcher/Wox/total.svg)](https://github.com/Wox-launcher/Wox/releases) +[![Github All Releases](https://img.shields.io/github/downloads/jjw24/Wox/total.svg)](https://github.com/jjw24/Wox/releases) [![RamenBless](https://cdn.rawgit.com/LunaGao/BlessYourCodeTag/master/tags/ramen.svg)](https://github.com/LunaGao/BlessYourCodeTag) **WoX** is a launcher for Windows that simply works. It's an alternative to [Alfred](https://www.alfredapp.com/) and [Launchy](http://www.launchy.net/). You can call it Windows omni-eXecutor if you want a long name. From 0bec780a1bbc4cbb9e87077c7c89d4210b000b6c Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 13 Jan 2020 07:50:34 +1100 Subject: [PATCH 38/42] Update yaml file comment --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 32aa5f677..5307ec006 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -8,7 +8,7 @@ trigger: - dev pool: - vmImage: 'vs2017-win2016' #'windows-latest' + vmImage: 'vs2017-win2016' #'due to windows SDK dependency for building UWP project' variables: solution: '**/*.sln' From 60959338479f44e4982604fbcad54d45df609482 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 14 Jan 2020 07:29:21 +1100 Subject: [PATCH 39/42] simplify condition as per comment --- Wox.Infrastructure/StringMatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 9c667ced0..a82d58582 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -121,7 +121,7 @@ namespace Wox.Infrastructure if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length) { // if any of the substrings was not matched then consider as all are not matched - allSubstringsContainedInCompareString = !matchFoundInPreviousLoop ? false : allSubstringsContainedInCompareString; + allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString; currentQuerySubstringIndex++; From 71d8c2080c9e1fd5f534e351ee500e63b35ee375 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 14 Jan 2020 07:30:40 +1100 Subject: [PATCH 40/42] update comment typo --- Wox.Infrastructure/StringMatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index a82d58582..c4d340efa 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -105,7 +105,7 @@ namespace Wox.Infrastructure { matchFoundInPreviousLoop = true; - // if it's the begining character of the first query substring that is matched then we need to update start index + // 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); From 592f1cafdbfce0282a63faec0a98163bc353e813 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 14 Jan 2020 07:36:53 +1100 Subject: [PATCH 41/42] update allSubstringsContainedInCompareString calculation as per comment --- Wox.Infrastructure/StringMatcher.cs | 6 +++++- Wox.Test/FuzzyMatcherTest.cs | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index c4d340efa..5d5a9b9a8 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -210,7 +210,11 @@ namespace Wox.Infrastructure } if (allSubstringsContainedInCompareString) - score += 10 * string.Concat(query.Where(c => !char.IsWhiteSpace(c))).Count(); + { + int count = query.Count(c => !char.IsWhiteSpace(c)); + int factor = count < 4 ? 10 : 5; + score += factor * count; + } return score; } diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index 1a8255987..b7e0374f0 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -121,13 +121,13 @@ namespace Wox.Test } } - [TestCase(Chrome, Chrome, 167)] - [TestCase(Chrome, LastIsChrome, 113)] + [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, 119)]//double spacing intended + [TestCase("sql manag", MicrosoftSqlServerManagementStudio, 79)]//double spacing intended public void WhenGivenQueryStringThenShouldReturnCurrentScoring(string queryString, string compareString, int expectedScore) { // When, Given From 504c08a0fc5128e608e3f341330c306bee574105 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 14 Jan 2020 07:53:59 +1100 Subject: [PATCH 42/42] Update test per comment --- Wox.Infrastructure/StringMatcher.cs | 8 +------- Wox.Test/FuzzyMatcherTest.cs | 21 +++++---------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 5d5a9b9a8..d1cc1fdec 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -145,8 +145,7 @@ namespace Wox.Infrastructure { Success = true, MatchData = indexList, - RawScore = Math.Max(score, pinyinScore), - AllSubstringsContainedInCompareString = allSubstringsContainedInCompareString + RawScore = Math.Max(score, pinyinScore) }; return result; @@ -283,11 +282,6 @@ namespace Wox.Infrastructure } } - /// - /// Indicates if all query's substrings are contained in the string to compare - /// - public bool AllSubstringsContainedInCompareString { get; set; } - /// /// Matched data to highlight. /// diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index b7e0374f0..f2de39a28 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -182,10 +182,15 @@ namespace Wox.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("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( @@ -214,21 +219,5 @@ namespace Wox.Test $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + $"Precision Score: {(int)expectedPrecisionScore}"); } - - [TestCase("sql servman", MicrosoftSqlServerManagementStudio, false)] - [TestCase("sql serv man", MicrosoftSqlServerManagementStudio, true)] - [TestCase("sql", MicrosoftSqlServerManagementStudio, true)] - [TestCase("sqlserv", MicrosoftSqlServerManagementStudio, false)] - [TestCase("mssms", MicrosoftSqlServerManagementStudio, false)] - [TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", false)] - [TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", true)] - public void WhenGivenQueryShouldEvaluateTrueFalseIfCompareStringContainsAllSubstrings(string queryString, string compareString, bool expectedResult) - { - // When, Given - var matchResult = StringMatcher.FuzzySearch(queryString, compareString).AllSubstringsContainedInCompareString; - - // Should - Assert.AreEqual(matchResult, expectedResult); - } } } \ No newline at end of file