diff --git a/Plugins/Wox.Plugin.CMD/CMD.cs b/Plugins/Wox.Plugin.CMD/CMD.cs index 0cd255bc3..7b100dc6b 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) => { diff --git a/Plugins/Wox.Plugin.PluginIndicator/PluginIndicator.cs b/Plugins/Wox.Plugin.PluginIndicator/PluginIndicator.cs index f7b2bd031..f7f518889 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/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs b/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs index 4d258e74f..cb3b315aa 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 16e0961b3..6cec459bf 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; @@ -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 cb0cf7e76..b266cc654 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.Infrastructure.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 7f9819864..8ec1ea9a3 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/Plugins/Wox.Plugin.Sys/Sys.cs b/Plugins/Wox.Plugin.Sys/Sys.cs index bc3db80b6..d3a0db1da 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() diff --git a/Plugins/Wox.Plugin.WebSearch/EasyTimer.cs b/Plugins/Wox.Plugin.WebSearch/EasyTimer.cs deleted file mode 100644 index 49c31e63d..000000000 --- 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/Properties/Annotations.cs b/Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs new file mode 100644 index 000000000..58fbee9fa --- /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 d4ee840df..68bd9e9c6 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; - private IDisposable suggestionTimer; + public PluginInitContext Context { get; private set; } public List Query(Query query) { @@ -21,71 +23,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; + this.Context = context; if (WebSearchStorage.Instance.WebSearches == null) WebSearchStorage.Instance.WebSearches = WebSearchStorage.Instance.LoadDefaultWebSearches(); @@ -93,9 +87,9 @@ namespace Wox.Plugin.WebSearch #region ISettingProvider Members - public System.Windows.Controls.Control CreateSettingPanel() + public Control CreateSettingPanel() { - return new WebSearchesSetting(context); + return new WebSearchesSetting(this); } #endregion @@ -107,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 86beaf291..39976e669 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 a08eb6337..1c85340f6 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); @@ -47,13 +50,7 @@ namespace Wox.Plugin.WebSearch private void ShowIcon(string path) { - try - { - imgIcon.Source = new BitmapImage(new Uri(path)); - } - catch (Exception) - { - } + imgIcon.Source = new BitmapImage(new Uri($"{_defaultWebSearchImageDirectory}\\{path}", UriKind.Absolute)); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) @@ -61,12 +58,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,89 +74,76 @@ 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 (_isUpdate) { - string warning = context.API.GetTranslation("wox_plugin_websearch_input_action_keyword"); - MessageBox.Show(warning); - return; - } - - - if (!update) - { - 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(); 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 db5d0616b..2f6d92435 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 }; diff --git a/Plugins/Wox.Plugin.WebSearch/WebSearchesSetting.xaml.cs b/Plugins/Wox.Plugin.WebSearch/WebSearchesSetting.xaml.cs index 1f56a240b..f925f9d05 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 32a1b26e4..9a450b010 100644 --- a/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj +++ b/Plugins/Wox.Plugin.WebSearch/Wox.Plugin.WebSearch.csproj @@ -49,7 +49,7 @@ - + @@ -93,9 +93,6 @@ Designer - - - {4fd29318-a8ab-4d8f-aa47-60bc241b8da3} diff --git a/Wox.Core/Exception/WoxPluginException.cs b/Wox.Core/Exception/WoxPluginException.cs deleted file mode 100644 index e435be5cd..000000000 --- a/Wox.Core/Exception/WoxPluginException.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Wox.Core.Exception -{ - public class WoxPluginException : WoxException - { - public string PluginName { get; set; } - - public WoxPluginException(string pluginName,System.Exception e) - : base(e.Message,e) - { - PluginName = pluginName; - } - } -} diff --git a/Wox.Core/Plugin/CSharpPluginLoader.cs b/Wox.Core/Plugin/CSharpPluginLoader.cs index 7041d26c2..5eb4a830e 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.Infrastructure.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 6f65d78c9..5c08f9a8a 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; @@ -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 63abe41ff..30bacef83 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; @@ -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 6123508c3..7ff406b71 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -4,13 +4,13 @@ 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; +using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; using Wox.Plugin; -using Stopwatch = Wox.Infrastructure.Stopwatch; namespace Wox.Core.Plugin { @@ -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; set; } = new Dictionary(); private static IEnumerable InstantQueryPlugins { get; set; } public static IPublicAPI API { private set; get; } @@ -56,9 +56,9 @@ namespace Wox.Core.Plugin { Directory.CreateDirectory(pluginDirectory); } - catch (System.Exception e) + catch (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; @@ -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. @@ -152,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); }); @@ -161,7 +173,10 @@ namespace Wox.Core.Plugin { ThreadPool.QueueUserWorkItem(state => { - QueryForPlugin(plugin, query); + Stopwatch.Normal($"Normal QueryForPlugin for {plugin.Metadata.Name}", () => + { + QueryForPlugin(plugin, query); + }); }); } } @@ -172,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; }); @@ -181,27 +196,12 @@ 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, e); + throw new WoxPluginException(pair.Metadata.Name, $"QueryForPlugin failed", e); } } - /// - /// 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 +225,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); @@ -247,18 +240,66 @@ namespace Wox.Core.Plugin { return plugin.LoadContextMenus(result); } - catch (System.Exception e) + catch (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)); } } 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/Theme/Theme.cs b/Wox.Core/Theme/Theme.cs index 114b5742c..9d937b51b 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/Updater/SemanticVersion.cs b/Wox.Core/Updater/SemanticVersion.cs index 693ce73ee..ed3ef7b31 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/UserSettings/UserSettingStorage.cs b/Wox.Core/UserSettings/UserSettingStorage.cs index 3af191132..32b3a20cc 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.Core/Wox.Core.csproj b/Wox.Core/Wox.Core.csproj index c7e8072a2..d69ae6a8d 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 cc0d80e83..0cfea890a 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; @@ -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 99% rename from Wox.Core/Exception/ExceptionFormatter.cs rename to Wox.Infrastructure/Exception/ExceptionFormatter.cs index d4e8beb8a..ce232d07d 100644 --- a/Wox.Core/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.Core/Exception/WoxException.cs b/Wox.Infrastructure/Exception/WoxException.cs similarity index 89% rename from Wox.Core/Exception/WoxException.cs rename to Wox.Infrastructure/Exception/WoxException.cs index 2840e34aa..89a61119a 100644 --- a/Wox.Core/Exception/WoxException.cs +++ b/Wox.Infrastructure/Exception/WoxException.cs @@ -1,4 +1,4 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { /// /// Base Wox Exceptions @@ -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 50% rename from Wox.Core/Exception/WoxCritialException.cs rename to Wox.Infrastructure/Exception/WoxFatalException.cs index cf840f9b1..6a1da4a62 100644 --- a/Wox.Core/Exception/WoxCritialException.cs +++ b/Wox.Infrastructure/Exception/WoxFatalException.cs @@ -1,11 +1,11 @@ -namespace Wox.Core.Exception +namespace Wox.Infrastructure.Exception { /// /// 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 76% rename from Wox.Core/Exception/WoxHttpException.cs rename to Wox.Infrastructure/Exception/WoxHttpException.cs index 55e3431a0..2626a3dc7 100644 --- a/Wox.Core/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.Core/Exception/WoxI18nException.cs b/Wox.Infrastructure/Exception/WoxI18nException.cs similarity index 50% rename from Wox.Core/Exception/WoxI18nException.cs rename to Wox.Infrastructure/Exception/WoxI18nException.cs index 72b62df98..c06ad7802 100644 --- a/Wox.Core/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.Core/Exception/WoxJsonRPCException.cs b/Wox.Infrastructure/Exception/WoxJsonRPCException.cs similarity index 79% rename from Wox.Core/Exception/WoxJsonRPCException.cs rename to Wox.Infrastructure/Exception/WoxJsonRPCException.cs index d3c0bfb0d..88bb3f5ed 100644 --- a/Wox.Core/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 new file mode 100644 index 000000000..0987ca69d --- /dev/null +++ b/Wox.Infrastructure/Exception/WoxPluginException.cs @@ -0,0 +1,20 @@ +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($"{pluginName} : {msg}", e) + { + PluginName = pluginName; + } + + public WoxPluginException(string pluginName, string msg) : base(msg) + { + PluginName = pluginName; + } + } +} diff --git a/Wox.Infrastructure/Http/HttpRequest.cs b/Wox.Infrastructure/Http/HttpRequest.cs index c979cca6d..628d74788 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 8abaf16a4..25527baef 100644 --- a/Wox.Infrastructure/Logger/Log.cs +++ b/Wox.Infrastructure/Logger/Log.cs @@ -1,5 +1,5 @@ -using System; -using NLog; +using NLog; +using Wox.Infrastructure.Exception; namespace Wox.Infrastructure.Logger { @@ -7,34 +7,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) + public static void Error(System.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(System.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 c0d828854..7387734d5 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/Storage/BinaryStorage.cs b/Wox.Infrastructure/Storage/BinaryStorage.cs index fb49507ec..de1c732f6 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 b01f7277b..ceace6000 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 9a638a6cc..549815f96 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.Infrastructure/Wox.Infrastructure.csproj b/Wox.Infrastructure/Wox.Infrastructure.csproj index e3740ee2c..31100d3ae 100644 --- a/Wox.Infrastructure/Wox.Infrastructure.csproj +++ b/Wox.Infrastructure/Wox.Infrastructure.csproj @@ -46,9 +46,17 @@ + + + + + + + + diff --git a/Wox.Plugin/Feature.cs b/Wox.Plugin/Feature.cs index 3a1cbff22..6df0da438 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 85d228c9c..7c64e9b0f 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 21998ec59..5352898d8 100644 --- a/Wox.Plugin/Result.cs +++ b/Wox.Plugin/Result.cs @@ -42,12 +42,23 @@ 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() + { + var hashcode = (Title?.GetHashCode() ?? 0) ^ (SubTitle?.GetHashCode() ?? 0); + return hashcode; } public override string ToString() diff --git a/Wox.Test/Plugins/PluginInitTest.cs b/Wox.Test/Plugins/PluginInitTest.cs index 3bb5658c5..2afd89153 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 { @@ -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/ActionKeywords.xaml.cs b/Wox/ActionKeywords.xaml.cs index 06a70fdfb..67d17aa7b 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/App.xaml.cs b/Wox/App.xaml.cs index dc9d15946..5b239e085 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); diff --git a/Wox/Helper/ErrorReporting.cs b/Wox/Helper/ErrorReporting.cs index e7b5a81fb..b13ed405b 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 @@ -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 new file mode 100644 index 000000000..5d62edf11 --- /dev/null +++ b/Wox/Helper/ListBoxItems.cs @@ -0,0 +1,67 @@ +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 + // todo implement custom moveItem,removeItem,insertItem for better performance + { + 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 + // 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)); + } + } + + 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/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index 1792eafae..5a289131e 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 @@ -43,11 +44,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 +73,7 @@ namespace Wox { Dispatcher.Invoke(new Action(() => { - ignoreTextChange = true; + _ignoreTextChange = true; tbQuery.Text = query; tbQuery.CaretIndex = tbQuery.Text.Length; if (selectAll) @@ -162,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) @@ -176,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; } @@ -418,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 { @@ -435,54 +437,74 @@ namespace Wox filterResults.Add(contextMenu); } } - pnlContextMenu.AddResults(filterResults); + pnlContextMenu.AddResults(filterResults, contextMenuId); } } private void TbQuery_OnTextChanged(object sender, TextChangedEventArgs e) { - if (ignoreTextChange) { ignoreTextChange = false; return; } + if (_ignoreTextChange) { _ignoreTextChange = false; return; } toolTip.IsOpen = false; - pnlResult.Dirty = true; if (IsInContextMenuMode) { QueryContextMenu(); return; } - queryHasReturn = false; - - Dispatcher.DelayInvoke("ClearResults", () => + string query = tbQuery.Text.Trim(); + if (!string.IsNullOrEmpty(query)) { - // 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", () => + Query(query); + //reset query history index after user start new query + ResetQueryHistoryIndex(); + } + else { - if (!string.IsNullOrEmpty(tbQuery.Text.Trim()) && tbQuery.Text != lastQuery && !queryHasReturn) - { - StartProgress(); - } - }, TimeSpan.FromMilliseconds(150)); - //reset query history index after user start new query - ResetQueryHistoryIndex(); + pnlResult.Clear(); + } } private void ResetQueryHistoryIndex() { + pnlResult.RemoveResultsFor(QueryHistoryStorage.MetaData); QueryHistoryStorage.Instance.Reset(); } private void Query(string text) { + _queryHasReturn = false; 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].Metadata); + } + } + else + { + if (string.IsNullOrEmpty(keyword)) + { + pnlResult.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); + } + else if (lastKeyword != keyword) + { + pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + } + } + _lastQuery = query; + Dispatcher.DelayInvoke("ShowProgressbar", () => + { + if (!string.IsNullOrEmpty(query.RawQuery) && query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) + { + StartProgress(); + } + }, TimeSpan.FromMilliseconds(150)); PluginManager.QueryForAllPlugins(query); } StopProgress(); @@ -719,10 +741,11 @@ namespace Wox { if (history != null) { + var historyMetadata = QueryHistoryStorage.MetaData; ChangeQueryText(history.Query, true); - pnlResult.Dirty = true; var executeQueryHistoryTitle = GetTranslation("executeQuery"); var lastExecuteTime = GetTranslation("lastExecuteTime"); + pnlResult.RemoveResultsExcept(historyMetadata); UpdateResultViewInternal(new List() { new Result(){ @@ -735,7 +758,7 @@ namespace Wox return false; } } - }); + }, historyMetadata); } } @@ -816,28 +839,27 @@ namespace Wox } } - private void UpdateResultView(List list) + private void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) { - queryHasReturn = true; + _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).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) { Dispatcher.Invoke(new Action(() => { - pnlResult.AddResults(list); + Stopwatch.Normal($"UI update cost for {metadata.Name}", + () => { pnlResult.AddResults(list, metadata.ID); }); })); } @@ -886,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/Properties/Resources.Designer.cs b/Wox/Properties/Resources.Designer.cs index 7665c7143..20e0fb225 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 eb3d4dc30..7a4226349 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()))); diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index 7c44bf36b..121ee9b85 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -1,20 +1,29 @@ using System; +using System.Collections; 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.Helper; 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 ListBoxItems _results; + private readonly object _resultsUpdateLock = new object(); protected virtual void OnRightMouseClick(Result result) { @@ -28,36 +37,78 @@ 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) + internal void RemoveResultsFor(PluginMetadata metadata) { - if (Dirty) + lock (_resultsUpdateLock) { - Dirty = false; - lbResults.Items.Clear(); + _results.RemoveAll(r => r.PluginID == metadata.ID); } - foreach (var result in results) + } + + internal void RemoveResultsExcept(PluginMetadata metadata) + { + lock (_resultsUpdateLock) { - int position = 0; - if (IsTopMostResult(result)) + _results.RemoveAll(r => r.PluginID != metadata.ID); + } + } + + public void AddResults(List newResults, string resultId) + { + 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)) { - result.Score = int.MaxValue; + resultCopy.Remove(result); } - else + + // update scores + foreach (var result in newResults) { - if (result.Score >= int.MaxValue) + if (IsTopMostResult(result)) { - result.Score = int.MaxValue - 1; + result.Score = int.MaxValue; } - position = GetInsertLocation(result.Score); } - lbResults.Items.Insert(position, result); + + // update index for result in intersection of A and B + foreach (var result in intersection) + { + int oldIndex = resultCopy.IndexOf(result); + int oldScore = resultCopy[oldIndex].Score; + if (result.Score != oldScore) + { + int newIndex = InsertIndexOf(result.Score, resultCopy); + if (newIndex != oldIndex) + { + var item = resultCopy[oldIndex]; + resultCopy.RemoveAt(oldIndex); + resultCopy.Insert(newIndex, item); + } + } + } + + // insert result in relative complement of A in B + foreach (var result in newResults.Except(intersection)) + { + 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(); } - lbResults.Margin = lbResults.Items.Count > 0 ? new Thickness { Top = 8 } : new Thickness { Top = 0 }; - SelectFirst(); } private bool IsTopMostResult(Result result) @@ -65,33 +116,18 @@ namespace Wox return TopMostRecordStorage.Instance.IsTopMost(result); } - private int GetInsertLocation(int currentScore) + private int InsertIndexOf(int newScore, IList list) { - 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 < list.Count; index++) { - Result next = lbResults.Items[index] as Result; - Result prev = lbResults.Items[index - 1] as Result; - if (next != null && prev != null) + var result = list[index]; + 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 +247,17 @@ namespace Wox public ResultPanel() { InitializeComponent(); + _results = new ListBoxItems(); + 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) diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index e96720ae2..4546d8f79 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 @@ -429,7 +412,7 @@ namespace Wox IcoPath = "Images/app.png", PluginDirectory = Path.GetDirectoryName(Application.ExecutablePath) } - }); + }, "test id"); foreach (string theme in ThemeManager.Theme.LoadAvailableThemes()) { @@ -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 > 0) - { - 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; diff --git a/Wox/Storage/QueryHistoryStorage.cs b/Wox/Storage/QueryHistoryStorage.cs index d39ebaad1..81ccc353a 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; } diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index ff6ac6882..7d46e6109 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 @@ -305,9 +308,6 @@ Wox.Plugin - - -