diff --git a/Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs b/Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs new file mode 100644 index 0000000000..58fbee9fa1 --- /dev/null +++ b/Plugins/Wox.Plugin.WebSearch/Properties/Annotations.cs @@ -0,0 +1,996 @@ +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace Wox.Plugin.WebSearch.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + public sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event)] + public sealed class NotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute { } + + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute(string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + public string FormatParameterName { get; private set; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute(string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

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