diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index ce1f104d6..9b0bbe36d 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -78,7 +78,7 @@ namespace ICSharpCode.ILSpy if (textView != null) reference = textView.GetReferenceSegmentAtMousePosition(); else if (listBox?.SelectedItem is SearchResult result) - reference = new ReferenceSegment { Reference = result.Member }; + reference = new ReferenceSegment { Reference = result.Reference }; else reference = null; var position = textView != null ? textView.GetPositionFromMousePosition() : null; diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 224a13594..07a789a4a 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -167,6 +167,10 @@ True Resources.resx + + Code + MSBuild:Compile + @@ -204,6 +208,10 @@ + + Code + MSBuild:Compile + SearchPane.xaml diff --git a/ILSpy/Search/AbstractEntitySearchStrategy.cs b/ILSpy/Search/AbstractEntitySearchStrategy.cs new file mode 100644 index 000000000..57382846b --- /dev/null +++ b/ILSpy/Search/AbstractEntitySearchStrategy.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Concurrent; +using System.Text.RegularExpressions; +using System.Threading; +using System.Windows.Media; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.TreeNodes; + +namespace ICSharpCode.ILSpy.Search +{ + abstract class AbstractEntitySearchStrategy : AbstractSearchStrategy + { + protected readonly Language language; + protected readonly ApiVisibility apiVisibility; + + protected AbstractEntitySearchStrategy(Language language, ApiVisibility apiVisibility, IProducerConsumerCollection resultQueue, params string[] terms) + : base(resultQueue, terms) + { + this.language = language; + this.apiVisibility = apiVisibility; + } + + protected bool CheckVisibility(IEntity entity) + { + if (apiVisibility == ApiVisibility.All) + return true; + + do { + if (apiVisibility == ApiVisibility.PublicOnly) { + if (!(entity.Accessibility == Accessibility.Public || + entity.Accessibility == Accessibility.Protected || + entity.Accessibility == Accessibility.ProtectedOrInternal)) + return false; + } else if (apiVisibility == ApiVisibility.PublicAndInternal) { + if (!language.ShowMember(entity)) + return false; + } + entity = entity.DeclaringTypeDefinition; + } + while (entity != null); + + return true; + } + + protected void OnFoundResult(IEntity entity) + { + var result = ResultFromEntity(entity); + OnFoundResult(result); + } + + SearchResult ResultFromEntity(IEntity item) + { + var declaringType = item.DeclaringTypeDefinition; + return new SearchResult { + Reference = item, + Fitness = CalculateFitness(item), + Image = GetIcon(item), + Name = GetLanguageSpecificName(item), + LocationImage = declaringType != null ? TypeTreeNode.GetIcon(declaringType) : Images.Namespace, + Location = declaringType != null ? language.TypeToString(declaringType, includeNamespace: true) : item.Namespace, + AssemblyImage = Images.Assembly, + Assembly = item.ParentModule.FullAssemblyName, + ToolTip = item.ParentModule.PEFile?.FileName + }; + } + + float CalculateFitness(IEntity member) + { + string text = member.Name; + + // Probably compiler generated types without meaningful names, show them last + if (text.StartsWith("<")) { + return 0; + } + + // Constructors always have the same name in IL: + // Use type name instead + if (text == ".cctor" || text == ".ctor") { + text = member.DeclaringType.Name; + } + + // Ignore generic arguments, it not possible to search based on them either + text = ReflectionHelper.SplitTypeParameterCountFromReflectionName(text); + + return 1.0f / text.Length; + } + + string GetLanguageSpecificName(IEntity member) + { + switch (member) { + case ITypeDefinition t: + return language.TypeToString(t, false); + case IField f: + return language.FieldToString(f, true, false, false); + case IProperty p: + return language.PropertyToString(p, true, false, false); + case IMethod m: + return language.MethodToString(m, true, false, false); + case IEvent e: + return language.EventToString(e, true, false, false); + default: + throw new NotSupportedException(member?.GetType() + " not supported!"); + } + } + + ImageSource GetIcon(IEntity member) + { + switch (member) { + case ITypeDefinition t: + return TypeTreeNode.GetIcon(t); + case IField f: + return FieldTreeNode.GetIcon(f); + case IProperty p: + return PropertyTreeNode.GetIcon(p); + case IMethod m: + return MethodTreeNode.GetIcon(m); + case IEvent e: + return EventTreeNode.GetIcon(e); + default: + throw new NotSupportedException(member?.GetType() + " not supported!"); + } + } + } +} diff --git a/ILSpy/Search/AbstractSearchStrategy.cs b/ILSpy/Search/AbstractSearchStrategy.cs index 80291ae31..b87bc3aed 100644 --- a/ILSpy/Search/AbstractSearchStrategy.cs +++ b/ILSpy/Search/AbstractSearchStrategy.cs @@ -15,14 +15,10 @@ namespace ICSharpCode.ILSpy.Search protected readonly Regex regex; protected readonly bool fullNameSearch; protected readonly bool omitGenerics; - protected readonly Language language; - protected readonly ApiVisibility apiVisibility; private readonly IProducerConsumerCollection resultQueue; - protected AbstractSearchStrategy(Language language, ApiVisibility apiVisibility, IProducerConsumerCollection resultQueue, params string[] terms) + protected AbstractSearchStrategy(IProducerConsumerCollection resultQueue, params string[] terms) { - this.language = language; - this.apiVisibility = apiVisibility; this.resultQueue = resultQueue; if (terms.Length == 1 && terms[0].Length > 2) { @@ -87,28 +83,6 @@ namespace ICSharpCode.ILSpy.Search return true; } - protected bool CheckVisibility(IEntity entity) - { - if (apiVisibility == ApiVisibility.All) - return true; - - do { - if (apiVisibility == ApiVisibility.PublicOnly) { - if (!(entity.Accessibility == Accessibility.Public || - entity.Accessibility == Accessibility.Protected || - entity.Accessibility == Accessibility.ProtectedOrInternal)) - return false; - } else if (apiVisibility == ApiVisibility.PublicAndInternal) { - if (!language.ShowMember(entity)) - return false; - } - entity = entity.DeclaringTypeDefinition; - } - while (entity != null); - - return true; - } - bool IsNoncontiguousMatch(string text, string searchTerm) { if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(searchTerm)) { @@ -135,10 +109,9 @@ namespace ICSharpCode.ILSpy.Search } return false; } - - protected void OnFoundResult(IEntity entity) + + protected void OnFoundResult(SearchResult result) { - var result = ResultFromEntity(entity); resultQueue.TryAdd(result); } @@ -150,75 +123,5 @@ namespace ICSharpCode.ILSpy.Search return null; } } - - SearchResult ResultFromEntity(IEntity item) - { - var declaringType = item.DeclaringTypeDefinition; - return new SearchResult { - Member = item, - Fitness = CalculateFitness(item), - Name = GetLanguageSpecificName(item), - Location = declaringType != null ? language.TypeToString(declaringType, includeNamespace: true) : item.Namespace, - Assembly = item.ParentModule.FullAssemblyName, - ToolTip = item.ParentModule.PEFile?.FileName - }; - } - - float CalculateFitness(IEntity member) - { - string text = member.Name; - - // Probably compiler generated types without meaningful names, show them last - if (text.StartsWith("<")) { - return 0; - } - - // Constructors always have the same name in IL: - // Use type name instead - if (text == ".cctor" || text == ".ctor") { - text = member.DeclaringType.Name; - } - - // Ignore generic arguments, it not possible to search based on them either - text = ReflectionHelper.SplitTypeParameterCountFromReflectionName(text); - - return 1.0f / text.Length; - } - - string GetLanguageSpecificName(IEntity member) - { - switch (member) { - case ITypeDefinition t: - return language.TypeToString(t, false); - case IField f: - return language.FieldToString(f, true, false, false); - case IProperty p: - return language.PropertyToString(p, true, false, false); - case IMethod m: - return language.MethodToString(m, true, false, false); - case IEvent e: - return language.EventToString(e, true, false, false); - default: - throw new NotSupportedException(member?.GetType() + " not supported!"); - } - } - - internal static ImageSource GetIcon(IEntity member) - { - switch (member) { - case ITypeDefinition t: - return TypeTreeNode.GetIcon(t); - case IField f: - return FieldTreeNode.GetIcon(f); - case IProperty p: - return PropertyTreeNode.GetIcon(p); - case IMethod m: - return MethodTreeNode.GetIcon(m); - case IEvent e: - return EventTreeNode.GetIcon(e); - default: - throw new NotSupportedException(member?.GetType() + " not supported!"); - } - } } } diff --git a/ILSpy/Search/LiteralSearchStrategy.cs b/ILSpy/Search/LiteralSearchStrategy.cs index 83c802d6a..c369d8f6f 100644 --- a/ILSpy/Search/LiteralSearchStrategy.cs +++ b/ILSpy/Search/LiteralSearchStrategy.cs @@ -15,7 +15,7 @@ using System.Collections.Concurrent; namespace ICSharpCode.ILSpy.Search { - class LiteralSearchStrategy : AbstractSearchStrategy + class LiteralSearchStrategy : AbstractEntitySearchStrategy { readonly TypeCode searchTermLiteralType; readonly object searchTermLiteralValue; diff --git a/ILSpy/Search/MemberSearchStrategy.cs b/ILSpy/Search/MemberSearchStrategy.cs index 4a631986b..fa1a11662 100644 --- a/ILSpy/Search/MemberSearchStrategy.cs +++ b/ILSpy/Search/MemberSearchStrategy.cs @@ -6,7 +6,7 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.ILSpy.Search { - class MemberSearchStrategy : AbstractSearchStrategy + class MemberSearchStrategy : AbstractEntitySearchStrategy { readonly MemberSearchKind searchKind; diff --git a/ILSpy/Search/MetadataTokenSearchStrategy.cs b/ILSpy/Search/MetadataTokenSearchStrategy.cs index 7ccec19b2..b2eec37df 100644 --- a/ILSpy/Search/MetadataTokenSearchStrategy.cs +++ b/ILSpy/Search/MetadataTokenSearchStrategy.cs @@ -9,7 +9,7 @@ using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.ILSpy.Search { - class MetadataTokenSearchStrategy : AbstractSearchStrategy + class MetadataTokenSearchStrategy : AbstractEntitySearchStrategy { readonly EntityHandle searchTermToken; diff --git a/ILSpy/Search/ResourceSearchStrategy.cs b/ILSpy/Search/ResourceSearchStrategy.cs new file mode 100644 index 000000000..b3da8b8f7 --- /dev/null +++ b/ILSpy/Search/ResourceSearchStrategy.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.TreeView; + +namespace ICSharpCode.ILSpy.Search +{ + class ResourceSearchStrategy : AbstractSearchStrategy + { + protected readonly bool searchInside; + protected readonly ApiVisibility apiVisibility; + + public ResourceSearchStrategy(ApiVisibility apiVisibility, IProducerConsumerCollection resultQueue, string term) + : this(apiVisibility, resultQueue, new[] { term }) + { + } + + public ResourceSearchStrategy(ApiVisibility apiVisibility, IProducerConsumerCollection resultQueue, string[] terms) + : base(resultQueue, terms) + { + this.apiVisibility = apiVisibility; + this.searchInside = true; + } + + protected bool CheckVisibility(Resource resource) + { + if (apiVisibility == ApiVisibility.All) + return true; + + if (apiVisibility == ApiVisibility.PublicOnly && (resource.Attributes & ManifestResourceAttributes.VisibilityMask) == ManifestResourceAttributes.Private) + return false; + + return true; + } + + public override void Search(PEFile module, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var resourcesNode = new ResourceListTreeNode(module); + + resourcesNode.EnsureLazyChildren(); + foreach (var node in resourcesNode.Children) + Search(module, null, resourcesNode, node, cancellationToken); + return; + } + + void Search(PEFile module, object reference, SharpTreeNode parent, SharpTreeNode node, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (node is ResourceTreeNode treeNode) { + if (!CheckVisibility(treeNode.Resource)) + return; + reference = treeNode.Resource; + } + + if (node.Text != null && IsMatch((string)node.Text)) + OnFoundResult(module, reference, node, parent); + + if (!searchInside) + return; + + node.EnsureLazyChildren(); + foreach (var child in node.Children) + Search(module, reference, node, child, cancellationToken); + } + + void OnFoundResult(PEFile module, object reference, SharpTreeNode node, SharpTreeNode parent) + { + var result = new SearchResult { + Reference = reference, + Fitness = 1f, + Image = (ImageSource)node.Icon, + Name = (string)node.Text, + LocationImage = (ImageSource)parent.Icon, + Location = (string)parent.Text, + AssemblyImage = Images.Assembly, + Assembly = module.Name, + ToolTip = module.FileName, + }; + OnFoundResult(result); + } + } +} diff --git a/ILSpy/Search/SearchPane.cs b/ILSpy/Search/SearchPane.cs index 3eb492740..5901b47ca 100644 --- a/ILSpy/Search/SearchPane.cs +++ b/ILSpy/Search/SearchPane.cs @@ -76,6 +76,7 @@ namespace ICSharpCode.ILSpy searchModeComboBox.Items.Add(new { Image = Images.Event, Name = "Event" }); searchModeComboBox.Items.Add(new { Image = Images.Literal, Name = "Constant" }); searchModeComboBox.Items.Add(new { Image = Images.Library, Name = "Metadata Token" }); + searchModeComboBox.Items.Add(new { Image = Images.ResourceResourcesFile, Name = "Resource" }); ContextMenuProvider.Add(listBox); MainWindow.Instance.CurrentAssemblyListChanged += MainWindow_Instance_CurrentAssemblyListChanged; @@ -263,7 +264,7 @@ namespace ICSharpCode.ILSpy void JumpToSelectedItem() { if (listBox.SelectedItem is SearchResult result) { - MainWindow.Instance.JumpToReference(result.Member); + MainWindow.Instance.JumpToReference(result.Reference); } } @@ -342,6 +343,9 @@ namespace ICSharpCode.ILSpy if (searchTerm[0].StartsWith("@", StringComparison.Ordinal)) return new MetadataTokenSearchStrategy(language, apiVisibility, resultQueue, searchTerm[0].Substring(1)); + + if (searchTerm[0].StartsWith("r:", StringComparison.Ordinal)) + return new ResourceSearchStrategy(apiVisibility, resultQueue, searchTerm[0].Substring(2)); } switch (searchMode) @@ -364,6 +368,8 @@ namespace ICSharpCode.ILSpy return new MemberSearchStrategy(language, apiVisibility, resultQueue, searchTerm, MemberSearchKind.Event); case SearchMode.Token: return new MetadataTokenSearchStrategy(language, apiVisibility, resultQueue, searchTerm); + case SearchMode.Resource: + return new ResourceSearchStrategy(apiVisibility, resultQueue, searchTerm); } return null; @@ -371,14 +377,16 @@ namespace ICSharpCode.ILSpy } } - public sealed class SearchResult : IMemberTreeNode + public sealed class SearchResult // : IMemberTreeNode { + public static readonly System.Collections.Generic.IComparer Comparer = new SearchResultComparer(); + ImageSource image; ImageSource locationImage; public static readonly IComparer Comparer = new SearchResultComparer(); - public IEntity Member { get; set; } + public object Reference { get; set; } public float Fitness { get; set; } public string Assembly { get; set; } @@ -447,6 +455,7 @@ namespace ICSharpCode.ILSpy Property, Event, Literal, - Token + Token, + Resource } } \ No newline at end of file