From 2dd2208f489f2d89669e31ff5382709b0d3a88ec Mon Sep 17 00:00:00 2001 From: bao-qian Date: Fri, 6 Nov 2015 18:12:46 +0000 Subject: [PATCH 01/19] Fix ui for action keywords --- Wox/SettingWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index e96720ae2f..09e269d56c 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -549,7 +549,7 @@ namespace Wox string.Format(InternationalizationManager.Instance.GetTranslation("plugin_init_time"), pair.InitTime); pluginQueryTime.Text = string.Format(InternationalizationManager.Instance.GetTranslation("plugin_query_time"), pair.AvgQueryTime); - if (pair.Metadata.ActionKeywords.Count > 0) + if (pair.Metadata.ActionKeywords.Count > 1) { pluginActionKeywordsTitle.Visibility = Visibility.Collapsed; pluginActionKeywords.Visibility = Visibility.Collapsed; From 64e020b1adc1c355ec0b35681c3f1254a18aae73 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Fri, 6 Nov 2015 19:55:48 +0000 Subject: [PATCH 02/19] Better crash report --- Wox/App.xaml.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index dc9d159461..5b239e0851 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Windows; @@ -32,9 +33,7 @@ namespace Wox Stopwatch.Debug("Startup Time", () => { base.OnStartup(e); - DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException; - AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle; - + RegisterUnhandledException(); ThreadPool.QueueUserWorkItem(o => { ImageLoader.ImageLoader.PreloadImages(); }); Window = new MainWindow(); PluginManager.Init(Window); @@ -43,6 +42,14 @@ namespace Wox } + [Conditional("DEBUG")] + private void RegisterUnhandledException() + { + // let exception throw as normal is better for Debug + DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException; + AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle; + } + public bool OnActivate(IList args) { CommandArgsFactory.Execute(args); From cae8485092821e7b6761b77ec9e6b80c89f41f51 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Fri, 6 Nov 2015 21:30:38 +0000 Subject: [PATCH 03/19] Fix UI flickering See #350 --- Wox.Plugin/Result.cs | 18 ++++-- Wox/MainWindow.xaml.cs | 48 ++++++++-------- Wox/ResultPanel.xaml.cs | 118 +++++++++++++++++++++++++--------------- 3 files changed, 110 insertions(+), 74 deletions(-) diff --git a/Wox.Plugin/Result.cs b/Wox.Plugin/Result.cs index 21998ec59c..0a36735078 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -42,12 +42,22 @@ namespace Wox.Plugin /// public string PluginDirectory { get; internal set; } - public new bool Equals(object obj) + public override bool Equals(object obj) { - if (obj == null || !(obj is Result)) return false; + Result r = obj as Result; + if (r != null) + { + return string.Equals(r.Title, Title) && string.Equals(r.SubTitle, SubTitle); + } + else + { + return false; + } + } - Result r = (Result)obj; - return r.Title == Title && r.SubTitle == SubTitle; + public override int GetHashCode() + { + return (Title?.GetHashCode() ?? 0) ^ (SubTitle?.GetHashCode() ?? 0); } public override string ToString() diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index 1792eafae7..13481cd019 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -441,36 +441,33 @@ namespace Wox private void TbQuery_OnTextChanged(object sender, TextChangedEventArgs e) { + if (ignoreTextChange) { ignoreTextChange = false; return; } - toolTip.IsOpen = false; - pnlResult.Dirty = true; - if (IsInContextMenuMode) + if (!string.IsNullOrEmpty(tbQuery.Text.Trim())) { - QueryContextMenu(); - return; - } - - queryHasReturn = false; - - Dispatcher.DelayInvoke("ClearResults", () => - { - // Delay the invocation of clear method of pnlResult, minimize the time-span between clear results and add new results. - // So this will reduce splash issues. After waiting 100ms, if there still no results added, we - // must clear the result. otherwise, it will be confused why the query changed, but the results - // didn't. - if (pnlResult.Dirty) pnlResult.Clear(); - }, TimeSpan.FromMilliseconds(100)); - Query(tbQuery.Text); - Dispatcher.DelayInvoke("ShowProgressbar", () => - { - if (!string.IsNullOrEmpty(tbQuery.Text.Trim()) && tbQuery.Text != lastQuery && !queryHasReturn) + toolTip.IsOpen = false; + if (IsInContextMenuMode) { - StartProgress(); + QueryContextMenu(); + return; } - }, TimeSpan.FromMilliseconds(150)); - //reset query history index after user start new query - ResetQueryHistoryIndex(); + + Query(tbQuery.Text); + Dispatcher.DelayInvoke("ShowProgressbar", () => + { + if (!string.IsNullOrEmpty(tbQuery.Text.Trim()) && tbQuery.Text != lastQuery && !queryHasReturn) + { + StartProgress(); + } + }, TimeSpan.FromMilliseconds(150)); + //reset query history index after user start new query + ResetQueryHistoryIndex(); + } + else + { + pnlResult.Clear(); + } } private void ResetQueryHistoryIndex() @@ -720,7 +717,6 @@ namespace Wox if (history != null) { ChangeQueryText(history.Query, true); - pnlResult.Dirty = true; var executeQueryHistoryTitle = GetTranslation("executeQuery"); var lastExecuteTime = GetTranslation("lastExecuteTime"); UpdateResultViewInternal(new List() diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index 7c44bf36bd..3115ece6ef 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -1,20 +1,27 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using System.Linq; +using System.Runtime.Remoting.Contexts; using Wox.Core.UserSettings; using Wox.Plugin; using Wox.Storage; namespace Wox { + [Synchronization] public partial class ResultPanel : UserControl { public event Action LeftMouseClickEvent; public event Action RightMouseClickEvent; public event Action ItemDropEvent; + private readonly ObservableCollection _results; //todo, for better performance, override the default linear search + private readonly object _resultsUpdateLock = new object(); protected virtual void OnRightMouseClick(Result result) { @@ -28,36 +35,69 @@ namespace Wox if (handler != null) handler(result); } - public bool Dirty { get; set; } public int MaxResultsToShow { get { return UserSettingStorage.Instance.MaxResultsToShow * 50; } } - public void AddResults(List results) + public void AddResults(List newResults) { - if (Dirty) + if (newResults != null && newResults.Count > 0) { - Dirty = false; - lbResults.Items.Clear(); - } - foreach (var result in results) - { - int position = 0; - if (IsTopMostResult(result)) + lock (_resultsUpdateLock) { - result.Score = int.MaxValue; - } - else - { - if (result.Score >= int.MaxValue) + var pluginId = newResults[0].PluginID; + var actionKeyword = newResults[0].OriginQuery.ActionKeyword; + List oldResults; + if (string.IsNullOrEmpty(actionKeyword)) { - result.Score = int.MaxValue - 1; + oldResults = _results.Where(r => r.PluginID == pluginId).ToList(); } - position = GetInsertLocation(result.Score); + else + { + oldResults = _results.ToList(); + } + // intersection of A (old results) and B (new newResults) + var intersection = oldResults.Intersect(newResults).ToList(); + + // remove result of relative complement of B in A + foreach (var result in oldResults.Except(intersection)) + { + _results.Remove(result); + } + + // update scores + foreach (var result in newResults) + { + if (IsTopMostResult(result)) + { + result.Score = int.MaxValue; + } + } + + // update index for result in intersection of A and B + foreach (var result in intersection) + { + int oldIndex = _results.IndexOf(result); + int oldScore = _results[oldIndex].Score; + if (result.Score != oldScore) + { + int newIndex = InsertIndexOf(result.Score); + if (newIndex != oldIndex) + { + _results.Move(oldIndex, newIndex); + } + } + } + + // insert result in relative complement of A in B + foreach (var result in newResults.Except(intersection)) + { + int newIndex = InsertIndexOf(result.Score); + _results.Insert(newIndex, result); + } + lbResults.Margin = lbResults.Items.Count > 0 ? new Thickness { Top = 8 } : new Thickness { Top = 0 }; + SelectFirst(); } - lbResults.Items.Insert(position, result); } - lbResults.Margin = lbResults.Items.Count > 0 ? new Thickness { Top = 8 } : new Thickness { Top = 0 }; - SelectFirst(); } private bool IsTopMostResult(Result result) @@ -65,33 +105,18 @@ namespace Wox return TopMostRecordStorage.Instance.IsTopMost(result); } - private int GetInsertLocation(int currentScore) + private int InsertIndexOf(int newScore) { - int location = lbResults.Items.Count; - if (lbResults.Items.Count == 0) return 0; - if (currentScore > ((Result)lbResults.Items[0]).Score) return 0; - - for (int index = 1; index < lbResults.Items.Count; index++) + int index = 0; + for (; index < lbResults.Items.Count; index++) { - Result next = lbResults.Items[index] as Result; - Result prev = lbResults.Items[index - 1] as Result; - if (next != null && prev != null) + Result result = lbResults.Items[index] as Result; + if (newScore > result?.Score) { - if ((currentScore >= next.Score && currentScore <= prev.Score)) - { - if (currentScore == next.Score) - { - location = index + 1; - } - else - { - location = index; - } - } + break; } } - - return location; + return index; } public void SelectNext() @@ -211,12 +236,17 @@ namespace Wox public ResultPanel() { InitializeComponent(); + _results = new ObservableCollection(); + lbResults.ItemsSource = _results; } public void Clear() { - lbResults.Items.Clear(); - lbResults.Margin = new Thickness { Top = 0 }; + lock (_resultsUpdateLock) + { + _results.Clear(); + lbResults.Margin = new Thickness { Top = 0 }; + } } private void lbResults_SelectionChanged(object sender, SelectionChangedEventArgs e) From 8662e963ac80155313d2dc1cd601cfd26201cd84 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sat, 7 Nov 2015 03:50:15 +0000 Subject: [PATCH 04/19] Misc --- .../PluginIndicator.cs | 5 +- Wox.Core/Plugin/PluginManager.cs | 48 ++++++++----------- Wox/Properties/Resources.Designer.cs | 26 +++++----- Wox/Properties/Settings.Designer.cs | 10 ++-- 4 files changed, 39 insertions(+), 50 deletions(-) diff --git a/Plugins/Wox.Plugin.PluginIndicator/PluginIndicator.cs b/Plugins/Wox.Plugin.PluginIndicator/PluginIndicator.cs index f7b2bd0314..f7f518889c 100644 --- a/Plugins/Wox.Plugin.PluginIndicator/PluginIndicator.cs +++ b/Plugins/Wox.Plugin.PluginIndicator/PluginIndicator.cs @@ -13,10 +13,9 @@ namespace Wox.Plugin.PluginIndicator public List Query(Query query) { - var results = from plugin in PluginManager.NonGlobalPlugins - select plugin.Metadata into metadata - from keyword in metadata.ActionKeywords + var results = from keyword in PluginManager.NonGlobalPlugins.Keys where keyword.StartsWith(query.Terms[0]) + let metadata = PluginManager.NonGlobalPlugins[keyword].Metadata let customizedPluginConfig = UserSettingStorage.Instance.CustomizedPluginConfigs.FirstOrDefault(o => o.ID == metadata.ID) where customizedPluginConfig == null || !customizedPluginConfig.Disabled diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index 6123508c3a..bb7a700796 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -29,8 +29,8 @@ namespace Wox.Core.Plugin public static IEnumerable AllPlugins { get; private set; } - public static IEnumerable GlobalPlugins { get; private set; } - public static IEnumerable NonGlobalPlugins { get; private set; } + public static List GlobalPlugins { get; } = new List(); + public static Dictionary NonGlobalPlugins { get; } = new Dictionary(); private static IEnumerable InstantQueryPlugins { get; set; } public static IPublicAPI API { private set; get; } @@ -104,8 +104,20 @@ namespace Wox.Core.Plugin { InstantQueryPlugins = GetPluginsForInterface(); contextMenuPlugins = GetPluginsForInterface(); - GlobalPlugins = AllPlugins.Where(p => IsGlobalPlugin(p.Metadata)); - NonGlobalPlugins = AllPlugins.Where(p => !IsGlobalPlugin(p.Metadata)); + foreach (var plugin in AllPlugins) + { + if (IsGlobalPlugin(plugin.Metadata)) + { + GlobalPlugins.Add(plugin); + } + else + { + foreach (string actionKeyword in plugin.Metadata.ActionKeywords) + { + NonGlobalPlugins[actionKeyword] = plugin; + } + } + } }); } @@ -123,7 +135,7 @@ namespace Wox.Core.Plugin var search = rawQuery; List actionParameters = terms.ToList(); if (terms.Length == 0) return null; - if (IsVailldActionKeyword(terms[0])) + if (NonGlobalPlugins.ContainsKey(terms[0])) { actionKeyword = terms[0]; actionParameters = terms.Skip(1).ToList(); @@ -143,8 +155,8 @@ namespace Wox.Core.Plugin public static void QueryForAllPlugins(Query query) { - var pluginPairs = GetPluginForActionKeyword(query.ActionKeyword) != null ? - new List { GetPluginForActionKeyword(query.ActionKeyword) } : GlobalPlugins; + var pluginPairs = NonGlobalPlugins.ContainsKey(query.ActionKeyword) ? + new List { NonGlobalPlugins[query.ActionKeyword] } : GlobalPlugins; foreach (var plugin in pluginPairs) { var customizedPluginConfig = UserSettingStorage.Instance. @@ -187,21 +199,6 @@ namespace Wox.Core.Plugin } } - /// - /// Check if a query contains valid action keyword - /// - /// - /// - private static bool IsVailldActionKeyword(string actionKeyword) - { - if (string.IsNullOrEmpty(actionKeyword) || actionKeyword == Query.GlobalPluginWildcardSign) return false; - PluginPair pair = AllPlugins.FirstOrDefault(o => o.Metadata.ActionKeywords.Contains(actionKeyword)); - if (pair == null) return false; - var customizedPluginConfig = UserSettingStorage.Instance. - CustomizedPluginConfigs.FirstOrDefault(o => o.ID == pair.Metadata.ID); - return customizedPluginConfig == null || !customizedPluginConfig.Disabled; - } - private static bool IsGlobalPlugin(PluginMetadata metadata) { return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign); @@ -225,13 +222,6 @@ namespace Wox.Core.Plugin return AllPlugins.FirstOrDefault(o => o.Metadata.ID == id); } - private static PluginPair GetPluginForActionKeyword(string actionKeyword) - { - //if a query doesn't contain a vaild action keyword, it should be a query for system plugin - if (string.IsNullOrEmpty(actionKeyword) || actionKeyword == Query.GlobalPluginWildcardSign) return null; - return NonGlobalPlugins.FirstOrDefault(o => o.Metadata.ActionKeywords.Contains(actionKeyword)); - } - public static IEnumerable GetPluginsForInterface() where T : IFeatures { return AllPlugins.Where(p => p.Plugin is T); diff --git a/Wox/Properties/Resources.Designer.cs b/Wox/Properties/Resources.Designer.cs index 7665c71436..20e0fb2257 100644 --- a/Wox/Properties/Resources.Designer.cs +++ b/Wox/Properties/Resources.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// 此代码由工具生成。 -// 运行时版本:4.0.30319.0 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// 对此文件的更改可能会导致不正确的行为,并且如果 -// 重新生成代码,这些更改将会丢失。 +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ @@ -13,12 +13,12 @@ namespace Wox.Properties { /// - /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // 此类是由 StronglyTypedResourceBuilder - // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 - // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen - // (以 /str 作为命令选项),或重新生成 VS 项目。 + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -33,7 +33,7 @@ namespace Wox.Properties { } /// - /// 返回此类使用的缓存的 ResourceManager 实例。 + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ namespace Wox.Properties { } /// - /// 使用此强类型资源类,为所有资源查找 - /// 重写当前线程的 CurrentUICulture 属性。 + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -61,7 +61,7 @@ namespace Wox.Properties { } /// - /// 查找类似于 (Icon) 的 System.Drawing.Icon 类型的本地化资源。 + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// internal static System.Drawing.Icon app { get { diff --git a/Wox/Properties/Settings.Designer.cs b/Wox/Properties/Settings.Designer.cs index eb3d4dc309..7a4226349e 100644 --- a/Wox/Properties/Settings.Designer.cs +++ b/Wox/Properties/Settings.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// 此代码由工具生成。 -// 运行时版本:4.0.30319.0 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// 对此文件的更改可能会导致不正确的行为,并且如果 -// 重新生成代码,这些更改将会丢失。 +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ @@ -12,7 +12,7 @@ namespace Wox.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); From 7d52b0cc962ead79e24dafab88655487009c6387 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sat, 7 Nov 2015 03:50:26 +0000 Subject: [PATCH 05/19] Fix exclusive for action keyword --- Wox/Helper/ListBoxItems.cs | 29 ++++++++++++++++++++++++ Wox/MainWindow.xaml.cs | 45 ++++++++++++++++++++++++++++---------- Wox/ResultPanel.xaml.cs | 32 +++++++++++++++++---------- Wox/Wox.csproj | 5 ++++- 4 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 Wox/Helper/ListBoxItems.cs diff --git a/Wox/Helper/ListBoxItems.cs b/Wox/Helper/ListBoxItems.cs new file mode 100644 index 0000000000..2a7f880bad --- /dev/null +++ b/Wox/Helper/ListBoxItems.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using Wox.Plugin; + +namespace Wox.Helper +{ + class ListBoxItems : ObservableCollection + { + public void RemoveAll(Predicate predicate) + { + CheckReentrancy(); + + List itemsToRemove = Items.Where(x => predicate(x)).ToList(); + if (itemsToRemove.Count > 0) + { + itemsToRemove.ForEach(item => Items.Remove(item)); + + OnPropertyChanged(new PropertyChangedEventArgs("Count")); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + // fuck ms http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + } +} diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index 13481cd019..61d56a032b 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -43,11 +43,11 @@ namespace Wox private readonly Storyboard progressBarStoryboard = new Storyboard(); private NotifyIcon notifyIcon; - private bool queryHasReturn; - private string lastQuery; + private bool _queryHasReturn; + private Query _lastQuery = new Query(); private ToolTip toolTip = new ToolTip(); - private bool ignoreTextChange = false; + private bool _ignoreTextChange = false; private List CurrentContextMenus = new List(); private string textBeforeEnterContextMenuMode; @@ -72,7 +72,7 @@ namespace Wox { Dispatcher.Invoke(new Action(() => { - ignoreTextChange = true; + _ignoreTextChange = true; tbQuery.Text = query; tbQuery.CaretIndex = tbQuery.Text.Length; if (selectAll) @@ -441,10 +441,10 @@ namespace Wox private void TbQuery_OnTextChanged(object sender, TextChangedEventArgs e) { - - if (ignoreTextChange) { ignoreTextChange = false; return; } - if (!string.IsNullOrEmpty(tbQuery.Text.Trim())) + if (_ignoreTextChange) { _ignoreTextChange = false; return; } + string query = tbQuery.Text.Trim(); + if (!string.IsNullOrEmpty(query)) { toolTip.IsOpen = false; if (IsInContextMenuMode) @@ -453,10 +453,10 @@ namespace Wox return; } - Query(tbQuery.Text); + Query(query); Dispatcher.DelayInvoke("ShowProgressbar", () => { - if (!string.IsNullOrEmpty(tbQuery.Text.Trim()) && tbQuery.Text != lastQuery && !queryHasReturn) + if (!string.IsNullOrEmpty(query) && query != _lastQuery.RawQuery && !_queryHasReturn) { StartProgress(); } @@ -479,7 +479,28 @@ namespace Wox var query = PluginManager.QueryInit(text); if (query != null) { - lastQuery = query.RawQuery; + // handle the exclusiveness of plugin using action keyword + string lastKeyword = _lastQuery.ActionKeyword; + string keyword = query.ActionKeyword; + if (string.IsNullOrEmpty(lastKeyword)) + { + if (!string.IsNullOrEmpty(keyword)) + { + pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword]); + } + } + else + { + if (string.IsNullOrEmpty(keyword)) + { + pnlResult.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword]); + } + else if (lastKeyword != keyword) + { + pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword]); + } + } + _lastQuery = query; PluginManager.QueryForAllPlugins(query); } StopProgress(); @@ -814,7 +835,7 @@ namespace Wox private void UpdateResultView(List list) { - queryHasReturn = true; + _queryHasReturn = true; progressBar.Dispatcher.Invoke(new Action(StopProgress)); if (list == null || list.Count == 0) return; @@ -824,7 +845,7 @@ namespace Wox { o.Score += UserSelectedRecordStorage.Instance.GetSelectedCount(o) * 5; }); - List l = list.Where(o => o.OriginQuery != null && o.OriginQuery.RawQuery == lastQuery).ToList(); + List l = list.Where(o => o.OriginQuery != null && o.OriginQuery.RawQuery == _lastQuery.RawQuery).ToList(); UpdateResultViewInternal(l); } } diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index 3115ece6ef..f3e0148fd7 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -9,6 +9,7 @@ using System.Windows.Media; using System.Linq; using System.Runtime.Remoting.Contexts; using Wox.Core.UserSettings; +using Wox.Helper; using Wox.Plugin; using Wox.Storage; @@ -20,7 +21,7 @@ namespace Wox public event Action LeftMouseClickEvent; public event Action RightMouseClickEvent; public event Action ItemDropEvent; - private readonly ObservableCollection _results; //todo, for better performance, override the default linear search + private readonly ListBoxItems _results; //todo, for better performance, override the default linear search private readonly object _resultsUpdateLock = new object(); protected virtual void OnRightMouseClick(Result result) @@ -38,6 +39,22 @@ namespace Wox public int MaxResultsToShow { get { return UserSettingStorage.Instance.MaxResultsToShow * 50; } } + internal void RemoveResultsFor(PluginPair plugin) + { + lock (_resultsUpdateLock) + { + _results.RemoveAll(r => r.PluginID == plugin.Metadata.ID); + } + } + + internal void RemoveResultsExcept(PluginPair plugin) + { + lock (_resultsUpdateLock) + { + _results.RemoveAll(r => r.PluginID != plugin.Metadata.ID); + } + } + public void AddResults(List newResults) { if (newResults != null && newResults.Count > 0) @@ -45,16 +62,7 @@ namespace Wox lock (_resultsUpdateLock) { var pluginId = newResults[0].PluginID; - var actionKeyword = newResults[0].OriginQuery.ActionKeyword; - List oldResults; - if (string.IsNullOrEmpty(actionKeyword)) - { - oldResults = _results.Where(r => r.PluginID == pluginId).ToList(); - } - else - { - oldResults = _results.ToList(); - } + var oldResults = _results.Where(r => r.PluginID == pluginId).ToList(); // intersection of A (old results) and B (new newResults) var intersection = oldResults.Intersect(newResults).ToList(); @@ -236,7 +244,7 @@ namespace Wox public ResultPanel() { InitializeComponent(); - _results = new ObservableCollection(); + _results = new ListBoxItems(); lbResults.ItemsSource = _results; } diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index ff6ac6882c..06d0cb7946 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -106,6 +106,7 @@ + @@ -280,7 +281,9 @@ ResXFileCodeGenerator Resources.Designer.cs - + + Designer + SettingsSingleFileGenerator Settings.Designer.cs From 705354a3d6617d1ad83e2499f9c3830e6b3a16fc Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sat, 7 Nov 2015 17:32:58 +0000 Subject: [PATCH 06/19] Better logger 1. Throw exception for fatal/error log when debugging 2. Write to debug output for warn/debug/info log when debugging 3. part of #355 --- .../Wox.Plugin.Program/FileChangeWatcher.cs | 4 +-- .../ProgramSources/AppPathsProgramSource.cs | 2 +- .../ProgramSources/FileSystemProgramSource.cs | 4 ++- Plugins/Wox.Plugin.Program/Programs.cs | 9 ++--- Wox.Core/Plugin/CSharpPluginLoader.cs | 12 +++---- Wox.Core/Plugin/JsonRPCPlugin.cs | 2 +- Wox.Core/Plugin/PluginConfig.cs | 33 +++++-------------- Wox.Core/Plugin/PluginManager.cs | 22 ++++++------- Wox.Core/Theme/Theme.cs | 2 +- Wox.Core/Wox.Core.csproj | 7 ---- Wox.Core/i18n/Internationalization.cs | 10 ++---- .../Exception/ExceptionFormatter.cs | 0 .../Exception/WoxException.cs | 1 - .../Exception/WoxFatalException.cs | 4 +-- .../Exception/WoxHttpException.cs | 0 .../Exception/WoxI18nException.cs | 0 .../Exception/WoxJsonRPCException.cs | 0 .../Exception/WoxPluginException.cs | 4 +-- Wox.Infrastructure/Logger/Log.cs | 21 ++++++++---- Wox.Infrastructure/Stopwatch.cs | 11 ++----- Wox.Infrastructure/Wox.Infrastructure.csproj | 8 +++++ Wox.Test/Plugins/PluginInitTest.cs | 2 +- Wox.sln | 3 ++ Wox/Helper/ErrorReporting.cs | 2 +- Wox/Helper/ListBoxItems.cs | 6 +++- Wox/MainWindow.xaml.cs | 11 +++++-- 26 files changed, 85 insertions(+), 95 deletions(-) rename {Wox.Core => Wox.Infrastructure}/Exception/ExceptionFormatter.cs (100%) rename {Wox.Core => Wox.Infrastructure}/Exception/WoxException.cs (99%) rename Wox.Core/Exception/WoxCritialException.cs => Wox.Infrastructure/Exception/WoxFatalException.cs (62%) rename {Wox.Core => Wox.Infrastructure}/Exception/WoxHttpException.cs (100%) rename {Wox.Core => Wox.Infrastructure}/Exception/WoxI18nException.cs (100%) rename {Wox.Core => Wox.Infrastructure}/Exception/WoxJsonRPCException.cs (100%) rename {Wox.Core => Wox.Infrastructure}/Exception/WoxPluginException.cs (60%) diff --git a/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs b/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs index 4d258e74fc..cb3b315aa6 100644 --- a/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs +++ b/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using System.IO; using System.Threading; - +using Wox.Infrastructure.Logger; namespace Wox.Plugin.Program { internal class FileChangeWatcher @@ -15,7 +15,7 @@ namespace Wox.Plugin.Program if (watchedPath.Contains(path)) return; if (!Directory.Exists(path)) { - Debug.WriteLine(string.Format("FileChangeWatcher: {0} doesn't exist", path)); + Log.Warn($"FileChangeWatcher: {path} doesn't exist"); return; } diff --git a/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs b/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs index 16e0961b3d..817f1e174f 100644 --- a/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs +++ b/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs @@ -57,7 +57,7 @@ namespace Wox.Plugin.Program.ProgramSources } catch (Exception e) { - Log.Error(e.StackTrace); + Log.Error(e); } } } diff --git a/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs b/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs index cb0cf7e765..f5571a18df 100644 --- a/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs +++ b/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Wox.Core.Exception; using Wox.Infrastructure.Logger; namespace Wox.Plugin.Program.ProgramSources @@ -70,7 +71,8 @@ namespace Wox.Plugin.Program.ProgramSources } catch (Exception e) { - Log.Warn(string.Format("GetAppFromDirectory failed: {0} - {1}", path, e.Message)); + var woxPluginException = new WoxPluginException("Program", $"GetAppFromDirectory failed: {path}", e); + Log.Error(woxPluginException); } } diff --git a/Plugins/Wox.Plugin.Program/Programs.cs b/Plugins/Wox.Plugin.Program/Programs.cs index 7f98198644..8ec1ea9a30 100644 --- a/Plugins/Wox.Plugin.Program/Programs.cs +++ b/Plugins/Wox.Plugin.Program/Programs.cs @@ -8,6 +8,7 @@ using System.Windows; using IWshRuntimeLibrary; using Wox.Infrastructure; using Wox.Plugin.Program.ProgramSources; +using Wox.Infrastructure.Logger; using Stopwatch = Wox.Infrastructure.Stopwatch; namespace Wox.Plugin.Program @@ -17,7 +18,7 @@ namespace Wox.Plugin.Program private static object lockObject = new object(); private static List programs = new List(); private static List sources = new List(); - private static Dictionary SourceTypes = new Dictionary() { + private static Dictionary SourceTypes = new Dictionary() { {"FileSystemProgramSource", typeof(FileSystemProgramSource)}, {"CommonStartMenuProgramSource", typeof(CommonStartMenuProgramSource)}, {"UserStartMenuProgramSource", typeof(UserStartMenuProgramSource)}, @@ -27,7 +28,7 @@ namespace Wox.Plugin.Program public List Query(Query query) { - + var fuzzyMather = FuzzyMatcher.Create(query.Search); List returnList = programs.Where(o => MatchProgram(o, fuzzyMather)).ToList(); returnList.ForEach(ScoreFilter); @@ -75,7 +76,7 @@ namespace Wox.Plugin.Program { programs = ProgramCacheStorage.Instance.Programs; }); - Debug.WriteLine($"Preload {programs.Count} programs from cache"); + Log.Info($"Preload {programs.Count} programs from cache"); Stopwatch.Debug("Program Index", IndexPrograms); } @@ -98,7 +99,7 @@ namespace Wox.Plugin.Program } sources.Clear(); - foreach(var source in programSources.Where(o => o.Enabled)) + foreach (var source in programSources.Where(o => o.Enabled)) { Type sourceClass; if (SourceTypes.TryGetValue(source.Type, out sourceClass)) diff --git a/Wox.Core/Plugin/CSharpPluginLoader.cs b/Wox.Core/Plugin/CSharpPluginLoader.cs index 7041d26c21..c5d1c78edc 100644 --- a/Wox.Core/Plugin/CSharpPluginLoader.cs +++ b/Wox.Core/Plugin/CSharpPluginLoader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Wox.Core.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; @@ -19,10 +20,10 @@ namespace Wox.Core.Plugin try { Assembly asm = Assembly.Load(AssemblyName.GetAssemblyName(metadata.ExecuteFilePath)); - List types = asm.GetTypes().Where(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin))).ToList(); + List types = asm.GetTypes().Where(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Contains(typeof(IPlugin))).ToList(); if (types.Count == 0) { - Log.Warn(string.Format("Couldn't load plugin {0}: didn't find the class that implement IPlugin", metadata.Name)); + Log.Warn($"Couldn't load plugin {metadata.Name}: didn't find the class that implement IPlugin"); continue; } @@ -39,12 +40,7 @@ namespace Wox.Core.Plugin } catch (System.Exception e) { - Log.Error(string.Format("Couldn't load plugin {0}: {1}", metadata.Name, e.Message)); -#if (DEBUG) - { - throw; - } -#endif + Log.Error(new WoxPluginException(metadata.Name, $"Couldn't load plugin", e)); } } diff --git a/Wox.Core/Plugin/JsonRPCPlugin.cs b/Wox.Core/Plugin/JsonRPCPlugin.cs index 6f65d78c93..a7d5db8cc5 100644 --- a/Wox.Core/Plugin/JsonRPCPlugin.cs +++ b/Wox.Core/Plugin/JsonRPCPlugin.cs @@ -74,7 +74,7 @@ namespace Wox.Core.Plugin } catch (System.Exception e) { - Log.Error(e.Message); + Log.Error(e); } } return null; diff --git a/Wox.Core/Plugin/PluginConfig.cs b/Wox.Core/Plugin/PluginConfig.cs index 63abe41ff4..05f72bbe64 100644 --- a/Wox.Core/Plugin/PluginConfig.cs +++ b/Wox.Core/Plugin/PluginConfig.cs @@ -47,7 +47,7 @@ namespace Wox.Core.Plugin } catch (System.Exception e) { - Log.Error(ExceptionFormatter.FormatExcpetion(e)); + Log.Fatal(e); } } PluginMetadata metadata = GetPluginMetadata(directory); @@ -63,7 +63,7 @@ namespace Wox.Core.Plugin string configPath = Path.Combine(pluginDirectory, pluginConfigName); if (!File.Exists(configPath)) { - Log.Warn(string.Format("parse plugin {0} failed: didn't find config file.", configPath)); + Log.Warn($"parse plugin {configPath} failed: didn't find config file."); return null; } @@ -77,40 +77,25 @@ namespace Wox.Core.Plugin // for plugin still use old ActionKeyword metadata.ActionKeyword = metadata.ActionKeywords?[0]; } - catch (System.Exception) + catch (System.Exception e) { - string error = string.Format("Parse plugin config {0} failed: json format is not valid", configPath); - Log.Warn(error); -#if (DEBUG) - { - throw new WoxException(error); - } -#endif + string msg = $"Parse plugin config {configPath} failed: json format is not valid"; + Log.Error(new WoxException(msg)); return null; } if (!AllowedLanguage.IsAllowed(metadata.Language)) { - string error = string.Format("Parse plugin config {0} failed: invalid language {1}", configPath, metadata.Language); - Log.Warn(error); -#if (DEBUG) - { - throw new WoxException(error); - } -#endif + string msg = $"Parse plugin config {configPath} failed: invalid language {metadata.Language}"; + Log.Error(new WoxException(msg)); return null; } if (!File.Exists(metadata.ExecuteFilePath)) { - string error = string.Format("Parse plugin config {0} failed: ExecuteFile {1} didn't exist", configPath, metadata.ExecuteFilePath); - Log.Warn(error); -#if (DEBUG) - { - throw new WoxException(error); - } -#endif + string msg = $"Parse plugin config {configPath} failed: ExecuteFile {metadata.ExecuteFilePath} didn't exist"; + Log.Error(new WoxException(msg)); return null; } diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index bb7a700796..06f10f7278 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -58,7 +58,7 @@ namespace Wox.Core.Plugin } catch (System.Exception e) { - Log.Error(e.Message); + Log.Error(e); } } } @@ -69,7 +69,7 @@ namespace Wox.Core.Plugin /// public static void Init(IPublicAPI api) { - if (api == null) throw new WoxCritialException("api is null"); + if (api == null) throw new WoxFatalException("api is null"); SetupPluginDirectories(); API = api; @@ -164,7 +164,7 @@ namespace Wox.Core.Plugin if (customizedPluginConfig != null && customizedPluginConfig.Disabled) continue; if (IsInstantQueryPlugin(plugin)) { - Stopwatch.Debug($"Instant Query for {plugin.Metadata.Name}", () => + Stopwatch.Normal($"Instant QueryForPlugin for {plugin.Metadata.Name}", () => { QueryForPlugin(plugin, query); }); @@ -173,7 +173,10 @@ namespace Wox.Core.Plugin { ThreadPool.QueueUserWorkItem(state => { - QueryForPlugin(plugin, query); + Stopwatch.Normal($"Normal QueryForPlugin for {plugin.Metadata.Name}", () => + { + QueryForPlugin(plugin, query); + }); }); } } @@ -184,7 +187,7 @@ namespace Wox.Core.Plugin try { List results = new List(); - var milliseconds = Stopwatch.Normal($"Query for {pair.Metadata.Name}", () => + var milliseconds = Stopwatch.Normal($"Plugin.Query cost for {pair.Metadata.Name}", () => { results = pair.Plugin.Query(query) ?? results; results.ForEach(o => { o.PluginID = pair.Metadata.ID; }); @@ -195,7 +198,7 @@ namespace Wox.Core.Plugin } catch (System.Exception e) { - throw new WoxPluginException(pair.Metadata.Name, e); + throw new WoxPluginException(pair.Metadata.Name, $"QueryForPlugin failed", e); } } @@ -239,12 +242,7 @@ namespace Wox.Core.Plugin } catch (System.Exception e) { - Log.Error($"Couldn't load plugin context menus {pluginPair.Metadata.Name}: {e.Message}"); -#if (DEBUG) - { - throw; - } -#endif + Log.Error(new WoxPluginException(pluginPair.Metadata.Name, $"Couldn't load plugin context menus", e)); } } diff --git a/Wox.Core/Theme/Theme.cs b/Wox.Core/Theme/Theme.cs index 114b5742c7..9d937b51b1 100644 --- a/Wox.Core/Theme/Theme.cs +++ b/Wox.Core/Theme/Theme.cs @@ -35,7 +35,7 @@ namespace Wox.Core.Theme } catch (System.Exception e) { - Log.Error(e.Message); + Log.Error(e); } } } diff --git a/Wox.Core/Wox.Core.csproj b/Wox.Core/Wox.Core.csproj index c7e8072a24..d69ae6a8d6 100644 --- a/Wox.Core/Wox.Core.csproj +++ b/Wox.Core/Wox.Core.csproj @@ -57,13 +57,6 @@ - - - - - - - diff --git a/Wox.Core/i18n/Internationalization.cs b/Wox.Core/i18n/Internationalization.cs index cc0d80e837..d94044a821 100644 --- a/Wox.Core/i18n/Internationalization.cs +++ b/Wox.Core/i18n/Internationalization.cs @@ -32,7 +32,7 @@ namespace Wox.Core.i18n } catch (System.Exception e) { - Log.Error(e.Message); + Log.Error(e); } } } @@ -122,12 +122,8 @@ namespace Wox.Core.i18n } catch (System.Exception e) { - Log.Warn("Update Plugin metadata translation failed:" + e.Message); -#if (DEBUG) - { - throw; - } -#endif + var woxPluginException = new WoxPluginException(pluginPair.Metadata.Name, "Update Plugin metadata translation failed:", e); + Log.Error(woxPluginException); } } diff --git a/Wox.Core/Exception/ExceptionFormatter.cs b/Wox.Infrastructure/Exception/ExceptionFormatter.cs similarity index 100% rename from Wox.Core/Exception/ExceptionFormatter.cs rename to Wox.Infrastructure/Exception/ExceptionFormatter.cs diff --git a/Wox.Core/Exception/WoxException.cs b/Wox.Infrastructure/Exception/WoxException.cs similarity index 99% rename from Wox.Core/Exception/WoxException.cs rename to Wox.Infrastructure/Exception/WoxException.cs index 2840e34aaa..bd23429ab6 100644 --- a/Wox.Core/Exception/WoxException.cs +++ b/Wox.Infrastructure/Exception/WoxException.cs @@ -14,7 +14,6 @@ public WoxException(string msg, System.Exception innerException) : base(msg, innerException) { - } } } diff --git a/Wox.Core/Exception/WoxCritialException.cs b/Wox.Infrastructure/Exception/WoxFatalException.cs similarity index 62% rename from Wox.Core/Exception/WoxCritialException.cs rename to Wox.Infrastructure/Exception/WoxFatalException.cs index cf840f9b19..21984f18d4 100644 --- a/Wox.Core/Exception/WoxCritialException.cs +++ b/Wox.Infrastructure/Exception/WoxFatalException.cs @@ -3,9 +3,9 @@ /// /// Represent exceptions that wox can't handle and MUST close running Wox. /// - public class WoxCritialException : WoxException + public class WoxFatalException : WoxException { - public WoxCritialException(string msg) : base(msg) + public WoxFatalException(string msg) : base(msg) { } } diff --git a/Wox.Core/Exception/WoxHttpException.cs b/Wox.Infrastructure/Exception/WoxHttpException.cs similarity index 100% rename from Wox.Core/Exception/WoxHttpException.cs rename to Wox.Infrastructure/Exception/WoxHttpException.cs diff --git a/Wox.Core/Exception/WoxI18nException.cs b/Wox.Infrastructure/Exception/WoxI18nException.cs similarity index 100% rename from Wox.Core/Exception/WoxI18nException.cs rename to Wox.Infrastructure/Exception/WoxI18nException.cs diff --git a/Wox.Core/Exception/WoxJsonRPCException.cs b/Wox.Infrastructure/Exception/WoxJsonRPCException.cs similarity index 100% rename from Wox.Core/Exception/WoxJsonRPCException.cs rename to Wox.Infrastructure/Exception/WoxJsonRPCException.cs diff --git a/Wox.Core/Exception/WoxPluginException.cs b/Wox.Infrastructure/Exception/WoxPluginException.cs similarity index 60% rename from Wox.Core/Exception/WoxPluginException.cs rename to Wox.Infrastructure/Exception/WoxPluginException.cs index e435be5cdd..bc1854f564 100644 --- a/Wox.Core/Exception/WoxPluginException.cs +++ b/Wox.Infrastructure/Exception/WoxPluginException.cs @@ -4,8 +4,8 @@ { public string PluginName { get; set; } - public WoxPluginException(string pluginName,System.Exception e) - : base(e.Message,e) + public WoxPluginException(string pluginName, string msg, System.Exception e) + : base($"{msg}: {pluginName}", e) { PluginName = pluginName; } diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index 8abaf16a4f..e03c7045bc 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -1,5 +1,6 @@ using System; using NLog; +using Wox.Core.Exception; namespace Wox.Infrastructure.Logger { @@ -7,34 +8,40 @@ namespace Wox.Infrastructure.Logger { private static NLog.Logger logger = LogManager.GetCurrentClassLogger(); - public static void Error(string msg) - { - logger.Error(msg); - } - public static void Error(Exception e) { +#if DEBUG + throw e; +#else logger.Error(e.Message + "\r\n" + e.StackTrace); +#endif } public static void Debug(string msg) { + System.Diagnostics.Debug.WriteLine($"DEBUG: {msg}"); logger.Debug(msg); } public static void Info(string msg) { + System.Diagnostics.Debug.WriteLine($"INFO: {msg}"); logger.Info(msg); } public static void Warn(string msg) { + System.Diagnostics.Debug.WriteLine($"WARN: {msg}"); logger.Warn(msg); } - public static void Fatal(string msg) + public static void Fatal(Exception e) { - logger.Fatal(msg); +#if DEBUG + throw e; +#else + logger.Fatal(ExceptionFormatter.FormatExcpetion(e)); +#endif } } } diff --git a/Wox.Infrastructure/Stopwatch.cs b/Wox.Infrastructure/Stopwatch.cs index c0d8288540..7387734d5b 100644 --- a/Wox.Infrastructure/Stopwatch.cs +++ b/Wox.Infrastructure/Stopwatch.cs @@ -18,14 +18,6 @@ namespace Wox.Infrastructure #endif } - [Conditional("DEBUG")] - private static void WriteTimeInfo(string name, long milliseconds) - { - string info = $"{name} : {milliseconds}ms"; - System.Diagnostics.Debug.WriteLine(info); - Log.Info(info); - } - /// /// This stopwatch will also appear only in Debug mode /// @@ -36,7 +28,8 @@ namespace Wox.Infrastructure action(); stopWatch.Stop(); var milliseconds = stopWatch.ElapsedMilliseconds; - WriteTimeInfo(name, milliseconds); + string info = $"{name} : {milliseconds}ms"; + Log.Debug(info); return milliseconds; } diff --git a/Wox.Infrastructure/Wox.Infrastructure.csproj b/Wox.Infrastructure/Wox.Infrastructure.csproj index e3740ee2cc..31100d3ae4 100644 --- a/Wox.Infrastructure/Wox.Infrastructure.csproj +++ b/Wox.Infrastructure/Wox.Infrastructure.csproj @@ -46,9 +46,17 @@ + + + + + + + + diff --git a/Wox.Test/Plugins/PluginInitTest.cs b/Wox.Test/Plugins/PluginInitTest.cs index 3bb5658c5e..e23fa54322 100644 --- a/Wox.Test/Plugins/PluginInitTest.cs +++ b/Wox.Test/Plugins/PluginInitTest.cs @@ -11,7 +11,7 @@ namespace Wox.Test.Plugins [Test] public void PublicAPIIsNullTest() { - Assert.Throws(typeof(WoxCritialException), () => PluginManager.Init(null)); + Assert.Throws(typeof(WoxFatalException), () => PluginManager.Init(null)); } } } diff --git a/Wox.sln b/Wox.sln index d50b7e3b37..efad41543f 100644 --- a/Wox.sln +++ b/Wox.sln @@ -62,6 +62,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Plugin.Everything", "Plugins\Wox.Plugin.Everything\Wox.Plugin.Everything.csproj", "{230AE83F-E92E-4E69-8355-426B305DA9C0}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU diff --git a/Wox/Helper/ErrorReporting.cs b/Wox/Helper/ErrorReporting.cs index e7b5a81fb9..63c6f80dc8 100644 --- a/Wox/Helper/ErrorReporting.cs +++ b/Wox/Helper/ErrorReporting.cs @@ -9,7 +9,7 @@ namespace Wox.Helper { public static void Report(Exception e) { - Log.Error(ExceptionFormatter.FormatExcpetion(e)); + Log.Fatal(e); new CrashReporter.CrashReporter(e).Show(); } diff --git a/Wox/Helper/ListBoxItems.cs b/Wox/Helper/ListBoxItems.cs index 2a7f880bad..103630d6e4 100644 --- a/Wox/Helper/ListBoxItems.cs +++ b/Wox/Helper/ListBoxItems.cs @@ -9,6 +9,7 @@ using Wox.Plugin; namespace Wox.Helper { class ListBoxItems : ObservableCollection + // todo implement custom moveItem,removeItem,insertItem { public void RemoveAll(Predicate predicate) { @@ -21,7 +22,10 @@ namespace Wox.Helper OnPropertyChanged(new PropertyChangedEventArgs("Count")); OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); - // fuck ms http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx + // fuck ms + // http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx + // http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx + // PS: don't use Reset for other data updates, it will cause UI flickering OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index 61d56a032b..71204acde8 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -32,6 +32,7 @@ using IDataObject = System.Windows.IDataObject; using KeyEventArgs = System.Windows.Input.KeyEventArgs; using MenuItem = System.Windows.Forms.MenuItem; using MessageBox = System.Windows.MessageBox; +using Stopwatch = Wox.Infrastructure.Stopwatch; using ToolTip = System.Windows.Controls.ToolTip; namespace Wox @@ -852,10 +853,14 @@ namespace Wox private void UpdateResultViewInternal(List list) { - Dispatcher.Invoke(new Action(() => + if (list != null && list.Count > 0) { - pnlResult.AddResults(list); - })); + Dispatcher.Invoke(new Action(() => + { + Stopwatch.Normal($"UI update cost for {list[0].PluginDirectory.Split('\\').Last()}", + () =>{pnlResult.AddResults(list);}); + })); + } } private Result GetTopMostContextMenu(Result result) From 9627272b57d1ce0a66cdfed6d56f79c4fcfb570a Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sat, 7 Nov 2015 20:48:18 +0000 Subject: [PATCH 07/19] Fix clearance of old results part of #350 --- Wox/MainWindow.xaml.cs | 60 ++++++++++++++++---------------- Wox/ResultPanel.xaml.cs | 73 ++++++++++++++++++--------------------- Wox/SettingWindow.xaml.cs | 2 +- 3 files changed, 65 insertions(+), 70 deletions(-) diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index 71204acde8..d842aa2fdb 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -163,7 +163,7 @@ namespace Wox o.PluginID = plugin.ID; o.OriginQuery = query; }); - UpdateResultView(results); + UpdateResultView(results, plugin, query); } public void ShowContextMenu(PluginMetadata plugin, List results) @@ -177,7 +177,7 @@ namespace Wox o.ContextMenu = null; }); pnlContextMenu.Clear(); - pnlContextMenu.AddResults(results); + pnlContextMenu.AddResults(results, plugin.ID); pnlContextMenu.Visibility = Visibility.Visible; pnlResult.Visibility = Visibility.Collapsed; } @@ -419,11 +419,12 @@ namespace Wox private void QueryContextMenu() { + var contextMenuId = "Context Menu Id"; pnlContextMenu.Clear(); var query = tbQuery.Text.ToLower(); if (string.IsNullOrEmpty(query)) { - pnlContextMenu.AddResults(CurrentContextMenus); + pnlContextMenu.AddResults(CurrentContextMenus, contextMenuId); } else { @@ -436,7 +437,7 @@ namespace Wox filterResults.Add(contextMenu); } } - pnlContextMenu.AddResults(filterResults); + pnlContextMenu.AddResults(filterResults, contextMenuId); } } @@ -445,15 +446,14 @@ namespace Wox if (_ignoreTextChange) { _ignoreTextChange = false; return; } string query = tbQuery.Text.Trim(); + toolTip.IsOpen = false; + if (IsInContextMenuMode) + { + QueryContextMenu(); + return; + } if (!string.IsNullOrEmpty(query)) { - toolTip.IsOpen = false; - if (IsInContextMenuMode) - { - QueryContextMenu(); - return; - } - Query(query); Dispatcher.DelayInvoke("ShowProgressbar", () => { @@ -738,6 +738,11 @@ namespace Wox { if (history != null) { + var historyMetadata = new PluginMetadata + { + ID = "Query history", + Name = "Query history" + }; ChangeQueryText(history.Query, true); var executeQueryHistoryTitle = GetTranslation("executeQuery"); var lastExecuteTime = GetTranslation("lastExecuteTime"); @@ -753,7 +758,7 @@ namespace Wox return false; } } - }); + }, historyMetadata); } } @@ -834,33 +839,28 @@ namespace Wox } } - private void UpdateResultView(List list) + private void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) { _queryHasReturn = true; progressBar.Dispatcher.Invoke(new Action(StopProgress)); - if (list == null || list.Count == 0) return; - if (list.Count > 0) + list.ForEach(o => { - list.ForEach(o => - { - o.Score += UserSelectedRecordStorage.Instance.GetSelectedCount(o) * 5; - }); - List l = list.Where(o => o.OriginQuery != null && o.OriginQuery.RawQuery == _lastQuery.RawQuery).ToList(); - UpdateResultViewInternal(l); + o.Score += UserSelectedRecordStorage.Instance.GetSelectedCount(o) * 5; + }); + if (originQuery.RawQuery == _lastQuery.RawQuery) + { + UpdateResultViewInternal(list, metadata); } } - private void UpdateResultViewInternal(List list) + private void UpdateResultViewInternal(List list, PluginMetadata metadata) { - if (list != null && list.Count > 0) + Dispatcher.Invoke(new Action(() => { - Dispatcher.Invoke(new Action(() => - { - Stopwatch.Normal($"UI update cost for {list[0].PluginDirectory.Split('\\').Last()}", - () =>{pnlResult.AddResults(list);}); - })); - } + Stopwatch.Normal($"UI update cost for {metadata.Name}", + () => { pnlResult.AddResults(list, metadata.ID); }); + })); } private Result GetTopMostContextMenu(Result result) @@ -908,7 +908,7 @@ namespace Wox textBeforeEnterContextMenuMode = tbQuery.Text; ChangeQueryText(""); pnlContextMenu.Clear(); - pnlContextMenu.AddResults(results); + pnlContextMenu.AddResults(results, result.PluginID); CurrentContextMenus = results; pnlContextMenu.Visibility = Visibility.Visible; pnlResult.Visibility = Visibility.Collapsed; diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index f3e0148fd7..66f9a9fea9 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -55,56 +55,51 @@ namespace Wox } } - public void AddResults(List newResults) + public void AddResults(List newResults, string resultId) { - if (newResults != null && newResults.Count > 0) + var oldResults = _results.Where(r => r.PluginID == resultId).ToList(); + // intersection of A (old results) and B (new newResults) + var intersection = oldResults.Intersect(newResults).ToList(); + lock (_resultsUpdateLock) { - lock (_resultsUpdateLock) + // remove result of relative complement of B in A + foreach (var result in oldResults.Except(intersection)) { - var pluginId = newResults[0].PluginID; - var oldResults = _results.Where(r => r.PluginID == pluginId).ToList(); - // intersection of A (old results) and B (new newResults) - var intersection = oldResults.Intersect(newResults).ToList(); + _results.Remove(result); + } - // remove result of relative complement of B in A - foreach (var result in oldResults.Except(intersection)) + // update scores + foreach (var result in newResults) + { + if (IsTopMostResult(result)) { - _results.Remove(result); + result.Score = int.MaxValue; } + } - // update scores - foreach (var result in newResults) - { - if (IsTopMostResult(result)) - { - result.Score = int.MaxValue; - } - } - - // update index for result in intersection of A and B - foreach (var result in intersection) - { - int oldIndex = _results.IndexOf(result); - int oldScore = _results[oldIndex].Score; - if (result.Score != oldScore) - { - int newIndex = InsertIndexOf(result.Score); - if (newIndex != oldIndex) - { - _results.Move(oldIndex, newIndex); - } - } - } - - // insert result in relative complement of A in B - foreach (var result in newResults.Except(intersection)) + // update index for result in intersection of A and B + foreach (var result in intersection) + { + int oldIndex = _results.IndexOf(result); + int oldScore = _results[oldIndex].Score; + if (result.Score != oldScore) { int newIndex = InsertIndexOf(result.Score); - _results.Insert(newIndex, result); + if (newIndex != oldIndex) + { + _results.Move(oldIndex, newIndex); + } } - lbResults.Margin = lbResults.Items.Count > 0 ? new Thickness { Top = 8 } : new Thickness { Top = 0 }; - SelectFirst(); } + + // insert result in relative complement of A in B + foreach (var result in newResults.Except(intersection)) + { + int newIndex = InsertIndexOf(result.Score); + _results.Insert(newIndex, result); + } + lbResults.Margin = lbResults.Items.Count > 0 ? new Thickness { Top = 8 } : new Thickness { Top = 0 }; + SelectFirst(); } } diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index 09e269d56c..5b57bb8d85 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -429,7 +429,7 @@ namespace Wox IcoPath = "Images/app.png", PluginDirectory = Path.GetDirectoryName(Application.ExecutablePath) } - }); + }, "test id"); foreach (string theme in ThemeManager.Theme.LoadAvailableThemes()) { From 2b27e84956173fc33f2f0c2ebe510fd9feb20e2e Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sun, 8 Nov 2015 01:44:28 +0000 Subject: [PATCH 08/19] Enhance result panel update The last commit f132cb54baa000a245feb5da87149960f6dbd9f5 only fix UI flickering for different icon. This commit also fix the commit for same icon. e.g. in web search plugin, although the title is different, but the icon is not changes. part of #350 --- Wox/Helper/ListBoxItems.cs | 36 +++++++++++++++++++++++++++++++++++- Wox/ResultPanel.xaml.cs | 38 +++++++++++++++++++++++--------------- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Wox/Helper/ListBoxItems.cs b/Wox/Helper/ListBoxItems.cs index 103630d6e4..5d62edf11d 100644 --- a/Wox/Helper/ListBoxItems.cs +++ b/Wox/Helper/ListBoxItems.cs @@ -9,7 +9,7 @@ using Wox.Plugin; namespace Wox.Helper { class ListBoxItems : ObservableCollection - // todo implement custom moveItem,removeItem,insertItem + // todo implement custom moveItem,removeItem,insertItem for better performance { public void RemoveAll(Predicate predicate) { @@ -18,6 +18,7 @@ namespace Wox.Helper List itemsToRemove = Items.Where(x => predicate(x)).ToList(); if (itemsToRemove.Count > 0) { + itemsToRemove.ForEach(item => Items.Remove(item)); OnPropertyChanged(new PropertyChangedEventArgs("Count")); @@ -29,5 +30,38 @@ namespace Wox.Helper OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } + + public void Update(List newItems) + { + int newCount = newItems.Count; + int oldCount = Items.Count; + int location = newCount > oldCount ? oldCount : newCount; + for (int i = 0; i < location; i++) + { + Result oldItem = Items[i]; + Result newItem = newItems[i]; + if (!Equals(oldItem, newItem)) + { + this[i] = newItem; + } + } + + if (newCount > oldCount) + { + for (int i = oldCount; i < newCount; i++) + { + Add(newItems[i]); + } + } + else + { + int removeIndex = newCount; + for (int i = newCount; i < oldCount; i++) + { + RemoveAt(removeIndex); + } + } + + } } } diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index 66f9a9fea9..24d5385c3b 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -21,7 +22,7 @@ namespace Wox public event Action LeftMouseClickEvent; public event Action RightMouseClickEvent; public event Action ItemDropEvent; - private readonly ListBoxItems _results; //todo, for better performance, override the default linear search + private readonly ListBoxItems _results; private readonly object _resultsUpdateLock = new object(); protected virtual void OnRightMouseClick(Result result) @@ -57,15 +58,16 @@ namespace Wox public void AddResults(List newResults, string resultId) { - var oldResults = _results.Where(r => r.PluginID == resultId).ToList(); - // intersection of A (old results) and B (new newResults) - var intersection = oldResults.Intersect(newResults).ToList(); lock (_resultsUpdateLock) { + var resultCopy = _results.ToList(); + var oldResults = resultCopy.Where(r => r.PluginID == resultId).ToList(); + // intersection of A (old results) and B (new newResults) + var intersection = oldResults.Intersect(newResults).ToList(); // remove result of relative complement of B in A foreach (var result in oldResults.Except(intersection)) { - _results.Remove(result); + resultCopy.Remove(result); } // update scores @@ -80,14 +82,16 @@ namespace Wox // update index for result in intersection of A and B foreach (var result in intersection) { - int oldIndex = _results.IndexOf(result); - int oldScore = _results[oldIndex].Score; + int oldIndex = resultCopy.IndexOf(result); + int oldScore = resultCopy[oldIndex].Score; if (result.Score != oldScore) { - int newIndex = InsertIndexOf(result.Score); + int newIndex = InsertIndexOf(result.Score, resultCopy); if (newIndex != oldIndex) { - _results.Move(oldIndex, newIndex); + var item = resultCopy[oldIndex]; + resultCopy.RemoveAt(oldIndex); + resultCopy.Insert(newIndex, item); } } } @@ -95,9 +99,13 @@ namespace Wox // insert result in relative complement of A in B foreach (var result in newResults.Except(intersection)) { - int newIndex = InsertIndexOf(result.Score); - _results.Insert(newIndex, result); + int newIndex = InsertIndexOf(result.Score, resultCopy); + resultCopy.Insert(newIndex, result); } + + // update UI in one run, so it can avoid UI flickering + _results.Update(resultCopy); + lbResults.Margin = lbResults.Items.Count > 0 ? new Thickness { Top = 8 } : new Thickness { Top = 0 }; SelectFirst(); } @@ -108,13 +116,13 @@ namespace Wox return TopMostRecordStorage.Instance.IsTopMost(result); } - private int InsertIndexOf(int newScore) + private int InsertIndexOf(int newScore, IList list) { int index = 0; - for (; index < lbResults.Items.Count; index++) + for (; index < list.Count; index++) { - Result result = lbResults.Items[index] as Result; - if (newScore > result?.Score) + var result = list[index]; + if (newScore > result.Score) { break; } From e928b4c9e080d0eee32bfc7cf72daea310c2861b Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sun, 8 Nov 2015 02:27:08 +0000 Subject: [PATCH 09/19] Fix web search plugin for new result panel --- Plugins/Wox.Plugin.WebSearch/EasyTimer.cs | 40 --------- .../Wox.Plugin.WebSearch/WebSearchPlugin.cs | 86 +++++++++---------- .../Wox.Plugin.WebSearch.csproj | 4 - 3 files changed, 39 insertions(+), 91 deletions(-) delete mode 100644 Plugins/Wox.Plugin.WebSearch/EasyTimer.cs diff --git a/Plugins/Wox.Plugin.WebSearch/EasyTimer.cs b/Plugins/Wox.Plugin.WebSearch/EasyTimer.cs deleted file mode 100644 index 49c31e63d3..0000000000 --- a/Plugins/Wox.Plugin.WebSearch/EasyTimer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Wox.Plugin.WebSearch -{ - public static class EasyTimer - { - public static IDisposable SetInterval(Action method, int delayInMilliseconds) - { - System.Timers.Timer timer = new System.Timers.Timer(delayInMilliseconds); - timer.Elapsed += (source, e) => - { - method(); - }; - - timer.Enabled = true; - timer.Start(); - - // Returns a stop handle which can be used for stopping - // the timer, if required - return timer as IDisposable; - } - - public static IDisposable SetTimeout(Action method, int delayInMilliseconds) - { - System.Timers.Timer timer = new System.Timers.Timer(delayInMilliseconds); - timer.Elapsed += (source, e) => - { - method(); - }; - - timer.AutoReset = false; - timer.Enabled = true; - timer.Start(); - - // Returns a stop handle which can be used for stopping - // the timer, if required - return timer as IDisposable; - } - } -} diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs index d4ee840df0..f2dba4a35f 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs @@ -4,14 +4,14 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Windows.Controls; using Wox.Plugin.WebSearch.SuggestionSources; namespace Wox.Plugin.WebSearch { public class WebSearchPlugin : IPlugin, ISettingProvider, IPluginI18n, IInstantQuery { - private PluginInitContext context; - private IDisposable suggestionTimer; + private PluginInitContext _context; public List Query(Query query) { @@ -21,71 +21,63 @@ namespace Wox.Plugin.WebSearch if (webSearch != null) { - string keyword = query.ActionKeyword; + string keyword = query.Search; string title = keyword; - string subtitle = context.API.GetTranslation("wox_plugin_websearch_search") + " " + webSearch.Title; + string subtitle = _context.API.GetTranslation("wox_plugin_websearch_search") + " " + webSearch.Title; if (string.IsNullOrEmpty(keyword)) { title = subtitle; - subtitle = null; + subtitle = string.Empty; } - context.API.PushResults(query, context.CurrentPluginMetadata, new List() + var result = new Result { - new Result() + Title = title, + SubTitle = subtitle, + Score = 6, + IcoPath = webSearch.IconPath, + Action = c => { - Title = title, - SubTitle = subtitle, - Score = 6, - IcoPath = webSearch.IconPath, - Action = (c) => - { - Process.Start(webSearch.Url.Replace("{q}", Uri.EscapeDataString(keyword))); - return true; - } + Process.Start(webSearch.Url.Replace("{q}", Uri.EscapeDataString(keyword ?? string.Empty))); + return true; } - }); + }; + results.Add(result); if (WebSearchStorage.Instance.EnableWebSearchSuggestion && !string.IsNullOrEmpty(keyword)) { - if (suggestionTimer != null) - { - suggestionTimer.Dispose(); - } - suggestionTimer = EasyTimer.SetTimeout(() => { QuerySuggestions(keyword, query, subtitle, webSearch); }, 350); + // todo use Task.Wait when .net upgraded + results.AddRange(ResultsFromSuggestions(keyword, subtitle, webSearch)); } } - return results; } - private void QuerySuggestions(string keyword, Query query, string subtitle, WebSearch webSearch) + private IEnumerable ResultsFromSuggestions(string keyword, string subtitle, WebSearch webSearch) { - ISuggestionSource sugg = SuggestionSourceFactory.GetSuggestionSource(WebSearchStorage.Instance.WebSearchSuggestionSource, context); - if (sugg != null) + ISuggestionSource sugg = SuggestionSourceFactory.GetSuggestionSource(WebSearchStorage.Instance.WebSearchSuggestionSource, _context); + var suggestions = sugg?.GetSuggestions(keyword); + if (suggestions != null) { - var result = sugg.GetSuggestions(keyword); - if (result != null) + var resultsFromSuggestion = suggestions.Select(o => new Result { - context.API.PushResults(query, context.CurrentPluginMetadata, - result.Select(o => new Result() - { - Title = o, - SubTitle = subtitle, - Score = 5, - IcoPath = webSearch.IconPath, - Action = (c) => - { - Process.Start(webSearch.Url.Replace("{q}", Uri.EscapeDataString(o))); - return true; - } - }).ToList()); - } + Title = o, + SubTitle = subtitle, + Score = 5, + IcoPath = webSearch.IconPath, + Action = c => + { + Process.Start(webSearch.Url.Replace("{q}", Uri.EscapeDataString(o))); + return true; + } + }); + return resultsFromSuggestion; } + return new List(); } public void Init(PluginInitContext context) { - this.context = context; + _context = context; if (WebSearchStorage.Instance.WebSearches == null) WebSearchStorage.Instance.WebSearches = WebSearchStorage.Instance.LoadDefaultWebSearches(); @@ -93,9 +85,9 @@ namespace Wox.Plugin.WebSearch #region ISettingProvider Members - public System.Windows.Controls.Control CreateSettingPanel() + public Control CreateSettingPanel() { - return new WebSearchesSetting(context); + return new WebSearchesSetting(_context); } #endregion @@ -107,12 +99,12 @@ namespace Wox.Plugin.WebSearch public string GetTranslatedPluginTitle() { - return context.API.GetTranslation("wox_plugin_websearch_plugin_name"); + return _context.API.GetTranslation("wox_plugin_websearch_plugin_name"); } public string GetTranslatedPluginDescription() { - return context.API.GetTranslation("wox_plugin_websearch_plugin_description"); + return _context.API.GetTranslation("wox_plugin_websearch_plugin_description"); } public bool IsInstantQuery(string query) => false; diff --git a/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj b/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj index 32a1b26e4a..a407ad508e 100644 --- a/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj +++ b/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj @@ -49,7 +49,6 @@ - @@ -93,9 +92,6 @@ Designer - - - {4fd29318-a8ab-4d8f-aa47-60bc241b8da3} From b33b696336c6ae7e7558be68a27a55d5198b7772 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sun, 8 Nov 2015 02:27:13 +0000 Subject: [PATCH 10/19] Misc --- Wox/Wox.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index 06d0cb7946..7d46e6109a 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -308,9 +308,6 @@ Wox.Plugin - - - From d946e18fa2d327d96889ffb1918a70eaf8fcba16 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sun, 8 Nov 2015 02:27:37 +0000 Subject: [PATCH 11/19] Fix CMD plugin for new result panel --- Plugins/Wox.Plugin.CMD/CMD.cs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Plugins/Wox.Plugin.CMD/CMD.cs b/Plugins/Wox.Plugin.CMD/CMD.cs index 0cd255bc37..7b100dc6bb 100644 --- a/Plugins/Wox.Plugin.CMD/CMD.cs +++ b/Plugins/Wox.Plugin.CMD/CMD.cs @@ -21,22 +21,17 @@ namespace Wox.Plugin.CMD public List Query(Query query) { List results = new List(); - List pushedResults = new List(); string cmd = query.Search; if (string.IsNullOrEmpty(cmd)) { - return GetAllHistoryCmds(); + return ResultsFromlHistory(); } else { var queryCmd = GetCurrentCmd(cmd); - context.API.PushResults(query, context.CurrentPluginMetadata, new List() { queryCmd }); - pushedResults.Add(queryCmd); - + results.Add(queryCmd); var history = GetHistoryCmds(cmd, queryCmd); - context.API.PushResults(query, context.CurrentPluginMetadata, history); - pushedResults.AddRange(history); - + results.AddRange(history); try { @@ -57,7 +52,11 @@ namespace Wox.Plugin.CMD if (basedir != null) { - List autocomplete = Directory.GetFileSystemEntries(basedir).Select(o => dir + Path.GetFileName(o)).Where(o => o.StartsWith(cmd, StringComparison.OrdinalIgnoreCase) && !results.Any(p => o.Equals(p.Title, StringComparison.OrdinalIgnoreCase)) && !pushedResults.Any(p => o.Equals(p.Title, StringComparison.OrdinalIgnoreCase))).ToList(); + var autocomplete = Directory.GetFileSystemEntries(basedir). + Select(o => dir + Path.GetFileName(o)). + Where(o => o.StartsWith(cmd, StringComparison.OrdinalIgnoreCase) && + !results.Any(p => o.Equals(p.Title, StringComparison.OrdinalIgnoreCase)) && + !results.Any(p => o.Equals(p.Title, StringComparison.OrdinalIgnoreCase))).ToList(); autocomplete.Sort(); results.AddRange(autocomplete.ConvertAll(m => new Result() { @@ -94,7 +93,7 @@ namespace Wox.Plugin.CMD var ret = new Result { Title = m.Key, - SubTitle = string.Format(context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = string.Format(context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value), IcoPath = "Images/cmd.png", Action = (c) => { @@ -125,13 +124,13 @@ namespace Wox.Plugin.CMD return result; } - private List GetAllHistoryCmds() + private List ResultsFromlHistory() { IEnumerable history = CMDStorage.Instance.CMDHistory.OrderByDescending(o => o.Value) .Select(m => new Result { Title = m.Key, - SubTitle = string.Format(context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value), + SubTitle = string.Format(context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value), IcoPath = "Images/cmd.png", Action = (c) => { From e3cdfe27d18a82ab5195f3e5b11344f27afa59f1 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sun, 8 Nov 2015 02:50:33 +0000 Subject: [PATCH 12/19] Fix query history for the new result panel --- Wox/MainWindow.xaml.cs | 14 ++++++-------- Wox/ResultPanel.xaml.cs | 8 ++++---- Wox/Storage/QueryHistoryStorage.cs | 6 +++++- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index d842aa2fdb..97cb97d162 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -473,6 +473,7 @@ namespace Wox private void ResetQueryHistoryIndex() { + pnlResult.RemoveResultsFor(QueryHistoryStorage.MetaData); QueryHistoryStorage.Instance.Reset(); } private void Query(string text) @@ -487,18 +488,18 @@ namespace Wox { if (!string.IsNullOrEmpty(keyword)) { - pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword]); + pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); } } else { if (string.IsNullOrEmpty(keyword)) { - pnlResult.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword]); + pnlResult.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); } else if (lastKeyword != keyword) { - pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword]); + pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); } } _lastQuery = query; @@ -738,14 +739,11 @@ namespace Wox { if (history != null) { - var historyMetadata = new PluginMetadata - { - ID = "Query history", - Name = "Query history" - }; + var historyMetadata = QueryHistoryStorage.MetaData; ChangeQueryText(history.Query, true); var executeQueryHistoryTitle = GetTranslation("executeQuery"); var lastExecuteTime = GetTranslation("lastExecuteTime"); + pnlResult.RemoveResultsExcept(historyMetadata); UpdateResultViewInternal(new List() { new Result(){ diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index 24d5385c3b..121ee9b852 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -40,19 +40,19 @@ namespace Wox public int MaxResultsToShow { get { return UserSettingStorage.Instance.MaxResultsToShow * 50; } } - internal void RemoveResultsFor(PluginPair plugin) + internal void RemoveResultsFor(PluginMetadata metadata) { lock (_resultsUpdateLock) { - _results.RemoveAll(r => r.PluginID == plugin.Metadata.ID); + _results.RemoveAll(r => r.PluginID == metadata.ID); } } - internal void RemoveResultsExcept(PluginPair plugin) + internal void RemoveResultsExcept(PluginMetadata metadata) { lock (_resultsUpdateLock) { - _results.RemoveAll(r => r.PluginID != plugin.Metadata.ID); + _results.RemoveAll(r => r.PluginID != metadata.ID); } } diff --git a/Wox/Storage/QueryHistoryStorage.cs b/Wox/Storage/QueryHistoryStorage.cs index d39ebaad14..81ccc353ae 100644 --- a/Wox/Storage/QueryHistoryStorage.cs +++ b/Wox/Storage/QueryHistoryStorage.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using Newtonsoft.Json; using Wox.Infrastructure.Storage; +using Wox.Plugin; namespace Wox.Storage { @@ -16,6 +17,9 @@ namespace Wox.Storage private int MaxHistory = 300; private int cursor = 0; + public static PluginMetadata MetaData { get; } = new PluginMetadata + { ID = "Query history", Name = "Query history" }; + protected override string ConfigFolder { get { return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Config"); } @@ -77,7 +81,7 @@ namespace Wox.Storage return History.OrderByDescending(o => o.ExecutedDateTime).ToList(); } } - + public class HistoryItem { public string Query { get; set; } From 543cd5af802c17e9728f3123d72eb13ce797df86 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Sun, 8 Nov 2015 03:06:34 +0000 Subject: [PATCH 13/19] Fix progress bar Bug introduced in 9962ddf2ed80a00036ebdb2965429675c4355472 --- Wox/MainWindow.xaml.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index 97cb97d162..5a289131ed 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -443,25 +443,19 @@ namespace Wox private void TbQuery_OnTextChanged(object sender, TextChangedEventArgs e) { - if (_ignoreTextChange) { _ignoreTextChange = false; return; } - string query = tbQuery.Text.Trim(); + toolTip.IsOpen = false; if (IsInContextMenuMode) { QueryContextMenu(); return; } + + string query = tbQuery.Text.Trim(); if (!string.IsNullOrEmpty(query)) { Query(query); - Dispatcher.DelayInvoke("ShowProgressbar", () => - { - if (!string.IsNullOrEmpty(query) && query != _lastQuery.RawQuery && !_queryHasReturn) - { - StartProgress(); - } - }, TimeSpan.FromMilliseconds(150)); //reset query history index after user start new query ResetQueryHistoryIndex(); } @@ -478,6 +472,7 @@ namespace Wox } private void Query(string text) { + _queryHasReturn = false; var query = PluginManager.QueryInit(text); if (query != null) { @@ -503,6 +498,13 @@ namespace Wox } } _lastQuery = query; + Dispatcher.DelayInvoke("ShowProgressbar", () => + { + if (!string.IsNullOrEmpty(query.RawQuery) && query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) + { + StartProgress(); + } + }, TimeSpan.FromMilliseconds(150)); PluginManager.QueryForAllPlugins(query); } StopProgress(); From 2be68342bb865c89b97c1bb5a17fe0953191222e Mon Sep 17 00:00:00 2001 From: qianlifeng Date: Sun, 8 Nov 2015 19:21:48 +0800 Subject: [PATCH 14/19] use caculated score for sys plugin item --- Plugins/Wox.Plugin.Sys/Sys.cs | 144 +++++++++++++++++----------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/Plugins/Wox.Plugin.Sys/Sys.cs b/Plugins/Wox.Plugin.Sys/Sys.cs index bc3db80b63..d3a0db1da6 100644 --- a/Plugins/Wox.Plugin.Sys/Sys.cs +++ b/Plugins/Wox.Plugin.Sys/Sys.cs @@ -39,8 +39,11 @@ namespace Wox.Plugin.Sys List results = new List(); foreach (Result availableResult in availableResults) { - if (StringMatcher.IsMatch(availableResult.Title, query.Search) || StringMatcher.IsMatch(availableResult.SubTitle, query.Search)) + int titleScore = StringMatcher.Match(availableResult.Title, query.Search); + int subTitleScore = StringMatcher.Match(availableResult.SubTitle, query.Search); + if (titleScore > 0 || subTitleScore > 0) { + availableResult.Score = titleScore > 0 ? titleScore : subTitleScore; results.Add(availableResult); } } @@ -55,92 +58,85 @@ namespace Wox.Plugin.Sys private void LoadCommands() { - availableResults.AddRange(new Result[] { - new Result - { - Title = "Shutdown", - SubTitle = context.API.GetTranslation("wox_plugin_sys_shutdown_computer"), - Score = 100, - IcoPath = "Images\\exit.png", - Action = (c) => - { - if (MessageBox.Show("Are you sure you want to shut the computer down?","Shutdown Computer?",MessageBoxButtons.YesNo,MessageBoxIcon.Warning) == DialogResult.Yes) { - Process.Start("shutdown", "/s /t 0"); - } - return true; - } - }, - new Result - { - Title = "Log off", + availableResults.AddRange(new Result[] { + new Result + { + Title = "Shutdown", + SubTitle = context.API.GetTranslation("wox_plugin_sys_shutdown_computer"), + IcoPath = "Images\\exit.png", + Action = (c) => + { + if (MessageBox.Show("Are you sure you want to shut the computer down?","Shutdown Computer?",MessageBoxButtons.YesNo,MessageBoxIcon.Warning) == DialogResult.Yes) { + Process.Start("shutdown", "/s /t 0"); + } + return true; + } + }, + new Result + { + Title = "Log off", SubTitle = context.API.GetTranslation("wox_plugin_sys_log_off"), - Score = 100, - IcoPath = "Images\\logoff.png", - Action = (c) => ExitWindowsEx(EWX_LOGOFF, 0) - }, - new Result - { - Title = "Lock", + IcoPath = "Images\\logoff.png", + Action = (c) => ExitWindowsEx(EWX_LOGOFF, 0) + }, + new Result + { + Title = "Lock", SubTitle = context.API.GetTranslation("wox_plugin_sys_lock"), - Score = 100, - IcoPath = "Images\\lock.png", - Action = (c) => - { - LockWorkStation(); - return true; - } - }, + IcoPath = "Images\\lock.png", + Action = (c) => + { + LockWorkStation(); + return true; + } + }, new Result { Title = "Sleep", SubTitle = context.API.GetTranslation("wox_plugin_sys_sleep"), - Score = 100, IcoPath = "Images\\sleep.png", Action = (c) => Application.SetSuspendState(PowerState.Suspend, false, false) }, new Result - { - Title = "Exit", + { + Title = "Exit", SubTitle = context.API.GetTranslation("wox_plugin_sys_exit"), - Score = 110, - IcoPath = "Images\\app.png", - Action = (c) => - { - context.API.CloseApp(); - return true; - } - }, - new Result - { - Title = "Restart Wox", + IcoPath = "Images\\app.png", + Action = (c) => + { + context.API.CloseApp(); + return true; + } + }, + new Result + { + Title = "Restart Wox", SubTitle = context.API.GetTranslation("wox_plugin_sys_restart"), - Score = 110, - IcoPath = "Images\\restart.png", - Action = (c) => - { - ProcessStartInfo Info = new ProcessStartInfo(); - Info.Arguments = "/C ping 127.0.0.1 -n 1 && \"" + Application.ExecutablePath + "\""; - Info.WindowStyle = ProcessWindowStyle.Hidden; - Info.CreateNoWindow = true; - Info.FileName = "cmd.exe"; - Process.Start(Info); - context.API.CloseApp(); - return true; - } - }, - new Result - { - Title = "Settings", + IcoPath = "Images\\restart.png", + Action = (c) => + { + ProcessStartInfo Info = new ProcessStartInfo(); + Info.Arguments = "/C ping 127.0.0.1 -n 1 && \"" + Application.ExecutablePath + "\""; + Info.WindowStyle = ProcessWindowStyle.Hidden; + Info.CreateNoWindow = true; + Info.FileName = "cmd.exe"; + Process.Start(Info); + context.API.CloseApp(); + return true; + } + }, + new Result + { + Title = "Settings", SubTitle = context.API.GetTranslation("wox_plugin_sys_setting"), - Score = 100, - IcoPath = "Images\\app.png", - Action = (c) => - { - context.API.OpenSettingDialog(); - return true; - } - } - }); + IcoPath = "Images\\app.png", + Action = (c) => + { + context.API.OpenSettingDialog(); + return true; + } + } + }); } public string GetLanguagesFolder() From 60f06f97fa83f3335e819471df3f00e8588b0d5a Mon Sep 17 00:00:00 2001 From: qianlifeng Date: Sun, 8 Nov 2015 19:31:44 +0800 Subject: [PATCH 15/19] fix a null pointer issue --- .../Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs | 2 +- Wox.sln | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs b/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs index 817f1e174f..6cec459bfc 100644 --- a/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs +++ b/Plugins/Wox.Plugin.Program/ProgramSources/AppPathsProgramSource.cs @@ -38,7 +38,7 @@ namespace Wox.Plugin.Program.ProgramSources using (var key = root.OpenSubKey(item)) { string path = key.GetValue("") as string; - if (path == null) continue; + if (string.IsNullOrEmpty(path)) continue; // fix path like this ""\"C:\\folder\\executable.exe\""" const int begin = 0; diff --git a/Wox.sln b/Wox.sln index efad41543f..d50b7e3b37 100644 --- a/Wox.sln +++ b/Wox.sln @@ -62,9 +62,6 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Plugin.Everything", "Plugins\Wox.Plugin.Everything\Wox.Plugin.Everything.csproj", "{230AE83F-E92E-4E69-8355-426B305DA9C0}" EndProject Global - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU From da5a930e89d0e0ec94655ea195dbe00c748e0091 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Mon, 9 Nov 2015 01:32:33 +0000 Subject: [PATCH 16/19] Move namespace --- .../ProgramSources/FileSystemProgramSource.cs | 2 +- Wox.Core/Plugin/CSharpPluginLoader.cs | 2 +- Wox.Core/Plugin/JsonRPCPlugin.cs | 2 +- Wox.Core/Plugin/PluginConfig.cs | 2 +- Wox.Core/Plugin/PluginManager.cs | 4 ++-- Wox.Core/Updater/SemanticVersion.cs | 2 +- Wox.Core/i18n/Internationalization.cs | 2 +- Wox.Infrastructure/Exception/ExceptionFormatter.cs | 2 +- Wox.Infrastructure/Exception/WoxException.cs | 2 +- Wox.Infrastructure/Exception/WoxFatalException.cs | 2 +- Wox.Infrastructure/Exception/WoxHttpException.cs | 2 +- Wox.Infrastructure/Exception/WoxI18nException.cs | 4 ++-- Wox.Infrastructure/Exception/WoxJsonRPCException.cs | 2 +- Wox.Infrastructure/Exception/WoxPluginException.cs | 2 +- Wox.Infrastructure/Http/HttpRequest.cs | 7 +++---- Wox.Infrastructure/Logger/Log.cs | 8 +++----- Wox.Infrastructure/Storage/BinaryStorage.cs | 6 +++--- Wox.Infrastructure/Storage/JsonStorage.cs | 2 +- Wox.Infrastructure/WindowsShellRun.cs | 4 ++-- Wox.Test/Plugins/PluginInitTest.cs | 2 +- Wox/Helper/ErrorReporting.cs | 2 +- 21 files changed, 30 insertions(+), 33 deletions(-) diff --git a/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs b/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs index f5571a18df..b266cc654f 100644 --- a/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs +++ b/Plugins/Wox.Plugin.Program/ProgramSources/FileSystemProgramSource.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Wox.Core.Exception; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; namespace Wox.Plugin.Program.ProgramSources diff --git a/Wox.Core/Plugin/CSharpPluginLoader.cs b/Wox.Core/Plugin/CSharpPluginLoader.cs index c5d1c78edc..5eb4a830e0 100644 --- a/Wox.Core/Plugin/CSharpPluginLoader.cs +++ b/Wox.Core/Plugin/CSharpPluginLoader.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Wox.Core.Exception; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; diff --git a/Wox.Core/Plugin/JsonRPCPlugin.cs b/Wox.Core/Plugin/JsonRPCPlugin.cs index a7d5db8cc5..5c08f9a8a2 100644 --- a/Wox.Core/Plugin/JsonRPCPlugin.cs +++ b/Wox.Core/Plugin/JsonRPCPlugin.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Threading; using System.Windows.Forms; using Newtonsoft.Json; -using Wox.Core.Exception; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; diff --git a/Wox.Core/Plugin/PluginConfig.cs b/Wox.Core/Plugin/PluginConfig.cs index 05f72bbe64..30bacef838 100644 --- a/Wox.Core/Plugin/PluginConfig.cs +++ b/Wox.Core/Plugin/PluginConfig.cs @@ -2,8 +2,8 @@ using System.IO; using System.Linq; using Newtonsoft.Json; -using Wox.Core.Exception; using Wox.Core.UserSettings; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index 06f10f7278..f04c5a045f 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -4,10 +4,10 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; -using Wox.Core.Exception; using Wox.Core.i18n; using Wox.Core.UI; using Wox.Core.UserSettings; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; using Stopwatch = Wox.Infrastructure.Stopwatch; @@ -30,7 +30,7 @@ namespace Wox.Core.Plugin public static IEnumerable AllPlugins { get; private set; } public static List GlobalPlugins { get; } = new List(); - public static Dictionary NonGlobalPlugins { get; } = new Dictionary(); + public static Dictionary NonGlobalPlugins { get; set; } = new Dictionary(); private static IEnumerable InstantQueryPlugins { get; set; } public static IPublicAPI API { private set; get; } diff --git a/Wox.Core/Updater/SemanticVersion.cs b/Wox.Core/Updater/SemanticVersion.cs index 693ce73eef..ed3ef7b317 100644 --- a/Wox.Core/Updater/SemanticVersion.cs +++ b/Wox.Core/Updater/SemanticVersion.cs @@ -1,5 +1,5 @@ using System; -using Wox.Core.Exception; +using Wox.Infrastructure.Exception; namespace Wox.Core.Updater { diff --git a/Wox.Core/i18n/Internationalization.cs b/Wox.Core/i18n/Internationalization.cs index d94044a821..0cfea890a3 100644 --- a/Wox.Core/i18n/Internationalization.cs +++ b/Wox.Core/i18n/Internationalization.cs @@ -4,9 +4,9 @@ using System.IO; using System.Linq; using System.Reflection; using System.Windows; -using Wox.Core.Exception; using Wox.Core.UI; using Wox.Core.UserSettings; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; diff --git a/Wox.Infrastructure/Exception/ExceptionFormatter.cs b/Wox.Infrastructure/Exception/ExceptionFormatter.cs index d4e8beb8a8..ce232d07d8 100644 --- a/Wox.Infrastructure/Exception/ExceptionFormatter.cs +++ b/Wox.Infrastructure/Exception/ExceptionFormatter.cs @@ -5,7 +5,7 @@ using System.Text; using System.Xml; using Microsoft.Win32; -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { public class ExceptionFormatter { diff --git a/Wox.Infrastructure/Exception/WoxException.cs b/Wox.Infrastructure/Exception/WoxException.cs index bd23429ab6..89a61119af 100644 --- a/Wox.Infrastructure/Exception/WoxException.cs +++ b/Wox.Infrastructure/Exception/WoxException.cs @@ -1,4 +1,4 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { /// /// Base Wox Exceptions diff --git a/Wox.Infrastructure/Exception/WoxFatalException.cs b/Wox.Infrastructure/Exception/WoxFatalException.cs index 21984f18d4..6a1da4a62b 100644 --- a/Wox.Infrastructure/Exception/WoxFatalException.cs +++ b/Wox.Infrastructure/Exception/WoxFatalException.cs @@ -1,4 +1,4 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { /// /// Represent exceptions that wox can't handle and MUST close running Wox. diff --git a/Wox.Infrastructure/Exception/WoxHttpException.cs b/Wox.Infrastructure/Exception/WoxHttpException.cs index 55e3431a0e..2626a3dc76 100644 --- a/Wox.Infrastructure/Exception/WoxHttpException.cs +++ b/Wox.Infrastructure/Exception/WoxHttpException.cs @@ -1,4 +1,4 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { public class WoxHttpException :WoxException { diff --git a/Wox.Infrastructure/Exception/WoxI18nException.cs b/Wox.Infrastructure/Exception/WoxI18nException.cs index 72b62df988..c06ad78022 100644 --- a/Wox.Infrastructure/Exception/WoxI18nException.cs +++ b/Wox.Infrastructure/Exception/WoxI18nException.cs @@ -1,6 +1,6 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { - public class WoxI18nException:WoxException + public class WoxI18nException : WoxException { public WoxI18nException(string msg) : base(msg) { diff --git a/Wox.Infrastructure/Exception/WoxJsonRPCException.cs b/Wox.Infrastructure/Exception/WoxJsonRPCException.cs index d3c0bfb0da..88bb3f5ed5 100644 --- a/Wox.Infrastructure/Exception/WoxJsonRPCException.cs +++ b/Wox.Infrastructure/Exception/WoxJsonRPCException.cs @@ -1,4 +1,4 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { public class WoxJsonRPCException : WoxException { diff --git a/Wox.Infrastructure/Exception/WoxPluginException.cs b/Wox.Infrastructure/Exception/WoxPluginException.cs index bc1854f564..7aea174fd3 100644 --- a/Wox.Infrastructure/Exception/WoxPluginException.cs +++ b/Wox.Infrastructure/Exception/WoxPluginException.cs @@ -1,4 +1,4 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { public class WoxPluginException : WoxException { diff --git a/Wox.Infrastructure/Http/HttpRequest.cs b/Wox.Infrastructure/Http/HttpRequest.cs index c979cca6d1..628d747887 100644 --- a/Wox.Infrastructure/Http/HttpRequest.cs +++ b/Wox.Infrastructure/Http/HttpRequest.cs @@ -1,5 +1,4 @@ -using System; -using System.IO; +using System.IO; using System.Net; using System.Text; using Wox.Plugin; @@ -55,7 +54,7 @@ namespace Wox.Infrastructure.Http } } } - catch (Exception e) + catch (System.Exception e) { Logger.Log.Error(e); return string.Empty; @@ -108,7 +107,7 @@ namespace Wox.Infrastructure.Http } } } - catch (Exception e) + catch (System.Exception e) { Logger.Log.Error(e); return string.Empty; diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index e03c7045bc..5573450986 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -1,6 +1,4 @@ -using System; -using NLog; -using Wox.Core.Exception; +using NLog; namespace Wox.Infrastructure.Logger { @@ -8,7 +6,7 @@ namespace Wox.Infrastructure.Logger { private static NLog.Logger logger = LogManager.GetCurrentClassLogger(); - public static void Error(Exception e) + public static void Error(System.Exception e) { #if DEBUG throw e; @@ -35,7 +33,7 @@ namespace Wox.Infrastructure.Logger logger.Warn(msg); } - public static void Fatal(Exception e) + public static void Fatal(System.Exception e) { #if DEBUG throw e; diff --git a/Wox.Infrastructure/Storage/BinaryStorage.cs b/Wox.Infrastructure/Storage/BinaryStorage.cs index fb49507ec0..de1c732f6e 100644 --- a/Wox.Infrastructure/Storage/BinaryStorage.cs +++ b/Wox.Infrastructure/Storage/BinaryStorage.cs @@ -42,7 +42,7 @@ namespace Wox.Infrastructure.Storage serializedObject = LoadDefault(); #if (DEBUG) { - throw new Exception("deserialize failed"); + throw new System.Exception("deserialize failed"); } #endif } @@ -53,7 +53,7 @@ namespace Wox.Infrastructure.Storage } } } - catch (Exception e) + catch (System.Exception e) { Log.Error(e); serializedObject = LoadDefault(); @@ -101,7 +101,7 @@ namespace Wox.Infrastructure.Storage binaryFormatter.Serialize(fileStream, serializedObject); fileStream.Close(); } - catch (Exception e) + catch (System.Exception e) { Log.Error(e); #if (DEBUG) diff --git a/Wox.Infrastructure/Storage/JsonStorage.cs b/Wox.Infrastructure/Storage/JsonStorage.cs index b01f7277b5..ceace60009 100644 --- a/Wox.Infrastructure/Storage/JsonStorage.cs +++ b/Wox.Infrastructure/Storage/JsonStorage.cs @@ -25,7 +25,7 @@ namespace Wox.Infrastructure.Storage { serializedObject = JsonConvert.DeserializeObject(json); } - catch (Exception) + catch (System.Exception) { serializedObject = LoadDefault(); } diff --git a/Wox.Infrastructure/WindowsShellRun.cs b/Wox.Infrastructure/WindowsShellRun.cs index 9a638a6cc5..549815f962 100644 --- a/Wox.Infrastructure/WindowsShellRun.cs +++ b/Wox.Infrastructure/WindowsShellRun.cs @@ -115,7 +115,7 @@ namespace Wox.Infrastructure needsCommandLine = peHeaderReader.OptionalHeader64.Subsystem == 3; } - catch (Exception) + catch (System.Exception) { // Error reading the headers. We will try to run the command the standard way. needsCommandLine = false; @@ -149,7 +149,7 @@ namespace Wox.Infrastructure { global::System.Diagnostics.Process.Start(startInfo); } - catch (Exception e) + catch (System.Exception e) { if (!startInfo.ErrorDialog) throw e; diff --git a/Wox.Test/Plugins/PluginInitTest.cs b/Wox.Test/Plugins/PluginInitTest.cs index e23fa54322..2afd891533 100644 --- a/Wox.Test/Plugins/PluginInitTest.cs +++ b/Wox.Test/Plugins/PluginInitTest.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -using Wox.Core.Exception; using Wox.Core.Plugin; +using Wox.Infrastructure.Exception; namespace Wox.Test.Plugins { diff --git a/Wox/Helper/ErrorReporting.cs b/Wox/Helper/ErrorReporting.cs index 63c6f80dc8..b13ed405bc 100644 --- a/Wox/Helper/ErrorReporting.cs +++ b/Wox/Helper/ErrorReporting.cs @@ -1,6 +1,6 @@ using System; using System.Windows.Threading; -using Wox.Core.Exception; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; namespace Wox.Helper From 8aee2858eabb4232c8056e369e59a721037a8ba9 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Mon, 9 Nov 2015 03:20:02 +0000 Subject: [PATCH 17/19] Fix setting for multiple action keywords 1. completed rewrite the action keyword setting logic. 2. Fix setting for multiple action keywords in #352 3. Fix setting for Web Search plugin --- .../Properties/Annotations.cs | 996 ++++++++++++++++++ .../Wox.Plugin.WebSearch/WebSearchPlugin.cs | 38 +- .../WebSearchSetting.xaml | 2 +- .../WebSearchSetting.xaml.cs | 112 +- .../WebSearchesSetting.xaml.cs | 28 +- .../Wox.Plugin.WebSearch.csproj | 1 + Wox.Core/Plugin/PluginManager.cs | 61 +- Wox.Core/UserSettings/UserSettingStorage.cs | 24 +- .../Exception/WoxPluginException.cs | 11 +- Wox.Plugin/Feature.cs | 13 + Wox.Plugin/PluginPair.cs | 19 + Wox.Plugin/Result.cs | 3 +- Wox/ActionKeywords.xaml.cs | 55 +- Wox/SettingWindow.xaml.cs | 97 +- 14 files changed, 1286 insertions(+), 174 deletions(-) create mode 100644 Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs diff --git a/Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs b/Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs new file mode 100644 index 0000000000..58fbee9fa1 --- /dev/null +++ b/Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs @@ -0,0 +1,996 @@ +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace Wox.Plugin.WebSearch.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + public sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + public sealed class NotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute(string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + public string FormatParameterName { get; private set; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute(string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) + /// for method output means that the methos doesn't return normally.
+ /// canbenull annotation is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, + /// or use single attribute with rows separated by semicolon.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + public string Contract { get; private set; } + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes + /// as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly when marked + /// with or . + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used. + Members = 2, + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class PureAttribute : Attribute { } + + /// + /// Indicates that the return value of method invocation must be used. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() { } + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + public string Justification { get; private set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | + AttributeTargets.Method)] + public sealed class ProvidesContextAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + public PathReferenceAttribute([PathReference] string basePath) + { + BasePath = basePath; + } + + public string BasePath { get; private set; } + } + + /// + /// An extension method marked with this attribute is processed by ReSharper code completion + /// as a 'Source Template'. When extension method is completed over some expression, it's source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SourceTemplateAttribute : Attribute { } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + public sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// > + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute(string format) + { + Format = format; + } + + public string Format { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + public AspMvcActionAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() { } + public AspMvcAreaAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + public AspMvcControllerAttribute(string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcPartialViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcViewComponentAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewComponentViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + public HtmlElementAttributesAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class RazorSectionAttribute : Attribute { } + + /// + /// Indicates how method, constructor invocation or property access + /// over collection type affects content of the collection. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + public sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; private set; } + } + + [Flags] + public enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AssertionMethodAttribute : Attribute { } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; private set; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + public enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class TerminatesProgramAttribute : Attribute { } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LinqTunnelAttribute : Attribute { } + + /// + /// Indicates that IEnumerable, passed as parameter, is not enumerated. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class NoEnumerationAttribute : Attribute { } + + /// + /// Indicates that parameter is regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RegexPatternAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XamlItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute(string tagName, Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + public string TagName { get; private set; } + public Type ControlType { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspMethodPropertyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + public string Attribute { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; private set; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute(string name) + { + Name = name; + } + + public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute(string type, string fieldName) + { + Type = type; + FieldName = fieldName; + } + + public string Type { get; private set; } + public string FieldName { get; private set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorHelperCommonAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class RazorLayoutAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteLiteralMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RazorWriteMethodParameterAttribute : Attribute { } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class NoReorder : Attribute { } +} \ No newline at end of file diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs index f2dba4a35f..68bd9e9c6f 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchPlugin.cs @@ -1,17 +1,19 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Windows.Controls; +using Wox.Plugin.WebSearch.Annotations; using Wox.Plugin.WebSearch.SuggestionSources; namespace Wox.Plugin.WebSearch { - public class WebSearchPlugin : IPlugin, ISettingProvider, IPluginI18n, IInstantQuery + public class WebSearchPlugin : IPlugin, ISettingProvider, IPluginI18n, IInstantQuery, IMultipleActionKeywords { - private PluginInitContext _context; + public PluginInitContext Context { get; private set; } public List Query(Query query) { @@ -23,7 +25,7 @@ namespace Wox.Plugin.WebSearch { string keyword = query.Search; string title = keyword; - string subtitle = _context.API.GetTranslation("wox_plugin_websearch_search") + " " + webSearch.Title; + string subtitle = Context.API.GetTranslation("wox_plugin_websearch_search") + " " + webSearch.Title; if (string.IsNullOrEmpty(keyword)) { title = subtitle; @@ -54,7 +56,7 @@ namespace Wox.Plugin.WebSearch private IEnumerable ResultsFromSuggestions(string keyword, string subtitle, WebSearch webSearch) { - ISuggestionSource sugg = SuggestionSourceFactory.GetSuggestionSource(WebSearchStorage.Instance.WebSearchSuggestionSource, _context); + ISuggestionSource sugg = SuggestionSourceFactory.GetSuggestionSource(WebSearchStorage.Instance.WebSearchSuggestionSource, Context); var suggestions = sugg?.GetSuggestions(keyword); if (suggestions != null) { @@ -77,7 +79,7 @@ namespace Wox.Plugin.WebSearch public void Init(PluginInitContext context) { - _context = context; + this.Context = context; if (WebSearchStorage.Instance.WebSearches == null) WebSearchStorage.Instance.WebSearches = WebSearchStorage.Instance.LoadDefaultWebSearches(); @@ -87,7 +89,7 @@ namespace Wox.Plugin.WebSearch public Control CreateSettingPanel() { - return new WebSearchesSetting(_context); + return new WebSearchesSetting(this); } #endregion @@ -99,15 +101,35 @@ namespace Wox.Plugin.WebSearch public string GetTranslatedPluginTitle() { - return _context.API.GetTranslation("wox_plugin_websearch_plugin_name"); + return Context.API.GetTranslation("wox_plugin_websearch_plugin_name"); } public string GetTranslatedPluginDescription() { - return _context.API.GetTranslation("wox_plugin_websearch_plugin_description"); + return Context.API.GetTranslation("wox_plugin_websearch_plugin_description"); } public bool IsInstantQuery(string query) => false; + [NotifyPropertyChangedInvocator] + public void NotifyActionKeywordsUpdated(string oldActionKeywords, string newActionKeywords) + { + ActionKeywordsChanged?.Invoke(this, new ActionKeywordsChangedEventArgs + { + OldActionKeyword = oldActionKeywords, + NewActionKeyword = newActionKeywords + }); + } + + [NotifyPropertyChangedInvocator] + public void NotifyActionKeywordsAdded(string newActionKeywords) + { + ActionKeywordsChanged?.Invoke(this, new ActionKeywordsChangedEventArgs + { + NewActionKeyword = newActionKeywords + }); + } + + public event ActionKeywordsChangedEventHandler ActionKeywordsChanged; } } diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml b/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml index 86beaf2917..39976e6695 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml @@ -37,7 +37,7 @@ - diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs index a08eb6337f..012e2b8d80 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs @@ -5,37 +5,40 @@ using System.Reflection; using System.Windows; using System.Windows.Media.Imaging; using Microsoft.Win32; +using Wox.Infrastructure.Exception; namespace Wox.Plugin.WebSearch { public partial class WebSearchSetting : Window { - private string defaultWebSearchImageDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Images\\websearch"); - private WebSearchesSetting settingWindow; - private bool update; - private WebSearch updateWebSearch; - private PluginInitContext context; + private string _defaultWebSearchImageDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Images\\websearch"); + private readonly WebSearchesSetting _settingWindow; + private bool _isUpdate; + private WebSearch _updateWebSearch; + private readonly PluginInitContext _context; + private readonly WebSearchPlugin _plguin; - public WebSearchSetting(WebSearchesSetting settingWidow,PluginInitContext context) + public WebSearchSetting(WebSearchesSetting settingWidow) { - this.context = context; - this.settingWindow = settingWidow; + _plguin = settingWidow.Plugin; + _context = settingWidow.Context; + _settingWindow = settingWidow; InitializeComponent(); } public void UpdateItem(WebSearch webSearch) { - updateWebSearch = WebSearchStorage.Instance.WebSearches.FirstOrDefault(o => o == webSearch); - if (updateWebSearch == null || string.IsNullOrEmpty(updateWebSearch.Url)) + _updateWebSearch = WebSearchStorage.Instance.WebSearches.FirstOrDefault(o => o == webSearch); + if (_updateWebSearch == null || string.IsNullOrEmpty(_updateWebSearch.Url)) { - string warning = context.API.GetTranslation("wox_plugin_websearch_invalid_web_search"); + string warning = _context.API.GetTranslation("wox_plugin_websearch_invalid_web_search"); MessageBox.Show(warning); Close(); return; } - update = true; + _isUpdate = true; lblAdd.Text = "Update"; tbIconPath.Text = webSearch.IconPath; ShowIcon(webSearch.IconPath); @@ -49,7 +52,7 @@ namespace Wox.Plugin.WebSearch { try { - imgIcon.Source = new BitmapImage(new Uri(path)); + imgIcon.Source = new BitmapImage(new Uri(path, UriKind.Relative)); } catch (Exception) { @@ -61,12 +64,15 @@ namespace Wox.Plugin.WebSearch Close(); } - private void btnAdd_OnClick(object sender, RoutedEventArgs e) + /// + /// Confirm button for both add and update + /// + private void btnConfirm_OnClick(object sender, RoutedEventArgs e) { string title = tbTitle.Text; if (string.IsNullOrEmpty(title)) { - string warning = context.API.GetTranslation("wox_plugin_websearch_input_title"); + string warning = _context.API.GetTranslation("wox_plugin_websearch_input_title"); MessageBox.Show(warning); return; } @@ -74,81 +80,75 @@ namespace Wox.Plugin.WebSearch string url = tbUrl.Text; if (string.IsNullOrEmpty(url)) { - string warning = context.API.GetTranslation("wox_plugin_websearch_input_url"); + string warning = _context.API.GetTranslation("wox_plugin_websearch_input_url"); MessageBox.Show(warning); return; } - string action = tbActionword.Text; - if (string.IsNullOrEmpty(action)) + string newActionKeyword = tbActionword.Text.Trim(); + if (string.IsNullOrEmpty(newActionKeyword)) { - string warning = context.API.GetTranslation("wox_plugin_websearch_input_action_keyword"); + string warning = _context.API.GetTranslation("wox_plugin_websearch_input_action_keyword"); MessageBox.Show(warning); return; } - - if (!update) + if (_isUpdate) { - if (WebSearchStorage.Instance.WebSearches.Exists(o => o.ActionKeyword == action)) + try { - string warning = context.API.GetTranslation("wox_plugin_websearch_action_keyword_exist"); - MessageBox.Show(warning); + _plguin.NotifyActionKeywordsUpdated(_updateWebSearch.ActionKeyword, newActionKeyword); + } + catch (WoxPluginException exception) + { + MessageBox.Show(exception.Message); + return; + } + + _updateWebSearch.ActionKeyword = newActionKeyword; + _updateWebSearch.IconPath = tbIconPath.Text; + _updateWebSearch.Enabled = cbEnable.IsChecked ?? false; + _updateWebSearch.Url = url; + _updateWebSearch.Title = title; + } + else + { + try + { + _plguin.NotifyActionKeywordsAdded(newActionKeyword); + } + catch (WoxPluginException exception) + { + MessageBox.Show(exception.Message); return; } WebSearchStorage.Instance.WebSearches.Add(new WebSearch() { - ActionKeyword = action, + ActionKeyword = newActionKeyword, Enabled = cbEnable.IsChecked ?? false, IconPath = tbIconPath.Text, Url = url, Title = title }); - - //save the action keywords, the order is not metters. Wox will read this metadata when save settings. - context.CurrentPluginMetadata.ActionKeywords.Add(action); - - string msg = context.API.GetTranslation("wox_plugin_websearch_succeed"); - MessageBox.Show(msg); } - else - { - if (WebSearchStorage.Instance.WebSearches.Exists(o => o.ActionKeyword == action && o != updateWebSearch)) - { - string warning = context.API.GetTranslation("wox_plugin_websearch_action_keyword_exist"); - MessageBox.Show(warning); - return; - } - updateWebSearch.ActionKeyword = action; - updateWebSearch.IconPath = tbIconPath.Text; - updateWebSearch.Enabled = cbEnable.IsChecked ?? false; - updateWebSearch.Url = url; - updateWebSearch.Title= title; - - //save the action keywords, the order is not metters. Wox will read this metadata when save settings. - context.CurrentPluginMetadata.ActionKeywords.Add(action); - string msg = context.API.GetTranslation("wox_plugin_websearch_succeed"); - MessageBox.Show(msg); - } WebSearchStorage.Instance.Save(); - - settingWindow.ReloadWebSearchView(); + _settingWindow.ReloadWebSearchView(); Close(); } private void BtnSelectIcon_OnClick(object sender, RoutedEventArgs e) { - if(!Directory.Exists(defaultWebSearchImageDirectory)) + if (!Directory.Exists(_defaultWebSearchImageDirectory)) { - defaultWebSearchImageDirectory = + _defaultWebSearchImageDirectory = Path.GetDirectoryName(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); } var dlg = new OpenFileDialog { - InitialDirectory = defaultWebSearchImageDirectory, - Filter ="Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp" + InitialDirectory = _defaultWebSearchImageDirectory, + Filter = "Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp" }; bool? result = dlg.ShowDialog(); diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchesSetting.xaml.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchesSetting.xaml.cs index 1f56a240b5..f925f9d05a 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchesSetting.xaml.cs +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchesSetting.xaml.cs @@ -10,14 +10,14 @@ namespace Wox.Plugin.WebSearch ///
public partial class WebSearchesSetting : UserControl { - PluginInitContext context; + public PluginInitContext Context { get; } + public WebSearchPlugin Plugin { get; } - public WebSearchesSetting(PluginInitContext context) + public WebSearchesSetting(WebSearchPlugin plugin) { - this.context = context; - + Context = plugin.Context; + Plugin = plugin; InitializeComponent(); - Loaded += Setting_Loaded; } @@ -28,7 +28,7 @@ namespace Wox.Plugin.WebSearch comboBoxSuggestionSource.Visibility = WebSearchStorage.Instance.EnableWebSearchSuggestion ? Visibility.Visible : Visibility.Collapsed; - + List items = new List() { new ComboBoxItem() {Content = "Google"}, @@ -51,7 +51,7 @@ namespace Wox.Plugin.WebSearch private void btnAddWebSearch_OnClick(object sender, RoutedEventArgs e) { - WebSearchSetting webSearch = new WebSearchSetting(this,context); + WebSearchSetting webSearch = new WebSearchSetting(this); webSearch.ShowDialog(); } @@ -60,9 +60,9 @@ namespace Wox.Plugin.WebSearch WebSearch selectedWebSearch = webSearchView.SelectedItem as WebSearch; if (selectedWebSearch != null) { - string msg = string.Format(context.API.GetTranslation("wox_plugin_websearch_delete_warning"),selectedWebSearch.Title); - - if (MessageBox.Show(msg,string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) + string msg = string.Format(Context.API.GetTranslation("wox_plugin_websearch_delete_warning"), selectedWebSearch.Title); + + if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes) { WebSearchStorage.Instance.WebSearches.Remove(selectedWebSearch); webSearchView.Items.Refresh(); @@ -70,7 +70,7 @@ namespace Wox.Plugin.WebSearch } else { - string warning =context.API.GetTranslation("wox_plugin_websearch_pls_select_web_search"); + string warning = Context.API.GetTranslation("wox_plugin_websearch_pls_select_web_search"); MessageBox.Show(warning); } } @@ -80,13 +80,13 @@ namespace Wox.Plugin.WebSearch WebSearch selectedWebSearch = webSearchView.SelectedItem as WebSearch; if (selectedWebSearch != null) { - WebSearchSetting webSearch = new WebSearchSetting(this,context); + WebSearchSetting webSearch = new WebSearchSetting(this); webSearch.UpdateItem(selectedWebSearch); webSearch.ShowDialog(); } else { - string warning = context.API.GetTranslation("wox_plugin_websearch_pls_select_web_search"); + string warning = Context.API.GetTranslation("wox_plugin_websearch_pls_select_web_search"); MessageBox.Show(warning); } } @@ -109,7 +109,7 @@ namespace Wox.Plugin.WebSearch { if (e.AddedItems.Count > 0) { - WebSearchStorage.Instance.WebSearchSuggestionSource = ((ComboBoxItem) e.AddedItems[0]).Content.ToString(); + WebSearchStorage.Instance.WebSearchSuggestionSource = ((ComboBoxItem)e.AddedItems[0]).Content.ToString(); WebSearchStorage.Instance.Save(); } } diff --git a/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj b/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj index a407ad508e..9a450b0107 100644 --- a/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj +++ b/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj @@ -49,6 +49,7 @@ + diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index f04c5a045f..7ff406b710 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -7,10 +7,10 @@ using System.Threading; using Wox.Core.i18n; using Wox.Core.UI; using Wox.Core.UserSettings; +using Wox.Infrastructure; using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; -using Stopwatch = Wox.Infrastructure.Stopwatch; namespace Wox.Core.Plugin { @@ -56,7 +56,7 @@ namespace Wox.Core.Plugin { Directory.CreateDirectory(pluginDirectory); } - catch (System.Exception e) + catch (Exception e) { Log.Error(e); } @@ -196,7 +196,7 @@ namespace Wox.Core.Plugin pair.AvgQueryTime = pair.QueryCount == 1 ? milliseconds : (pair.AvgQueryTime + milliseconds) / 2; API.PushResults(query, pair.Metadata, results); } - catch (System.Exception e) + catch (Exception e) { throw new WoxPluginException(pair.Metadata.Name, $"QueryForPlugin failed", e); } @@ -240,7 +240,7 @@ namespace Wox.Core.Plugin { return plugin.LoadContextMenus(result); } - catch (System.Exception e) + catch (Exception e) { Log.Error(new WoxPluginException(pluginPair.Metadata.Name, $"Couldn't load plugin context menus", e)); } @@ -248,5 +248,58 @@ namespace Wox.Core.Plugin return new List(); } + + public static void UpdateActionKeywordForPlugin(PluginPair plugin, string oldActionKeyword, string newActionKeyword) + { + var actionKeywords = plugin.Metadata.ActionKeywords; + if (string.IsNullOrEmpty(newActionKeyword)) + { + string msg = InternationalizationManager.Instance.GetTranslation("newActionKeywordsCannotBeEmpty"); + throw new WoxPluginException(plugin.Metadata.Name, msg); + } + if (NonGlobalPlugins.ContainsKey(newActionKeyword)) + { + string msg = InternationalizationManager.Instance.GetTranslation("newActionKeywordsHasBeenAssigned"); + throw new WoxPluginException(plugin.Metadata.Name, msg); + } + + // add new action keyword + if (string.IsNullOrEmpty(oldActionKeyword)) + { + actionKeywords.Add(newActionKeyword); + if (newActionKeyword == Query.GlobalPluginWildcardSign) + { + GlobalPlugins.Add(plugin); + } + else + { + NonGlobalPlugins[newActionKeyword] = plugin; + } + } + // update existing action keyword + else + { + int index = actionKeywords.IndexOf(oldActionKeyword); + actionKeywords[index] = newActionKeyword; + if (oldActionKeyword == Query.GlobalPluginWildcardSign) + { + GlobalPlugins.Remove(plugin); + } + else + { + NonGlobalPlugins.Remove(oldActionKeyword); + } + if (newActionKeyword == Query.GlobalPluginWildcardSign) + { + GlobalPlugins.Add(plugin); + } + else + { + NonGlobalPlugins[newActionKeyword] = plugin; + } + } + + } + } } diff --git a/Wox.Core/UserSettings/UserSettingStorage.cs b/Wox.Core/UserSettings/UserSettingStorage.cs index 3af1911324..32b3a20cc7 100644 --- a/Wox.Core/UserSettings/UserSettingStorage.cs +++ b/Wox.Core/UserSettings/UserSettingStorage.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Drawing; using System.IO; +using System.Linq; using System.Reflection; using Newtonsoft.Json; using Wox.Infrastructure.Storage; +using Wox.Plugin; namespace Wox.Core.UserSettings { @@ -118,7 +120,7 @@ namespace Wox.Core.UserSettings public void IncreaseActivateTimes() { ActivateTimes++; - if (ActivateTimes%15 == 0) + if (ActivateTimes % 15 == 0) { Save(); } @@ -162,6 +164,26 @@ namespace Wox.Core.UserSettings storage.Language = "en"; } } + + public void UpdateActionKeyword(PluginMetadata metadata) + { + var customizedPluginConfig = CustomizedPluginConfigs.FirstOrDefault(o => o.ID == metadata.ID); + if (customizedPluginConfig == null) + { + CustomizedPluginConfigs.Add(new CustomizedPluginConfig() + { + Disabled = false, + ID = metadata.ID, + Name = metadata.Name, + ActionKeywords = metadata.ActionKeywords + }); + } + else + { + customizedPluginConfig.ActionKeywords = metadata.ActionKeywords; + } + Save(); + } } public enum OpacityMode diff --git a/Wox.Infrastructure/Exception/WoxPluginException.cs b/Wox.Infrastructure/Exception/WoxPluginException.cs index 7aea174fd3..0987ca69d5 100644 --- a/Wox.Infrastructure/Exception/WoxPluginException.cs +++ b/Wox.Infrastructure/Exception/WoxPluginException.cs @@ -1,11 +1,18 @@ -namespace Wox.Infrastructure.Exception +using Wox.Plugin; + +namespace Wox.Infrastructure.Exception { public class WoxPluginException : WoxException { public string PluginName { get; set; } public WoxPluginException(string pluginName, string msg, System.Exception e) - : base($"{msg}: {pluginName}", e) + : base($"{pluginName} : {msg}", e) + { + PluginName = pluginName; + } + + public WoxPluginException(string pluginName, string msg) : base(msg) { PluginName = pluginName; } diff --git a/Wox.Plugin/Feature.cs b/Wox.Plugin/Feature.cs index 3a1cbff222..6df0da438e 100644 --- a/Wox.Plugin/Feature.cs +++ b/Wox.Plugin/Feature.cs @@ -38,4 +38,17 @@ namespace Wox.Plugin string GetTranslatedPluginDescription(); } + + public interface IMultipleActionKeywords + { + event ActionKeywordsChangedEventHandler ActionKeywordsChanged; + } + + public class ActionKeywordsChangedEventArgs : EventArgs + { + public string OldActionKeyword { get; set; } + public string NewActionKeyword { get; set; } + } + + public delegate void ActionKeywordsChangedEventHandler(IMultipleActionKeywords sender, ActionKeywordsChangedEventArgs e); } diff --git a/Wox.Plugin/PluginPair.cs b/Wox.Plugin/PluginPair.cs index 85d228c9c7..7c64e9b0fe 100644 --- a/Wox.Plugin/PluginPair.cs +++ b/Wox.Plugin/PluginPair.cs @@ -15,5 +15,24 @@ { return Metadata.Name; } + + public override bool Equals(object obj) + { + PluginPair r = obj as PluginPair; + if (r != null) + { + return string.Equals(r.Metadata.ID, Metadata.ID); + } + else + { + return false; + } + } + + public override int GetHashCode() + { + var hashcode = Metadata.ID?.GetHashCode() ?? 0; + return hashcode; + } } } diff --git a/Wox.Plugin/Result.cs b/Wox.Plugin/Result.cs index 0a36735078..5352898d82 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -57,7 +57,8 @@ namespace Wox.Plugin public override int GetHashCode() { - return (Title?.GetHashCode() ?? 0) ^ (SubTitle?.GetHashCode() ?? 0); + var hashcode = (Title?.GetHashCode() ?? 0) ^ (SubTitle?.GetHashCode() ?? 0); + return hashcode; } public override string ToString() diff --git a/Wox/ActionKeywords.xaml.cs b/Wox/ActionKeywords.xaml.cs index 06a70fdfb6..67d17aa7b0 100644 --- a/Wox/ActionKeywords.xaml.cs +++ b/Wox/ActionKeywords.xaml.cs @@ -1,34 +1,34 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Windows; using Wox.Core.i18n; using Wox.Core.Plugin; using Wox.Core.UserSettings; +using Wox.Infrastructure.Exception; using Wox.Plugin; namespace Wox { public partial class ActionKeywords : Window { - private PluginMetadata pluginMetadata; + private PluginPair _plugin; public ActionKeywords(string pluginId) { InitializeComponent(); - PluginPair plugin = PluginManager.GetPluginForId(pluginId); - if (plugin == null) + _plugin = PluginManager.GetPluginForId(pluginId); + if (_plugin == null) { MessageBox.Show(InternationalizationManager.Instance.GetTranslation("cannotFindSpecifiedPlugin")); Close(); return; } - - pluginMetadata = plugin.Metadata; } private void ActionKeyword_OnLoaded(object sender, RoutedEventArgs e) { - tbOldActionKeyword.Text = string.Join(Query.ActionKeywordSeperater, pluginMetadata.ActionKeywords.ToArray()); + tbOldActionKeyword.Text = string.Join(Query.ActionKeywordSeperater, _plugin.Metadata.ActionKeywords.ToArray()); tbAction.Focus(); } @@ -37,42 +37,23 @@ namespace Wox Close(); } - private void btnDone_OnClick(object sender, RoutedEventArgs e) + private void btnDone_OnClick(object sender, RoutedEventArgs _) { - if (string.IsNullOrEmpty(tbAction.Text)) + var oldActionKeyword = _plugin.Metadata.ActionKeywords[0]; + var newActionKeyword = tbAction.Text.Trim(); + try { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("newActionKeywordCannotBeEmpty")); + // update in-memory data + PluginManager.UpdateActionKeywordForPlugin(_plugin, oldActionKeyword, newActionKeyword); + } + catch (WoxPluginException e) + { + MessageBox.Show(e.Message); return; } + // update persistant data + UserSettingStorage.Instance.UpdateActionKeyword(_plugin.Metadata); - var actionKeywords = tbAction.Text.Trim().Split(new[] { Query.ActionKeywordSeperater }, StringSplitOptions.RemoveEmptyEntries).ToList(); - //check new action keyword didn't used by other plugin - if (actionKeywords[0] != Query.GlobalPluginWildcardSign && PluginManager.AllPlugins. - SelectMany(p => p.Metadata.ActionKeywords). - Any(k => actionKeywords.Contains(k))) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("newActionKeywordHasBeenAssigned")); - return; - } - - - pluginMetadata.ActionKeywords = actionKeywords; - var customizedPluginConfig = UserSettingStorage.Instance.CustomizedPluginConfigs.FirstOrDefault(o => o.ID == pluginMetadata.ID); - if (customizedPluginConfig == null) - { - UserSettingStorage.Instance.CustomizedPluginConfigs.Add(new CustomizedPluginConfig() - { - Disabled = false, - ID = pluginMetadata.ID, - Name = pluginMetadata.Name, - ActionKeywords = actionKeywords - }); - } - else - { - customizedPluginConfig.ActionKeywords = actionKeywords; - } - UserSettingStorage.Instance.Save(); MessageBox.Show(InternationalizationManager.Instance.GetTranslation("succeed")); Close(); } diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index 5b57bb8d85..4546d8f797 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -17,7 +17,7 @@ using Wox.Core.Theme; using Wox.Core.Updater; using Wox.Core.UserSettings; using Wox.Helper; -using Wox.Infrastructure; +using Wox.Infrastructure.Exception; using Wox.Plugin; using Application = System.Windows.Forms.Application; using Stopwatch = Wox.Infrastructure.Stopwatch; @@ -188,23 +188,6 @@ namespace Wox { OnHotkeyTabSelected(); } - - // save multiple action keywords settings, todo: this hack is ugly - var tab = e.RemovedItems.Count > 0 ? e.RemovedItems[0] : null; - if (ReferenceEquals(tab, tabPlugin)) - { - var metadata = (lbPlugins.SelectedItem as PluginPair)?.Metadata; - if (metadata != null) - { - var customizedPluginConfig = UserSettingStorage.Instance.CustomizedPluginConfigs.FirstOrDefault(o => o.ID == metadata.ID); - if (customizedPluginConfig != null && !customizedPluginConfig.Disabled) - { - customizedPluginConfig.ActionKeywords = metadata.ActionKeywords; - UserSettingStorage.Instance.Save(); - } - - } - } } #region General @@ -535,50 +518,64 @@ namespace Wox #region Plugin - private void lbPlugins_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + private void lbPlugins_OnSelectionChanged(object sender, SelectionChangedEventArgs _) { - ISettingProvider provider = null; + var pair = lbPlugins.SelectedItem as PluginPair; string pluginId = string.Empty; - - if (pair != null) + List actionKeywords = null; + if (pair == null) return; + actionKeywords = pair.Metadata.ActionKeywords; + pluginAuthor.Visibility = Visibility.Visible; + pluginInitTime.Text = + string.Format(InternationalizationManager.Instance.GetTranslation("plugin_init_time"), pair.InitTime); + pluginQueryTime.Text = + string.Format(InternationalizationManager.Instance.GetTranslation("plugin_query_time"), pair.AvgQueryTime); + if (actionKeywords.Count > 1) { - provider = pair.Plugin as ISettingProvider; - pluginAuthor.Visibility = Visibility.Visible; - pluginInitTime.Text = - string.Format(InternationalizationManager.Instance.GetTranslation("plugin_init_time"), pair.InitTime); - pluginQueryTime.Text = - string.Format(InternationalizationManager.Instance.GetTranslation("plugin_query_time"), pair.AvgQueryTime); - if (pair.Metadata.ActionKeywords.Count > 1) - { - pluginActionKeywordsTitle.Visibility = Visibility.Collapsed; - pluginActionKeywords.Visibility = Visibility.Collapsed; - } - else - { - pluginActionKeywordsTitle.Visibility = Visibility.Visible; - pluginActionKeywords.Visibility = Visibility.Visible; - } - tbOpenPluginDirecoty.Visibility = Visibility.Visible; - pluginTitle.Text = pair.Metadata.Name; - pluginTitle.Cursor = Cursors.Hand; - pluginActionKeywords.Text = string.Join(Query.ActionKeywordSeperater, pair.Metadata.ActionKeywords.ToArray()); - pluginAuthor.Text = InternationalizationManager.Instance.GetTranslation("author") + ": " + pair.Metadata.Author; - pluginSubTitle.Text = pair.Metadata.Description; - pluginId = pair.Metadata.ID; - pluginIcon.Source = ImageLoader.ImageLoader.Load(pair.Metadata.FullIcoPath); + pluginActionKeywordsTitle.Visibility = Visibility.Collapsed; + pluginActionKeywords.Visibility = Visibility.Collapsed; } + else + { + pluginActionKeywordsTitle.Visibility = Visibility.Visible; + pluginActionKeywords.Visibility = Visibility.Visible; + } + tbOpenPluginDirecoty.Visibility = Visibility.Visible; + pluginTitle.Text = pair.Metadata.Name; + pluginTitle.Cursor = Cursors.Hand; + pluginActionKeywords.Text = string.Join(Query.ActionKeywordSeperater, actionKeywords.ToArray()); + pluginAuthor.Text = InternationalizationManager.Instance.GetTranslation("author") + ": " + pair.Metadata.Author; + pluginSubTitle.Text = pair.Metadata.Description; + pluginId = pair.Metadata.ID; + pluginIcon.Source = ImageLoader.ImageLoader.Load(pair.Metadata.FullIcoPath); var customizedPluginConfig = UserSettingStorage.Instance.CustomizedPluginConfigs.FirstOrDefault(o => o.ID == pluginId); cbDisablePlugin.IsChecked = customizedPluginConfig != null && customizedPluginConfig.Disabled; PluginContentPanel.Content = null; - if (provider != null) + var settingProvider = pair.Plugin as ISettingProvider; + if (settingProvider != null) { - Control control = null; - if (!featureControls.TryGetValue(provider, out control)) - featureControls.Add(provider, control = provider.CreateSettingPanel()); + Control control; + if (!featureControls.TryGetValue(settingProvider, out control)) + { + var multipleActionKeywordsProvider = settingProvider as IMultipleActionKeywords; + if (multipleActionKeywordsProvider != null) + { + multipleActionKeywordsProvider.ActionKeywordsChanged += (o, e) => + { + // update in-memory data + PluginManager.UpdateActionKeywordForPlugin(pair, e.OldActionKeyword, e.NewActionKeyword); + // update persistant data + UserSettingStorage.Instance.UpdateActionKeyword(pair.Metadata); + MessageBox.Show(InternationalizationManager.Instance.GetTranslation("succeed")); + }; + } + + featureControls.Add(settingProvider, control = settingProvider.CreateSettingPanel()); + } PluginContentPanel.Content = control; control.HorizontalAlignment = HorizontalAlignment.Stretch; control.VerticalAlignment = VerticalAlignment.Stretch; From 4351080aea99ac6b5bfec91848f6fff519389623 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Mon, 9 Nov 2015 03:56:56 +0000 Subject: [PATCH 18/19] Fix icon path for Web Search plugin --- .../WebSearchSetting.xaml.cs | 19 +++---------------- .../Wox.Plugin.WebSearch/WebSearchStorage.cs | 6 +++--- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs index 012e2b8d80..1c85340f67 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchSetting.xaml.cs @@ -50,13 +50,7 @@ namespace Wox.Plugin.WebSearch private void ShowIcon(string path) { - try - { - imgIcon.Source = new BitmapImage(new Uri(path, UriKind.Relative)); - } - catch (Exception) - { - } + imgIcon.Source = new BitmapImage(new Uri($"{_defaultWebSearchImageDirectory}\\{path}", UriKind.Absolute)); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) @@ -86,13 +80,6 @@ namespace Wox.Plugin.WebSearch } string newActionKeyword = tbActionword.Text.Trim(); - if (string.IsNullOrEmpty(newActionKeyword)) - { - string warning = _context.API.GetTranslation("wox_plugin_websearch_input_action_keyword"); - MessageBox.Show(warning); - return; - } - if (_isUpdate) { try @@ -155,8 +142,8 @@ namespace Wox.Plugin.WebSearch if (result == true) { string filename = dlg.FileName; - tbIconPath.Text = filename; - ShowIcon(filename); + tbIconPath.Text = Path.GetFileName(filename); + ShowIcon(tbIconPath.Text); } } } diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchStorage.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchStorage.cs index db5d0616bd..2f6d92435a 100644 --- a/Plugins/Wox.Plugin.WebSearch/WebSearchStorage.cs +++ b/Plugins/Wox.Plugin.WebSearch/WebSearchStorage.cs @@ -41,7 +41,7 @@ namespace Wox.Plugin.WebSearch { Title = "Google", ActionKeyword = "g", - IconPath = @"Images\websearch\google.png", + IconPath = @"google.png", Url = "https://www.google.com/search?q={q}", Enabled = true }; @@ -52,7 +52,7 @@ namespace Wox.Plugin.WebSearch { Title = "Wikipedia", ActionKeyword = "wiki", - IconPath = @"Images\websearch\wiki.png", + IconPath = @"wiki.png", Url = "http://en.wikipedia.org/wiki/{q}", Enabled = true }; @@ -62,7 +62,7 @@ namespace Wox.Plugin.WebSearch { Title = "FindIcon", ActionKeyword = "findicon", - IconPath = @"Images\websearch\pictures.png", + IconPath = @"pictures.png", Url = "http://findicons.com/search/{q}", Enabled = true }; From c456ef9118356057e2e917420c92113535b5550d Mon Sep 17 00:00:00 2001 From: bao-qian Date: Mon, 9 Nov 2015 03:59:20 +0000 Subject: [PATCH 19/19] Fix namespace bug introduced since e037e88 --- Wox.Infrastructure/Logger/Log.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Wox.Infrastructure/Logger/Log.cs b/Wox.Infrastructure/Logger/Log.cs index 5573450986..25527baefb 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -1,4 +1,5 @@ using NLog; +using Wox.Infrastructure.Exception; namespace Wox.Infrastructure.Logger {