From 434ba827057f5aa7ad6c9342aa8bb52877fc8619 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 17 Nov 2019 18:06:25 +1100 Subject: [PATCH 01/90] Fix build fail from task reindex run --- Plugins/Wox.Plugin.Program/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Main.cs b/Plugins/Wox.Plugin.Program/Main.cs index 009dd84fb..e165a1082 100644 --- a/Plugins/Wox.Plugin.Program/Main.cs +++ b/Plugins/Wox.Plugin.Program/Main.cs @@ -111,9 +111,9 @@ namespace Wox.Plugin.Program public static void IndexPrograms() { - var t1 = Task.Run(IndexWin32Programs); + var t1 = Task.Run(()=>IndexWin32Programs()); - var t2 = Task.Run(IndexUWPPrograms); + var t2 = Task.Run(()=>IndexUWPPrograms()); Task.WaitAll(t1, t2); From 05f66f9bead5e5c4eb7cad070d2eb06efcc742a7 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Thu, 28 Nov 2019 12:44:01 +0100 Subject: [PATCH 02/90] Improve code formatting --- Wox.Infrastructure/StringMatcher.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 5b82b1891..8d66e19c2 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Text; using Wox.Infrastructure.Logger; @@ -95,14 +95,20 @@ namespace Wox.Infrastructure private static int CalScore(string query, string stringToCompare, int firstIndex, int matchLen) { - //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, while the score is lower if they are more spread out + // 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, + // while the score is lower if they are more spread out var score = 100 * (query.Length + 1) / ((1 + firstIndex) + (matchLen + 1)); - //a match with less characters assigning more weights + + // A match with less characters assigning more weights if (stringToCompare.Length - query.Length < 5) + { score += 20; + } else if (stringToCompare.Length - query.Length < 10) + { score += 10; + } return score; } From 3d55ad783eda0b9b2624b833d87f259bf401c9a6 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 14:55:22 +0100 Subject: [PATCH 03/90] Only pass needed setting to Alphabet As Alphabet class is static, its methods could be used without ever calling Alphabet.initialize(_settings) beforehand which would end in an exception. Therefor only _shouldUsePinyin settings needed will be transferred with a given default value. --- Wox.Infrastructure/Alphabet.cs | 74 +++++++++++++++++++--------------- Wox/App.xaml.cs | 4 +- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/Wox.Infrastructure/Alphabet.cs b/Wox.Infrastructure/Alphabet.cs index d7fae882c..a2f64241a 100644 --- a/Wox.Infrastructure/Alphabet.cs +++ b/Wox.Infrastructure/Alphabet.cs @@ -15,17 +15,26 @@ namespace Wox.Infrastructure private static readonly HanyuPinyinOutputFormat Format = new HanyuPinyinOutputFormat(); private static ConcurrentDictionary PinyinCache; private static BinaryStorage> _pinyinStorage; - private static Settings _settings; - - public static void Initialize(Settings settings) + private static bool _shouldUsePinyin = true; + + public static void Initialize(bool shouldUsePinyin = true) + { + _shouldUsePinyin = shouldUsePinyin; + if (_shouldUsePinyin) + { + InitializePinyinHelpers(); + } + } + + private static void InitializePinyinHelpers() { - _settings = settings; Format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); Stopwatch.Normal("|Wox.Infrastructure.Alphabet.Initialize|Preload pinyin cache", () => { _pinyinStorage = new BinaryStorage>("Pinyin"); PinyinCache = _pinyinStorage.TryLoad(new ConcurrentDictionary()); + // force pinyin library static constructor initialize PinyinHelper.toHanyuPinyinStringArray('T', Format); }); @@ -34,6 +43,10 @@ namespace Wox.Infrastructure public static void Save() { + if (!_shouldUsePinyin) + { + return; + } _pinyinStorage.Save(PinyinCache); } @@ -46,7 +59,7 @@ namespace Wox.Infrastructure /// public static string[] Pinyin(string word) { - if (!_settings.ShouldUsePinyin) + if (!_shouldUsePinyin) { return EmptyStringArray; } @@ -68,39 +81,36 @@ namespace Wox.Infrastructure /// public static string[][] PinyinComination(string characters) { - if (_settings.ShouldUsePinyin && !string.IsNullOrEmpty(characters)) + if (!_shouldUsePinyin || string.IsNullOrEmpty(characters)) { - if (!PinyinCache.ContainsKey(characters)) - { + return Empty2DStringArray; + } - var allPinyins = new List(); - foreach (var c in characters) + if (!PinyinCache.ContainsKey(characters)) + { + var allPinyins = new List(); + foreach (var c in characters) + { + var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format); + if (pinyins != null) { - var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format); - if (pinyins != null) - { - var r = pinyins.Distinct().ToArray(); - allPinyins.Add(r); - } - else - { - var r = new[] { c.ToString() }; - allPinyins.Add(r); - } + var r = pinyins.Distinct().ToArray(); + allPinyins.Add(r); } + else + { + var r = new[] { c.ToString() }; + allPinyins.Add(r); + } + } - var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray(); - PinyinCache[characters] = combination; - return combination; - } - else - { - return PinyinCache[characters]; - } + var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray(); + PinyinCache[characters] = combination; + return combination; } else { - return Empty2DStringArray; + return PinyinCache[characters]; } } @@ -112,7 +122,7 @@ namespace Wox.Infrastructure public static bool ContainsChinese(string word) { - if (!_settings.ShouldUsePinyin) + if (!_shouldUsePinyin) { return false; } @@ -130,7 +140,7 @@ namespace Wox.Infrastructure private static string[] Combination(string[] array1, string[] array2) { - if (!_settings.ShouldUsePinyin) + if (!_shouldUsePinyin) { return EmptyStringArray; } diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index 0ec1614bf..acfb88d26 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using System.Timers; @@ -53,7 +53,7 @@ namespace Wox _settingsVM = new SettingWindowViewModel(); _settings = _settingsVM.Settings; - Alphabet.Initialize(_settings); + Alphabet.Initialize(_settings.ShouldUsePinyin); StringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision; From 6ebca7fa7cf0462d9ff9734f216d7ef460fef16d Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 14:58:52 +0100 Subject: [PATCH 04/90] Apply search precision filter on assignment The search precision must be used, so it makes sense to apply the filter directly on score assignment. Score will be "0" if precision filter was not met. --- Wox.Infrastructure/StringMatcher.cs | 49 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index 8d66e19c2..d24bdf2c3 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -120,19 +120,6 @@ namespace Wox.Infrastructure None = 0 } - public static bool IsSearchPrecisionScoreMet(this MatchResult matchResult) - { - var precisionScore = (SearchPrecisionScore)Enum.Parse(typeof(SearchPrecisionScore), - UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString()); - return matchResult.Score >= (int)precisionScore; - } - - public static int ScoreAfterSearchPrecisionFilter(this MatchResult matchResult) - { - return matchResult.IsSearchPrecisionScoreMet() ? matchResult.Score : 0; - - } - public static int ScoreForPinyin(string source, string target) { if (!string.IsNullOrEmpty(source) && !string.IsNullOrEmpty(target)) @@ -164,12 +151,42 @@ namespace Wox.Infrastructure public class MatchResult { public bool Success { get; set; } - public int Score { get; set; } + + private int _score; + public int Score + { + get + { + return _score; + } + set + { + _score = ApplySearchPrecisionFilter(value); + } + } /// - /// highlight string + /// Matched data to highlight. /// - public string Value { get; set; } + public List MatchData { get; set; } + + public bool IsSearchPrecisionScoreMet() + { + return IsSearchPrecisionScoreMet(Score); + } + + private bool IsSearchPrecisionScoreMet(int score) + { + var precisionScore = (SearchPrecisionScore)Enum.Parse( + typeof(SearchPrecisionScore), + UserSettingSearchPrecision ?? SearchPrecisionScore.Regular.ToString()); + return score >= (int)precisionScore; + } + + private int ApplySearchPrecisionFilter(int score) + { + return IsSearchPrecisionScoreMet(score) ? score : 0; + } } public class MatchOption From 627e99859d76ee624062e96e3e76ddb308288910 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 15:02:59 +0100 Subject: [PATCH 05/90] Directly calculate pinyin if needed The MatchResult will always get the maximum score from either ScoreForPinyin() or CalculateSearchScore(). ScoreForPinyin() is completely dependant on the "ShouldUsePinyin" global setting. --- Wox.Infrastructure/StringMatcher.cs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index d24bdf2c3..dd5a118fb 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -1,10 +1,12 @@ -using System; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; using Wox.Infrastructure.Logger; using Wox.Infrastructure.UserSettings; +using static Wox.Infrastructure.StringMatcher; -namespace Wox.Infrastructure +namespace Wox.Infrastructure { public static class StringMatcher { @@ -54,6 +56,9 @@ namespace Wox.Infrastructure var firstMatchIndex = -1; var lastMatchIndex = 0; char ch; + + var indexList = new List(); + for (var idx = 0; idx < len; idx++) { ch = stringToCompare[idx]; @@ -63,6 +68,7 @@ namespace Wox.Infrastructure firstMatchIndex = idx; lastMatchIndex = idx + 1; + indexList.Add(idx); sb.Append(opt.Prefix + ch + opt.Suffix); patternIdx += 1; } @@ -82,18 +88,25 @@ namespace Wox.Infrastructure // return rendered string if we have a match for every char if (patternIdx == pattern.Length) { - return new MatchResult + var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex); + var pinyinScore = ScoreForPinyin(stringToCompare, query); + + var result = new MatchResult { Success = true, - Value = sb.ToString(), - Score = CalScore(query, stringToCompare, firstMatchIndex, lastMatchIndex - firstMatchIndex) + MatchData = indexList, + Score = Math.Max(score, pinyinScore) }; + + return result.Score > 0 ? + result : + new MatchResult { Success = false }; } return new MatchResult { Success = false }; } - private static int CalScore(string query, string stringToCompare, int firstIndex, int matchLen) + private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen) { // 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, @@ -164,7 +177,7 @@ namespace Wox.Infrastructure _score = ApplySearchPrecisionFilter(value); } } - + /// /// Matched data to highlight. /// From 2aeceb7ea8e305fed8567240a45c738b944e4ce1 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 15:04:02 +0100 Subject: [PATCH 06/90] Fix tests for new search precision handling --- Wox.Test/FuzzyMatcherTest.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index ce1746a3a..aa8778a54 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using NUnit.Framework; using Wox.Infrastructure; +using Wox.Infrastructure.UserSettings; using Wox.Plugin; namespace Wox.Test @@ -48,6 +49,8 @@ namespace Wox.Test "aac" }; + Alphabet.Initialize(false); + StringMatcher.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Low.ToString(); var results = new List(); foreach (var str in sources) @@ -130,6 +133,9 @@ namespace Wox.Test var results = new List(); + Alphabet.Initialize(false); + StringMatcher.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.None.ToString(); + foreach (var str in searchStrings) { results.Add(new Result @@ -168,8 +174,11 @@ namespace Wox.Test [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)] - public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual(string queryString, string compareString, - int expectedPrecisionScore, bool expectedPrecisionResult) + public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual( + string queryString, + string compareString, + int expectedPrecisionScore, + bool expectedPrecisionResult) { var expectedPrecisionString = (StringMatcher.SearchPrecisionScore)expectedPrecisionScore; StringMatcher.UserSettingSearchPrecision = expectedPrecisionString.ToString(); From 8997c27819e514027a5410f18c32353ebc5c90a8 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 15:08:21 +0100 Subject: [PATCH 07/90] Add highlight converter component --- Wox/Converters/HighlightTextConverter.cs | 53 ++++++++++++++++++++++++ Wox/ResultListBox.xaml | 22 +++++++++- Wox/ViewModel/RelayCommand.cs | 1 - Wox/ViewModel/ResultsViewModel.cs | 31 ++++++++++++++ Wox/Wox.csproj | 1 + 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 Wox/Converters/HighlightTextConverter.cs diff --git a/Wox/Converters/HighlightTextConverter.cs b/Wox/Converters/HighlightTextConverter.cs new file mode 100644 index 000000000..b7d6d0683 --- /dev/null +++ b/Wox/Converters/HighlightTextConverter.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using System.Windows.Documents; + +namespace Wox.Converters +{ + public class HighlightTextConverter : IMultiValueConverter + { + public object Convert(object[] value, Type targetType, object parameter, CultureInfo cultureInfo) + { + var text = value[0] as string; + var highlightData = value[1] as List; + + var textBlock = new Span(); + + if (highlightData == null || !highlightData.Any()) + { + // No highlight data, just return the text + return new Run(text); + } + + for (var i = 0; i < text.Length; i++) + { + var currentCharacter = text.Substring(i, 1); + if (this.ShouldHighlight(highlightData, i)) + { + textBlock.Inlines.Add(new Bold(new Run(currentCharacter))); + } + else + { + textBlock.Inlines.Add(new Run(currentCharacter)); + } + } + return textBlock; + } + + public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) + { + return new[] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue }; + } + + private bool ShouldHighlight(List highlightData, int index) + { + return highlightData.Contains(index); + } + } +} diff --git a/Wox/ResultListBox.xaml b/Wox/ResultListBox.xaml index cad02d519..dc3100e0e 100644 --- a/Wox/ResultListBox.xaml +++ b/Wox/ResultListBox.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:Wox.ViewModel" + xmlns:converter="clr-namespace:Wox.Converters" mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100" d:DataContext="{d:DesignInstance vm:ResultsViewModel}" MaxHeight="{Binding MaxHeight}" @@ -30,6 +31,9 @@ + + + @@ -44,9 +48,23 @@ + Text="{Binding Result.Title}"> + + + + + + + + Grid.Row="1" x:Name="SubTitle" Text="{Binding Result.SubTitle}"> + + + + + + + diff --git a/Wox/ViewModel/RelayCommand.cs b/Wox/ViewModel/RelayCommand.cs index 3a52f3000..b1dbc551c 100644 --- a/Wox/ViewModel/RelayCommand.cs +++ b/Wox/ViewModel/RelayCommand.cs @@ -5,7 +5,6 @@ namespace Wox.ViewModel { public class RelayCommand : ICommand { - private Action _action; public RelayCommand(Action action) diff --git a/Wox/ViewModel/ResultsViewModel.cs b/Wox/ViewModel/ResultsViewModel.cs index 674923228..1496647d2 100644 --- a/Wox/ViewModel/ResultsViewModel.cs +++ b/Wox/ViewModel/ResultsViewModel.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Windows; +using System.Windows.Controls; using System.Windows.Data; +using System.Windows.Documents; using Wox.Infrastructure.UserSettings; using Wox.Plugin; @@ -193,8 +195,37 @@ namespace Wox.ViewModel return results; } + #endregion + #region FormattedText Dependency Property + public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached( + "FormattedText", + typeof(Inline), + typeof(ResultsViewModel), + new PropertyMetadata(null, FormattedTextPropertyChanged)); + public static void SetFormattedText(DependencyObject textBlock, IList value) + { + textBlock.SetValue(FormattedTextProperty, value); + } + + public static Inline GetFormattedText(DependencyObject textBlock) + { + return (Inline)textBlock.GetValue(FormattedTextProperty); + } + + private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var textBlock = d as TextBlock; + if (textBlock == null) return; + + var inline = (Inline)e.NewValue; + + textBlock.Inlines.Clear(); + if (inline == null) return; + + textBlock.Inlines.Add(inline); + } #endregion public class ResultCollection : ObservableCollection diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index ef7714829..4a4dc62bf 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -158,6 +158,7 @@ Properties\SolutionAssemblyInfo.cs + From a004ef65aff61d0bd9088babd3a71bac7ae6e837 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 15:10:21 +0100 Subject: [PATCH 08/90] Set title & subtitle highlight data --- Wox.Plugin/Result.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Wox.Plugin/Result.cs b/Wox.Plugin/Result.cs index 2f7459170..aff56554a 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -42,6 +42,16 @@ namespace Wox.Plugin public int Score { get; set; } + /// + /// A list of indexes for the characters to be highlighted in Title + /// + public IList TitleHighlightData { get; set; } + + /// + /// A list of indexes for the characters to be highlighted in SubTitle + /// + public IList SubTitleHighlightData { get; set; } + /// /// Only resulsts that originQuery match with curren query will be displayed in the panel /// @@ -69,7 +79,9 @@ namespace Wox.Plugin var equality = string.Equals(r?.Title, Title) && string.Equals(r?.SubTitle, SubTitle) && - string.Equals(r?.IcoPath, IcoPath); + string.Equals(r?.IcoPath, IcoPath) && + TitleHighlightData == r.TitleHighlightData && + SubTitleHighlightData == r.SubTitleHighlightData; return equality; } From 601d6f37af61ad14ac9493904440a3a84a99e755 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 15:25:19 +0100 Subject: [PATCH 09/90] Highlight how results matched --- Plugins/Wox.Plugin.ControlPanel/Main.cs | 36 ++++++++------------ Plugins/Wox.Plugin.Everything/Main.cs | 2 ++ Plugins/Wox.Plugin.Folder/Main.cs | 6 ++++ Plugins/Wox.Plugin.PluginManagement/Main.cs | 5 +++ Plugins/Wox.Plugin.Program/Programs/UWP.cs | 12 +++---- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 13 +++---- Plugins/Wox.Plugin.Sys/Main.cs | 15 ++++++-- 7 files changed, 52 insertions(+), 37 deletions(-) diff --git a/Plugins/Wox.Plugin.ControlPanel/Main.cs b/Plugins/Wox.Plugin.ControlPanel/Main.cs index 0e0a8898d..77b57fe86 100644 --- a/Plugins/Wox.Plugin.ControlPanel/Main.cs +++ b/Plugins/Wox.Plugin.ControlPanel/Main.cs @@ -27,7 +27,6 @@ namespace Wox.Plugin.ControlPanel Directory.CreateDirectory(iconFolder); } - foreach (ControlPanelItem item in controlPanelItems) { if (!File.Exists(iconFolder + item.GUID + fileType) && item.Icon != null) @@ -43,7 +42,10 @@ namespace Wox.Plugin.ControlPanel foreach (var item in controlPanelItems) { - item.Score = Score(item, query.Search); + var titleMatch = StringMatcher.FuzzySearch(query.Search, item.LocalizedString); + var subTitleMatch = StringMatcher.FuzzySearch(query.Search, item.InfoTip); + + item.Score = Math.Max(titleMatch.Score, subTitleMatch.Score); if (item.Score > 0) { var result = new Result @@ -66,6 +68,16 @@ namespace Wox.Plugin.ControlPanel return true; } }; + + if (item.Score == titleMatch.Score) + { + result.TitleHighlightData = titleMatch.MatchData; + } + else + { + result.SubTitleHighlightData = subTitleMatch.MatchData; + } + results.Add(result); } } @@ -74,26 +86,6 @@ namespace Wox.Plugin.ControlPanel return panelItems; } - private int Score(ControlPanelItem item, string query) - { - var scores = new List {0}; - if (!string.IsNullOrEmpty(item.LocalizedString)) - { - var score1 = StringMatcher.FuzzySearch(query, item.LocalizedString).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(item.LocalizedString, query); - scores.Add(score1); - scores.Add(score2); - } - if (!string.IsNullOrEmpty(item.InfoTip)) - { - var score1 = StringMatcher.FuzzySearch(query, item.InfoTip).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(item.InfoTip, query); - scores.Add(score1); - scores.Add(score2); - } - return scores.Max(); - } - public string GetTranslatedPluginTitle() { return context.API.GetTranslation("wox_plugin_controlpanel_plugin_name"); diff --git a/Plugins/Wox.Plugin.Everything/Main.cs b/Plugins/Wox.Plugin.Everything/Main.cs index 7969a079f..4188f89dd 100644 --- a/Plugins/Wox.Plugin.Everything/Main.cs +++ b/Plugins/Wox.Plugin.Everything/Main.cs @@ -55,6 +55,7 @@ namespace Wox.Plugin.Everything r.Title = Path.GetFileName(path); r.SubTitle = path; r.IcoPath = path; + r.TitleHighlightData = StringMatcher.FuzzySearch(keyword, Path.GetFileName(path)).MatchData; r.Action = c => { bool hide; @@ -78,6 +79,7 @@ namespace Wox.Plugin.Everything return hide; }; r.ContextData = s; + r.SubTitleHighlightData = StringMatcher.FuzzySearch(keyword, path).MatchData; results.Add(r); } } diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index e8f33a951..f3aef7d1d 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; +using Wox.Infrastructure; using Wox.Infrastructure.Storage; namespace Wox.Plugin.Folder @@ -53,6 +54,7 @@ namespace Wox.Plugin.Folder Title = item.Nickname, IcoPath = item.Path, SubTitle = "Ctrl + Enter to open the directory", + TitleHighlightData = StringMatcher.FuzzySearch(item.Nickname, search).MatchData, Action = c => { if (c.SpecialKeyState.CtrlPressed) @@ -148,6 +150,8 @@ namespace Wox.Plugin.Folder } }); + string searchNickname = new FolderLink { Path = query.Search }.Nickname; + //Add children directories DirectoryInfo[] dirs = new DirectoryInfo(search).GetDirectories(); foreach (DirectoryInfo dir in dirs) @@ -162,6 +166,7 @@ namespace Wox.Plugin.Folder Title = dir.Name, IcoPath = dir.FullName, SubTitle = "Ctrl + Enter to open the directory", + TitleHighlightData = StringMatcher.FuzzySearch(dir.Name, searchNickname).MatchData, Action = c => { if (c.SpecialKeyState.CtrlPressed) @@ -197,6 +202,7 @@ namespace Wox.Plugin.Folder { Title = Path.GetFileName(filePath), IcoPath = filePath, + TitleHighlightData = StringMatcher.FuzzySearch(file.Name, searchNickname).MatchData, Action = c => { try diff --git a/Plugins/Wox.Plugin.PluginManagement/Main.cs b/Plugins/Wox.Plugin.PluginManagement/Main.cs index 785ca3b1f..1ee0fd7ae 100644 --- a/Plugins/Wox.Plugin.PluginManagement/Main.cs +++ b/Plugins/Wox.Plugin.PluginManagement/Main.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; using Newtonsoft.Json; +using Wox.Infrastructure; using Wox.Infrastructure.Http; using Wox.Infrastructure.Logger; @@ -142,6 +143,8 @@ namespace Wox.Plugin.PluginManagement Title = r.name, SubTitle = r.description, IcoPath = "Images\\plugin.png", + TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.name).MatchData, + SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, r.description).MatchData, Action = c => { MessageBoxResult result = MessageBox.Show("Are you sure you wish to install the \'" + r.name + "\' plugin", @@ -191,6 +194,8 @@ namespace Wox.Plugin.PluginManagement Title = plugin.Name, SubTitle = plugin.Description, IcoPath = plugin.IcoPath, + TitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Name).MatchData, + SubTitleHighlightData = StringMatcher.FuzzySearch(query.SecondSearch, plugin.Description).MatchData, Action = e => { UnInstallPlugin(plugin); diff --git a/Plugins/Wox.Plugin.Program/Programs/UWP.cs b/Plugins/Wox.Plugin.Program/Programs/UWP.cs index 77d33e2e2..3cd75fbae 100644 --- a/Plugins/Wox.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Wox.Plugin.Program/Programs/UWP.cs @@ -35,7 +35,6 @@ namespace Wox.Plugin.Program.Programs public UWP(Package package) { - Location = package.InstalledLocation.Path; Name = package.Id.Name; FullName = package.Id.FullName; @@ -266,11 +265,9 @@ namespace Wox.Plugin.Program.Programs private int Score(string query) { - var score1 = StringMatcher.FuzzySearch(query, DisplayName).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(DisplayName, query); - var score3 = StringMatcher.FuzzySearch(query, Description).ScoreAfterSearchPrecisionFilter(); - var score4 = StringMatcher.ScoreForPinyin(Description, query); - var score = new[] { score1, score2, score3, score4 }.Max(); + var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName); + var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + var score = new[] { displayNameMatch.Score, descriptionMatch.Score }.Max(); return score; } @@ -293,14 +290,17 @@ namespace Wox.Plugin.Program.Programs Description.Substring(0, DisplayName.Length) == DisplayName) { result.Title = Description; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; } else if (!string.IsNullOrEmpty(Description)) { result.Title = $"{DisplayName}: {Description}"; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, DisplayName).MatchData; } else { result.Title = DisplayName; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, DisplayName).MatchData; } return result; } diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index dc11a5665..b6a5cf01c 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -33,12 +33,10 @@ namespace Wox.Plugin.Program.Programs private int Score(string query) { - var score1 = StringMatcher.FuzzySearch(query, Name).ScoreAfterSearchPrecisionFilter(); - var score2 = StringMatcher.ScoreForPinyin(Name, query); - var score3 = StringMatcher.FuzzySearch(query, Description).ScoreAfterSearchPrecisionFilter(); - var score4 = StringMatcher.ScoreForPinyin(Description, query); - var score5 = StringMatcher.FuzzySearch(query, ExecutableName).ScoreAfterSearchPrecisionFilter(); - var score = new[] { score1, score2, score3, score4, score5 }.Max(); + var nameMatch = StringMatcher.FuzzySearch(query, Name); + var descriptionMatch = StringMatcher.FuzzySearch(query, Description); + var executableNameMatch = StringMatcher.FuzzySearch(query, ExecutableName); + var score = new[] { nameMatch.Score, descriptionMatch.Score, executableNameMatch.Score }.Max(); return score; } @@ -67,14 +65,17 @@ namespace Wox.Plugin.Program.Programs Description.Substring(0, Name.Length) == Name) { result.Title = Description; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; } else if (!string.IsNullOrEmpty(Description)) { result.Title = $"{Name}: {Description}"; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; } else { result.Title = Name; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData; } return result; diff --git a/Plugins/Wox.Plugin.Sys/Main.cs b/Plugins/Wox.Plugin.Sys/Main.cs index 32e888514..e888dbb37 100644 --- a/Plugins/Wox.Plugin.Sys/Main.cs +++ b/Plugins/Wox.Plugin.Sys/Main.cs @@ -56,12 +56,21 @@ namespace Wox.Plugin.Sys var results = new List(); foreach (var c in commands) { - var titleScore = StringMatcher.FuzzySearch(query.Search, c.Title).ScoreAfterSearchPrecisionFilter(); - var subTitleScore = StringMatcher.FuzzySearch(query.Search, c.SubTitle).ScoreAfterSearchPrecisionFilter(); - var score = Math.Max(titleScore, subTitleScore); + var titleMatch = StringMatcher.FuzzySearch(query.Search, c.Title); + var subTitleMatch = StringMatcher.FuzzySearch(query.Search, c.SubTitle); + + var score = Math.Max(titleMatch.Score, subTitleMatch.Score); if (score > 0) { c.Score = score; + if (score == titleMatch.Score) + { + c.TitleHighlightData = titleMatch.MatchData; + } + else + { + c.SubTitleHighlightData = subTitleMatch.MatchData; + } results.Add(c); } } From f4a2f67db389260f7744f1ed992638ba412063f8 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Tue, 3 Dec 2019 19:22:03 +0100 Subject: [PATCH 10/90] Fix wrong comparison parameter Wrong string was used for FuzzySearch comparison which resulted wrong highlighted letters in the results. --- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index b6a5cf01c..b9a6e401c 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -69,8 +69,9 @@ namespace Wox.Plugin.Program.Programs } else if (!string.IsNullOrEmpty(Description)) { - result.Title = $"{Name}: {Description}"; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, Description).MatchData; + var title = $"{Name}: {Description}"; + result.Title = title; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData; } else { From 92bde25a49fdbc25febe7c1cb5a1cc9a4a8fb265 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 6 Dec 2019 07:49:20 +1100 Subject: [PATCH 11/90] Add option to run as administrator for Shell plugin --- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 10 ++------ Wox.Plugin/SharedCommands/ShellCommand.cs | 24 ++++++++++++++++++++ Wox.Plugin/Wox.Plugin.csproj | 1 + 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 Wox.Plugin/SharedCommands/ShellCommand.cs diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index 36e08a3c4..f0697abef 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -10,6 +10,7 @@ using Microsoft.Win32; using Shell; using Wox.Infrastructure; using Wox.Plugin.Program.Logger; +using Wox.Plugin.SharedCommands; namespace Wox.Plugin.Program.Programs { @@ -96,14 +97,7 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_run_as_administrator"), Action = _ => { - var info = new ProcessStartInfo - { - FileName = FullPath, - WorkingDirectory = ParentDirectory, - Verb = "runas" - }; - var hide = Main.StartProcess(info); - return hide; + return Main.StartProcess(ShellCommand.SetCMDRunAsAdministrator(FullPath, ParentDirectory)); }, IcoPath = "Images/cmd.png" }, diff --git a/Wox.Plugin/SharedCommands/ShellCommand.cs b/Wox.Plugin/SharedCommands/ShellCommand.cs new file mode 100644 index 000000000..ac3f4e7cb --- /dev/null +++ b/Wox.Plugin/SharedCommands/ShellCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Wox.Plugin.SharedCommands +{ + public static class ShellCommand + { + public static ProcessStartInfo SetCMDRunAsAdministrator(this string fullPath, string parentDirectory) + { + var info = new ProcessStartInfo + { + FileName = fullPath, + WorkingDirectory = parentDirectory, + Verb = "runas" + }; + + return info; + } + } +} diff --git a/Wox.Plugin/Wox.Plugin.csproj b/Wox.Plugin/Wox.Plugin.csproj index 843cc7d8b..3a486fb42 100644 --- a/Wox.Plugin/Wox.Plugin.csproj +++ b/Wox.Plugin/Wox.Plugin.csproj @@ -78,6 +78,7 @@ + From 37a4eefc4669dd8c6138fb47d05eeb828fb0fd83 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Mon, 9 Dec 2019 00:26:21 +0200 Subject: [PATCH 12/90] updated to use share folder --- Plugins/Wox.Plugin.Folder/Main.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index b5c122b10..f36029cb8 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -44,7 +44,7 @@ namespace Wox.Plugin.Folder var results = GetUserFolderResults(query); string search = query.Search.ToLower(); - if (_driverNames != null && !_driverNames.Any(search.StartsWith)) + if (!IsDriveOrSharedFolder(search)) return results; results.AddRange(QueryInternal_Directory_Exists(query)); @@ -58,6 +58,26 @@ namespace Wox.Plugin.Folder return results; } + private static bool IsDriveOrSharedFolder(string search) + { + if (search.StartsWith(@"\\")) + { + return true; + } + + if (_driverNames != null && _driverNames.Any(search.StartsWith)) + { + return true; + } + + if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':') + { + return true; // we don't know so let's give it the possibility + } + + return false; + } + private Result CreateFolderResult(string title, string path) { return new Result From 78f243651e82155b5cb80b70e5aaa1dfc3899b8f Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Mon, 9 Dec 2019 20:47:24 +0100 Subject: [PATCH 13/90] Pass Settings instance to Alphabet again --- Wox.Infrastructure/Alphabet.cs | 21 +++++++++------------ Wox/App.xaml.cs | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Wox.Infrastructure/Alphabet.cs b/Wox.Infrastructure/Alphabet.cs index a2f64241a..ee6b84b41 100644 --- a/Wox.Infrastructure/Alphabet.cs +++ b/Wox.Infrastructure/Alphabet.cs @@ -15,15 +15,12 @@ namespace Wox.Infrastructure private static readonly HanyuPinyinOutputFormat Format = new HanyuPinyinOutputFormat(); private static ConcurrentDictionary PinyinCache; private static BinaryStorage> _pinyinStorage; - private static bool _shouldUsePinyin = true; + private static Settings _settings; - public static void Initialize(bool shouldUsePinyin = true) + public static void Initialize(Settings settings) { - _shouldUsePinyin = shouldUsePinyin; - if (_shouldUsePinyin) - { - InitializePinyinHelpers(); - } + _settings = settings; + InitializePinyinHelpers(); } private static void InitializePinyinHelpers() @@ -43,7 +40,7 @@ namespace Wox.Infrastructure public static void Save() { - if (!_shouldUsePinyin) + if (!_settings.ShouldUsePinyin) { return; } @@ -59,7 +56,7 @@ namespace Wox.Infrastructure /// public static string[] Pinyin(string word) { - if (!_shouldUsePinyin) + if (!_settings.ShouldUsePinyin) { return EmptyStringArray; } @@ -81,7 +78,7 @@ namespace Wox.Infrastructure /// public static string[][] PinyinComination(string characters) { - if (!_shouldUsePinyin || string.IsNullOrEmpty(characters)) + if (!_settings.ShouldUsePinyin || string.IsNullOrEmpty(characters)) { return Empty2DStringArray; } @@ -122,7 +119,7 @@ namespace Wox.Infrastructure public static bool ContainsChinese(string word) { - if (!_shouldUsePinyin) + if (!_settings.ShouldUsePinyin) { return false; } @@ -140,7 +137,7 @@ namespace Wox.Infrastructure private static string[] Combination(string[] array1, string[] array2) { - if (!_shouldUsePinyin) + if (!_settings.ShouldUsePinyin) { return EmptyStringArray; } diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index acfb88d26..8cdc4a8b8 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -53,7 +53,7 @@ namespace Wox _settingsVM = new SettingWindowViewModel(); _settings = _settingsVM.Settings; - Alphabet.Initialize(_settings.ShouldUsePinyin); + Alphabet.Initialize(_settings); StringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision; From 49c5c5bbdeb355541c587f9f60f5f432a491bc1d Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Mon, 9 Dec 2019 20:57:59 +0100 Subject: [PATCH 14/90] Pass "ShouldUsePinyin" to StringMatcher Flag is used in method "ShouldUsePinyin()" to avoid calling Alphabet service. Otherwise, tests applying to StringMatcher.FuzzySearch() would fail because the pinyin helper library fails to initialize. --- Wox.Infrastructure/StringMatcher.cs | 8 +++++++- Wox.Infrastructure/UserSettings/Settings.cs | 12 +++++++++++- Wox/App.xaml.cs | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index dd5a118fb..baca1020f 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -13,6 +13,7 @@ namespace Wox.Infrastructure public static MatchOption DefaultMatchOption = new MatchOption(); public static string 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")] public static int Score(string source, string target) @@ -135,6 +136,11 @@ namespace Wox.Infrastructure public static int ScoreForPinyin(string source, string target) { + if (!ShouldUsePinyin) + { + return 0; + } + if (!string.IsNullOrEmpty(source) && !string.IsNullOrEmpty(target)) { if (Alphabet.ContainsChinese(source)) diff --git a/Wox.Infrastructure/UserSettings/Settings.cs b/Wox.Infrastructure/UserSettings/Settings.cs index 7371ea5d0..de5a2e662 100644 --- a/Wox.Infrastructure/UserSettings/Settings.cs +++ b/Wox.Infrastructure/UserSettings/Settings.cs @@ -24,7 +24,17 @@ namespace Wox.Infrastructure.UserSettings /// /// when false Alphabet static service will always return empty results /// - public bool ShouldUsePinyin { get; set; } = true; + private bool _shouldUsePinyin = true; + public bool ShouldUsePinyin + { + get { return _shouldUsePinyin; } + set + { + _shouldUsePinyin = value; + StringMatcher.ShouldUsePinyin = value; + } + } + private string _querySearchPrecision { get; set; } = StringMatcher.SearchPrecisionScore.Regular.ToString(); public string QuerySearchPrecision diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index 8cdc4a8b8..9436df475 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -56,6 +56,7 @@ namespace Wox Alphabet.Initialize(_settings); StringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision; + StringMatcher.ShouldUsePinyin = _settings.ShouldUsePinyin; PluginManager.LoadPlugins(_settings.PluginSettings); _mainVM = new MainViewModel(_settings); From e60797cd0f55d5566c54bb755dd3c998633f7b33 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Mon, 9 Dec 2019 21:06:31 +0100 Subject: [PATCH 15/90] Introduce RawScore property New property RawScore is used to save the calculated score without any search precision filtering added. --- Wox.Infrastructure/StringMatcher.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index baca1020f..f350e0e40 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -96,7 +96,7 @@ namespace Wox.Infrastructure { Success = true, MatchData = indexList, - Score = Math.Max(score, pinyinScore) + RawScore = Math.Max(score, pinyinScore) }; return result.Score > 0 ? @@ -171,16 +171,22 @@ namespace Wox.Infrastructure { public bool Success { get; set; } - private int _score; - public int Score + /// + /// The final score of the match result with all search precision filters applied. + /// + public int Score { get; private set; } + + /// + /// The raw calculated search score without any search precision filtering applied. + /// + private int _rawScore; + public int RawScore { - get - { - return _score; - } + get { return _rawScore; } set { - _score = ApplySearchPrecisionFilter(value); + _rawScore = value; + Score = ApplySearchPrecisionFilter(_rawScore); } } From ce89ff3c85bbe3f0bc05650fb392ee48cc7c1222 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Mon, 9 Dec 2019 21:08:17 +0100 Subject: [PATCH 16/90] FIx test methods Use RawScore or Score depending on the test case. Also removed unnecessary test settings introduced earlier. --- Wox.Test/FuzzyMatcherTest.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Wox.Test/FuzzyMatcherTest.cs b/Wox.Test/FuzzyMatcherTest.cs index aa8778a54..21563f91f 100644 --- a/Wox.Test/FuzzyMatcherTest.cs +++ b/Wox.Test/FuzzyMatcherTest.cs @@ -12,7 +12,7 @@ namespace Wox.Test [TestFixture] public class FuzzyMatcherTest { - public List GetSearchStrings() + public List GetSearchStrings() => new List { "Chrome", @@ -49,16 +49,13 @@ namespace Wox.Test "aac" }; - Alphabet.Initialize(false); - StringMatcher.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Low.ToString(); - var results = new List(); foreach (var str in sources) { results.Add(new Result { Title = str, - Score = StringMatcher.FuzzySearch("inst", str).Score + Score = StringMatcher.FuzzySearch("inst", str).RawScore }); } @@ -75,7 +72,7 @@ namespace Wox.Test { var compareString = "Can have rum only in my glass"; - var scoreResult = StringMatcher.FuzzySearch(searchString, compareString).Score; + var scoreResult = StringMatcher.FuzzySearch(searchString, compareString).RawScore; Assert.True(scoreResult == 0); } @@ -132,16 +129,12 @@ namespace Wox.Test .ToList(); var results = new List(); - - Alphabet.Initialize(false); - StringMatcher.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.None.ToString(); - foreach (var str in searchStrings) { results.Add(new Result { Title = str, - Score = StringMatcher.FuzzySearch(searchTerm, str).Score + Score = StringMatcher.FuzzySearch(searchTerm, str).RawScore }); } From 7d5c827dec085d29e4631b4a8be96c363d60e4cf Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Mon, 9 Dec 2019 21:08:38 +0100 Subject: [PATCH 17/90] Remove whitespace --- Wox.Infrastructure/Alphabet.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Wox.Infrastructure/Alphabet.cs b/Wox.Infrastructure/Alphabet.cs index ee6b84b41..63e178ba1 100644 --- a/Wox.Infrastructure/Alphabet.cs +++ b/Wox.Infrastructure/Alphabet.cs @@ -149,7 +149,5 @@ namespace Wox.Infrastructure ).ToArray(); return combination; } - - } } From 201c26f7c853c0e758782eff2af18832f9504f32 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 10 Dec 2019 08:23:34 +1100 Subject: [PATCH 18/90] Add run as administrator to Shell plugin settings --- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 2 +- Plugins/Wox.Plugin.Shell/Main.cs | 32 +++++++------------- Plugins/Wox.Plugin.Shell/Settings.cs | 2 ++ Wox.Plugin/SharedCommands/ShellCommand.cs | 9 +++--- 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index f0697abef..12b54b524 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -97,7 +97,7 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_run_as_administrator"), Action = _ => { - return Main.StartProcess(ShellCommand.SetCMDRunAsAdministrator(FullPath, ParentDirectory)); + return Main.StartProcess(ShellCommand.SetProcessStartInfo(FullPath, ParentDirectory)); }, IcoPath = "Images/cmd.png" }, diff --git a/Plugins/Wox.Plugin.Shell/Main.cs b/Plugins/Wox.Plugin.Shell/Main.cs index 1e60ef8c9..323cfb5cd 100644 --- a/Plugins/Wox.Plugin.Shell/Main.cs +++ b/Plugins/Wox.Plugin.Shell/Main.cs @@ -9,6 +9,7 @@ using WindowsInput.Native; using Wox.Infrastructure.Hotkey; using Wox.Infrastructure.Logger; using Wox.Infrastructure.Storage; +using Wox.Plugin.SharedCommands; using Application = System.Windows.Application; using Control = System.Windows.Controls.Control; using Keys = System.Windows.Forms.Keys; @@ -164,16 +165,15 @@ namespace Wox.Plugin.Shell { command = command.Trim(); command = Environment.ExpandEnvironmentVariables(command); + var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? "" : "runas"; ProcessStartInfo info; if (_settings.Shell == Shell.Cmd) { var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause"; - info = new ProcessStartInfo - { - FileName = "cmd.exe", - Arguments = arguments, - }; + + info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsAdministratorArg); } else if (_settings.Shell == Shell.Powershell) { @@ -186,11 +186,8 @@ namespace Wox.Plugin.Shell { arguments = $"\"{command} ; Read-Host -Prompt \\\"Press Enter to continue\\\"\""; } - info = new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = arguments - }; + + info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsAdministratorArg); } else if (_settings.Shell == Shell.RunCommand) { @@ -200,21 +197,17 @@ namespace Wox.Plugin.Shell var filename = parts[0]; if (ExistInPath(filename)) { - var arguemtns = parts[1]; - info = new ProcessStartInfo - { - FileName = filename, - Arguments = arguemtns - }; + var arguments = parts[1]; + info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsAdministratorArg); } else { - info = new ProcessStartInfo(command); + info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg); } } else { - info = new ProcessStartInfo(command); + info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg); } } else @@ -222,10 +215,7 @@ namespace Wox.Plugin.Shell return; } - info.UseShellExecute = true; - info.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - info.Verb = runAsAdministrator ? "runas" : ""; try { diff --git a/Plugins/Wox.Plugin.Shell/Settings.cs b/Plugins/Wox.Plugin.Shell/Settings.cs index 62eb31f4e..121875308 100644 --- a/Plugins/Wox.Plugin.Shell/Settings.cs +++ b/Plugins/Wox.Plugin.Shell/Settings.cs @@ -7,6 +7,8 @@ namespace Wox.Plugin.Shell public Shell Shell { get; set; } = Shell.Cmd; public bool ReplaceWinR { get; set; } = true; public bool LeaveShellOpen { get; set; } + internal bool RunAsAdministrator { get; set; } = true; + public Dictionary Count = new Dictionary(); public void AddCmdHistory(string cmdName) diff --git a/Wox.Plugin/SharedCommands/ShellCommand.cs b/Wox.Plugin/SharedCommands/ShellCommand.cs index ac3f4e7cb..d7071e735 100644 --- a/Wox.Plugin/SharedCommands/ShellCommand.cs +++ b/Wox.Plugin/SharedCommands/ShellCommand.cs @@ -9,13 +9,14 @@ namespace Wox.Plugin.SharedCommands { public static class ShellCommand { - public static ProcessStartInfo SetCMDRunAsAdministrator(this string fullPath, string parentDirectory) + public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory="", string arguments = "", string verb = "") { var info = new ProcessStartInfo { - FileName = fullPath, - WorkingDirectory = parentDirectory, - Verb = "runas" + FileName = fileName, + WorkingDirectory = workingDirectory, + Arguments = arguments, + Verb = verb }; return info; From b123d95b71520540c73e92d5dc776a7630f323ab Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 10 Dec 2019 20:03:43 +1100 Subject: [PATCH 19/90] Add run as admin toggle option to plugin settings --- Plugins/Wox.Plugin.Shell/Languages/en.xaml | 1 + Plugins/Wox.Plugin.Shell/ShellSetting.xaml | 4 +++- Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Plugins/Wox.Plugin.Shell/Languages/en.xaml b/Plugins/Wox.Plugin.Shell/Languages/en.xaml index 7a953986c..6f56e83f5 100644 --- a/Plugins/Wox.Plugin.Shell/Languages/en.xaml +++ b/Plugins/Wox.Plugin.Shell/Languages/en.xaml @@ -4,6 +4,7 @@ Replace Win+R Do not close Command Prompt after command execution + Always run as administrator Shell Allows to execute system commands from Wox. Commands should start with > this command has been executed {0} times diff --git a/Plugins/Wox.Plugin.Shell/ShellSetting.xaml b/Plugins/Wox.Plugin.Shell/ShellSetting.xaml index f631f6e22..dc6de53ba 100644 --- a/Plugins/Wox.Plugin.Shell/ShellSetting.xaml +++ b/Plugins/Wox.Plugin.Shell/ShellSetting.xaml @@ -12,10 +12,12 @@ + - + + CMD PowerShell RunCommand diff --git a/Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs b/Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs index 639c5c3c9..ffa3b5856 100644 --- a/Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs +++ b/Plugins/Wox.Plugin.Shell/ShellSetting.xaml.cs @@ -17,6 +17,7 @@ namespace Wox.Plugin.Shell { ReplaceWinR.IsChecked = _settings.ReplaceWinR; LeaveShellOpen.IsChecked = _settings.LeaveShellOpen; + AlwaysRunAsAdministrator.IsChecked = _settings.RunAsAdministrator; LeaveShellOpen.IsEnabled = _settings.Shell != Shell.RunCommand; LeaveShellOpen.Checked += (o, e) => @@ -29,6 +30,16 @@ namespace Wox.Plugin.Shell _settings.LeaveShellOpen = false; }; + AlwaysRunAsAdministrator.Checked += (o, e) => + { + _settings.RunAsAdministrator = true; + }; + + AlwaysRunAsAdministrator.Unchecked += (o, e) => + { + _settings.RunAsAdministrator = false; + }; + ReplaceWinR.Checked += (o, e) => { _settings.ReplaceWinR = true; From c22ab4afdf2dfdb8a88351b273aeed3058dc3d28 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 10 Dec 2019 20:11:12 +1100 Subject: [PATCH 20/90] Revert changes to other plugin. Will refactor as a separate PR --- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index 12b54b524..36e08a3c4 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -10,7 +10,6 @@ using Microsoft.Win32; using Shell; using Wox.Infrastructure; using Wox.Plugin.Program.Logger; -using Wox.Plugin.SharedCommands; namespace Wox.Plugin.Program.Programs { @@ -97,7 +96,14 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_run_as_administrator"), Action = _ => { - return Main.StartProcess(ShellCommand.SetProcessStartInfo(FullPath, ParentDirectory)); + var info = new ProcessStartInfo + { + FileName = FullPath, + WorkingDirectory = ParentDirectory, + Verb = "runas" + }; + var hide = Main.StartProcess(info); + return hide; }, IcoPath = "Images/cmd.png" }, From e60e574902a582c8dbbc50fbc502c604a5faa8ba Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 10 Dec 2019 20:24:18 +1100 Subject: [PATCH 21/90] update --- Plugins/Wox.Plugin.Shell/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Wox.Plugin.Shell/Settings.cs b/Plugins/Wox.Plugin.Shell/Settings.cs index 121875308..af149e829 100644 --- a/Plugins/Wox.Plugin.Shell/Settings.cs +++ b/Plugins/Wox.Plugin.Shell/Settings.cs @@ -7,7 +7,7 @@ namespace Wox.Plugin.Shell public Shell Shell { get; set; } = Shell.Cmd; public bool ReplaceWinR { get; set; } = true; public bool LeaveShellOpen { get; set; } - internal bool RunAsAdministrator { get; set; } = true; + public bool RunAsAdministrator { get; set; } = true; public Dictionary Count = new Dictionary(); From bcc9379ebc9087f6cd5ae75f143354dfbb766026 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Tue, 10 Dec 2019 12:18:24 +0200 Subject: [PATCH 22/90] typo in log --- Wox.Infrastructure/Logger/Log.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index dfa05c715..ff72dff1c 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -87,7 +87,7 @@ namespace Wox.Infrastructure.Logger do { - logger.Error($"Exception fulle name:\n <{e.GetType().FullName}>"); + 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}>"); From 3f3deb8e2afcd4660c0d79659abefc348370d100 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Tue, 10 Dec 2019 12:38:03 +0200 Subject: [PATCH 23/90] added share folder --- Plugins/Wox.Plugin.Folder/Main.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index 2c8cf2703..4f8fac60d 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -44,7 +44,7 @@ namespace Wox.Plugin.Folder var results = GetUserFolderResults(query); string search = query.Search.ToLower(); - if (_driverNames != null && !_driverNames.Any(search.StartsWith)) + if (!IsDriveOrSharedFolder(search)) return results; results.AddRange(QueryInternal_Directory_Exists(query)); @@ -58,6 +58,26 @@ namespace Wox.Plugin.Folder return results; } + private static bool IsDriveOrSharedFolder(string search) + { + if (search.StartsWith(@"\\")) + { // share folder + return true; + } + + if (_driverNames != null && _driverNames.Any(search.StartsWith)) + { // normal drive letter + return true; + } + + if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':') + { // when we don't have the drive letters we can try... + return true; // we don't know so let's give it the possibility + } + + return false; + } + private Result CreateFolderResult(string title, string path, string queryActionKeyword) { return new Result From 695be896569c9c2634155e4e1361c39040ddfbf6 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 10 Dec 2019 22:01:12 +1100 Subject: [PATCH 24/90] Add ability to run as a different user for win32 programs --- Plugins/Wox.Plugin.Program/Images/user.png | Bin 0 -> 65650 bytes Plugins/Wox.Plugin.Program/Languages/en.xaml | 1 + Plugins/Wox.Plugin.Program/Main.cs | 4 +- Plugins/Wox.Plugin.Program/Programs/UWP.cs | 2 +- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 21 ++++++- .../Wox.Plugin.Program.csproj | 3 + Wox.Plugin/SharedCommands/ShellCommand.cs | 53 +++++++++++++++++- 7 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 Plugins/Wox.Plugin.Program/Images/user.png diff --git a/Plugins/Wox.Plugin.Program/Images/user.png b/Plugins/Wox.Plugin.Program/Images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..2d45c1ee919304a973641b387934dbd517979271 GIT binary patch literal 65650 zcmV*nKuEudP)KoEefB#uyts#u%fQWvShg+Wqc(&#Brg ze^l+-YrpDLoqKNg?e4Q|^{uMkUTgnq*WPQdy=&J#goP|*R{5P100{Wb>wy2|3E)4y z1vh`_6rhkZBgguMoQv@ggV8b+Eu$DOV=`W$e6T|Ka1}(WD9U9$K6gF1+y$aFkXi$1 z6+~-b9)a}=z(W8dFmiwu00jVC%?_NSy`II+#xaI1Sb(!TKarc0vtz zJ`a_h2lzA>jYd()+z44*}#L%E7Gah&3T1hHbkXb+{1)p_i~MjcO2SFc$y{C^ZJD z^I*LW%1+Z@=>*F0aX>vygQdUYvV4?TKZ(KUB=qF`WU#cZM_NOT^>c69*P7Ep7P4@> z3#sHco~gXkHA5VHE`y#R=kbLhseBcLJt)ilAUed83ojy-A7ot|gf91h)CgJ+!8)%D zF&I!yyotJ!MVuf5AkQN@Seu?lf4WTugF12H8X#*!+elhfPFd@9sO&gp`ID@R$7#6Z zGtm4vvg~OvjlXfhT`cPHE?EJMejefaLNl=INpW)+DYsxCm89$kc!DNsca3C z@c{sbpeJ7o&9_0R!(2}G16YABR?P69!HU>8r0R+}VXiz^=pY$j0#SVofIv2YWd;e4 zC)_1iV7~LM@-x;v+cR?UbK?4#Y-$al;-vDk0M1f2cp6$i3Z=dPmH#ckk0W1ticy@@ z+3@@Z@%TiZ18=>i?Wru}3YUfBy-H=@uRN>Nq!<$AYplzI#C$uzUk@&CgVwjRE_VaG zQcV@Gs+?4}?~#sHlql|2WgJ`c*i0HXiMM4u;>J;}N_sRx7cGw07~{Nye3daMh%Ol0AB zuOeCc&11Os>@Xj4zJ~FIBhdOTl#`c&a68IkFTg9z

PbOc!t+-8s$(Xa?|2G$~zC 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≠V82n0$h7|Q}MW-`-nIZ*@(Hi-qZj`A#@9Yv_z?V!7ZquB6y>JBJYNexgamKza7lViSHYss4l_O^8GtXiC zrN4$gcLF!AU4vKN^+H^K&D2#gbC*l;K}Aa1xa``I;eo?Dal^7|o{RGzMXtdXeKb8u zjN|sCUHNR;OYdWCdJAk*7?_7d>L{HmS5x&R>Uj5l@L8oN8+SmLM+i`j$;Nq!T2V{o z_u{SD_W0DCqv`Se-5gwzfxb5ETwJ?qVbY6pInS;@NWj+@($bWwgNJJC{+QhenS8v@EFtA~r=U z3to)d{I;G8dT^08(_E-C7Y2hBm&F|zuOHd}$aB#~6TetahS2&hXgzetq}F+v-O1$r z%4Jt}7FYBHZgDt0M{}>9PErqk-toUt%xhoN{1ZwnkIq7IE~yTn8On;v5sVASO&fN2 z)wyieu3m&-bB&?TK97wreFnUK1^{r)jwQVE#V^EYI9SZ>dg4rL0N625c;N6dmUFj; z2EhvXwb#XhF_OUKv!*ti>|>%UU=h63`B}{FkznpYb|D-IxU5YXY`Ie@P21ZP7a3Og zT)6q44wnp{x^RIMcEq4;w9Bj zx10O2y|B40*Xg@W-8xLy?YMEUldyIEa9Wd(ROL=sADM?V5H z1YbDaqAT4_szy3H7Dvf-sS}dCDXKVpC!+Eaq;CF_U^!o_6=n)RvU>NMs`6M>P8*rlFvJ-$sg_s5NGS8rA>m17HS*1u50aW0hN z(%fJ5oa0>N(t-|LhyX4X%Hm~|XS?vnUxY7#AZ9=Bj=z4(_zVSdx>v|XT%hD{{U=*j2BjPmp` zjGz28^qFUyetvm4z$;($LR`19+}5Akg4J2=PwRq$046PxuYnnVk0(xP{g$!$RAsU=~?KJiO}c9g{)qz4m1P~L`eve$64Qvf!^ zcITm->>T$haXJ0a6gXQ4Z1{7ahfvQuk4dt5a)`;Yi&d_vHX|6d=%t>sA1Fy>ztiet z*xfTj*wh;3sb?yud+w_+jg}d>>H1Z?__hNWN3*<8GKa zmG3P4I_O~VWX1;4C;IJ*^>OT*k>*9(sb?^L;xD1ko~ZNSrkw9QybrhB81~j(B{f{{ zbL@XP)+Nu_2Nqk!C5((22iFETvS-*dNA~|Cdo1xFExYL<(jbH<7%FM60$b z^~mVeCzUN~{FA>5Uq-A=1ZeEQomZ`c&P{Vcgp0}F=VI?av(Ux7L<+Fpm8fRk8C#;RywMQm_m{_?nbI^q(w4;bIcLUix{Jrb-;F%5#OE6 zjB|o^EGu@ThP$fO~%Pycy5{efBJ@OX6}2sow<#A~OfHoVJxtkX4L4j1DURd4*-&fY`})bdFu1qaeJxi%?+saY_% zHXVe*G*dB}f>lTElCkG($5ac0aoy@NUi88P(QO~Q%GD6@`oathGs27Z?ZB=bvg{~& z*vvgft^I-O?wZ%o_`Mz_Ykw`>2=}4tiU+JWqIJHXWYdNj^iyg=KvpqE_X9Q`ELio zwh}TB>`vAcS$)$~hj(%(v1)ixDcN3{Ehg?I&`V%4?}kexNQ&)T)=Sv?yCqm8@!T)~ zTzwp|@#LqQ)eBY#RX0f3zw4T-W(Ts(NVusEK#Fj~&J25YR*sj&zfD?mg{2#^m@{YU z1q0Rz_+uM)DOPipY^PkcOKKc9k-gqT2F{BpfwQ<)r!n^Ieg?iCIeMGrLgN19ZefOt z(Pc7I3f8834=x75C58cReqFc#P^`<{#5hP=JD%5Zyn3=eV1j2TMM4KRcf^HJ$Ut(Pp;=5Sl`a2g^zXCCHuBGCpcG1e$f z9mB?xp8~H}PB$!zs>eOF_d4uewNq2GeCTjBPBl~sfnCcP4qlr>Au+Xoc&Sh3Em0D!C zFp(d8J%(LQ7AymjcjMcW-GH}qStl+4z^kmwuhqqQXPZdJ@lrNeCDylAA5Ku+s6~V; zvSm|yB9GWFNT=5b(hB<=$9$VU=7=0(r7#LH3J%(BcS=qj5=9I)9Yte=B~hN@FHi2=gNldv0mdTq-?hw=lSRflWeH*1k>26JDt4a7p`{ zb}m5WOZ9D(WzOfrjpM!kqmP1gu^U9UR@ZHG%4`Bmn?WjqWQ5g%BA9gHO*-;0ZqZ}+ zJP;0f$dbLP+=$lAhBwucF;s=!yk@RTxURFKfx^wZS1}ySKEHdlNGeJmW`up$jz0bH_gLULm`(6dYtyD~QOGw!?%L=7Fz-6(w@=PMiG93wQ0kW6? zZk(Z<-*9>XvU=j!kK}?dp1NVC9GnrE*U5^0L!{$osaZ2`?r8aSSvE2_;;dq~KEHjN8d17)!nj05{Ve75no_F!YE zpz=dRv}zilX}HE+I+d*U9t`RHY+Cbjz%2rQ?n!F5N1@XqRPxgJ$8n6T-=Po#+~?#r zGCzOOZwjC0rX|PExi;greeSaJAcQ=8J$>#3CQpA3`us^ZyXq3;1rhDhOxUhIm`+ zPrSG38H#Njn#hAf%&#wV!CJV)68MA4a0jw@4t}L=BHa^DV&jYdAM}}L-3Cg^9)nO- z1Jhdo%YzInOIPO)Sp5H^ZKZkKVi-goDwofYZvYH^?s<%#`j6m^ zv;NJ5ZCi36!cvxDu!;4%SDJL0c8F+UkQ4IEjh>7&bsq56m&c-lWENg}(ITYH*l^m( zPdIhC+$s_`IxoWojXK72f-F~W>$o)OJuYz4PUqrxG4tXAW{}DbQ9fA4SF2tBsi z03=_NR#IZ`T;l8<2bk~3*@o*96CH}{oLlluVVjkCJMj1rY8Cv&P#&i`+YU+UiXl7^~)_Gr&b+KMU7uZ>5Q1WrJK+KhUyK*!aAEC(exbQWQ4Stbn2eR zN=CZ~%5YN>48Ia7x?!zp*!lz6Y? z?5<|ovuWqeb#srkO^MCQ1u>ic(l$orM-um7xisJ7HTPg`$Msm~_2#1W5R>(<$@M4) z>N(y_Wy=8egS9e+TYa0>oH!p)ROW1E<{M}8Y)nUDF}Qk7PtzENwBDSH3to&CcS@qy za^`W_!!U1Y^W>YrlCX<8OU8`&J)k3Qxi?qqLR>F3#%0;Ut($D-_s^u>JieOSDlW-6 zUm{FOjY6}XcWnX=8^C}dSAIDwZ>Bv$zCzNqIS~tL|+OT(zT&FcW6MELR zXl#uhC-i3F0?%exE>@3P^b%1`tg1OSXXK(w1wxfO zf&Iw!)ZQk|P+zE;b}TR!=}pSiE?mJf(3%i~>X&h{M=+5hZJLX;=`Qc`^h5|7=t)el zu~Qu(Pn(`*<3do#2m4VLy8%G$`_NP1jq__3h=pLCddm+#kCXH(=&G3@=f+ zOh;(=w(d>_PcMPVoAm!iu^!%Z{?T!_9OJ7$M05p4dG-V*U;Z3;<6OHv-`UFZ$XoTI zC~^ASI!e8{Zs%&4YDhWdQUj+h6qu9|zbOK-*!9LxpK3R~K`jhzSAs3oF^VN-0+CY!Mo*JWmuXP(96>A!(K|5ef0N&G7=IWN@&;9M8a9fb8s ziKm`FgK<$djwTj$&N^4>OW!%%t=uVXKJ&@IxL`bUw#2w?s(fl*emKz~IpqGeh7g*% zU_Q!;VuDi;ZIg`Z+u|QaW=j*b|*0w7F^8cCvKnZpJqD3;}|r3 zY0t+Rs0C}&OQiQQ7D6pp%4u;LV-0=gSxlb(JoJTA|376($ zF*Dc~5+oLaAVHBLMVS;STP(|#>?pFVSV|;I6f3e-wo6t1D^@C%ROF?~m2y=oj*`e# zDaCfFoO!-*jeneqDNHEe9R&-3mz0lt%to}3g*&NC(g zHU=ZSzBa_Dty+u)F=c$?DMdp*b??myxxnhNiKFzmVT&Qv?K7(vrM){W%1i?mIYVJm zEc(oaRxJLpBBd#?0063L9)q33ytC0Ms?o5=gPmijM)M|dedRqw0qC6Th}EU(Co=C) zk+NWu@v_iaco*K=L|mBR!%G>J8L^USd2(_Yhxj=?PRd$s8jPWwe1vGE*Jw!p4J|O&)z|Heve)KbEBS>p+d=CiSG_mK>blj*2zFRq$FjY` zVmIqW^;*?$j%}DY=o$FFH+fA1<3v=kR~iK zq35%FEOkxIvRE$-tcDkoXs#PTA`=28U3RK~y|YI;w!3DLNl~YIB(hMG!W>VchmamJ zHW8(lZFQk()lnJM<~0o7d>OoTqpnE4<5B%}n`hL}izk)T**kxA9ha_eyranQofz`R zLIRgIhPb#NU(K85Cdt--q)Mn2ZcSklv8jr+ zxAk0Cu{T(9csPU8x-x0VVM)q5m+pjE+|px*itZ|P7v>8p%0(3AQ4n>cC-&Prp!m&u zgxa-p|6`YcUPmypv^5LC!-b_T5OI<4_+p= z8N=&uVCT%!^{)XtRwpVzgpj9*Yrjty@KA^~>0d$=h7ntx@5J zDS=EYL!emOs$)`%w~Tqcn}Q`5@>pyXYjZLzG$R%Qh&ohuj#5!B7Z~o$^JusN<|6hM zBd5~H!gewt56YZQMxjk>2rhoytFU+nkRFF7l8IekK(OyO$hcYN)5*!$+-GHbU^9*b zFh-l#FnHsIaLSm9;6Gc0>vN5<@JVBuh<}-J*X{r8j4FKlwF_9=+!^cjPP=#Zdnrtk z_xO5kr^54>`WSM)&LF7|w!vnaor$0KA47*-I~j(dQw&Cn%$=QjvOR=7?9rm*$c}g1 z%`pCEV9^-rxH&BB^n!o@EsL-~=V4Ed1dewyvUB|mD`Bzj>Sxvz1yvyK;gzc7n6)(f&Wy|j}eSi5;yYSTdF3ZomBFnIk1 zaDNj~K}A+{f@uXeb;clRepUeZ_G=e#=E5~K3fkK+D_QUMmnx>gGR}jKry7_EuU#A9 z`HTJVN;sugd0sma5!PIft@J2kPwT~P%n*y|tc8r!bLJ>M{PV1vpzKkkmn^RA5{v%X zoh;R2XFr6BcIhXt2WloyCGW-Bl2+!2cQr2dk>k7s5d79iUQ{@-@ubEeAn#f-P3%|I6bl3U#i$kEIKfNK~>>T&u-%U#z+-u zWHWx=B#%ZR3Tz&Lal-VC7G-o@T_UBYA^0a|L3aWyu3qgW7P-j&z@+Fk+00-GQ8HYU z!D3|7V<|vbp-#D4P&v0kMLDlDf;T3vIO}HXhB9s2$)c|p$jkJ0GM5HxJ)3cvhG1wL z((6Zsg!DBkDoK05C~j^T=pUt99#XxGV$e%%HVzoM!bnJWdoAN8H3iB?qUc#t7)`ba zTZ0iE|FcuLdSfdaYuxX#eLn=d=E1nKHNqFavx$CoADP7S|I-iBK<(lTn4qe!_3Kyd zF--HNu2FJNX!Nw>wkcRbjoce7nsAaWV-X&!*-T} zJtLEzFx$!IiE^}Y1%vZ1)pNS8<1@du5l`{N-fDtV7S}{Kye0p}{DNJuMF5I|@bszk z_~v)sNb)9WPwOGxOrpLSQx+53a(};dehbfD7@){iBoij+QMvJ^!6fCc5!!(N$55wf zsZ+R6H$+B8MzKuU^65AkS(}WE-qctuE+cbJfLU+I<-uP^dh%a3?_s;D*P8(g01#-N zh?WafgT?yeiJYq^ov53~P+J^GWqsw>9T;a$^ZZ$07*naRJ6ffVDa}1Z0uC{!b=<27^upe>$KY{YA_ttlL#9{HXR0k z*{}_*ZhGPKSkf}ur3_Qbro`eYBd4eSA^O{jMVBlik0p8~vtp^!sCpO;mW!&td5Ei_ z`ej1`IH-mr)O6#d#A1`HEi$bq*KA}^GUR34EOVVsD+W?=V_B0!!O2C6C##zU*`&u9 zUVjt)v(JM4(T=jI+eQuEFQu6!i5R{5z_z9k!pgHN9qm24KO+YI-)BzaOW!(;{$QBN z=xlp!OSaS9URfR8|-oTf?yIp%X!9{Y7vkEW1QWZlgpSX>(0OW`D?BdrlR(h;$ zG6STB_l{(|o|*nrl;HG`=+%{}`4leL-zoHLB=skRi5; z5IvPoLUY>sZ&ntyFUlpD3DY%W_!k#ru}Lwi&FdImc(vZWpvaL_hG`;@wxrp2^IF#u zc5OCiH$1y|eG^}N`a8JNFxUJ27IGf-mCX^JIK7E0n-#(fi=36rXapIVovO34#_d+J z9^)1BGSBlNGhs*#fZI+4LwYVju$kfxzDGPVn-vQXx}F$|nvh^wLz4EF!@`~vy{_72 zVX<(SYEdnC7A#=ypc)-4K;^0~n7ZTAu3DbT>fd1sPwdBa!a2z&5UMJ2i0o}fG8OWa z!>K$rD|p7*%7YppaEhSqAfC4P(}bIeg#kSI-Az1kdJ8*ki)tQ>SDpyV#1)Sl zZ^H4bYFMn{kRO8QPvHWykCW(eeRfNt=XfO1+Z7gSz(T&(CtF?Hu!trj)$>@w`6qtk z!Hie{6rf_Y0MRN4{s43a{G4F@-i0bjRd~{#b?DKR4Iu!%kvcT^*c+&0>hyHxbv%yny_}yX=C!t{rSK5FQEXIS@j^j8i zN&aTVLa2!sSYaT0XsDzarT{_$V^1g3Ns5=PJYG&@A&=RuX39P?_qotmtvMr81)u88 zCX0JdpFCHn)~{f2;Z^YVx{~H9w)`)=xbQ9_vTX~VjOAVvJ$Zgl>Yw*~3w-(6Gx+^K zc>&w~;rIPEGN2~s?Lmd#e|`;LJy$P2W?#d&cN3T8gr069j<&X$h>^@X#~GP_WZkB* zdf|b&_BQtz3$^KGu+ZC#g@Fo$l>$Voq<`{9ne$h^a|nKBvYkw=7TUQU4KTNzETkqG zi+tz2jDt*Wo9dmHDI(V?n7FYldIsdnhAQ9ZQ_| zW-Ku2O^QWtdz%G|dmk$WnCJDw(A?ONS?vu4KT~_0q!NSI7n$~~^mei+vXO-hm}o}N z`n=v-Ba6?^m$I$wHe%7QGx}FQi_b(+ddj(K%3C{@cn|vC zW63p(c>7v5%?Yvi`P*5s)VhlWxLVd=?5Dx?q`AAS|D=<kHH_-k*O^d-CLRHQSw&&M+b$H6(M>ptAUNyE@ANci>TbujsV=` zJB53PkBI8i#fX&o5XVp4QxPfyl;y8Pe8Zvbu5+b$L6fBJC`?Fh`lAZ} z`isxt^H08n?f$^q)PE-p@1%1VCXK&67~v0Ix`E&Q_6CMk!^u}b&Oh-%I~D%g#Mi@B zYcuDJAkxuFx)ZKrxy~7q=g6Oybp(>`T-j6$Uov$lu{hF0MsK%Rv`x-n@nxrENx3x0 zTDRwAY|etkeIM5=0L+Id&LfIhEEB?ZGUcghnIvk==q+Pu5=p(!Zo*Dw0edi=8#;Jd zCO@29C>giNGPbmgxx#4UN=>?ht>{#>@B+n9x-1ZFsuAy0%hY{{q}yJcab5Z2zcyt3 zEFzr0zKMVF`%mJDXHM_x5S>ZRdzj|OF137?4=Bdz42BiHbZP_t^6S@eX{+*YFzou& z2J1gb8L~JJ!9hvuXV|7>sthO86t1}$OCSPP*TF_uATqm773tcV7|4p%wR+A7Te!%k zxf?8EA>O_QgmNK7<>F;(1aD-5Dz>L>HccPRf(5+-4D-tq3|5{YtLM?ZlynAiT#%;M zNMI|yq~7M^#WE8juP-a3R7S~#lC^MCn~y|b@aA<4FP^Ta7a+Z=(^SKJ>FvWbfZU5G zkR}}b5d_abbc9@EU9oQ_&!VakWmt9ahUA|=dl~=y-~9=`_TpLeCpa*@DSWT|*kylM z;cMUB#J~93HN0}geZ{9M&a}AHJ8OtA-SZ|a#?Oh`lyWug)D#x>BUJXpqeqAq3!C+fU1MSH6%3Ak^2e@<3t5Iig)3L> zjhezqs|96baWBRQ-msUk)?=&3Hf|C)Q-!!imPDFV*5D3vh0(?}3@)Cke}Y}eR{kZ0 zBvUU^kt4R(WGN+?^a$!NscNlp%tMcYlvt3~EA1%OQ2@2$$nA#i}n_bX!+-ZXPvB2qI*0B4pmnG}i7Xc;nLd~`y>=O}-5=t~@7};~e)%$GLnh?CYgV|O2$gYv0e)nv(-#&#M`?N^!&CdRR_cNu4w9jk$S_T$553n z*tuy~qU*6(qJLwtNZgsR01OlbfWn@DAEMTTRIAsGHfoscn_;cNO|oFKsT0?B;zIE@ zTRec2$nAA}FIVx%+r)~V+g}@KLeJoxb&Rfj7uC)Nbf@O^^e`X#1;X^MAOV3WSE8U3&UR>;MRa(~FE!R*-+cSb1^oB_`b+rAx6k-JzPVw0+rn4dFn^K- zvsu$?lMO}{zVhld{F5(T!V8y&>Qw~K!guO^1=zL|Ki8hA#MB}KDcj0zGyyY zke-1EY6k+0oHF-K#3?c;dHlT2j4KXi*SQ3%c|D;Gi=A>n-ZB|uw0;@GORs?kTbA?~ z09CFPR;Ggad?S1vqrVbj*9q#i(ok`98XRo7#^5U&2U|LBtcygh{<155muHST?5e78 zXlWk*^^boDKmEfG;g*BTD2nkX)4tOJ1E@w7-ne=L|K=NS;He~1b{f~CDjEcF_I1=!Vp*GcA1+f&%6tA?5btn zhRDF+?RAVUo(2Y6Dh2hvU>-M=lw5_5h>5)iW=esDWSvariPaEEykaI>){Q}!-&alw z?gDWr15Q;5z*c{V7tUP7^^I+uJiH%=_b;aPv7>v@oojhNwbo!&%PGE-J$u3{`{%e5`fLesvSw#!N{w3oLJ z3nJ}Fv0#s}0I2BXZ@x4Dp<^5<@t6!vWokh5D2lr1#^Kt(I9407S=sRMutj>IW#6hoTa%ge&D_n_=Ugp z0es{=w_$a8zIB@5oe(N!TwCA5A3u8iAZw_9<77Itrj_$pr^J{`akP0I)uq=^^*16>`06G>nI&J5>6BsO8bVJ9QoTI} z%-Kh>f~X3VjjqL=O+QH|ROZkJ1f2rFvHgqq(GT2-9p z8VduUZ94gnU%mn6Wl;=GHw^CM4!CJfLz8wMbg<}$%+7eO3|KwXGP$6PUM~GuXIhW9 zk>r`dTh}qX{N4ID(rjvhLxu1XLIc3@!Eia^4cB2mU<)}0fSEYKX5#tkp0m+@{@$o) zjLawjJbLd5{FM*C8$bN;UAXJ`0W8jS0nl5)YuO<4)0;a3oO=B-zWDU3`1gPE9G|U;dtp5S5tFZMw61*bCi|e7 zWm-Y)_{b3$0$Bu^`ZPOZ!oZdb`$!@zZ4$Jp8#;M5y6;eP(f7x2t?&SR^yg!%hEiemA= z#1$lI2a_mC@r{Y^>#1g`8L)6JQqmCrT=rJT+|Hv*bn+j+at_QV>S1YEOVPDd){gG!pyEViE(C3DA=2frh!>H4oF9{%&aeAh5U_8qi~CL<#HSv;7oU9pJ$U%e zW9W8DNY2y6O+wrL-8e{m`KvpE{&0k6PQ8K0zwt62f9hqteC{%CZ1;i1RrKz7KRT-? zvJQlUrpiTK>kcddFL5Z8Mda>946VWKAsP+H>kFrj6g?5ca z434>*t2K44fE3|X0Ald z;6^g-fqQ)N$n+eMEJ-~t6SlT8H+HL*_c=>A14&UJD2@)|}}r5H9G zXCkY=`RqsK zX}*U}QJ^T~YrJ5dnNcxgII6I*wSy}+Hu2h<*KzLRHN158B2K+=1*hJ)f{WKTu+<-c z0d%@0m>I?LVf1c&$dhhLZPZ#xq|O%waN9};+e5~i8cwUr?jX;3??#VozAJMP1M1 zYKEB>i+{A)0kB53yITOl@Bor&0vlp?J@ismrioTyMT!9)g$!k?eXhRE@>3|by9>#CJUQ}}j-zhH*cuG++NCv| zxx9uydj1UNx*g1SJ2L5zi{U~e(sU| zs2KR|Kf8{vo!i0o2&z@2_RK<3k`4zsr@Fa7=G8{DURN65aVL6^MW>9O^y>AKAOm2% z!8ASC*t^6+%~-sbn(Ud)V{z$fN3s=5H19eq7MG*74!~=G{jtlMoZ=4#T*XusSIsyi z#Ns=#=)NhgKZ_ym5i>S|o-HU-Cn?p@pi<8!j~}RC3UA-Q=;}FC>O5W2L?%6&D?oKL zhH_$DWs`Gp;}_>!j`%vdr{M5%oGo1ftaJ^Ym|cZnNZ-dr#5k}#kDvU3cj4!M2#xxgQAPve63Vg2KpmLf7Hi~tv)VZ-vqn=3IL@P z6#)5^lNlH>`^|HKJY`;5l92QX<-SAc-Tny5l_Q}d$0Ro0R@=X%9T(5+GoK0pw^Y_rX))N zAOU6wp#o`dMq$itCkviw@Oa~`X5Oai0|jH^mwT*vkDXkGnkU%P1wkQQ?z=D2v^ZXN z)}=_bJn{H_o6<(MPg%$BhUxsC`pzFgv`9$dz+e&itTIXsVofU+PQSSa!Ev3VR>D)Hv} z5SO>A#3`68ktyRFZ%AE}y-$fwHMH1M(|U;)7w}Qs7DX>gdv{nc7E5T2`U}q{EVYAV zi<$CRyiGlSOGDS4vr)fShGUfG> zPcMLvU#ur!j_`Ur85sr2(W4NnXFXl+Zw77&7b*|BXd|+-g_w$b4ueql)P}et(v;zv z=DMU8_Hd#G0Ci1-URmIgJCEQue)fm(nGfEBmBqR6E*!BP+_!*7?>&yA`xf!~RDu_a5ot)hm5m+8ALN93eeEFt(eq1jj?-CnHJiA*yTW zdsPMM5twO>5D!+k#kVnrKY=Xo`$j~n>tI~Ydx*s{gRKbOmL73<&d`mQ+?=??ml0>w zB3ap*2@3&01?DHpl^^}J4+Ho?xKUgPfv}{rNKPk1^ztJ=r7Y+To+I}458WJk!BkW^Y# zUXr&^0#2f*gb*r7jYVX7K>#R-@bL$3#ozy{kKv>5If-tk;R`b&%ym1s=lCk_J#he6 z*0yl^&Fd~NEks7@CN4jU@2Nqc6k=?NmqJzsLa}%Nz1!Z0a{tkU&^%30BJy=a_FVt) z9rO6RKX?$Iyk{SldmY~h|R2J+!FopUG zsF9m2j#^pp=qxT>{7HR|ywO0U0E{hL{_SmDcQbp7#geF`mX3CVe#1yOgC#C&sIs0N zOECb^7s}PY{0|=n^T)x}9NY|n9*Qe6cjRNYlY#0@`b_pi2`f_=eICq~eh*GOP7X$r z_mGqMch*3PjWW`f0{&|Ay4$_5hvMr^FpcSMjMw z?!sM1S8-`=6PIsnVWcNoeUI%bvgb=x)=eZ!aU98OC_hu)B8wVsUB?QPE5|T@_lHpK zKZe?Cv12H+BS68D>s|pobZicP?<1@DwT~XgyN@oQR~8mVTvk+*?}`0geEhCOfElk| z9%6gMrqc@}FjDs-iyE{kB3*U}^Zc>RXc5m|u^j;tyk{rQ0U_2U#J;+eVz|of@4s1C z(qK_VosV3zP4BJ1LS<*8=q-G{-2a#V;R6^AKZ$CzXc{q0`5=~sEn}+ElX@irIa~m}l8Idr672g8GB-<;lLSyftcZNPTmmoW}^sFNj zlc&DZIo$&+M4+;Box1a%FIRr-*YCq<@FP@JD_Vmn=dgzeMES}>(2(Q8jkh1!+{Qy& z>A@+I)x;k6VirqwgzgGm@qxig%&2zOQC)wt_H=bG5|@NaMBCC9{oslmc8#BG#SEGO zRQih`5-HN~&`4#RGKxCW_1}XFbNHnneHj1Mryjzw)y4eOf?#e~kR$+|vcS>(OL*Vi zx8S|E9R`^3=GApnt@uV|yQZZ8gb#)3Tk-@cMR-MQ?43IRTM9 zi&0vnG!@Gmf+e?|j1(55H!GH+yo$2>?>ju)Swl724p~>8lJ}Zq!@eZYa5k+?(*x9- zivyLix=-~&vExgY|v^!^HIx zinzwzkn?CoS-oBTG{G8aoh@bn!1Nga5K7b=D3uDMAQ?a5@ z6oeB8mvL6h*vctSNB4D`91h@Szh6_^a<(#)ocO#G%Cw%A#GhE$9*C;iV4#<|C^p z3Ha?lTf-Y0Rce@`3tQcqfoSg51Zmn;Mpt!?hIHKp*$dltTCq?Xf0tO~BKyTGC2n16 zGr|&zIKwp&7D{B3WAU+V7?Q28r45i`)VTPFOpZd^ zP#$l*ovgNLGwv5Pplu-4)8(t|=;uZj6TI_uN7v4y8f>Zj5W>(DX3oc3RWFW`!f+K& zhBpbowBp%sUUG4?p>aXC#L#y4&v)_nfBaGW&A;$o9Nsscben=6TFc*}AS})I@a~g` z@UaJP$Ah;Y!OFrMZfxyfII1ulRz8ou%yO8`&?G9?99)8bR{&71+=98=>Z^E8IsgD5 z07*naROyAL{7Kvbz*3iRa$kwh+_!|k{ee~d+@q^_--!h*&y^?(YPK=fgN|Slu+S}V z-{Co|&Jn(GZX4U9hOyYC*BMiu&w@^pr8NII8NnN0$mbc%+99oSer8PcGnn#}}|X zSNM8AYiJ{60?P{cfCumsy|yte7E5EY=ZyN0s6*+FM+n}D@y zG^oIAcawAQZV9#k$tRXA5_7K&2Cq z@TmvgdAK(>5CAF&`xbim*aNrY(R+{M;`RIS^0_Pc+KXrL_|xCPsn;)Ky+1^u-5+hh zbi4YmlbQSj<>CSKZhs_8y3C9r0}Cba?!z5?;;uzJcIyJ}Jv@iQOC2nB3zS5$nuSF6 zq_pPFhfMsO-h7wvx88pMn?uIGeRdsN!%S!Hiy%rzcSJse@Z}K`KefmVf4r5lDX~a> zr<@*A6F!+XEK0|4YK#;1mCS+#po)sl8kgN|I`Kcfd22D)`A0n5`7Z#T*8nne`w5vQ znQTlnCFBOmX_g7Dj4X$(4L2C|*$ZnIfZ>koWH{15+I#tX&OdVn$mU+Fux4w7106U@ zi%o_(#>1?pJ{kVp28E48kAeAafuH{9efagC_#lo>FsFO70t0?0#&9&kYZtHME8jYU z7tUP3%Wqu8n^)IyWqk`9{Q(BU5e9>iKWjlBy&`$@9SsP@{$rTC{SkDQR?#g9y^=8B zA*{|7xMg37yAO16|KT2f@Qy{?zS={lEW#*jisH~jI?WVBPLG*!b!&)!{zsSbJKtKz z&QRW~BW`)Gu0bXzZ7CU?%+$TCo@xtf9S5Tkv0gVzPy_n;B}7tJCk4CGNT90PZ}xiuJ7>T)nZ4*RR~bxyv_j z;o1f+U0cWNS8m|i<_^}kcCfwK$5y|O{;| zmrER7?%>E$2gmnyaAIEv2Np^!%@ruA@ZQQSMUmUN&=hB!9uZ-6p@W}&WEJ1JI>i5X zdfVvukyIVo)P0I%b$n55lPR&97uR~4M1$OZbQ7m%N52-S|NpMA$g(qKu;fk|tK;Ue z2-&Ds}KwgZ?2i?cCAHN1l1=60v(^!fn5$^ETuRHNmZ3^m*5smy-|@5wOg^Bmjg zo}qpn5jBS{21Jo8DXusK0A5o%myE6yF>i06TE7e)_Fd$VM{@U`|FnxO?CBHJL>bXM zQ8>m?lQKv-ys8`pn^YJUJT|ux0Dtf2o>;}9dY|t@w;jZ<{rLNF|H%WU)x+S_rfw>Zx>lEmegp%sr3e1w`_VjC!v}GB>mr`H zFsR?KlzIHR8^Oc0C*2B2+9Ri(LTK}GI}c4mzS`4;=k@$eqz8^ZJlG2?)PP0BkKS)9 z7U^7a+sQN*y+vqNEJeAE&iq9t?sw3eA7V7R1lrjKMo!bkHXt?8hHFcG7InJFCcRzG zQzqNvAgyoqHRSBWPld%EuXF}ge-qXE#d=QH8xsPRo+&~XajHRP$W#hDLy=mA#H_z? z_Vw;w-K#m@RX(!t8C4yISxjJW_T+&jeC{tkgvahZW>)8CA<=2WIO57-Da!(733NLJ z7Unw5w&j1m<9Gj0NGkpcvppa2#}GFo#J0tAfB%UEeD;H@_#eM~1!r%JLb<5u*?KL( zEaIl^5onsf<`6%g9&sZhvygr!y>2V2I()1*)oTmu<+rAR~Bw`Zu~HVNDShaPah)l>?1bC*F!lfJ!r?$M!zS zCP(*xAHj2f1EU*`bVU%qWanXoj~<#TmdNUV^={V%ZBKn)c^=kh7W53l4y1 zly?M{M!gIc+Dk0VKsDOt{`PsUFf6#(7-BTKfTFwxa21%V?s-Dnb{qSBVm2^fM&ka8 zG>5SFJOz+$7hWdz+GJ7s#+y|4a?Sk>R5vaGLb@ho$QLy=%Kqh$kqDIcN^&-$UR6li zmrG2xfGIFT{=y_GY5zRU2Q%=ghi=2q{Ls5`aNk_&05!}v%HL$z)k4}$UvkCaW_a1N z65?KA(dq9N3lVT&p@W}#ct1XM_aZ8WA1$#}Xt2bYE73uyOJpbf%-OQKI@Ou-dU{RR z>#1HY(G!~-*}N%O;BjR28nN(HSXkSf1`DA=(YZ#|;6hPu4U2QXbSr3X@d`SX|$Bj2YEn8`b(1@Ng%9)dZa+M(VF( zdnAebzAobV6cee|6K`3Ila70xEIdlD-6b>Q(7t*6@{c}sE`@GwkN4x&U&+j zn}MY*-efo@!P1Vd4GUG&>0P0@eOJzX_PG1yiB9(ts=;{xRq$#7fFZ{jdj4U>RGQy1 zUa-VrAC;U9(a6YD7J|$sm(%lJfg(tT`Z!`BtHp%+TcyZbOy-pWL)RUSkiHu7f>5~o zq6nQ;CAeW(f*nH`1Zr~ zysp-cyxB=T-nO9QzuiRmpF${z@Sdad_y-?7fLoTruP?_Te!{9l4^B|M0HhrR_QPr` z^KvyjX8mb5srKJ!Gu6|-w6a?)Ci59AUQ^9i=Jk;1vDZs&3Wj!@tyl_FgA3g0U8!#X zR46t9oCTGYH-zA%X|AmsIEMml7~c4zrSQj7B?0V+DadRdrj^Mwjef|3ZHzYR-3u(_ z$W(c3W%edsm@U2NcY6@P3o|;iyna#|d3f_@tti_qruM z@xV!Z>fzf^L_e-jmy`8oC-r#SyjmT9T90=UqMp{nzL^V%ZCKjkPm84>!VllIgiqYH zfVt$PY?yvp3j(Ft4pq8eg6!O}xI#9>semT{Qa1fptj=br&jdtz!4tCS?FLII0Sawb zVG%v!NO|t#ROa;p7WOiW#mJ_?0svI@0XkbB?(K+ov%4`wckxveML)bx?d|DCB}L7l zz@?CUVpbN{xq9n{vlPoDU)&g|Fq;YF3oZJ z(@cBl__HjaO^an5q0Wj0sHYtM^M_V&=l+h-7sn$usi*Hq3stA@ZdEIGt=!kO$Vm`F zaUPh~i?8)RJC&$Lx4XoG$Y$)AsVv)ap;RJHT>J)OEIvI}vT3mt#Sq>3S1%OXL%I3`DC7kf5_JCg_5SHMi#}5(&uR`IG_p;C_@z)-^ z8y~#;2s&jk)09CrkC`~*o=p?pimh$$1l!YwvLHNid;vfC-et^nGUeW=Nw!`lPD35S zmw&eHl4Ew$rrnemA$0UWL~!Z}Lwp=v_v1a((_*n7Eodyh({%rg%-d8vAkLrDV-r8Z zBJ=Dn{?u5AfNHdj(eRaicMgF3y6``J8Q{@bDm&*1RMlH0+&@#CA>;`+FY`ni5w66g z_}Y63f%6i*$Rn<2Kp5z6x{o6U-ZQl1r3lfZ)PI#hKfg)=2*TE-0IWe5K-ldN^mPR= ziDaI>BTn2d3C$-Xvf9h>uq7X z_1^b3KBHR_e*8Tv_~@;36Bsncji{d03ZxY1BFG5vp0bfSd*nS{3O3yHM5fcr*_;WB zvmT`fz)VSmy|tP2L{CqB5V?F(9;Y5j(DYaUR8)4)Gpe(=at(kG@8SJGcWw8A}BlqCe!%NCe`t{W$+sI-)J-rYwq{Xrv zDds*(Fhl1FhwCwxSa*_n=~=K$O7zprMeBSt;g+&Kv9F82_P`1bE#%8rX|_~V_H{JV zttNFh)JvAkr892sxpt_Go_Ahiy_<){J0cTe@m}ps!~y^)iqq8Tt>N(g`W67Nx({3p zH@O;}j#24-yLzHQ(hknH?PRqqB(Ews(gKKO96d)UyCm?a58k}yN!MD_hIPsjsR5Qi z&98UHh$k7wllp&x#wACtMBqaFW6UBDi)5MW;{vQIJ#>zTe}MA z?Pz+=JH$~v1Zlm1MGvy%xu140B2wu^*d6{`4=v-fpMEb+9BBXfo<9%Yn}Glv+tW`rI1}7 zamF%%qsl8=&14?RjuzD%c;Zx0RKRsUIz@qxz55ot`@|}!kgaPvJv%4cB$N9|^i2MC zkzUT$G}mtjmTAJI_9qDh!aWD)@X_1n(JgdKu#GPK)Re8yp&e&Fr)6cpIRUAol%^++ zPl%KAO>Zt1 zF@%iAtQyj&JyfSpvFEEb>;#R6$xasaEWT5b`6yM$`T!uoaeQ?VfAQg4abT(EJC=AD zo134NImV_TZ>-l8CQUDwM$XnW*KY@wX~LxTCm~@XV0EE`AGvo4$M=k6i!)oHT;tf&~$lniuj|Sm}w(IDZph zA)o^DDK5)PDt8g_R>L(=@e+!%592N7iPIDa(zqfEB%9ez#=bm)Om(E%ru04-Zll^> z14cs#i6vV|gmsly>3^RyBE;}=6;p>Nwh|j9O^g)C(Wf0{Hh$m>Dnq`xhNA*Au+S~> zlaJkjL(6j}(j>#=w_^)e)9U@S7`$x{Q=a z_N+RK!I3d;r!vLj>;$jY)?wvAMtY(Hwhq*+ShQyvZ{gDM6Ha0z+sQ(zidW22gzaRJ z?TN8414YqCQM|;X(VB*2h_~o2^ih^C0< zl$G(Yk7{cTJnAP3eps0#E(EG%iB2>; zPS17-X*_(}L45G;Bkr7Cg}7yy98VwQ;_mgZylKi}+*HQ2&!^GdD=d>u6HJGtO|Kx} z(Gv@J-?3iCAK=WD2P#HWa)lpaTJqGfRAPZ#l^&zo+kt;+5Yz!?Omc`aq|1mWZAss;802xA@STAbAzm;|-7Fo=a>5 zm~pQb4_jpT!NV<7vQL+!LP%-U+pYXm0TDojlH1)50bU)~ERLDe!Vvwhy*1`20M_gJQ1M;N=ybBMzGL%eHF7W-5=X3`5-qia8O7(-ZwV-*R; zU7zPm?cWCyl}`&PQBpoZ&NVQ0!aW0+>lXOH-G^~vbulyCoStwd345`z)GRI+g zD?Q$(vS^#qnZ$0Zmk;3Bau<)Dn8#ch;@LG3Rd+}lG9ax>)ZvfRO?0=2Ow@xEuTyMK#UC?oX zo332)(2$GFsb;D7ia^p7#m$6t&jum=0>q|801hwB<0JPT!O~o(l>~FLHViUbV;RUTLPaw1hB(?b zrEMx1B3W2`a`(H!LM>PvQ9F*%3+)LOXUDI9O3zT{fW?q%mnE~Q$ zsx-(Equ>1?sy+i0#nrtnTqhUxzHx?pN$9_lLG?VDazMBj(0*z{*>Y1L_uaCBhi*G) ze!S9}ipohf!Zr_VQrWZ%Q{%#JJkwdz+$$`5r8}*?vH;$DbPo3(=px%wn=(>W(AZR4 zxU@4twNqf6Q$H=|^!#%xGi_eW5Ym|)3%6ilKuwEC{AMg1u=q43ehv$!!opyn?4Aec zr9N->@k_Trhh+2T<@qImiZjgB3m^(#w#9Q$3J;qnTE>1_M3ZcdQ9Oa148X9DN=R23 z4g6;J_>sC%#aHjcjfMe4w zkKJ<^M^@(R3ee(Z$#u>vdn>k+<%qw{_N2u1PQucJ>tnx7LQhUby z0*KCBzPN=rq`U5aaTOJYSJ3Hx4OCQt3&x#JraeZLftcI;g*}1HuWB-YQ6E)*9jL-S zT|d;;9 zY}v7`r#TDKUSn0|R3v&iXJc1bLYjibaDn<~={R7q@s(Z{YYt25EM>7!S)uHFtpZ=e zZ`_+?A^W)#_vVH~#Z#c7@5|j~C!!snC;`bdX!b?f>?Zl-YJjT02~?v@3{4so9)&TF ziB}IRDDY#$5ZNxYsY*`@f#^zF{+%iuP|tiXlFNCVnhn08g)xx-d+Wg^+;ePSVkgd1 z?tZU^!f(}-!p*dKGsabl^b0JH-ZvYi_=buB_c@~8F^I`A(t4y)`AJ>^B{T-u` zK3-K=^j_%DPW$LMS+jqcLc}WG{atm9C744>2Wr8JJ0*) zVTRzQ8-DP%c~=e9Hm%?>WQN!J%5JyPKTHWT#`Q70UhHpL>WXQMZz7gl$YZfltj)== zP&<}-<*h>5d6s$$&z5(=<72V>K4xdjF*w^AdJku7$P+_ z!KgpB7!84)O@K#$+kzE7Sde|V!KC_e3|AB~b&;OJ8KxM7V1?)qw&od>N+wqHwJvBA z$Vij8q-3U2g|do2Xsq`+)_3!r5)a>T(Eq|jF2t+h`aZW(KIsP(ZS$&e8clk+`=&fh zDsM}BoZY?H7B{Pny-1r-Pd7Y#%N*uQTl+}2IEw47wLM@qMYd-$h{&YZFm#hNDV8}_mxA*h zf-=fhsX6DAJb}%O*st^1I?QB1w(~JFj;_q%J;ztj7AD=1a5MJ$j>9wQLf@?O&*Qds z&4OjpwA*wi!7`~{#t`0pWDZA{N>u*DGV+We{kv~#`Wg~V*E#laY6s7rpJxUt(AX7_reRr0)MXH7M7e(k zDeXu;mug zRB6DGk?VClpGk&|*DKQygt$6sF2TuaJ;xTETUJiSNNAKRy}T(``GP>}7tkb#JghZyB>#Bpye4;h@^o zW?+qg#p*$&*MXg^0?0f@Mu8DcS)g?F3a-0AFAOJ~3K~##H z2bBZqB3Hxu=hwNiWLJate`^mrCMs3QN&cr_92pz_waQRitEyo`JbmGxQAkFH>!fU2 zg&d*A^q-bYt*W|r$v+X839AcT+<9mTh1*))wg8s%-XbjK2D0I&QiICvh$5m+4EDhxzI|yLmKo^ zmd{erIWOL4T^U@lvCdN3%NRf^a5V%Eb|Oz#YwEw&Sg4Cp#SLSd3>oHC0;573Zh%3F z&<}I6=|%Wc^~iSUAVN11#K-n8;P`zpGxsV_yY;IH%dFE6 zxi|#@Czd-nx>$NyT-9@1LE~j;D;HwQZ&A~AG}cR=vQ2MxEb1XgvbK=f7S>j}>GHhA z+JprFsO+2vmCx)H!|XOP39Zfe;^$5P)!GVw;XPjOx{y1F0CgI>> z2e<9-n4OGJUY$LmOVwv)wEnwNogUyS+RsRm%~E7%{xUEH-m)) zi_Ae&GZr_8EAbn#P%&iStIXANS3h?QZDFkc#NE+0%JNT86l;mBm&0QGQ~?4RtLXx78zHWL&T(fD3b&EID7rA5s|GJ=tuyg-#{rE^Pmz!UNp{j- zdpzBuz=_obEY5XY+SAR$-t4ci-l`oE@02i!M?G$L$t<#_bcle(Zh_egE2gp^EN4hFB5NPA-faANF`2m1AINXGrS?eGshE_bXYnm~LCxq}!(28hDcjHpyJ`xG?Sc2$Oh&Zxxm%TyHg&#UA1KO4py;V9O{FwD<%- za9D^aoX(}^GvQj6+;FtOB+dc{nhUlmIGSo*Xm?m#Su|lGVFH>G3pp&*j78!r+Zs!) zSiC|-`P8NNuQYBWlQ5Qe`v;c6#HWeMuY+h?xoWD92!Q0yQ!}BSRv7icRp=Qlvj)8| z9}vmoX$K)k;bQ5VNYZy0^%O-+q)tU4GW3h}d>+kQ`DqaAjWb6>G?RCL z6&7*{EcHqp-8YYJNr>CvI1i~_oa03HR%|DmmWXB{uD2UY6Rv5n?8Uheoq}*=slY-f zeZK~lxru(95sDNM2*~UCQJKP{+G?D8{UeflgGD(&4om#Ok-bt5hi|NL`)bC*fSTQG z!vcV48%6nKRq*Nlfeyxn@k=%L7pUfW9YjxnI@e@NNg!PF8jK7;y+3y}2)#m%Z2dFO zdU?4bUvj;bh)IZ;LvQ6%OYDgiRb3RZTJjG%vVRz4I~u)D(c{!uBcdRm7fsX%6+5aN z%X1wZTbV;ym{cYXIYM|lws3tfZyS3jO{e6rlm+4FVh2lI@kF9H-ZNwGEh?|YKMyj5 z7%Xm7MvwD)b*wOqH?lp%LQ`Uizc*;ZqEAt4#{z&lR{%Uw^{|fn$J6RWYvdPbZOCQ! z>r|G{$rvH&-5k&W8}+V*(ExsWAww*-o72+{Fa%b+gUWi&(?eK5V;x7fqlpI*dDT9J zcsfF-=NUfWw&YIAUOZW^^mPd(y>MJMwa&s^2Z#5~&rH0z(6;3)H+_(suE@!zo%fss zV_TS&5O2m7wzm^YNrb~oB^G;ilTMJR`X0PRk+FYnK5zhJJPsgycx3dvqe7{k*v$G; zdyNGiM^>*93r~fGwM{>oEu;KdXtpO?yoV+@4;*xlC-L+_=pj-a zPkuKUO5a;lp0A#YfOvvVPVyYC*OU8#5KbTq=jPVa4C>zh2m4oK1QZ3~;6e|}^PR{s z4&^XSgOB4+>hVr$^X(?W@8_`8E3tpB2)h~q0P>MSm?C69thO>QmtnQr`lY=}*}jHG zo2i~YWo)-teDF3c7OyD|K%*WKJ@$I3O~H`ZB!G(I6@dR^IqZ+!%^+c7;(hHyN5MpA zK=dSt`hdLL2%PtHWebLrMBZy~*)<^|T>D@NQ=WR1O>h`xrxLDP1vku{h z=ImuMjb~Q)olULh=WC<%c7p}5He)QlG&J9M9_Cc$^#T_5GKh-|ix; zNRlz*U$|6+3>Htys>5I{^WKGhQ96Feg9eUhZYGXg-&YLmTkK-KTUs)2Y4E!tbo^Ns(5A&Qjv(JEEDT`2OITSbQ8CO}4fWKQt+zs$uB9qoQzwD} zfoN;@)9A(5`kyCEu^CM43JW5e`iYgQ=cIQq=r&}jMBC9=e0r>8zOyJVpy)nF_Go@Js%M^#i&C1n_iRWaM3F&8J za!;o{tW6!8A|0Kszf|{nO2JH-n7rigD+cy2_0V%a^^@c^6LvFYFsbcv&!!1C!yBA6 zypyI%rh0b(dS!wA^93qD8dSo}jZ6<}A`A|bJ-SueB|#`pY{Zt?|35e+U|@T6y{A5mQLX&`+OY{%ZKt|*d@^qzVn84=tNUtX`Z z9;anaHS#Nbd*5Ii`Z*N&hRzXT(F*35Eg-`U zJ(uMFM$;Cgtmj^o(NoXdbF-hqV2`dT*smB^=$0m$9`45+BrwFLX5EjAz5Tqta@9~l1m_VA|TUGI-_+rmjg5N6Udry5|gtQ!naJE9& zY9`YRntbgKYwDoAVzD2k=kbPo8$FjlKWx(8yZ-BJRSa~?0`kHcp?>IVxNE-{k%<#u zd%cEh>`|P!IwyZQjhjoSEq+tn?x!}}$^f(Bn;1*jhDIKXYsnRg@)a=uZ)Z0ipXh^$IE??OI2V5HuUGhI zr(Z(7wd__f_dHqXr9YL4=%Eq?cfS7P;`%&utYGfK4VjlDcI#h0Rx6LNuhJ_&SQ>CH%dgo@#Da!feC~fJ-`0Mfo_m`WQyT zTOG8zP^Dpea*`f|+=RKVlu-|+Dt(r}8wuQ+30|lBpAfw6aTPZ2l?GN;Cljfbm0r5D z8eFAk3so$u=_%hxOfDR@(C5}y-Lk+RJo_fjUfoh<%Tcj$QY5vFHY#$rjdPaibMqvp z$MH)L9GXhfD894en!56-h7$LVP(p7JN=70zLRQ+Wo*M(XI3jMM{Q6n))Z?LsvTDMb z7*YDFT#6FU_Ya{{^q^dgy1E$u$B~S079pM|B29SS6oWaEDJ*hkiP#kWWKz_5)3Dew zZo(3XS*14?OYpX9$GW`0W%)Q4I6uJ)%Z8bWw<^o+vci|Bz%x7=ogkoay@9EZcDMkj zw*f;S46y57>bY>Kpg|rk8pv0(kr1pTL&3WZTS7#Yz~nql_V2*StPe^`C@+nCegej@ z+N!WXk*o8FLvEyF5K#5Y?vw?-@azTr;kPf;aaa+R=n9^wF>~Cw4N=%eV+o643O*(! zeXq%_(U#Q>(ioG{@pfY(P1EB9YcBn!yi9^!i#U@`Yy87wBt5tdlS z?g3@6_%>#+P(@|uX)cN{_KRxbFV*5OGx5IhcW$k2{hu$MCdTKvDn5+S=wQf@UhNh_ zi8pIS1WMZHWMjiDycsoJ^C&Q8Ov0M{)oL=i$}6Vq%aIBnyi-$WRO&S$YcjOt?(?9Z zIj*Lt{p6+{Kte9$du(Xzg2PXlgfazWrvVwR4SCRmve3vWQ+{<>RJO)yUEjIH@5UE| z>D4&OZ>S3)e`#rF()C@Jy7wI~!4RlVCUOL%yq%0CKPFj)iBvi||Ew@j`i{(~i=*cz zo1r%OdtFw(A7fBaqI2XPlqVkmbr(W@TNdf8?a5+3Q%vNIhaDfpgT@+1pznct=s(oH`{w%IpZ zs#G~ARflc-z(-L$zrdFX+AFt$$PQ^2B`Tyiy~tE>HL~$oZb-+m^^CJHo61JECple@ zV>}tOXG9)x=9yHSj83-FM^VHhD>~>LxeMJ}9|ZLleSP8<(lz~AE*eE~lpZzci7cmw zF?y3>Q4Pvht*CBAvYZ~q=q2^lydna1y3bMB`H$cI+@aRrR2vs|MZ5qoD*C6XDE=Q5 z<))=ATiJwLvV{U!;YVmpnxueGkrGm{Z%aO42%AE$2|%-j zC%jyzkf{`&U`!*e)2%&dNlRhPRQ}Ya%7Th#KLS^_C1)h7#>&1*H_Z&9){5|)ybVbWc8n zqPNh{A!-u}b1tqtcFuKU2MAbw$+I{FnZb@49?pnGMT+CctHmxp#WERvB-1@m1E(B# z7m8vNRDOO`R;M$FrwY3!-q(NizCJ*|OH_Q-9gl`O2jLVhfYiv3qTVXCNvbl+mNC%{ zq@cEsx%7fL_gsERl5}GV1fttPZmXG84pugq3g{&;-AE`&3*lZ>GytnpAr2Crj=#NMNgNZ8m)ABP(P z$}W1h+>hR!kD{1gLcId7S%M9Tv=WXklIS@%#n*hvQJg5mHFQ^4jG-{fH8M@W5r{k% zCot*9xTqo&U&V0ryXQW4Y**ecum{8o02T4;K=B1ou^tG*c8STx@BhEOw~y8IxbDM# zXXbg|dw029E|<&Y?vh+l6sstTDk!R;q(-f#s!`iQlK^Sb1WuFIQ51d9`Y?>ZD2l)c zf0g1}HLxm+!&6}{Rl@7{gy`#h(A zJagupGc)gdKlXch2YdH@o|!pk&V2pm%$biT_rjusf=Wu*;<5m_cs6DCADRx9($#vQ zEJFs$%ssUy8slY}+#tyDuu^i)y{4O`Vbdu|Ab^=dw44faj@SOJoIYOZ zWPoioKLLDI9#qV->Y8a(`ECs~z~u0~m>hi@LbvK>JkESn$nHJ>V8*4KjtR4u1~%!i zL%J=5wRuXbF>&oc?aB^XzXcsr!5^ks6k)E(P&f;U|Jx9Uleh%#k^t}QrfwY+zD6NF zMMTwxVy*v_(cGi8aX4VssM%5G_MaN~$pEJFH>f$Ih(LxK%gNP|}x6#~cl4Na*Wp6_9PN+wX zGOtvie3?q{lAo;~np0uqJWI)Yy9?!;f1_LI(5tl9BfJ18BrH!>^19f#lWtc7ydb0y%VltgpcV`(;r4%OV z8<9jCprRbIrfC7B7`-K2P!y`pui&6$qE6}UTP(>*-sdW zxvXtaDU(A_1C}M&=`)%56`!h|DQ5;s&rDO<_FbfuZWEjf z@ZzTl=;ApF@ll$rd<*Hkm{s!~h{%KcmFL=@a%Z)GHv*Qo6}NbD_R5_+3u1s=d}SJG z!H(5NEiuUeL3OK{1yP$V2jDur&wwgF9$_+Ps`A$WLn_iVd2?xG_|<%C5wcNaQ@T#3 z(oSu_p^&+}tAeK-#x>^gL}zX_8&|H7#0^2pV-ovh)F>Z1*7_bf@Tc6F|$jA&t;W#CtW z_obJ`%e8e`>Rwg64CtocLI{s?Tsen-_@6D|ymfbJfEU0^_a7R%J^Q|kfG;C-Guy#@ z!PQe$zbFM$0n(sNMP;^ztEzQS#3(?)rn_KDS6l6L6`wFJtf7#VP=HX8k8yAa3IK&R z>|{1tRz6UXdDulWDLb1N>3nAK(8!}2V@PFztZCatBtMvWK9(kzjd?2TBww#AVL5+f zH1g!fl8ogc;!~E{)}@7%ykkTLjdD(ZVb$|B9Yl7-#V z)@9+w)A+&w5V{!!e0jQO^}8>B?AA-7?E;q&cmd#hAG&GIJbVoyJ_`ziYe>O`#Dl| zST$Dzp}y8o>!{q7Gd`!r&)!6k#WI6LQ`2tLMZ(D-9Ilvta((^B=lpN0;F&a+ef%0pQQxa@%PN@iU0=c@PZ%nNFKJ zY;}?<_o~E&AUB-ZM(?*&VKf0GK043~pjmAq6C_p=M{iNf3vL47nT!hT@Y~qz)+@c$ zOuQ^~Xx>WxlD2vk9X4yG@EWwp&XNzgAuZ%GP{2=do#sr5Lc{bIK5a6CgJ`R^+;V2oc zJlpqR%ZQw2pVR85=#IV>(>J{fVdbX8!t@9NtbS+PDVJeJ!tx}ar}OB;CVq*DE!$|z zYa!iEWLe<08rGDBT7i^4`Vcp5~d!wEH`rgS`@5x?<(K7HfJt!B3c^r?vw{|m6yF! zUc1}hjOmfLg1VI)$CeQnvgMru;973jq{w;7(RnMrZH*%UE;bjm0@Mu9rrWVBo_b5p z-8|Tmr3@TrM(YUi>jZw{!Y};AOJ-sacgcYl01)XUy6~?Mx)=Ru4(<4mSF06d&-}>6 zd+BugsX$hBh=RX{HJD{h*+eMuyW&=MvLb&inNe#f7F$L@7f=v?J796q*;}pOb>}g< zy~`qlfwk8bLNr|Eoe7nvGdmPzA|?vU%+H9Vi;=}slh^cPw$0ge2&9UkONV?sThFeI zdRaA28Yxnn+D`Z93O&dN=6Yd(0^-5rm>&6a>Fxy;Cfnol2x$bh;BLcA@pxg(;8WD8 zeI+y1lTZ){J0^PGy$?E|;B(>KDF{ zHA!2hnKzkqCDcSFQh8j(G-UExrpXsf9;`W}*@?;h`3239USmw;zFYi$;=-ejiP!d1b-en8Xe;N_m-5q#|t<(!Ee zr)uq1s}x6NKFh&}Tv*{f#xKF3NYBe7+oZI|yTPXdIOQvveBKWY1q_!CMo{rb!+rW0 zI2H9%2i3~T<-no{N_}X3=XwzS4#b6=)Er@DC8##)XtkbHn@!7~h&=U)8Sv9?eb=`N3{0RVpXZvyklcMv9jfDqH63KT#q9tw%o9!cAAU<$AT za+`qXVF3|UT&pSzUmLBomMI7eRuD|bH*$*RS1xcVYifu}+R3fVR^v`)2EDYVXgVO% z|CMg+@8r`md8)O~6hE6_Oj(mDdC^Ku{#;3G@6GQSeAS{XnrNBL z@pIgyVMSAri?i+Eo*f%_D+T~LQa%p0GKv&*Wi^)wLIi|>cShv7wV|Qa)27pGjnNN{uI@(a{w2eHaXhnQ- z@hk|I4KhiW0Ho}6ExT_8?+ZlKxQ)D18JQ3r3QQ|=v&LDh>abzc_#| zV=sMj=a-ys#a&j77XV)T#BBo){nG$F0vf+RUljpW52J=sEOb>iSJ+YA#ueZyHGuKY z;Q>-Ut--AuCIvY15a`n7^lYzfB=~*4(k{ftne|Q6=Q+j9KxT+@xa31q1~*z<9v>>u z^>LgA#CGV{NNyHw>wQfrpb!O$!NRvJ~&WSUNu1V(r8ZcGl}3+kr1o@A3l9=2r0 zmxSe9pgwFNomPw2^lFLnvoFi!S8AQrv+oNF9C*1YCNc|mXrro{8X$QS{WN9nn|0KP^PkM==3VxvRs7T4`=qL8Zlg&Fl9IXh0L-5 zgQ~n*byeD;gR8;}9;TD%F<{dHEstblE&FO7v`yLNX{(g4?ew{QXkHc6T*aAmii0%* ze1^`}qaQK}2p!_VyD_=rt?5LX8j!Lb+G*V;EEmRtFf{0-nDR&sSGJ{dbk(e}&??QK z>JCxbbgciy$ddb`8S>3eAt9(`(dTkej(~yU^hu)d`~AkonWhkz*j*9eT|d7z=RnUO zOdmp+oK<@-iVlwo49Rww(q?3N2O1y;|74ozCk;w~8S~^9w=_?1}gl)wJfbX zB%c9sau%U`kO|M8Keu*Cj|MMt%gXAKyE}jF`}?ple*pdLZ-D#pGErNsY0j*pX*ptA zn_Kpji4{t_s%Md^+t$y(P(xHH$_Vq zhLVlIh7XBN64DB7N{*k>8a>h{?CET|Oa0{NoCYLYKE=XkSx2@GlN?SpGi7S!wdr}= zI~7{f^1AXiUEdyT`_Zoi+cwA*I7sfa{EPIHxkR^S1a!CFiHSXtmIrdn)a1<$D5j2u z)(d3hwHojxa&Dk1ZP!=~DDolJi*p|Co3f-Rj5PQ)(sZ&=T%$PsU(kj7Pki#ua~CP# zmfaQ6c*VW^iKFKzga;^gPXRP9mxxAJLT>UU8)~Mqq+Q)ki=CF|cpJMb0q#~aH0WL8 zrN2Imk8*pN*L1kxKPoJglrrV*uV&OFOD!ztTmD>L+3+$NW6rnPQU%eBt;Qz4l+($q zMJu#gbIwb-Rm+qa!A;|(4neA!!_+5CS~i_(24LTf=AQGE*xfBHLZ;9K*Zy6>c?fu*lPu&EGtF zU6i1oiigSzv*#fvUE5Ms46$w2e;Is=K~R-J(U1l345&1Ww|M|_`G$+*Kgeu>9LNT7 zQ>J>Vu;`>x{QA%+08@LeX&KoJu!dWXqC5PS`j)W@Vk%C}F!f>Y(TrvDX5_B3UC`I@ zE0X{<9<40QWwf2kVjVBC3U4k808oghDa1bj=$R#KE{nV3z`L<$dJaUti*EXRpm@$t zE7KH@Y=<^YsGMFgOdJ6xc;K_~6Uf@dFbDGax9s$W4-xXsST?;7GEa%?u+V{!@4#H7 z*Vc03;@FRIYDc%gY8U|~&qkYNV=FGx&#zSd=?N~Qy~}{9+}nd0(Redg8c3>JBdgw~ z_393juoKdYS^%~ww94B-oTF9#K3D1Ep8zVe7OB>&i)%b?8N2-+OppEfe2IBEVB}x+ zAFUiu#c>X^ePt|T)ipgGnXnS)n+RPy=|g5KqLz($ucoI3U-IRCQL>cr$b_}pQuUYW zl;Syb)8FF|es{Kaaz(XW;3@#`sgK-_jj;9_h`yXm4udotVC-DXx_G0&AYm|P0w@8A zi6ek*YnN@MvETVX@E2dZw!Hv=VZBqs&ZD~5x~$?RW(Ot*PsKi9!!z@6J8uoW)iC9~ z2;;h6*bd|y>3UiLGCPA&t3ZGw#=htm-4;-rzIG1SyZY=N|R&vL0Io&5yp zGr(Hem52XKlEm>%LYrR={$*;Y$-}@V;Z5{7RH8HXGVsnnE{pQR6MsUi;YZvYiPMyO2W8!253O+o&wRAH)#E} zS3Z0gSK3{1;01tJK5^eLfSv(`2Pt&v_8+NSP6~~+1tMz`@Celc)umYUmOhye)yF4X;!3Nmfr+HFc3k@5Y-G0#=d3?*Ci83O%y~k_-i?l2O$_FCy zpp_bw)Pa;ES^lQ&*$ZnB0>Xi#=nmftOjeQiD+^f$6ISyJOkF1-%JR~Dms`fnkXz6Z z;I@ScCsDp7z1;e^pvwnDY9z&5)>X}&@-=m;@-=? zXSe093h)BJ$xj`dalj)8@n0guLe=Mt3BIveFw%t(r^A(6Of?-ZbquXes=&v_X?O|9 zH2_SLRV^!bOpwv6Qdh~B%p`vSBd2X>xKm~p$Gv%GLRt&`MA|;m%QX0z}WD++rm|qu`%c(9uO~-Y2lS2lDhI> zcNRV5Rx@`&4@rKFb}6maOzZ4!3gu@zg~?Oa4$Vzr%|>m@Nr5rPDZ_0UHsq@u(BGf zTBse=7A<7i(p;LgB2Gz{rQt?i(Drk3Ulv)oElZImD6CPOJjyZr_7M6ja!_KCyQ;u@ z>fN_vn0C)2PQFNS@=VoOOh+OEP)^GMGN4H(OTO+`<6F&Ar)UD$Zu(e#KvQ{`p5}io zy`)a1pYm%T;7}W=)$ANY>HDtRAs_O{TB4!zx0^;51K? zlNATh&e?_HYTFaHX>yA2ZPoJ3W#7^%9fw*L^Sp?96REcCwk-=aAGgGl;^Y~0-52|5 z_x$(&qa)jv^-{X43cUF7Rq)!{I@9n3V)rG)2^LR8P@-5FQ2*BRasN)KOEQXWND`jF zSLbk58tlBv4xiYREr*nj%E(Yne_>;Gpi$hr>cDAq`QmO+72kdoMccdxO@40dTD^dy zkuz1Djh+Vr(!(1ZXdwXxBQmcUhcBGG0* zW+2{t7?VTyjG)UkFT?es!y1wJw7#6c!trGF;^)Nxv$C zvTU^c@{L-O2h#L1&n#b&=T`m!3^2waqrqrCrH(!CeEyzw?W3w&+YU?~D2+b$hP5nP zDu2nV#^DA}rQ%u2CNr(W44n+1QXama zETZ${ryENvw>)%F%$jO(=taqrvRa1Zckbi$d{QR3fzDbs@n7g_fZ{@;o zBTl|f6xS=YB|nnUdg?06)8!_AuT|MDPe8u$3P>6!0d^Zy#_kGl3(qXNp_2eNNEC9z zoH(*LKsG*AwC4MbNxMrhNg`QYUYz7&1BUiNbENilVOS{`W_mlyn?Q~9J_FfmmdiSE zl@U7%Uo0cN z((c*<-ayY1g$F2f&%)e#;D7C7sh3qu&eVX#1GbUdQY$UR2Sed2zR2dt(qHN7Y>{7mV z01>-!mcuA&l*3I1#I9^*we5hkcD>MqyuVkn@5{kl!UEzgcc43T574dX z6^bHzda1tcMLxgHFwi(KN*spD&M;pU_JdDO&xE-$DF^=suu7i*kQeOi0GNkV6R{17 zHF)k=7Q2Q+3uLjpfB|9hEQk0YhwyAuysPA{Dd3%V=hyq8e++T*dBo{SK(tJ&C$XXi zfD-U><8bw-9cqlD;2`1NJZt_p3sFFsuJX|4qu>D3No@L5(iO6Dj+Ht*8L%*@>GvuZ zd2xzH5$0gpv;=_)w7vVnq~y^Sn$DhDpMNjM~K%W35m@GjcG z^1Kvf00v?=ZW%j#A1F@TOv@F$)D?>$?{g@hZc1EQ^?klILc!P@JBiF!@{`=rM$i^(Eq6ad%At?<@D;hwtwjW z;JMUXThCkJeX+7gv#sJtlL*~ubdxV~41auT&wO_B=iamp2`|69rhpd!@ZmS(Tz~#7 z1HVIY@<*Vc-#*HNoh@>w+7((1d(h5$h3UwwUD*Om#v>T*OlT~KSm1Y*tvGQ0{ur); zugB@6hqBVL_#4f}$(Y47vXjYMrNtZVUeYtaxmklTqyR%oZ_B2x2cPdsE{6oC!RNg%3Zcf(< zGd_tYFOda4{xamFB95V>1$223UBYu2-R82G`p?a$tlvZv!WzZNAA#{Z=lJ!r_(#VZ zjIXY{_J9|_*`L367!Xb%#4l2}@+2sXmlI1nT{zYj_QMRPE$1t;0%!uNF;#+Wqi(hw za%B2TD94NhR}E6X-`Ik8-KQ*l2_Bj!ou+$0DI|7I8oeu5`vFZ71&cI(3TyR!f6LuF8?% z&Dj*Up?8y5Z{A-p!VN4lt z@^&f{cVYS(U~C3u9CmW)2Q+Q|#_6NI^nGl!%B$W=r}@uu@>Z~7I-2t0^eCoWE?n@b z=xsSxe#W0wR^6&4a!dYb`BpPq2AcC+#1fW|((;&w+iI41L3w5o6+!g!BOX83v;Pis z2ao41W6M51dCi2Ge3s9fxrKDvG%g*_tgmRJ8fdcmHN?q38F>EG$xq#NMZPAn z6L(E&yyCw9sbh2QFFXwjpGBOU0MSr-U;gL zEo0@ulR}fT18BBu>-vGF?dcZrv|k~ig6Be6guY6-%bVb>^zm*bD@A3cA34SWnALi)w+c?d0r(vpfvFOg+ox?Y9=R^Bc3XJP@FR z3<|re32WVI#yM7rPDQ@UtK&p1GJ?om!d0D=c1wcmY1#as880f?zZFU6YO=&ToPs^bU@Es#VO2WU;)=#%Y~2_%Jy;W9dk? zsF|=ubXbsH$#d(nNbOVh)0rU##K{YY-DmrN$6mkf!Zo#nq2;c5;Qh1z_CB2IXdSV8 zI80U^qS&1pL&_GLT#2%P>yV<^RC{t|M9tu5wsoZzEgDLWm$f&;Lx6&mx0O^c_!@zN z?I!0vt`I ztd>qGJxPn%#w*iP8Fc~Bc^7eE%1C9kH^OCYRJ<5Kh`Nv#C+0$;0@d8JhGlKdGIjCo zOQnjN`jOd(`fo>;a(2{;ZXGXprUm}naTg_vF=a{bqS&3H$?8LhlZQ{mVg1bCzi)?< zT{(Bn11|vJ{-fAfKldtg|IZQQ!wB(oyP8NVCO3A6nkklVo#l7W1a$K$sNBIdzJ^H> z1sD~8qA8nBm^--#11BHJ3A)56s^Ea2C5!(>-{1(saiXcj<~|mQ=PfOpk6C zVOUO=Rlp*Qgl?O%6vLk>mNwi~_$?xv_+^}BKwN_NC6NUfw~fX4Ffjk;{rc;#;{Lmr zNOA4BMT5*W>Gu8FOE@ru;~0it*9=Aq{$0x z(%4c5;IZBCkPwedx~%DW*$j$eaQ=CbNfH)5j)mnfa+P)9QkpFPEGT@}Pzg5N#h%tH@S14xEq9cf zifWkBv5Pb#4wTKx0Erv+RYp#nxQJeJshe!|iJq4HOQMXtHNMDFftAUG*C2UI=`HGr z62^^uiw@_*w9Hf)rn=fkHNU|V*^X{qxal@@2anZT#wze{<)aZ+(lun+63^!C-dmBy zxU%e9QI^GD8o5EUNnLMC7V!&lpx8Y|Av`dTbnq{p%a*Q@rO}>vFbbJ&d-8M*jWDapl?n)*CKmBzgimq& zZCV}>vz%G|ZlrJW=Q=JMZQsNdt}MKGEonfS&4 zLU21RSma1*8)g5(_G>zyLJk=@saVwVyOZi)s``|%5i(LxRm_#2D*>B4e4V&7FU6Kk!vqAxGmMgw5O zI=259rD^v1Ukmr$XkUe7F&jjrI*|gpnmSs1&E5b|(QUPZK{qGZaX`XY!kfB?&u;?P zMs7{GZV%6vUkYT3fj^wi_2MJmB9v6eWGhQFSNti$7_d`v`MyHHfm~M213?RQJWn^OLP8W zcJTc36@{Emp*0ujIQvU^NQf(Pp{0PZ?>2O|9vyctY{BV$!q9xccPRVLIgT|yv~<$t zJTFF;W*EVjNhNPVXFS{atNmKMw<8MxAjCDAtbPOC^wSuIub=qD9oOVh;H|pr9(V!Z z&M*9M8V0-<^9#Q?Y@B~ThJNps2<_JmiroMs7D!j|fR=YVBtNSQ*xe2SCYk3lFn4k( z$mKN1yk~ye1GEv_bcQ?t*)KZ)0GUhQtt_K*V`OJdK<|bvrjJ&x%8tDVycYquDVrn< zkBMhnvP~~{ayyus{A94>B1~kYID+nhqv;rMqN2j)ZAG>EzT*dX0uVM1S-Kjz^pSGD zbF>!EZOI}!rfoet>-M4Ws& zbgNH-qWxHxc`zE-g2f*P$W=-WfT&`&LEnKK+m|{f(V}o~skKbYDW?;Q6?~g~9at5E zkn2$;?Sedz5ZXVh!cm!w0p_|)bJV=>*iKWi&YVC?<;12e07AIwU|L9@>XwZ20Yl?Nl~TO?`;MIZRd` zp>Fy=4#2Z7fBf)uZPP8?4Wsdjd-)TGhauoQh~2MHH~q@;Ll)_-1eQ_R59JJ07?gF6 zJX?|@x~ain?C$a=Tg_y0%rI#RR5q~y03YQ^L_t)!t!6fTyIm^%<*iqX4i_Xk!_scK zlD?C%*(FU*TeRr{4FG`vzSXjnf4x|jO3G=nRD@aPT-1cd&nE{ZFK#-Xa$2mEyANnG zK>H3N?mrBQQR_I9nTuc~e+*ydWXjNvi7CF+6I|FbIv6?|#)j@9WU;kjirz6Po+~;) zHCM@B!?~}CEQ!BJma!X!0p0W~h~2LYMBlm5Ko;D)$@j zUv#`UYhXa?y*d_KEcOSDD61NJ$18F~t09!Ot$f%*rdEO=SxciVin1iLFH(U7Kwg8P zG%5(#69fbk+!Lk44Ki@l>16qw(NQ_(e96~?(|{t!npOLfA%^TV#>iT~7LQsESkkdV zk!rn83mkVz3}p6Ao3ax@<@!ju))FAZn{Gk85lYl>Wwo@;+%voz2`-kOW=?b}ok%>Eki%F;K$z{VQ!^6n#-VH66 z@e}B(YO8U`@v3F!Md`URF4eh4`sSGp-6&dJ-Z_j2;ig*=_ur8g(lUgYm^x?C=X${K zohA=Uh9ehzYC459cIlR~*c~)xXxg?cc@SyR)z0^kMR`D0P8t}xFr9_6gzhxr`# z4-PRrbL!*Q=i!L0xEmUH0pQT*UhY-~93#d@c|QC5=;ucOu1qW$n9S(zfztzk?eZ4< zltw#93BYRaH63k`mTSRBK`Sqk$UHbl5vCr51c)qF=1y+sqzy3kU+J)NNI7^AgPz-ZI$y6mTPq`dRa2?Gild8Eu5^JAkW z=TO%!pr->Hv`xCCQ98Pv$|CK$(6l#qtpNdMhNe+ufNKrFe%4kAF;JYGLR|SB20w5i z;^gU1+<8N4y5eql;01uapL+?n_1$46`~z&Pe~9~ydywth#kgEHZ1Jo#HMPU$+sRV6 z(^5Evkpi}AA|@RJ7_k1)^0nbwikoYAmV?(smpTicGRwMXS>ORu z;yQ0*BvvtzUih^po?6yYyMmv~kqYxPnpSeJ4^ zj}DjGkGrS9WvP86SHgTIsqmNll@nh{xh*TZn5J8n+lvZWxCpb`#!6)??A?!e^X!ydpGc>@Fg1#bNpokhu7M@ERJsMR0HDl z1mfiL9KvsL7v2z{3%o%z-t2yT9Y+!oDw)}f zT+pj#J{0(8(ywg3wj2xFuIue$(>)9dof4b*hUVEXl9r|&y+BD#H?8S>ZYyL{YxB$l zyVHeJ{-~gn^mZ(ZU$k?{*MzD3Oxy)@TMctOa~08?R`z}kM4z1xc>L6!gX{RnD&8pW z4WaR7_mNdxIP>~C2#;a1@=s{8@;E525m0QdPmklM+|&Uwwi<2AvJfp;V%JxIEXH0Hov-#HkVrBs;xXicEaghhR-si^@t3}&9 z6&iL@|LS#a*$(K{R_e{N%wx!c-2Mwr00A z7f5tf9h#ZMj%9(;*@3~dZ!_-uxW#kc&RC9%mM8n?%rnOg=V-F>IJ%Wz0>{VCpILiD zfiCby(|C0Pnw7J@0L-Lj{9J2Wo?Z(=T*@T(=*%8h!HrBGH z1glb3c}lbx1))9r5pOvH#0Xhn<5bvspd~H#$RJ|2lZ}Is>d!h5>Yf5A0-3?cS4?Ce zQjJeFRjaG&V5f_dlm@)q0>#Eo@ab(=7Lc>$%DCcr)-H#Mwr$G8(JAYNJhNYzyh^eA zBgFXIeb;^S`}f~?dvv$r-mt(607rlIYgQOd!+fk5wC%fsm)wX9Z&=<$?u!x&mxnWV;BMS=Q#5JRmd% zDkU$$)&j7RwZ)?>4+cPlu;&(pn-7f_-k2BySnU4i3dsDnuEo!~fa?FJMjVl&1WzkV z$s3TNuh>hwV_DJ@0j^muaJ5t=*qTK)!_)8EcIgZhCnwOYJOqT_8E80p@{`AI=$#A8 z+#4Er0pQMG`93hA12pu14SoM{?q}}+539E6joOQ8hPnT9vBE2WoZ>p|(Cl`|iwz}^ zma_GG zxF%l3;6VtWy|*CTeA{@5xzvNIkG`BX-A-npCxN=Ebw#%+Y_?T>NHkK*Z7qv|I44WN zd#AGaX-Wx-TXs!4D_V<)Xq~$0GYH-15hs7l>;0LR|MAfsq<-VLH$3nH0Dk>@IC^1a zHG=*M`i+mGpTCd$jROGc`IJ>*iz)Ja5?cL)1uvUyh=}-d<&FB zMhCt&P6jwLU?>VV(u4AAm>xuq5jzSVb|Ea=pFIGNWl{_*j@|z8q1w zTtps#eAwpUZqcZuEsOjdb%T7d^fKEvM~^CRwT|@jW_3EmoR%{xHs0~+9LOnH-log&Ve3Bi2n?e)rZkdk-;qgPv#FA*4k>8 zpUSa~8o%U}v+)K+14m@yr4H-+fzhL=B~wctn-2qqrI;BY7Gi9c?45O}L_XcHdUFQd7CwmbTZhq-Fo4;X(IThi$;EpmLz51fu9Z@&p0_Vc5v6L9R6RFk zam{=|y&JTqI0YuF4knYH7j6^S2An6n{Q0w&oQmZ?qE#538*8gQW zuu`$m;)bSZ$PJn8~ZVOl=pE|jg(m4#jMv}LeESsV{j9+I>%HM9VDF~Bzh zV?0M;@^28Q4}s{3MPzSuw+p-r90fYtPvT>77+xfV7ntbp*n1vGR4Fz+HvwFA-~wvt zbt~$&_oVGi?nWrt0wwe=8JQn@FtsU^E7(3g`$O{2XqSe6*~M)KdkOU*moO{G+ir_%3}3t7E&$0TmZ7Q&IxX^ucNKjjRL2? zg)7OOOuUCwB9e^bR*{%cd)POtxj~|54^L_?@0K@#lJfFQA-0v%Qd1^;6ri1L{ zio$xUnMN_wa--4OcT2N$^}lr7O0WDthvDN`EaG4KOCB0@jd&S>oP6;9P{6dS_+jsSGSKr60z*Jr)V!<*u=7!DI#>qBLSS_gtBf}%n zBL7Mv<`^(# zExMdSXEnauGFH*G!k}`NV_7wGFcDmZ?}V-~ld{J%OHG_Q1 zSZRVt{99q>aR9|N>LyPiPW~0*^x=(_c;=N49e6{YLfeVk1>SACBmdJWbSrTmhWR_e z!$&cn{g>Qt><1%g^!Bfd@mg z80Nse1Yggy2H)a2@wPh_sujcZPAmHm_KoI+Hqdw&YlXEy*}`6XLsyxO*{Wb$Ke!R|1I0avRan@JM^1LEW?b<;lv#Rn;N&&esnxbR`_H{Oe(-v@9IEADnapqar`;u!$dBc$R-K92O)g5HC9 z4WL91JWO+_ zVWQz>X|!VqP??f(huWx67Pl3)8BfwW+7?}r9*kC8D7c$B1L*@ocaCEBB)XLcLHut{ zeEi6{Ewb5lh1&(*i*$E>;RmZUpT84*|F^-z2Y9~zRt)3L2h}XR7HibA(HrKZR%PRr z|JbZpD~Q>tz5`wpuUxdty)VaQ!%_?(mW2YNda~MpVS(Gr<`}s&RL&82q0f*(3!v5g z2zzfyha=2w1a|6fr{=H&kxa$Kue={Y<;puE+#LjNZtlu&p)5AXnU7St-$H1F;XB#rj0`4ihZ03RvNlKQ#em!DmBA{3wLPG~CK(GscDJ!?VTSw8%Vn!*r?U9l$#jr3gR%y})a5{-dkvxcCMGMN1>-56&d;2`{olV~FSof=Zdc>I zgzm1-yb{pSec<7zFyHvw++TPXhGDNeWo9*CjL-fsh1ZT-nlKy-tDUmJ606A;S~N_> zbD19Cx0;!B*|M@AP|IG**3y3UM4pu^0rARB2zzb;#h5u6Xf(Xt2vWr@HNP_cX5ri8 zSMx>{|7yrWmG45CWZ({Qjk@X6h?Cz3g+~c^?xh>>GwWB&?E>#5c1IpKjo4!^4gK3N z%>N$u8$X49ejGfkg5@KdgTpOAr`qxkpaNrZ;5ecn1A51-1+n~J4Tiqssn(Zm&~~7( z@<<%E)8Be)8wn4Wzw%U?^7Q5f0QTWnZygh! zMLFfC;^UK97BoI+$(d2L++}wRlswHgwkFSQ%Tj}Qm5pnVeLic5k;X8fD zYo|WEyLfgf-7fH6MtA%RukGWZe;?0h{}tvN@8f>szEPW8q#4TB07k$pfjMtPW2*@( zK#XC=t~iuhX7O%8P3*Jo_%kQ)EwpMYvZL|&S-RECEyzhYfu;hw%QoM)^fs=&nsiq9 z78rT+2o-UvB(jHY!ScHe7SNfrs&p!z7GQTOOCy@|2?M(Bd5Y7=5hs7hA%6Yk`)}J_ zJiAP8*T#D}UDutXIeiU*pXDz87=`c)+;6-S{k&u5plc*nQ`s}fy2mBZ4b+sNt&?~g{FHD_Uuoe>tWPDRit0| zP$_&BXKP!=lz?iCuTZN_(`&8^=&CIU%SFjn+?~p@C>moxoXjb9Pa{k|Lm@svae4~f zboU^{Wpuk5?-g>>2VMw=`e-P-$!H-o56y;4t_8NMz2*rn5s>-Sm~GmBU#%Vs-%QP4?x7bA;rTLhX@2(MAM`YnX`WyJ1luf*3+ z&p!I5T|4d-aJ#^Jh25Kf^IVu;xbRk<91u)w==6T<2~P>7j#4Otf4 zMe}3mo;}K=kq4axbS8cUS(c}hrU96e_vXEhrR+YeOvmxu!l8-n$dY}=g|jmhCQqT8 ze3jzlk&e3OzW1?Pc6TaVLAMLMSHa!=z}YZfGdaHxegA&$=Re8w3qOWozSr+e02F#< z8<#(&=D=8@=P-<0(6X|rpvNfbRZEhnK1>(QtXx^EW(DAJ5U`nGH0`|rsM`bDyB`R_ zZ0YiLPtG^Ksg(J-WzGA3QcuWI%ciQ>5zKe~Tc%e}&G7kICtd(&_HC=By{75#9G`;EVee&eUPpS>OZ z`~aA<{k_`KWjmI=a{&t49^@1RcYdoGn`k;1I6uc?0e@p}rpYR3b>D)!7vQu-13@`P z3#Lm=uuW%q`em!SL+JTBzig(`34a~9+Sjc&bn7khv}LkAS%5I75YHk`o<*E|mAdX< zgThHrSbOQC2ddoex+~*$f%h75$N%TorWoe;a6kVE9%ety{rp`R=7%xffEILiFRR?5 z$*Sh6ott&qY2xcw*DXS_dDqUJK`bd*88FpoY;e|Z7bCV0mR9{}x(BqfZ?x$$AS!wr z{0d0TDmh?3k?yD6fZtAL{e`E2Mv-k(Su9kekOgkd%*#?jyp_d1ma;71xl|S!Kw(Z4 zUq$SmrZD*`h3+wmlNbKK`)}D@G`j}eF7RGs?(PTAPI>nFam@R_f`0y=gZuYzKfez= zg&Z$`}NcP(qvGZ0t}3^X(&$n_4|BF&DZTru={)`dy=Lr3u$&bZjyHX zf^2}_dSRXyA!U>2B0BZY-X_h_{90TZB;d(vrPx}SE zTL|6%ju;-tWbcb7K63Le;9f&+7kIBhcgF)Sg}BkJhJlZOhqq!FK8T_JPk25%#N2ld zQHupPyGrs?1;7Lf7K9D2>Yzo|#uKDX_kgB*M*wozRcoJF7I>^diFrA_nyw71 zwdEGiI(*qJV~gY{={8NP$j?2QV)t#t$)5o92zArvU;5asyV~tF?skFq zI&pXX#tHC7w~zYaUi8Czcx zr15`y!SW>K$XFl`+Hes95r826*YC3Bpme&#= zr8w;AvaFeJN=LcA?CikvEb*8CiUW1sa|qp&pzv=I zzcxc$mK(eSZ+#uVzix*kE<7#vEt0dxZ_4YCH?Z z#yPDQU@FZQUSp(g3R>L9sf1a>Ch}oeeDoln?xP|ssk?bV$ z7RiNn61&5p)Wp;NX`!{6w+tkU=zyHQQ+Uk|5RBUGYZSU~Bg7{_;ZG2|7YCx(cpBHg zzx(xv>&oo{@Ac?@^9%ss*!l)S->)+FN6^pT3m$%)`}un@4EKTi{ouY+%{Rd*dmzc4 zIgFcob>)8x3i6u)s=C#TM$k=~Zi;32!a%GCwL;G>oWFyh98i|i$^VnCu2jQ0JbpNrd>Ppzst2eqorzbFbh>^Wh)8rOx-RyRO_W@ZL!7DaQ2d z9EZMrwhOb_UVz`m^VvJW{a?h;zl;0%z2M;hc$gwzQ3L^Tftd0 zUC^Ri#un?P%foi+wx6E*+Hf7RN{ZUgawrZDD_9RiVa62BP>9bX#HSJB|Ao*!8@llA zAx_s{{d24H+07BVpG&`S+%E9m!0zyGyoT<=g(>Fq0|Y*fjg5DL=YI*HckwWP2Zmu4 zL$7y4h(?)qByci2i0#v9ecAa#765=a8Fw&*h&HG#nt*Fq<0gVF(OufG+F3I~rB3U% zkj>MfI3R>|3f;57@H9gFQ-tX=On4F9pb2i&VV>xEJ3dd;xXaI$EB07sWIRy;g zrEcZ_1cj%DZu0H(E8*;Gt7~)o%+bwiaM#@+ZWnmpi0+{u;MfHUG&|P~2nT5xjuZE9 z<$?bK4fFc|9`^|k!+sXCL|!;+0B#ira3%qZ1_r-sYgBM=Mbk0-SxUiQ9`)S_a1@Lhm=6@c;@KzAr%l-Tqfc+RY z_UYjWxyq7&Fab?hk++O7^2a&zr-?v%cQAl@3zxGj8!dCQ?POvqL0V5E!*l1dm?b*s zfPe(FqkVP^B2ehgGH@25J4HM^&mnx5LihgycoAJZF${bblhw5gpxF;606w_?Qq|zD zyAIth@b0?N{q_&W-@WH>=c&D5o_9PuKcx`&6ZjD3^J7fmUTm!YhY0Z=^z-99Y#c%C zR*6D~&~<6cSOBH7!gYC05ZKx~3v($S53-C#=l~a9s%55|k06++H zP@IGNb?PRkK;Z@S^A|DM^M8Z+1;p+o1E;y~&&KKA87R(Qy7g6HZOn1`gf){3=4X7wc>L5yD-xvU)Fow}9v_>Q;`S-#CDNV?R^a3m#T6 z466XTw5Kej-4ylmyc$o_ds?Wq%lIDmNocupa%gTgxV zu!cA}OL1}r^VvxzoS<&?-+|}PBMc`fuAb$MjdSzK>I@+cr#^Da?m>oKx2f9&-d(pn z_lIi$_2b`PA2;s5`JuJY^RRCi`a_tl9{>-B(M=B1eB(Fm>d||!gemBkPV;;_6y5}5)IOD!Q$Mf^+ z6sI$qtj;;aIVLOfmxt59hmKvPg57mDn*09&N`eLU7W*&=00000NkvXXu0mjfPlT%e literal 0 HcmV?d00001 diff --git a/Plugins/Wox.Plugin.Program/Languages/en.xaml b/Plugins/Wox.Plugin.Program/Languages/en.xaml index bb218887c..b7202d590 100644 --- a/Plugins/Wox.Plugin.Program/Languages/en.xaml +++ b/Plugins/Wox.Plugin.Program/Languages/en.xaml @@ -31,6 +31,7 @@ Successfully updated file suffixes File suffixes can't be empty + Run As Different User Run As Administrator Open containing folder Disable this program from displaying diff --git a/Plugins/Wox.Plugin.Program/Main.cs b/Plugins/Wox.Plugin.Program/Main.cs index a37e9f202..a135d1e80 100644 --- a/Plugins/Wox.Plugin.Program/Main.cs +++ b/Plugins/Wox.Plugin.Program/Main.cs @@ -191,12 +191,12 @@ namespace Wox.Plugin.Program ); } - public static bool StartProcess(ProcessStartInfo info) + public static bool StartProcess(Func runProcess, ProcessStartInfo info) { bool hide; try { - Process.Start(info); + runProcess(info); hide = true; } catch (Exception) diff --git a/Plugins/Wox.Plugin.Program/Programs/UWP.cs b/Plugins/Wox.Plugin.Program/Programs/UWP.cs index 2c608807b..e73371cd1 100644 --- a/Plugins/Wox.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Wox.Plugin.Program/Programs/UWP.cs @@ -320,7 +320,7 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_open_containing_folder"), Action = _ => { - var hide = Main.StartProcess(new ProcessStartInfo(Package.Location)); + var hide = Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location)); return hide; }, IcoPath = "Images/folder.png" diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index 36e08a3c4..ee34328d9 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -6,10 +6,12 @@ using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; +using System.Threading.Tasks; using Microsoft.Win32; using Shell; using Wox.Infrastructure; using Wox.Plugin.Program.Logger; +using Wox.Plugin.SharedCommands; namespace Wox.Plugin.Program.Programs { @@ -64,7 +66,7 @@ namespace Wox.Plugin.Program.Programs FileName = FullPath, WorkingDirectory = ParentDirectory }; - var hide = Main.StartProcess(info); + var hide = Main.StartProcess(Process.Start, info); return hide; } }; @@ -91,6 +93,19 @@ namespace Wox.Plugin.Program.Programs { var contextMenus = new List { + new Result + { + Title = api.GetTranslation("wox_plugin_program_run_as_different_user"), + Action = _ => + { + var info = FullPath.SetProcessStartInfo(ParentDirectory); + + Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info)); + + return true; + }, + IcoPath = "Images/user.png" + }, new Result { Title = api.GetTranslation("wox_plugin_program_run_as_administrator"), @@ -102,7 +117,7 @@ namespace Wox.Plugin.Program.Programs WorkingDirectory = ParentDirectory, Verb = "runas" }; - var hide = Main.StartProcess(info); + var hide = Main.StartProcess(Process.Start, info); return hide; }, IcoPath = "Images/cmd.png" @@ -112,7 +127,7 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_open_containing_folder"), Action = _ => { - var hide = Main.StartProcess(new ProcessStartInfo(ParentDirectory)); + var hide = Main.StartProcess(Process.Start, new ProcessStartInfo(ParentDirectory)); return hide; }, IcoPath = "Images/folder.png" diff --git a/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj b/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj index 850f972ca..91dada067 100644 --- a/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj +++ b/Plugins/Wox.Plugin.Program/Wox.Plugin.Program.csproj @@ -112,6 +112,9 @@ PreserveNewest + + Always + MSBuild:Compile Designer diff --git a/Wox.Plugin/SharedCommands/ShellCommand.cs b/Wox.Plugin/SharedCommands/ShellCommand.cs index d7071e735..9da535150 100644 --- a/Wox.Plugin/SharedCommands/ShellCommand.cs +++ b/Wox.Plugin/SharedCommands/ShellCommand.cs @@ -2,14 +2,65 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Wox.Plugin.SharedCommands { public static class ShellCommand { - public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory="", string arguments = "", string verb = "") + public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam); + [DllImport("user32.dll")] static extern bool EnumThreadWindows(uint threadId, EnumThreadDelegate lpfn, IntPtr lParam); + [DllImport("user32.dll")] static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount); + [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr hwnd); + + private static bool containsSecurityWindow; + + public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo) + { + processStartInfo.Verb = "RunAsUser"; + var process = Process.Start(processStartInfo); + + containsSecurityWindow = false; + while (!containsSecurityWindow) // wait for windows to bring up the "Windows Security" dialog + { + CheckSecurityWindow(); + Thread.Sleep(25); + } + while (containsSecurityWindow) // while this process contains a "Windows Security" dialog, stay open + { + containsSecurityWindow = false; + CheckSecurityWindow(); + Thread.Sleep(50); + } + + return process; + } + + private static void CheckSecurityWindow() + { + ProcessThreadCollection ptc = Process.GetCurrentProcess().Threads; + for (int i = 0; i < ptc.Count; i++) + EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero); + } + + private static bool CheckSecurityThread(IntPtr hwnd, IntPtr lParam) + { + if (GetWindowTitle(hwnd) == "Windows Security") + containsSecurityWindow = true; + return true; + } + + private static string GetWindowTitle(IntPtr hwnd) + { + StringBuilder sb = new StringBuilder(GetWindowTextLength(hwnd) + 1); + GetWindowText(hwnd, sb, sb.Capacity); + return sb.ToString(); + } + + public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "") { var info = new ProcessStartInfo { From bec52b81fd3832fe5f3be14972ee1fbced2483da Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Wed, 11 Dec 2019 02:21:50 +0200 Subject: [PATCH 25/90] adding conetxt menu start --- .../Wox.Plugin.Folder/ContextMenuLoader.cs | 113 ++++++++++++++++++ Plugins/Wox.Plugin.Folder/Main.cs | 14 ++- .../Wox.Plugin.Folder.csproj | 1 + 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs diff --git a/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs new file mode 100644 index 000000000..e1a923b4e --- /dev/null +++ b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Windows; + +namespace Wox.Plugin.Folder +{ + internal class ContextMenuLoader : IContextMenu + { + private readonly PluginInitContext _context; + + public ContextMenuLoader(PluginInitContext context) + { + _context = context; + } + + public List LoadContextMenus(Result selectedResult) + { + var contextMenus = new List(); + if (selectedResult.ContextData is SearchResult record) + { + string editorPath = "notepad.exe"; // TODO add the ability to create a custom editor + + var name = "Open With Editor: " + Path.GetFileNameWithoutExtension(editorPath); + contextMenus.Add(new Result + { + Title = name, + Action = _ => + { + try + { + Process.Start(editorPath, record.FullPath); + } + catch + { + // TODO: update this + _context.API.ShowMsg( + string.Format(_context.API.GetTranslation("wox_plugin_everything_canot_start"), + record.FullPath), string.Empty, string.Empty); + return false; + } + + return true; + }, + IcoPath = editorPath + }); + + var icoPath = (record.Type == ResultType.File) ? "Images\\file.png" : "Images\\folder.png"; + contextMenus.Add(new Result + { + Title = _context.API.GetTranslation("wox_plugin_everything_copy_path"), + Action = (context) => + { + Clipboard.SetText(record.FullPath); + return true; + }, + IcoPath = icoPath + }); + + contextMenus.Add(new Result + { + Title = _context.API.GetTranslation("wox_plugin_everything_copy"), + Action = (context) => + { + Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath }); + return true; + }, + IcoPath = icoPath + }); + + if (record.Type == ResultType.File || record.Type == ResultType.Folder) + contextMenus.Add(new Result + { + Title = _context.API.GetTranslation("wox_plugin_everything_delete"), + Action = (context) => + { + try + { + if (record.Type == ResultType.File) + System.IO.File.Delete(record.FullPath); + else + System.IO.Directory.Delete(record.FullPath); + } + catch + { + _context.API.ShowMsg(string.Format(_context.API.GetTranslation("wox_plugin_everything_canot_delete"), record.FullPath), string.Empty, string.Empty); + return false; + } + + return true; + }, + IcoPath = icoPath + }); + + } + + return contextMenus; + } + } + + public class SearchResult + { + public string FullPath { get; set; } + public ResultType Type { get; set; } + } + + public enum ResultType + { + Volume, + Folder, + File + } +} \ No newline at end of file diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index 4f8fac60d..baac79b1e 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -9,13 +9,14 @@ using Wox.Infrastructure.Storage; namespace Wox.Plugin.Folder { - public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable + public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IContextMenu { private static List _driverNames; private PluginInitContext _context; private readonly Settings _settings; private readonly PluginJsonStorage _storage; + private readonly IContextMenu _contextMenuLoader = new ContextMenuLoader(); public Main() { @@ -104,7 +105,8 @@ namespace Wox.Plugin.Folder string changeTo = path.EndsWith("\\") ? path : path + "\\"; _context.API.ChangeQuery(string.IsNullOrEmpty(queryActionKeyword)? changeTo : queryActionKeyword + " " + changeTo); return false; - } + }, + ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path } }; } @@ -226,7 +228,8 @@ namespace Wox.Plugin.Folder } return true; - } + }, + ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath} }; return result; } @@ -263,5 +266,10 @@ namespace Wox.Plugin.Folder { return _context.API.GetTranslation("wox_plugin_folder_plugin_description"); } + + public List LoadContextMenus(Result selectedResult) + { + return _contextMenuLoader.LoadContextMenus(selectedResult); + } } } \ No newline at end of file diff --git a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj index 6a05ebd26..d29dc9874 100644 --- a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj +++ b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj @@ -58,6 +58,7 @@ Properties\SolutionAssemblyInfo.cs + From 971e1cdbecb8278604c131eabaa4c704f08d17d5 Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Wed, 11 Dec 2019 19:08:52 +0100 Subject: [PATCH 26/90] Fix highlighting for UWP results --- Plugins/Wox.Plugin.Program/Programs/UWP.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Programs/UWP.cs b/Plugins/Wox.Plugin.Program/Programs/UWP.cs index b86cad51c..9d893e765 100644 --- a/Plugins/Wox.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Wox.Plugin.Program/Programs/UWP.cs @@ -300,8 +300,9 @@ namespace Wox.Plugin.Program.Programs } else if (!string.IsNullOrEmpty(Description)) { - result.Title = $"{DisplayName}: {Description}"; - result.TitleHighlightData = StringMatcher.FuzzySearch(query, DisplayName).MatchData; + var title = $"{DisplayName}: {Description}"; + result.Title = title; + result.TitleHighlightData = StringMatcher.FuzzySearch(query, title).MatchData; } else { From 0f9da7f5194486f1eb84562a13df804dfff3c1fa Mon Sep 17 00:00:00 2001 From: SysC0mp Date: Wed, 11 Dec 2019 19:10:11 +0100 Subject: [PATCH 27/90] Always return filled result again --- Plugins/Wox.Plugin.Program/Main.cs | 4 ++-- Wox.Infrastructure/StringMatcher.cs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Main.cs b/Plugins/Wox.Plugin.Program/Main.cs index a37e9f202..106ac9cfa 100644 --- a/Plugins/Wox.Plugin.Program/Main.cs +++ b/Plugins/Wox.Plugin.Program/Main.cs @@ -78,8 +78,8 @@ namespace Wox.Plugin.Program } var results1 = win32.AsParallel() - .Where(p => p.Enabled) - .Select(p => p.Result(query.Search, _context.API)); + .Where(p => p.Enabled) + .Select(p => p.Result(query.Search, _context.API)); var results2 = uwps.AsParallel() .Where(p => p.Enabled) diff --git a/Wox.Infrastructure/StringMatcher.cs b/Wox.Infrastructure/StringMatcher.cs index f350e0e40..58ffa336f 100644 --- a/Wox.Infrastructure/StringMatcher.cs +++ b/Wox.Infrastructure/StringMatcher.cs @@ -99,9 +99,7 @@ namespace Wox.Infrastructure RawScore = Math.Max(score, pinyinScore) }; - return result.Score > 0 ? - result : - new MatchResult { Success = false }; + return result; } return new MatchResult { Success = false }; From da798e7a5a08efd89162ad6efa750ec28124b812 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 12 Dec 2019 20:55:41 +1100 Subject: [PATCH 28/90] add run as different user option to context menu --- Plugins/Wox.Plugin.Shell/Images/user.png | Bin 0 -> 65650 bytes Plugins/Wox.Plugin.Shell/Languages/en.xaml | 1 + Plugins/Wox.Plugin.Shell/Main.cs | 72 ++++++++++++------ .../Wox.Plugin.Shell/Wox.Plugin.Shell.csproj | 3 + 4 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 Plugins/Wox.Plugin.Shell/Images/user.png diff --git a/Plugins/Wox.Plugin.Shell/Images/user.png b/Plugins/Wox.Plugin.Shell/Images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..2d45c1ee919304a973641b387934dbd517979271 GIT binary patch literal 65650 zcmV*nKuEudP)KoEefB#uyts#u%fQWvShg+Wqc(&#Brg ze^l+-YrpDLoqKNg?e4Q|^{uMkUTgnq*WPQdy=&J#goP|*R{5P100{Wb>wy2|3E)4y z1vh`_6rhkZBgguMoQv@ggV8b+Eu$DOV=`W$e6T|Ka1}(WD9U9$K6gF1+y$aFkXi$1 z6+~-b9)a}=z(W8dFmiwu00jVC%?_NSy`II+#xaI1Sb(!TKarc0vtz zJ`a_h2lzA>jYd()+z44*}#L%E7Gah&3T1hHbkXb+{1)p_i~MjcO2SFc$y{C^ZJD z^I*LW%1+Z@=>*F0aX>vygQdUYvV4?TKZ(KUB=qF`WU#cZM_NOT^>c69*P7Ep7P4@> z3#sHco~gXkHA5VHE`y#R=kbLhseBcLJt)ilAUed83ojy-A7ot|gf91h)CgJ+!8)%D zF&I!yyotJ!MVuf5AkQN@Seu?lf4WTugF12H8X#*!+elhfPFd@9sO&gp`ID@R$7#6Z zGtm4vvg~OvjlXfhT`cPHE?EJMejefaLNl=INpW)+DYsxCm89$kc!DNsca3C z@c{sbpeJ7o&9_0R!(2}G16YABR?P69!HU>8r0R+}VXiz^=pY$j0#SVofIv2YWd;e4 zC)_1iV7~LM@-x;v+cR?UbK?4#Y-$al;-vDk0M1f2cp6$i3Z=dPmH#ckk0W1ticy@@ z+3@@Z@%TiZ18=>i?Wru}3YUfBy-H=@uRN>Nq!<$AYplzI#C$uzUk@&CgVwjRE_VaG zQcV@Gs+?4}?~#sHlql|2WgJ`c*i0HXiMM4u;>J;}N_sRx7cGw07~{Nye3daMh%Ol0AB zuOeCc&11Os>@Xj4zJ~FIBhdOTl#`c&a68IkFTg9z

PbOc!t+-8s$(Xa?|2G$~zC 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≠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 literal 0 HcmV?d00001 diff --git a/Plugins/Wox.Plugin.Shell/Languages/en.xaml b/Plugins/Wox.Plugin.Shell/Languages/en.xaml index 6f56e83f5..0d0a41b3f 100644 --- a/Plugins/Wox.Plugin.Shell/Languages/en.xaml +++ b/Plugins/Wox.Plugin.Shell/Languages/en.xaml @@ -5,6 +5,7 @@ Replace Win+R Do not close Command Prompt after command execution Always run as administrator + Run as different user Shell Allows to execute system commands from Wox. Commands should start with > this command has been executed {0} times diff --git a/Plugins/Wox.Plugin.Shell/Main.cs b/Plugins/Wox.Plugin.Shell/Main.cs index 323cfb5cd..1a5174c4e 100644 --- a/Plugins/Wox.Plugin.Shell/Main.cs +++ b/Plugins/Wox.Plugin.Shell/Main.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Windows; using WindowsInput; using WindowsInput.Native; @@ -84,7 +86,7 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(m); + Execute(Process.Start, PrepareProcessStartInfo(m)); return true; } })); @@ -117,7 +119,7 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(m.Key); + Execute(Process.Start, PrepareProcessStartInfo(m.Key)); return true; } }; @@ -136,7 +138,7 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(cmd); + Execute(Process.Start, PrepareProcessStartInfo(cmd)); return true; } }; @@ -154,14 +156,14 @@ namespace Wox.Plugin.Shell IcoPath = Image, Action = c => { - Execute(m.Key); + Execute(Process.Start, PrepareProcessStartInfo(m.Key)); return true; } }).Take(5); return history.ToList(); } - private void Execute(string command, bool runAsAdministrator = false) + private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false) { command = command.Trim(); command = Environment.ExpandEnvironmentVariables(command); @@ -212,19 +214,33 @@ namespace Wox.Plugin.Shell } else { - return; + throw new NotImplementedException(); } info.UseShellExecute = true; + _settings.AddCmdHistory(command); + + return info; + } + + private void Execute(Func startProcess,ProcessStartInfo info) + { try { - Process.Start(info); - _settings.AddCmdHistory(command); + startProcess(info); } catch (FileNotFoundException e) { - MessageBox.Show($"Command not found: {e.Message}"); + var name = "Plugin: Shell"; + var message = $"Command not found: {e.Message}"; + _context.API.ShowMsg(name, message); + } + catch(Win32Exception e) + { + var name = "Plugin: Shell"; + var message = $"Error running the command: {e.Message}"; + _context.API.ShowMsg(name, message); } } @@ -306,19 +322,31 @@ namespace Wox.Plugin.Shell public List LoadContextMenus(Result selectedResult) { - return new List + var resultlist = new List { - new Result - { - Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"), - Action = c => - { - Execute(selectedResult.Title, true); - return true; - }, - IcoPath = Image - } - }; + new Result + { + Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_different_user"), + Action = c => + { + Task.Run(() =>Execute(ShellCommand.RunAsDifferentUser, PrepareProcessStartInfo(selectedResult.Title))); + return true; + }, + IcoPath = "Images/user.png" + }, + new Result + { + Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"), + Action = c => + { + Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true)); + return true; + }, + IcoPath = Image + } + }; + + return resultlist; } } } diff --git a/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj b/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj index 2291f78bb..270a55c3b 100644 --- a/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj +++ b/Plugins/Wox.Plugin.Shell/Wox.Plugin.Shell.csproj @@ -70,6 +70,9 @@ + + Always + MSBuild:Compile Designer From 5139bc326a60377267deee243f83959ba18ddf1e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Fri, 13 Dec 2019 07:36:12 +1100 Subject: [PATCH 29/90] Fix bug from merge in Folder plugin --- Plugins/Wox.Plugin.Folder/Main.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index 7775398ff..d7b5a1569 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -165,8 +165,7 @@ namespace Wox.Plugin.Folder searchOption = SearchOption.AllDirectories; incompleteName = incompleteName.Substring(1); } - - string searchNickname = new FolderLink { Path = query.Search }.Nickname; + try { // search folder and add results 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 30/90] 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 2c87c00906175bb2c2d2c8176043d853b79443d4 Mon Sep 17 00:00:00 2001 From: AT <14300910+theClueless@users.noreply.github.com> Date: Fri, 13 Dec 2019 03:20:16 +0200 Subject: [PATCH 31/90] added context menu support (same as everything plugin) --- .../Wox.Plugin.Folder/ContextMenuLoader.cs | 130 +++++++++++++----- Plugins/Wox.Plugin.Folder/Images/file.png | Bin 0 -> 290 bytes Plugins/Wox.Plugin.Folder/Main.cs | 7 +- .../Wox.Plugin.Folder.csproj | 3 + 4 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 Plugins/Wox.Plugin.Folder/Images/file.png diff --git a/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs index e1a923b4e..92fae4620 100644 --- a/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs +++ b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Windows; +using Wox.Infrastructure.Logger; namespace Wox.Plugin.Folder { @@ -19,51 +21,55 @@ namespace Wox.Plugin.Folder var contextMenus = new List(); if (selectedResult.ContextData is SearchResult record) { - string editorPath = "notepad.exe"; // TODO add the ability to create a custom editor + if (record.Type == ResultType.File) + { + contextMenus.Add(CreateOpenWithEditorResult(record)); + contextMenus.Add(CreateOpenContainingFolderResult(record)); + } - var name = "Open With Editor: " + Path.GetFileNameWithoutExtension(editorPath); + var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath; + var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder"; contextMenus.Add(new Result { - Title = name, - Action = _ => + Title = "Copy Path", + SubTitle = $"Copy the path of {fileOrFolder} into the clipboard", + Action = (context) => { try { - Process.Start(editorPath, record.FullPath); + Clipboard.SetText(record.FullPath); + return true; } - catch + catch (Exception e) { - // TODO: update this - _context.API.ShowMsg( - string.Format(_context.API.GetTranslation("wox_plugin_everything_canot_start"), - record.FullPath), string.Empty, string.Empty); + var message = "Fail to set text in clipboard"; + LogException(message, e); + _context.API.ShowMsg(message); return false; } - - return true; - }, - IcoPath = editorPath - }); - - var icoPath = (record.Type == ResultType.File) ? "Images\\file.png" : "Images\\folder.png"; - contextMenus.Add(new Result - { - Title = _context.API.GetTranslation("wox_plugin_everything_copy_path"), - Action = (context) => - { - Clipboard.SetText(record.FullPath); - return true; }, IcoPath = icoPath }); contextMenus.Add(new Result { - Title = _context.API.GetTranslation("wox_plugin_everything_copy"), + Title = "Copy", + SubTitle = $"Copy the {fileOrFolder} to the clipboard", Action = (context) => { - Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath }); - return true; + try + { + Clipboard.SetFileDropList(new System.Collections.Specialized.StringCollection { record.FullPath }); + return true; + } + catch (Exception e) + { + var message = $"Fail to set {fileOrFolder} in clipboard"; + LogException(message, e); + _context.API.ShowMsg(message); + return false; + } + }, IcoPath = icoPath }); @@ -71,19 +77,21 @@ namespace Wox.Plugin.Folder if (record.Type == ResultType.File || record.Type == ResultType.Folder) contextMenus.Add(new Result { - Title = _context.API.GetTranslation("wox_plugin_everything_delete"), + Title = "Delete", Action = (context) => { try { if (record.Type == ResultType.File) - System.IO.File.Delete(record.FullPath); + File.Delete(record.FullPath); else - System.IO.Directory.Delete(record.FullPath); + Directory.Delete(record.FullPath); } - catch + catch(Exception e) { - _context.API.ShowMsg(string.Format(_context.API.GetTranslation("wox_plugin_everything_canot_delete"), record.FullPath), string.Empty, string.Empty); + var message = $"Fail to delete {fileOrFolder} at {record.FullPath}"; + LogException(message, e); + _context.API.ShowMsg(message); return false; } @@ -96,6 +104,64 @@ namespace Wox.Plugin.Folder return contextMenus; } + + private Result CreateOpenContainingFolderResult(SearchResult record) + { + return new Result + { + Title = "Open containing folder", + Action = _ => + { + try + { + Process.Start("explorer.exe", $" /select,\"{record.FullPath}\""); + } + catch(Exception e) + { + var message = $"Fail to open file at {record.FullPath}"; + LogException(message, e); + _context.API.ShowMsg(message); + return false; + } + + return true; + }, + IcoPath = Main.FolderImagePath + }; + } + + + private Result CreateOpenWithEditorResult(SearchResult record) + { + string editorPath = "notepad.exe"; // TODO add the ability to create a custom editor + + var name = "Open With Editor: " + Path.GetFileNameWithoutExtension(editorPath); + return new Result + { + Title = name, + Action = _ => + { + try + { + Process.Start(editorPath, record.FullPath); + return true; + } + catch (Exception e) + { + var message = $"Fail to editor for file at {record.FullPath}"; + LogException(message, e); + _context.API.ShowMsg(message); + return false; + } + }, + IcoPath = editorPath + }; + } + + public void LogException(string message, Exception e) + { + Log.Exception($"|Wox.Plugin.Folder.ContextMenu|{message}", e); + } } public class SearchResult diff --git a/Plugins/Wox.Plugin.Folder/Images/file.png b/Plugins/Wox.Plugin.Folder/Images/file.png new file mode 100644 index 0000000000000000000000000000000000000000..36156767a6572d31205104d16a8e909c5af378b4 GIT binary patch literal 290 zcmV+-0p0$IP)=FA?pM#)7Nq6>`@rGF3#p%4mjka3Wys(?018&wG;f _driverNames; private PluginInitContext _context; private readonly Settings _settings; private readonly PluginJsonStorage _storage; - private readonly IContextMenu _contextMenuLoader = new ContextMenuLoader(); + private IContextMenu _contextMenuLoader; public Main() { @@ -38,6 +42,7 @@ namespace Wox.Plugin.Folder public void Init(PluginInitContext context) { _context = context; + _contextMenuLoader = new ContextMenuLoader(context); InitialDriverList(); } diff --git a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj index d29dc9874..e0305a648 100644 --- a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj +++ b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj @@ -81,6 +81,9 @@ PreserveNewest + + PreserveNewest + MSBuild:Compile Designer 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 32/90] 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 33/90] 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 34/90] 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 35/90] 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 c279f405cfe55d43a33c9f1fe88ff6598bc44e33 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 15 Dec 2019 18:08:36 +1100 Subject: [PATCH 36/90] Add run as different user option when file is of certain type --- .../Wox.Plugin.Folder/ContextMenuLoader.cs | 37 ++++++++++++++++++ Plugins/Wox.Plugin.Folder/Images/user.png | Bin 0 -> 65650 bytes .../Wox.Plugin.Folder.csproj | 3 ++ 3 files changed, 40 insertions(+) create mode 100644 Plugins/Wox.Plugin.Folder/Images/user.png diff --git a/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs index 92fae4620..05e47dd52 100644 --- a/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs +++ b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Threading.Tasks; using System.Windows; using Wox.Infrastructure.Logger; +using Wox.Plugin.SharedCommands; namespace Wox.Plugin.Folder { @@ -100,6 +102,27 @@ namespace Wox.Plugin.Folder IcoPath = icoPath }); + if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath)) + contextMenus.Add(new Result + { + Title = "Run as different user", + Action = (context) => + { + try + { + Task.Run(()=> ShellCommand.RunAsDifferentUser(record.FullPath.SetProcessStartInfo())); + } + catch (FileNotFoundException e) + { + var name = "Plugin: Folder"; + var message = $"File not found: {e.Message}"; + _context.API.ShowMsg(name, message); + } + + return true; + }, + IcoPath = "Images/user.png" + }); } return contextMenus; @@ -162,6 +185,20 @@ namespace Wox.Plugin.Folder { Log.Exception($"|Wox.Plugin.Folder.ContextMenu|{message}", e); } + + private bool CanRunAsDifferentUser(string path) + { + switch(Path.GetExtension(path)) + { + case ".exe": + case ".bat": + return true; + + default: + return false; + + } + } } public class SearchResult diff --git a/Plugins/Wox.Plugin.Folder/Images/user.png b/Plugins/Wox.Plugin.Folder/Images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..2d45c1ee919304a973641b387934dbd517979271 GIT binary patch literal 65650 zcmV*nKuEudP)KoEefB#uyts#u%fQWvShg+Wqc(&#Brg ze^l+-YrpDLoqKNg?e4Q|^{uMkUTgnq*WPQdy=&J#goP|*R{5P100{Wb>wy2|3E)4y z1vh`_6rhkZBgguMoQv@ggV8b+Eu$DOV=`W$e6T|Ka1}(WD9U9$K6gF1+y$aFkXi$1 z6+~-b9)a}=z(W8dFmiwu00jVC%?_NSy`II+#xaI1Sb(!TKarc0vtz zJ`a_h2lzA>jYd()+z44*}#L%E7Gah&3T1hHbkXb+{1)p_i~MjcO2SFc$y{C^ZJD z^I*LW%1+Z@=>*F0aX>vygQdUYvV4?TKZ(KUB=qF`WU#cZM_NOT^>c69*P7Ep7P4@> z3#sHco~gXkHA5VHE`y#R=kbLhseBcLJt)ilAUed83ojy-A7ot|gf91h)CgJ+!8)%D zF&I!yyotJ!MVuf5AkQN@Seu?lf4WTugF12H8X#*!+elhfPFd@9sO&gp`ID@R$7#6Z zGtm4vvg~OvjlXfhT`cPHE?EJMejefaLNl=INpW)+DYsxCm89$kc!DNsca3C z@c{sbpeJ7o&9_0R!(2}G16YABR?P69!HU>8r0R+}VXiz^=pY$j0#SVofIv2YWd;e4 zC)_1iV7~LM@-x;v+cR?UbK?4#Y-$al;-vDk0M1f2cp6$i3Z=dPmH#ckk0W1ticy@@ z+3@@Z@%TiZ18=>i?Wru}3YUfBy-H=@uRN>Nq!<$AYplzI#C$uzUk@&CgVwjRE_VaG zQcV@Gs+?4}?~#sHlql|2WgJ`c*i0HXiMM4u;>J;}N_sRx7cGw07~{Nye3daMh%Ol0AB zuOeCc&11Os>@Xj4zJ~FIBhdOTl#`c&a68IkFTg9z /// 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 42/90] 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 43/90] 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 44/90] 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 45/90] 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 46/90] 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 47/90] 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 48/90] 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 49/90] 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 50/90] 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 51/90] 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 52/90] 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 53/90] 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 54/90] 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 55/90] 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 56/90] 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 57/90] 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 58/90] 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 59/90] 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 60/90] 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 61/90] 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 62/90] 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 63/90] 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 64/90] 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 65/90] 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 66/90] 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 67/90] 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 68/90] 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 69/90] 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 70/90] 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 71/90] 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 cccec8382fec5da6834cf26ce744410406ca19a2 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 11 Jan 2020 12:16:13 +1100 Subject: [PATCH 72/90] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..6a9684e79 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,33 @@ +# .NET Desktop +# Build and run tests for .NET Desktop or Windows classic desktop solutions. +# Add steps that publish symbols, save build artifacts, and more: +# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net + +trigger: +- master + +pool: + vmImage: 'windows-latest' + +variables: + solution: '**/*.sln' + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + +steps: +- task: NuGetToolInstaller@1 + +- task: NuGetCommand@2 + inputs: + restoreSolution: '$(solution)' + +- task: VSBuild@1 + inputs: + solution: '$(solution)' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: VSTest@2 + inputs: + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' From 10036b148c608086b2640ff52d19e252bc45a92b Mon Sep 17 00:00:00 2001 From: theClueless <14300910+theClueless@users.noreply.github.com> Date: Sun, 12 Jan 2020 12:50:03 +0200 Subject: [PATCH 73/90] Update azure-pipelines.yml updated to work on both dev and master and update pool to work --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6a9684e79..32aa5f677 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,9 +5,10 @@ trigger: - master +- dev pool: - vmImage: 'windows-latest' + vmImage: 'vs2017-win2016' #'windows-latest' variables: solution: '**/*.sln' From 6552aeaa3ba0a4d8456ded17e15590d0e7cefe7e Mon Sep 17 00:00:00 2001 From: theClueless <14300910+theClueless@users.noreply.github.com> Date: Sun, 12 Jan 2020 13:06:08 +0200 Subject: [PATCH 74/90] Update README.md update to show the azure devops pipeline badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52a158d89..0f4da16d9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ WoX [![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://ci.appveyor.com/api/projects/status/bfktntbivg32e103/branch/master?svg=true)](https://ci.appveyor.com/project/happlebao/wox/branch/master) +[![Build Status](https://dev.azure.com/IceCreamSandwichInc/Wox/_apis/build/status/jjw24.Wox?branchName=master)](https://dev.azure.com/IceCreamSandwichInc/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) [![RamenBless](https://cdn.rawgit.com/LunaGao/BlessYourCodeTag/master/tags/ramen.svg)](https://github.com/LunaGao/BlessYourCodeTag) From 2dd10130753b5a34e58c40dd146f695a27fee40f Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 13 Jan 2020 06:50:39 +1100 Subject: [PATCH 75/90] Update to new org name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f4da16d9..d0659d0fa 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ WoX [![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/IceCreamSandwichInc/Wox/_apis/build/status/jjw24.Wox?branchName=master)](https://dev.azure.com/IceCreamSandwichInc/Wox/_build/latest?definitionId=1&branchName=master) +[![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) [![RamenBless](https://cdn.rawgit.com/LunaGao/BlessYourCodeTag/master/tags/ramen.svg)](https://github.com/LunaGao/BlessYourCodeTag) From ed01a46a3f21f59e8d6e6c64de2f721812d15d65 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 13 Jan 2020 07:45:43 +1100 Subject: [PATCH 76/90] 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 77/90] 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 78/90] 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 79/90] 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 80/90] 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 81/90] 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 From 725df1501245550872a65d2b423e3c81944ec9c3 Mon Sep 17 00:00:00 2001 From: AT Date: Tue, 14 Jan 2020 21:10:54 +0200 Subject: [PATCH 82/90] update to package reference --- .../Wox.Plugin.BrowserBookmark.csproj | 39 ++---- Plugins/Wox.Plugin.BrowserBookmark/app.config | 9 +- .../packages.config | 9 -- .../Wox.Plugin.Calculator.csproj | 12 +- Plugins/Wox.Plugin.Calculator/packages.config | 6 - .../Wox.Plugin.Color/Wox.Plugin.Color.csproj | 9 +- Plugins/Wox.Plugin.Color/packages.config | 5 - .../Wox.Plugin.ControlPanel.csproj | 9 +- .../Wox.Plugin.ControlPanel/packages.config | 5 - .../Wox.Plugin.Everything.csproj | 12 +- Plugins/Wox.Plugin.Everything/packages.config | 6 - .../Wox.Plugin.Folder.csproj | 12 +- Plugins/Wox.Plugin.Folder/packages.config | 6 - .../Wox.Plugin.PluginIndicator.csproj | 13 +- .../packages.config | 5 - .../Wox.Plugin.PluginManagement.csproj | 20 +-- .../packages.config | 6 - .../Wox.Plugin.Program.csproj | 36 +++--- Plugins/Wox.Plugin.Program/packages.config | 8 -- .../Wox.Plugin.Shell/Wox.Plugin.Shell.csproj | 15 ++- Plugins/Wox.Plugin.Shell/packages.config | 7 - Plugins/Wox.Plugin.Sys/Wox.Plugin.Sys.csproj | 9 +- Plugins/Wox.Plugin.Sys/packages.config | 5 - Plugins/Wox.Plugin.Url/Wox.Plugin.Url.csproj | 9 +- Plugins/Wox.Plugin.Url/packages.config | 5 - .../Wox.Plugin.WebSearch.csproj | 20 +-- Plugins/Wox.Plugin.WebSearch/packages.config | 6 - Wox.Core/Wox.Core.csproj | 91 ++++--------- Wox.Core/packages.config | 13 -- Wox.Infrastructure/Wox.Infrastructure.csproj | 39 +++--- Wox.Infrastructure/packages.config | 9 -- Wox.Plugin/BaseModel.cs | 2 - Wox.Plugin/PluginMetadata.cs | 2 +- Wox.Plugin/Wox.Plugin.csproj | 45 ++++--- Wox.Plugin/packages.config | 8 -- Wox.Test/Wox.Test.csproj | 26 ++-- Wox.Test/packages.config | 6 - Wox/Wox.csproj | 120 ++++++------------ Wox/packages.config | 18 --- 39 files changed, 261 insertions(+), 421 deletions(-) delete mode 100644 Plugins/Wox.Plugin.BrowserBookmark/packages.config delete mode 100644 Plugins/Wox.Plugin.Calculator/packages.config delete mode 100644 Plugins/Wox.Plugin.Color/packages.config delete mode 100644 Plugins/Wox.Plugin.ControlPanel/packages.config delete mode 100644 Plugins/Wox.Plugin.Everything/packages.config delete mode 100644 Plugins/Wox.Plugin.Folder/packages.config delete mode 100644 Plugins/Wox.Plugin.PluginIndicator/packages.config delete mode 100644 Plugins/Wox.Plugin.PluginManagement/packages.config delete mode 100644 Plugins/Wox.Plugin.Program/packages.config delete mode 100644 Plugins/Wox.Plugin.Shell/packages.config delete mode 100644 Plugins/Wox.Plugin.Sys/packages.config delete mode 100644 Plugins/Wox.Plugin.Url/packages.config delete mode 100644 Plugins/Wox.Plugin.WebSearch/packages.config delete mode 100644 Wox.Core/packages.config delete mode 100644 Wox.Infrastructure/packages.config delete mode 100644 Wox.Plugin/packages.config delete mode 100644 Wox.Test/packages.config delete mode 100644 Wox/packages.config diff --git a/Plugins/Wox.Plugin.BrowserBookmark/Wox.Plugin.BrowserBookmark.csproj b/Plugins/Wox.Plugin.BrowserBookmark/Wox.Plugin.BrowserBookmark.csproj index b7d0690ab..413b0aec7 100644 --- a/Plugins/Wox.Plugin.BrowserBookmark/Wox.Plugin.BrowserBookmark.csproj +++ b/Plugins/Wox.Plugin.BrowserBookmark/Wox.Plugin.BrowserBookmark.csproj @@ -37,32 +37,13 @@ false - - ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll - - - ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll - - - ..\..\packages\System.Data.SQLite.Core.1.0.111.0\lib\net451\System.Data.SQLite.dll - - - ..\..\packages\System.Data.SQLite.EF6.1.0.111.0\lib\net451\System.Data.SQLite.EF6.dll - - - ..\..\packages\System.Data.SQLite.Linq.1.0.111.0\lib\net451\System.Data.SQLite.Linq.dll - - - ..\..\packages\UnidecodeSharp.1.0.0.0\lib\net35\UnidecodeSharp.dll - True - @@ -79,7 +60,6 @@ - PreserveNewest @@ -123,16 +103,19 @@ Designer - + + + 1.0.111 + + + 1.0.111 + + + 1.0.0 + + - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - +
@@ -18,10 +18,5 @@ - - - - - - + \ No newline at end of file diff --git a/Plugins/Wox.Plugin.BrowserBookmark/packages.config b/Plugins/Wox.Plugin.BrowserBookmark/packages.config deleted file mode 100644 index fc01b224e..000000000 --- a/Plugins/Wox.Plugin.BrowserBookmark/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj b/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj index cf7ce1241..b0a091934 100644 --- a/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj +++ b/Plugins/Wox.Plugin.Calculator/Wox.Plugin.Calculator.csproj @@ -56,7 +56,6 @@ - PreserveNewest @@ -118,6 +117,17 @@ PreserveNewest + + + 10.3.0 + + + 1.5.0 + + + 4.0.0 + + diff --git a/Plugins/Wox.Plugin.Calculator/packages.config b/Plugins/Wox.Plugin.Calculator/packages.config deleted file mode 100644 index c17f6726f..000000000 --- a/Plugins/Wox.Plugin.Calculator/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Plugins/Wox.Plugin.Color/Wox.Plugin.Color.csproj b/Plugins/Wox.Plugin.Color/Wox.Plugin.Color.csproj index f5b1ff160..ca75ed8f3 100644 --- a/Plugins/Wox.Plugin.Color/Wox.Plugin.Color.csproj +++ b/Plugins/Wox.Plugin.Color/Wox.Plugin.Color.csproj @@ -55,7 +55,6 @@ - PreserveNewest @@ -112,6 +111,14 @@ PreserveNewest + + + 10.3.0 + + + 4.0.0 + +

PbOc!t+-8s$(Xa?|2G$~zC zSIk`Ps#m{j>y)3A*}2HP-T4bM7m>zl3mOCsWa~t97TMq!m_Ls!|1*&K6b(n8-B9}U zsg-fD@q>HAhqjO_QWlQ)3YL8zdLByih)TT%#zB;mqs-+?0XmAZI0Du~=2B7PMC>90 z>t7{S548ox=ET#J!+U9yYdxZRz+QQEJB1ptY2ax#;po|^2h7aHUUK}anGj}5VBX&* znyx}{0s*8nNR5#VJ_qopNo9WqHTX1uCpl9m%N^PJvA0|oeXa|+{AJ;IuO#`j#_;$C z_Iz_c^71v(K49%;*5WdGogfb00%y=<7HS6o8 zFyj+Ln{hpxK5f%rGU}a;3p4q?+nH`Kj(0l7!ZtS!n3+IC)%s&nC!w-ONo9WpmHh?O z;IpLgS*HB_vxk?8jlE02(Ut`v3%RUh;drkQIq>1<6i>=k0v)cV5XzT=%X_&j_Co6w zKo3!Wc;Glq_>L@s!E?lbh!jI7Eu%~Ep3Dsl+w$}EE+Lods(is`oVw4#{nzTDwi)Ih znaI$98bhhGQ28-Z*(Z=?e@04uN)sNXJUji=k6f>@kjq{cj`wnuUwRq+&Ax@-SGhHcPy?b^*5J zk&LteI|OcAs+gN($SDSv?xE7FG%C#KNt=!y!}_rnn*wv(SJbA(nu*ydHe<`vX5vzJ zR!+nVe+oCm8vAg$#ITf}>@;d8ybhI}q%8kDDEm_o{w*>*$tr)2cMR5_e)DyUNcb|6 zh2y7;&1ckouZ*VkUVsJyK)xh#1?_ zOv0vvweaku-`8eeE}_03?{>#3iRIAECIe7_Qkt^tQ7HNZX!!3bAASN^{^%sn&K`T) zN`n7Fwv#Lz?`0qd9yu{YF}@9;*FsOemCMNy=wdgRhc=aob4BzihQHl0DM9QU(Ticm z$+X#=m1i55nBMGMqI0!T79Hkzz5LtntSvurPBbQ!KZk7aIb?%B2J1hdZ19CIy<_(n z3)$APaJ<`1K2+jHKCAL_eVH`xCB|(G{t*d;RL)d!W!5J z*?26r+99;h%{fauLNDMK;VD;4RJl{KRwj8x@7<=Cy!vu+KFkxz8{1-ib9}xz&P9Gn z7t@2sMUFH=E&wT2P(C<;Z14#{eHcVvfKpEvS-yVk?TcxM?IsJyyPf3!*0}NWXGi(Q z`XS=-ew33}g86PPCx^hgigxN3L9#&-7?AU?q6t^C2zBHn*Q*A4wU%i8=Hu8<57ygG zalA2yVP_@8i>=5?0;6GgHuq)Mei`cSy|bGpT%6pUi+5{{uJ3q_?U)0d#>Ltc#HOT= zaUmiwDX47lIEX%pZ18_DsgGkgdi2XbdhH?#-bS);yxT+$ef0cLPbP(74gHH#^TOC(oN{knYQmJgm%oaq%V zi(9#zyoz-3pe~DhQ5K`PEAd@}l$e{C9v{V=QCG`Sr)wnU>&6`4<;W%Vgr;%v(@HM> z@!Ys%`FYCnKZnZykg~y_Aj`i#{UVI zle?hH6>z;lc&zosyh59#TB5w{l}^7p4qC6~5Hwb29Y9=V=g@}3i19KH!I zF_|lugl);Ci!?S<9(SHx%#D-MP}y0^2A@Jc{NG3o{y+_uzVO6DdC0^j<|xP3uO-rCDik= zE=#z!FJ|sKj4Cfj%v+*HssLptC?9^BvcYe1p8fC7>EihMJN7Q3--{p%$Ge4c=x3in zxkN)I9021!l;d}CSsX!GvG*M ziqv^qp1#De&2sU#ZwCM-DAL6cx;zR!_!-Kwe+M=E-4gl70i153xR6cABF37t?EUYb zLY`;KG*CyFi#KsOc{R$(0WgmM8Rfc?no&rl6@JiZUDG11SoG9jk-)oEufCA98SENn z8AdeO^%&jyz~4u_v1@#+IF~P!`~Y4Io12rlaS2VQJFXt-V}qG4*EYi? z)`=$TB&l`Emp+Yb@IN!*50utl_|i}8U(El`NfwTGlXBf}oxtifB0JKY_65^F6j;@Iqv-XydyUZ&XWib+S=bG+ZC*Ie$aHzSv}2Q*W;OgFnl z`K7whlrwh zO=iWD^;@B`w~*#LDIb3LI?x}TTfBUmSr!p*PucVSC$W_6TtPWG%38mji;Ztb^@(q* zghRd?Zz;Q5Pgrc~OSic$NnBxZtX^ioix`rlDqK0@cpN zCM55#MAkqLdr?|*W0?@F5Pk}&9v(=d>e8}Map#1Fxs@uKw_S{HZoJR%m9;C zRO-E8ejBO$5UKnljPg&hSosi@O>VY*=k(}70-kNXxEY(KDulpHc4Po&j}@cy)<_p;{@g(ar9m+7RzxnT;u@3 z=R!@|Fu#zao0E?j?y-T*e7O)H)g*}O z8LUmOugwlzyx<;yoz#gv>6tO!0-HV-)Fp0kFCMP0bBI};h+fQQF$N|Vet>lGGtl~h zjdFd24?Tv3EFxYUc;s1t^%^F{cd{r3ax$o}P?WDXC{;;fS7R)6pTdj%@H*%JN4j zAN(G3cJiqoyB=4wT-AZ7h5z6nC1qjyUCu@6dm{g@EYo+ z#NrzWU1}~;nsku@Z+ixvur`~sVm+mkUQgp9xkfg4ng*kP2BME?;-{Yev3*zV&kbF* zj`!wAjw>$8dr=l|<+6Ak^rT+*B4@eW;^|W7+S8h7O*ZW`#5o}52pPfBux1Zm zPuZlGQIwMhBlwst$lKAkDQ4voq(?meR5<_%E(psseckFt%cTRCu*Uh~cS&1?5s(d@ zqb&b@sO%$(@;`s_$M;;7U%Op(j(6>Ud=5KL@rW{Y6dULNDT%7L-TU3Z8ihPDEfov|J^tDa7sr+n!XG+h3# zG?$+~ca2&<`NLP&df%(d@qXZO+{nY_oaq7L@|`@saKHVr$+{?=?~}H# z?P=-5C_d~1eMdM8=F2p_d2sQMW74a}`kcA=dOf)Swz!*f@fiFfCGW3;`Fhf3#74~K ztX$%HoaCA;DE6=*S$t_*kk~#Dg>t-~i}Aw*{WfB~cle9X#`C_yWE;dlTPrty;F)}& z`2aLO%;oqEC?|W+@}rae;(_VnM;#YWLhdOp-G}$HV$-H5++p^1;R3)J{rIRH{g`vP z)XR3f5eyK^uVPqbyIQ8TSuX78$qg!|E(>!C#}3D)Em4y%CR>-NRc}l+^1*S+2EW0q z9+~9))bSs?>55+Qa78=bn|}2<(E0J5;PPPen^*2 zF-xAl!|}VDKA1W0qSq$V@@va`tGGyM>yl&VDK0Ua)20qK!)2>wZuzPFIpl+X1(p2@ z=lH9qAKpI?tJ_kpNXL8qM~-8(jv?vtFv`jQz~$tA=rZ_~2q!9lsr2F7?8Y%89yO(G zimR|qiqgnTHF)#YKG|>?>D{}~?jYJa1Cu`HklLI`myRT6dsZ&7^p}duX3FKVYUCWnFj9&*`tlH|#Fvo4{H0|pz zZZ6v7>8kAPKN6%5^&F&Y3yIq9DJ@|i`3JF3u7Sia6bGL*hnbLg4rEL-d+DuS^UDzt z3?3%AgrB_5#)UmD&5$16mOKt7|HAv6Hr&dm8tt1!Y%UjOZfwoU#m&!v@;^(2#r0l# zoY3=hrrOlyDi@R2p)B4*;JcPVLtKG!#W~)aA33HJ;|R*)CsB-F19s4`owW0>GR^YBe}FV&6HliH`3`$w;9tD7rdS<{-g1}TSCxVxmcUtj5d34F|(7+ zC9(%;%-184z|+GNJ+brG=D3&|Kwh~C%z1uhr?QTEl+cUVoXUkua4~)jm6M+&<|79_ zc%;IU!G>OK^cf1)QB?q#i2^rx{AUh6W{+>A^c@-bpV`h=Dok9LtM z>6=_4*;&kQMDx(w3Y+ue(k5-sbAinXBc*Au;}et*-(4#Gr%yk8<2a#y*~t~tlN|Wq z@jRo!y(o*H*2Vfg;8MwU6dQKBeX&SW13lWI%%mMkq@gG#A_9peqv-^fgwyp$_-!_2 zR2b-yOy}E-=*?=gP0yUqkxQq(ZR&%VK`*{B^W1GxGI2+&r_J76xKv!M--B}U(;4!6 zZ+`!=E9QqAt_a6_(+7_YiPSft*!ahKa^XJcGLL&W>BUkNgPMwA=dC&lr)$3N1iIVY zq+Xl&9ZXwCP9{!RNoc52M;y%F!UMbas!DbBZTnk8tr!e+PW8Fte8$IiJ=IiVL| z@*ddyV-Y8c+5{KRjF%vB>Us4fHpON#gIT$-;ZoP_Xzx?M>PujOp0GKqUK&!5b(x1lmhcwac42MTn^s_{ zA5c&0n(GZ7T<9o4`iOzPHfNROO~tS_y{)j>U2p1-Yj!%@3cXovZiSWkoOR-&vO?vf zKgVGBV;cO~b(G`h-?1-YV|&YG+jHyy55f9w6yu-PlMDAk7r7f(#G_T~8sl4Ehk7jZ z;$n04w=-#v3T5@07PUIF9^NEmbcUmz?2OKqxa#q2TfitJZ8kUM-aXf}*umQLY8{SY z6zDOr6QypJOSFDOZ$92hr_+aet;r(Nhf9EU&~y9n7OYEtZD)PB=rY%n3-@v{{%K;q zYs8oDQnSm_@$P;9F;H0?#$@ANJU;(wux|MT*p<+w#<8Sc#L2bVY;kf?J>L`8f93B9 z&JBxjYG|~8Q7CG2PG{6PuoOcO(&gAfwkiCg;!%Teivyz zy#JAB`sTPTWY&6c$>hfOeJRhGj)2R5f@1tSutwW$bRE#hc&IH(ohH+!6#D6H#oRz> zb3Q}d1lDuLW7B$bwAm(GT4omsYmd(PT*u#?6ePMIX(4oks@RSxEcv~j&~n!>jk(_=Bj z^M3+9_B)u|tj3685u3gf61NgAx3VPtC0ntXj#wVCw5g(%*P~n##~eAHl?%f?m_Bvj z=GDd}mOjPJP1v7{)C-OiLd`!tWJ>zBQDbi|tbww)A6&kD0CiybzxxkeS#ArtY$Dzp zf9W^|JlPB8pG2|o!>r5Q;j65+coPXM)V5Y4dad1d8tl0~f2v51sTX?PZMf&L((*?K zv5`WXO_|oD_eUAixP;HA->79)E-C%CD03FuUAgz=a@jQU8H7~!9P*`q&P2aZa(?XC zyKcPHlsd`f5%E6qI0lOMkWz0!IeA01%NQc=Mpj$@dJ3yVR#0ozkA1__&fuJDzvMWp z>6G)N7R=Z4OK6&2>bwgVKaVypUGAYP7n!ClK=U1LSfA2*DK4q}`f%wZErYvOr7r3F za_Yi`b-5emuQsn zYwYN`7+SD4y{)j>K~JHa9OQEH&W!QE^79v3KBC$lvaKEO{ztx&D>XPutlx=pvY!BD z23hG}G41HYBEuu{ls7de%ZbyJ6HM7@^UTy@(w`8FC5erPX`UEe=2)cjYSOe}Xu-BZ z?_%5BmY700*$?J-D$0*u|69-Jc&x;BmF!XzNXh;Wd^uNzz8huvPr2B51v6W&;%6e{ z@1KbsP@OrjC`MhQ%;~9Gzi=WGUyEyo69x6ClcSw0Cit{sZ999l+H6bJkBd!oak$JX z-MGxp<}^JcrcF={aD6d#UFv}mtb&o(o%Uv;QSZ8JkbSvyEKhR&z-IoO7(*`rvy7VXVDuq}-2mEfjAo84Ho?VceLwR7=~XXlbmW2cTp&P}~a*=O-5 zWE^VA^l6)&xLi#b>t&S1>xl5?o!QPcUBKnY001BWNkl~l9!4H^Rg0|DeOh$Kv~IE~rA^Vp zxyh_tHkVI#E=fI_osK_Tr#e6KZIbzqoBSxE=WvmUgw53G$ayz5BbUHt^kzM*oa{$A zeps=3RaPu*tCbGh%JGuccd#zs4m~*nuyTfdvT)%Hu*AHRb+gSg^f2eg1U;G68Fw4F z&M&`v@pAcA=;A(R9>I5yY#UCWk7Q%fu!E8POY+>qq>g_rW;4aZvEM;Y^c+bn z8nJf4B9Nc$^pu`nJQNAfT1`gu!q8`aXKf*8Zv>s|=Dci9)01_UA%^gYWm;Tg&8X4S z^OtwArw91?SiM=f#IP6_=Pw9gJ!D;c3$%VKwvo&_l9`+A`-Lxo6s;1eH&U_j0bPuR z2Q)(msuoK7le}PWIhhrCez{=soFEUZL@tF`pC5y?#T>P6ue4!pvK4y$u+4MnVrgq_ zZUvXC2V+n+1m{Z+GwI*Z8mEuF>p(*5;>e|qcz^i`aF&mV=suK_H*q-uk^OSa977zF znJOUbTVzLPaDSIE2SbR3cHHAxur`}9ET}+!KO-(lXB}WIleYeHFJ~kwG7O{g7{(!& zprc6JY#$2#(6EtIx7TplriqL=7yJ%w`J8#4MYZCJvcAH+zO~i)3ZEKp___e9| z{G{XWau4(5l9p~-HdVhm*pE{r^>$tQbrCTxCwHJI9-^|mb?=6{q`vQxWq!+hk70FO zu0fY?MOnOxSue-;U9AhP1q-i-$G(k=AK`32>ErzSI{N8)oWMB1T#=akaC~}PJR6s| z^zFL+4kCuNNxzP|C3V{mOkVMrUgolR6?FOLjMUl-KC+HWC6~p zu{3_;NMS7gx($(CjKcJxWv8zU9C-?hap|JxW0ziLWn!X>t~SZ}<6ifpmxx|)HDYhm zTs*x^gwyfaA2)n(n9Y%ktzR5`-fkBHR5nTQu$#Ht{MgfzHXHt)O=i~ z0?($@tCEx}q|5Ik=5L=|IC06Am|YUbd($sIrT{+5Tt38Q(QNZbkrzUpFz;{WfX)A6 znA^N#&oPF{&y`)wKJ6IRrsrc$&H;%zVo14IvVlUv2M4RKNaPXiji@ERDO{#Sl7ZqB zkJO8c@5QIt_O-%Zz{$7uMXS(l0 z&w{YDi?sfJ6qBO>a-c3FQBZOZ2LG3GeJm(v?uPZ2WT?w?#Q5T1Xlc6a`chwl=q**2 zvIC$(N-5|o#Bmf0c*+wIWzN8*?YMJ!as*4*-gLQ4i#%rZQHsodbb+ z*Q8T+dfzac##6gNis@0UXO$QU%nfYfdgP(uO?h2<4=k{WmWnAFIyIN9uD^j!YvmUa zNoX_1C2cLL7nHk``=+?G-It9Kq~~nm5*Q1uxAmcI(uvEqTHbZ(F=hsIxtqEC9soyi zk>#Q~-kXYVTLtsmp^KvcS5pjNXT86>16l2K>vLH#nUUN$r-?GX@^+%!t}QL9&`WWf zVjL~klsH5B4hpg(3v_}G&fhJ$D;JJ)@x1BmTvF*pIYyT;Me-tHJK4+oHzIE!;??_b z*@nxzE-6L;pmUUyqhS5^1MmCtMU8rA?PPn&_3!yx?6`L42vD!%$;L;ZC#%LyO2^tJ z^VprN)xNOTX1fp8W^>j&W|eu}Q&+4rm!_wjlTB&Qvpo+kUCJZIg$c6Sn|-*!(hyKmT8gPYSk3 zE=t5Z+&Rj@bSHH25bMcmb(O@wmGpVxFS^fxV z+&SE_Gsi`ci{f~*0qp|H@8@!SkG*jg|5oC^p$=ZCIeG$X{#uOc>wPfj7e?o+o#I)UidcX(ZG{E6EIf5jPi4>Di=2%Amnk7u;1>9eQUI ziEWaL6MdR9_fJBEJue~oXI;E5**ZE4m!|$Pxwk8R?SaHnEE1T}tJ1g_JevlCXVcf4 zg^Rr%@8Qk`1GpUD1J>W44YFOh2(sml_rUv~P(-*3x_m9P7V{_sQz-z{zZU9x7v~2p z#qMMh<4Bm?HKMZqsIDN!rh~b9F3TW3OS**?zv*5h@4)T!Ol>z?y5USD4G!K=IK7A9!rb z{oX_?+c3Z0Ps7@zOW?Cr613l#-<7bC%v4kYEI#&wl&7& zt#ojI&{FQy;9zycZ1rTL4jU*Xsb`Y#b{?_+{4|aG0o* zSiN2wev9m4ZnKq3(jz6fZ~)K9#av2u)51a`v!K_2trWgmU_$nq=D0r>hV3}*{t0Z( z0219@({9+&1GwI2!PS#C4F=DquQv-9)Osc*3q6w`gdQS#F-+bA1As2pSQp<5!V!Gz zcd%8mC64#N&p(Zu&)u^E;D?yYqYSN@Vv%80EzYtODdIE2C>D%J{pD#S5OShp$Eo?I z6UQkm=6-yvO|NBynUpRg)EH*{t}}x^)n-_x?OeLpY(zMdK#$w*FTy3#S#6d}cp?78 zWBDXaM6LyT>`tT2%*Ao;v+w$ai>!BPY_eiq9A)Mo+JELHD+eAqfvu1&alEATkhOY% zb@>`-J(AIgf8;aCRW^y18Lr0r^xY+Z= zk7Rm05z&ib{yhK$Xgz|~uTfgv&yx#7Y=LZv<7L8rl;w9o7Y6`$WMxaU3I2tot8O^0 z*IvED3hTSQaOX0q8^eMPIvR`iiN>%tJs)#&4oJi?o<$Fd60BF4b233}^kMUe+l=UO zG)c(CL~mWWh#TB27pGn@`B##*V_c%TZ9RCK?U}e3J(({run5M?Kv^6>S$-F>ZvD0M zS!M2ycgOFYS76+SviugX9?5AHuKsB}aTS%;4b}eNvkq5>1zc%|zP{I>9bBeZ4NDo4Vrk;v&dw1(!~?BV7FM(K!k9 zBBlc$-j_=xze~M@Yg=P_1YLX!nC=_?!sq7hC+BW(_v6p+W?jCEb#aKnN=~f1zZYO$ zcIoE0Y5+A>r!x)DPO@_yk=2+C@DfpHc!KEeWHEo``z}sgi(zdx(`jSx=VLkb!_qd} z12s;k>J+G@_2O=3mj2MuYcR&}ExW+iX{EquAK?<)#;EtTq28h216+j{AUg4N>yn^mT9 zVW9cYh$9%7qt9OI8OrSGNt@1X2%Em%EL<8xv3uP*OhK1-faui+KYVhopAyf_@$Pxw zF{Lk@x{XWzL6k*JQpBo>u*zVuWunzb5{bx&(~YO{;c+LEYP@pv=ID6}ZkFa+9HRur z9_HT#1sbWl=#GUp?QwL8uEBx>ZjM`r#=Z2mhzoC$OOlBw$G|2d`V743fpGcLlzfuj zxg(eDup`*zhJk^y$f5CryuNuT{*)&_913!^wDsCgay)GQNw6h21dXt7Lj( zGapv~(ou4NxjD5(x8i$na!%tPklcz2J_DP(*b zbny?FwYSMc>K9{E4tbh%(}-8*v~HX8NLujbD13|(7<-tXL%L*QT}n5sdugZLKq|++ zHru&)jw?u(s!d(lbmtP(T(T+KN~!JX#70bQPtHxbSuX78xd|3n46DAJ)w@_1{}377 zy8FyCbMcvTF*3ZMM__({Ssw*BHy<)I`!Phd{2_QON=Qe=?Kaat58kHN$LUnb>-=I% zBw8?P(`)52Uo6&`!x|W|<|~Ty(GrwP)V%bLUaI$N;S()FnhzI>rRn7zr=_1>)~)&X z<+6>JaBXLd!8usp1>jXn=k#2DnRq_lyQKYKez5utH8V%TATQc;9Zdw26Zw!kkin@) z@(Z-sax!y4ss;1Gjvh(1@FjxKcWlax=EOx@4X(^vf@mn|yS*sUZ}0++MidmmqLF7D z>%}Eyb7n3bZ1+&4=ATIVuJMHRMTa^A zm#N2H#P4Khu`N618MwH?Lig+36q%Jy>U$F$!q?3 z3hOQ11F`vF7sECpWf9YJIy4Gi5*11Q!xIHou6X$A!f8nYh%2 zAgAxhxlKjAGdtcJKk&T5q&S4Kc#ySTOL;&uZCEVW<`f@S-6V)FpHCrJru)Y{rudyy3EX$2U zH@^Rgnf>R?Ut{FFu}sYOLl<`f>=AGy5o)fApZf8f0mqK(0|xAR-WV3k*#`hSCmg|M zlX$b357w67956~Ej@GQj6zJ9EZf%OA#$hlN5r!GCoGa`c5>}TKR)>VO9U0a}3M)f} zVV)sZ0Ekd%#&}ZV>{#REy2e-6HBN76oY`QU8*7{|G{z;MS-h@o;SwzuS!aR*v%1mW z%x-!SVV;2IWd))7MO}!xDRikWpJv_cEL==}Wb)%`Nh2??-WXVMSsW!r_vb2m3cz{) zDRhyU9WQaQ2U>qCxU9cpHeRicRGP{yf6=B+!J{W8cA@3-L#ax$k>Y5RW$MRRGcsa4c>XkMH96LHR8Dq~5manxPkk&MKZvkA z$gn)fu9Bbh&XL5Ve|Cs zfTW|c`%FiynY?7M}1&L?UgG}A-r zf6pduf{RmT6e+Wdi81HQ`fg~t4e$G%$MF;2sbi0*P1^iLQf~Ut$swIT^V?jkzYeUG zZP2bcb1fcy*?%3(JQR`A*JJw#;l|2sjsr5#4Y4-8t*{wM;FqC%*nV<(-sqWz;OO}_ zy^cJVzp>$zEeB7cl?&k5G0!<8VAn|Dw(AFY;P4K7``2EB{nswpIzCg4oG#7ML@>U5 zY6HLj`Lp==V;As+=O_5;x<=_mkkU&uwXnG#CxLVK@?1J|S?8Vd^^WBfgwqLNGhBpT zeMU_b$Mts{3#<BPb{T7mAJDXkO6S z7Txr4LB)yW?xab(t5{SJm%55a9ZYCnX75{%cNlS|G3GYv3!4KXSj<*BGVX_644Z9F zM>FG^p~8#zFX3CiW*Ogna2YqR4Y8C{6AfJqseCD8qhLIFa*Y4yiSzj5zdw&pJ+px` zW9>%RQb$OtY=Y-lKj4&G^s9fmV~y9JUR)vxqBa*;Cky~6UpkI#@DB#NuKV0$Z`^@C z(tE0*yzu-ADfL<&Pj<2SU_z2Z-Ak~!l!}xX;cMWu3$ji(=FUPJ6kLhfZ+8}L>>P}! zS+g9Z_pm;C&0KhpAdPWtGsazFEQ(J$1^)$+i-irJJuK$j3BW!3m+&1&R&ejZoj7pq zpq{Ud-Q4Dch!Fo2+wuVsMgzjFyGJ;1?GWF5%T9da$#eLf&z;4mj*T1c^4^l-+_dQJ z;g*FzjX`z#R4`$lk(tu=ZT9BkMDMPp1=b$}Gq{}WqC9`?DWU0OIsN-GcrZEc{RhU4lm=Omt2P% zR&orK3L=L~DJBA*XyEvn34ZlId=-E4=y`nkOo>u+@?~Bp@nVc%WrE;9{`J1cUBz%Q z`9@-)1=by-;owh@Wk0EhOP_uEM{n#r*V}y^+DHBxw4p~_s(ZNL+nt}SkwV6uI+tqx zEF2%TV6`6Gb83E_BE;gErC%b+WJdbd-!6Ky*yI+yt-_|EeTBi;IUwABXoUAXup9sR zeb?irYlj$Ss)^uIQE6MWx)oyT!7w8nSR3M}UVc6P+gI+!{f9ugBi~rEHSVrQq99s(Yx=BYdiR-R(8iKxMfbx_kw=++%*IvEk{w6^qBa1p5=~ zBA`d*hU>**%)t<}Nx_I>fT*0pu?LtqPub}!w)+SRy$wSGbIQeOt@Fj?FxH<8a=Baa zirAv$mhlbC8NTE8o%n%oT*C`?4Uws^bej#7ys3fk?SA{zLCmg6-&I#~Ssms02ZvX1 z%i0kC;xi}lyMJ>I&z;vz(E+kX+W?}r~!I2ZX zH6IRC5sn+y2|XWU;Q*vfuJwGI9I@Gzi?oS^O&{yZWtv`le&rtM;uR?PkI-XHZ|8{j zKz=)j=^&Wz0rSwltuW+Juz|1N6>3xi%s&kOG#=QwF32z=t zO%u7DqSWLxW*!?e_U=@8$31I!`%8D>@bybj5vS|wwmZhY5wQDHHM8kvitshNmhkqM z?!r6nS;LJx6=cNV)GZlE#OCtJt-Gc!HafCSde_Y}iq7-) z>EJD&oS>_8cD!`%^a#MWL6^5W*AxA%tW{1G@Y*3G<|jCElul#wb1Qr&tThi0Bk%8w zSjr}+3*l5&Ow*T}~`J z)EXnZrKj^eQB|UE+%v>a-M0((+`NpzlqmOd5dtt!3ilpd#!tO`7w*_IfI?99Y~Au> z0nyQhKL_CMVd7W)X1>ugYo3vLhtY{kJ>0XHm?l_YbHGXk*7rdd`_)IgeaP0y@$S~w z=FsKctcxS2n>FFK?4NCbWt+DfIk`XE?n@A$P&X{dKXOc#>h$%+msIBD-KSuw-v#O1 z3Dwqe0$z6W5`Ov>*W>8^9T@gpCdSOIQvxxpYx-HH8hjlhU@2F4(M`+vFJEyz?!9@4 zoNP(jddKw(;83Xf$6l~aSJ)68AZnc8)qOdNphP{7a3MzZ9)`KGyTImvp~AT=j-V{> zzCo{cdT%z}@&2{OU_4o2tzQ9Mu9+Ib%~SW{TBp&~JNt-pKT+4F5xgKvq@&Vt6%}q=R%v(~nb47UR z!Dal!%dW?Z_6~!FL!xQ6b$#xMo8GD8vTdnBZ1XJm}&!<3S)s8 zy%+}1rW0*$^?Z^X$b(&42(O_;~O=XPSd)EM{Pt-RLOv#iTvqbLzpaQHgeVty&RqmPuH zNg0GHb=>1R^cF5G^vpDdN#J2fU}(X*=yh58)zxNdS=gn(>hc|d%ex7DXzAoROp~dt zW%pm6&Y|_)(E3(8cPk@aBlfMgyLLfVuyAh%h;#D*{7NP(Scp|)a+hA-XhQOxz&(sa!@q_$)o5HB?-28HpdH4-2&E4hgi^QsDW=AV7=FapQOlyrB z2N&>%SrrdO!aGt3pV9nakO-wz^%0=4972)MC*olYltyVjj74uvvnA&Kg zJBgcPvTdX)(=bvFqhHfSl4IKmTx3)xxYRe(v@drF7n~(rK4$c)wiW8dY_ez5U5Ygu z7Z&+RjJF9^wP}4TbaB_(XHMk!}L{^4o^ADpZ zj$@A7G3@7uI0jTh+-kW8d+F_n$b5w1?_4HFm-d+4Wj{b@Q>3?LW4R=43Zv6@DG2yE zcOB2C!6NorG47zpjM4ZMzU$Tu_uqCshItlp?l2Y5mL`VDwYHnJ)mPtNDKW?t?mxT} z-*sdKOIghefT^40{bw#VHAGZ>qvw~q^ISzc^Xr|w?jCzwSm^QgSc-L}VbJ9&bosJD znJr_Av^w4!e&tJG@Exeu?&e?q8hO6&E1HSnrmp>C%0g( zp3Sl@8m}fgSVX2byVyN(001BWNkllsD7QoP73ePi(uKo$w})(zA>ALEACXD z%xs%3#k$fk<{X;tfgT*Wfd*)kR>wQgJBFn7ove%fCIh?OATU!44+f zof&xmb~EY*V;d1yy^8zwnTy4ph*@6a54#=A(_@*2V;D#E(4yBFgOqqa?m4$$ZF(W* zby-#Y@%S1cFv_#f;dXik-~G}T;D)u8Eq%$gUBsViFy{o|hLs%O{q?K(y6dY(7Ho@3 z@n`Sa4>1zg3AWxgHX(x9S%VO?ry7^n**>Fmx94`dZ1SvM zFqr(B4!H5;+}P%b9z4unZY6I)+MEX$o3XX&w6TOuU$1S+7(BhUdOE-~#|t_L)^~z==xmq)YNBpg4V)VlR#+Z;bw7^TsJcnV=6Z-F$VVfLC`GhY z;;GrpgiW)V((Tm2biqUz<+}@KB*BdG)GDOU2?7qdvX6IPj(r?8w zw7y@yjRp!2-oA=g92&WyX+mOU9j(iCsB6-??%t7Y3AdNqyuG{Z;eqTolQQI^l|Q8?WOeQijl>VTv*pm_ft5$3cTgP zmtbj-UlmgfU4>A6VDZCut>Fc0;fuyb*tXZ^LSrMbaIcP%T|JR8yyxdd#!Z++XqIM~ z<0fLQoP~*@Y&v%JVi-J|217@ieYlu&is(UbK?EbuW=wBZo30)KfPyX#K^J!}e{#cr zYK8pe`Jo*c<=)2vaG#SsC+FD3>?k(Wb8SQBA)c*#pHX4kcPZADhJksF zb@|fkzA#P>4U>*HKQ≠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 literal 0 HcmV?d00001 diff --git a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj index e0305a648..daccbbf88 100644 --- a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj +++ b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj @@ -84,6 +84,9 @@ PreserveNewest + + Always + MSBuild:Compile Designer From 233045483830a9c587d2421ab564899eab239248 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 16 Dec 2019 06:58:39 +1100 Subject: [PATCH 37/90] Fix wording and images for context menu --- Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs | 16 +++++++++------- .../Images/deletefilefolder.png | Bin 0 -> 19719 bytes Plugins/Wox.Plugin.Folder/Main.cs | 3 ++- .../Wox.Plugin.Folder/Wox.Plugin.Folder.csproj | 3 +++ 4 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 Plugins/Wox.Plugin.Folder/Images/deletefilefolder.png diff --git a/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs index 05e47dd52..130fd7007 100644 --- a/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs +++ b/Plugins/Wox.Plugin.Folder/ContextMenuLoader.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading.Tasks; using System.Windows; using Wox.Infrastructure.Logger; +using Wox.Infrastructure.Image; using Wox.Plugin.SharedCommands; namespace Wox.Plugin.Folder @@ -33,8 +34,8 @@ namespace Wox.Plugin.Folder var fileOrFolder = (record.Type == ResultType.File) ? "file" : "folder"; contextMenus.Add(new Result { - Title = "Copy Path", - SubTitle = $"Copy the path of {fileOrFolder} into the clipboard", + Title = "Copy path", + SubTitle = $"Copy the current {fileOrFolder} path to clipboard", Action = (context) => { try @@ -50,13 +51,13 @@ namespace Wox.Plugin.Folder return false; } }, - IcoPath = icoPath + IcoPath = Main.CopyImagePath }); contextMenus.Add(new Result { - Title = "Copy", - SubTitle = $"Copy the {fileOrFolder} to the clipboard", + Title = $"Copy {fileOrFolder}", + SubTitle = $"Copy the {fileOrFolder} to clipboard", Action = (context) => { try @@ -79,7 +80,8 @@ namespace Wox.Plugin.Folder if (record.Type == ResultType.File || record.Type == ResultType.Folder) contextMenus.Add(new Result { - Title = "Delete", + Title = $"Delete {fileOrFolder}", + SubTitle = $"Delete the selected {fileOrFolder}", Action = (context) => { try @@ -99,7 +101,7 @@ namespace Wox.Plugin.Folder return true; }, - IcoPath = icoPath + IcoPath = Main.DeleteFileFolderImagePath }); if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath)) diff --git a/Plugins/Wox.Plugin.Folder/Images/deletefilefolder.png b/Plugins/Wox.Plugin.Folder/Images/deletefilefolder.png new file mode 100644 index 0000000000000000000000000000000000000000..024cc92915c48cba2f706ba344cba0c8d8bcbce5 GIT binary patch literal 19719 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 a8omH#B09jfr)wU0a`eba`zl-C`2Pd0VS!Zu literal 0 HcmV?d00001 diff --git a/Plugins/Wox.Plugin.Folder/Main.cs b/Plugins/Wox.Plugin.Folder/Main.cs index 4fff0d40c..d6b4a8665 100644 --- a/Plugins/Wox.Plugin.Folder/Main.cs +++ b/Plugins/Wox.Plugin.Folder/Main.cs @@ -14,7 +14,8 @@ namespace Wox.Plugin.Folder { public const string FolderImagePath = "Images\\folder.png"; public const string FileImagePath = "Images\\file.png"; - + public const string DeleteFileFolderImagePath = "Images\\deletefilefolder.png"; + public const string CopyImagePath = "Images\\copy.png"; private static List _driverNames; private PluginInitContext _context; diff --git a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj index daccbbf88..35573c9ca 100644 --- a/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj +++ b/Plugins/Wox.Plugin.Folder/Wox.Plugin.Folder.csproj @@ -81,6 +81,9 @@ PreserveNewest + + Always + PreserveNewest From 9d031c1600a8e5b4c0e8df0b258f07669e740952 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 16 Dec 2019 21:01:45 +1100 Subject: [PATCH 38/90] Remove obsolete bool return because hiding is now delegated to MainWindow.xaml's OnDeactivated() --- Plugins/Wox.Plugin.Program/Main.cs | 7 ++----- Plugins/Wox.Plugin.Program/Programs/UWP.cs | 7 +++++-- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 11 +++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Main.cs b/Plugins/Wox.Plugin.Program/Main.cs index a8f74655f..e99298c78 100644 --- a/Plugins/Wox.Plugin.Program/Main.cs +++ b/Plugins/Wox.Plugin.Program/Main.cs @@ -191,22 +191,19 @@ namespace Wox.Plugin.Program ); } - public static bool StartProcess(Func runProcess, ProcessStartInfo info) + public static void StartProcess(Func runProcess, ProcessStartInfo info) { bool hide; try { runProcess(info); - hide = true; } catch (Exception) { var name = "Plugin: Program"; - var message = $"Can't start: {info.FileName}"; + var message = $"Unable to start: {info.FileName}"; _context.API.ShowMsg(name, message, string.Empty); - hide = false; } - return hide; } public void ReloadData() diff --git a/Plugins/Wox.Plugin.Program/Programs/UWP.cs b/Plugins/Wox.Plugin.Program/Programs/UWP.cs index 66b23eb81..35917cb31 100644 --- a/Plugins/Wox.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Wox.Plugin.Program/Programs/UWP.cs @@ -319,11 +319,14 @@ namespace Wox.Plugin.Program.Programs new Result { Title = api.GetTranslation("wox_plugin_program_open_containing_folder"), + Action = _ => { - var hide = Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location)); - return hide; + Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location)); + + return true; }, + IcoPath = "Images/folder.png" } }; diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index f204477bb..0c7ed7a45 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -64,8 +64,10 @@ namespace Wox.Plugin.Program.Programs FileName = FullPath, WorkingDirectory = ParentDirectory }; - var hide = Main.StartProcess(Process.Start, info); - return hide; + + Main.StartProcess(Process.Start, info); + + return true; } }; @@ -129,8 +131,9 @@ namespace Wox.Plugin.Program.Programs Title = api.GetTranslation("wox_plugin_program_open_containing_folder"), Action = _ => { - var hide = Main.StartProcess(Process.Start, new ProcessStartInfo(ParentDirectory)); - return hide; + Main.StartProcess(Process.Start, new ProcessStartInfo(ParentDirectory)); + + return true; }, IcoPath = "Images/folder.png" } From cb78e10a767b357cdab3346223cf844cbac86718 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 16 Dec 2019 21:03:44 +1100 Subject: [PATCH 39/90] Add threading task run for StartProcess Context menu hides immediately after user selection --- Plugins/Wox.Plugin.Program/Programs/Win32.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/Programs/Win32.cs b/Plugins/Wox.Plugin.Program/Programs/Win32.cs index 0c7ed7a45..53ac2065c 100644 --- a/Plugins/Wox.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Wox.Plugin.Program/Programs/Win32.cs @@ -121,8 +121,10 @@ namespace Wox.Plugin.Program.Programs WorkingDirectory = ParentDirectory, Verb = "runas" }; - var hide = Main.StartProcess(Process.Start, info); - return hide; + + Task.Run(() => Main.StartProcess(Process.Start, info)); + + return true; }, IcoPath = "Images/cmd.png" }, From 91e460391790bbc18dde97b170380e41f9f5360e Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Tue, 24 Dec 2019 20:16:27 +1100 Subject: [PATCH 40/90] Add usage tips --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d7ccabd40..52a158d89 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ Usage ----- - Launch: Alt+Space +- Context Menu: Ctrl+O +- Cancel/Return: Esc - Install/Uninstall plugin: type `wpm install/uninstall` - Reset: delete `%APPDATA%\Wox` - Log: `%APPDATA%\Wox\Logs` 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 41/90] 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; - } - ///