From a0a14488e47b54f78cee8343a3582baa4e0095c1 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 4 Mar 2011 21:42:37 +0100 Subject: [PATCH] Use MEF for extensibility in resource nodes. --- ILSpy/AboutPage.cs | 10 ++ ILSpy/App.xaml.cs | 12 +- ILSpy/BamlDecompiler.cs | 92 +++++++++- ILSpy/CSharpLanguage.cs | 2 +- ILSpy/ILSpy.csproj | 1 + ILSpy/TextView/AvalonEditTextOutput.cs | 11 +- ILSpy/TextView/DecompilerTextView.cs | 2 +- ILSpy/TextView/DecompilerTextView.xaml | 2 +- ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs | 2 +- ILSpy/TreeNodes/ILSpyTreeNode.cs | 4 +- ILSpy/TreeNodes/ResourceEntryNode.cs | 149 ++++++++-------- ILSpy/TreeNodes/ResourceListTreeNode.cs | 108 +----------- ILSpy/TreeNodes/ResourceTreeNode.cs | 174 +++++++++++++++++++ 13 files changed, 367 insertions(+), 202 deletions(-) create mode 100644 ILSpy/TreeNodes/ResourceTreeNode.cs diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index de8f7edd0..f11c412b9 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -52,6 +52,8 @@ namespace ICSharpCode.ILSpy }; }); output.WriteLine(); + foreach (var plugin in App.CompositionContainer.GetExportedValues()) + plugin.Write(output); output.WriteLine(); using (Stream s = typeof(AboutPage).Assembly.GetManifestResourceStream(typeof(AboutPage), "README.txt")) { using (StreamReader r = new StreamReader(s)) { @@ -256,4 +258,12 @@ namespace ICSharpCode.ILSpy return tcs.Task; } } + + /// + /// Interface that allows plugins to extend the about page. + /// + public interface IAboutPageAddition + { + void Write(ISmartTextOutput textOutput); + } } diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index 6004486c9..9f9cac6eb 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.ComponentModel.Composition.Hosting; using System.Diagnostics; using System.Threading; @@ -32,6 +33,12 @@ namespace ICSharpCode.ILSpy /// public partial class App : Application { + static CompositionContainer compositionContainer; + + public static CompositionContainer CompositionContainer { + get { return compositionContainer; } + } + public App() { InitializeComponent(); @@ -39,9 +46,10 @@ namespace ICSharpCode.ILSpy var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(typeof(App).Assembly)); catalog.Catalogs.Add(new DirectoryCatalog(".", "*.Plugin.dll")); - var container = new CompositionContainer(catalog); - Languages.Initialize(container); + compositionContainer = new CompositionContainer(catalog); + + Languages.Initialize(compositionContainer); if (!Debugger.IsAttached) { AppDomain.CurrentDomain.UnhandledException += ShowErrorBox; diff --git a/ILSpy/BamlDecompiler.cs b/ILSpy/BamlDecompiler.cs index df52eda97..66a38bbae 100644 --- a/ILSpy/BamlDecompiler.cs +++ b/ILSpy/BamlDecompiler.cs @@ -2,16 +2,23 @@ // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; +using System.ComponentModel.Composition; using System.IO; +using System.Linq; using System.Reflection; +using System.Threading.Tasks; using System.Windows.Baml2006; using System.Xaml; using System.Xml; -namespace ICSharpCode.ILSpy +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.TreeNodes; + +namespace ICSharpCode.ILSpy.Baml { /// Caution: use in separate AppDomain only! - public class BamlDecompiler : MarshalByRefObject + sealed class BamlDecompiler : MarshalByRefObject { public BamlDecompiler() { @@ -60,4 +67,85 @@ namespace ICSharpCode.ILSpy return w.ToString(); } } + + [Export(typeof(IResourceNodeFactory))] + sealed class BamlResourceNodeFactory : IResourceNodeFactory + { + public ILSpyTreeNode CreateNode(Mono.Cecil.Resource resource) + { + return null; + } + + public ILSpyTreeNode CreateNode(string key, Stream data) + { + if (key.EndsWith(".baml", StringComparison.OrdinalIgnoreCase)) + return new BamlResourceEntryNode(key, data); + else + return null; + } + } + + sealed class BamlResourceEntryNode : ResourceEntryNode + { + public BamlResourceEntryNode(string key, Stream data) : base(key, data) + { + } + + internal override bool View(DecompilerTextView textView) + { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + IHighlightingDefinition highlighting = null; + + textView.RunWithCancellation( + token => Task.Factory.StartNew( + () => { + try { + if (LoadBaml(output)) + highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".xml"); + } catch (Exception ex) { + output.Write(ex.ToString()); + } + return output; + }), + t => textView.Show(t.Result, highlighting) + ); + return true; + } + + bool LoadBaml(AvalonEditTextOutput output) + { + var asm = this.Ancestors().OfType().FirstOrDefault().LoadedAssembly; + + AppDomain bamlDecompilerAppDomain = null; + try { + BamlDecompiler decompiler = CreateBamlDecompilerInAppDomain(ref bamlDecompilerAppDomain, asm.FileName); + + MemoryStream bamlStream = new MemoryStream(); + data.Position = 0; + data.CopyTo(bamlStream); + + output.Write(decompiler.DecompileBaml(bamlStream, asm.FileName)); + return true; + } finally { + if (bamlDecompilerAppDomain != null) + AppDomain.Unload(bamlDecompilerAppDomain); + } + } + + public static BamlDecompiler CreateBamlDecompilerInAppDomain(ref AppDomain appDomain, string assemblyFileName) + { + if (appDomain == null) { + // Construct and initialize settings for a second AppDomain. + AppDomainSetup bamlDecompilerAppDomainSetup = new AppDomainSetup(); + bamlDecompilerAppDomainSetup.ApplicationBase = "file:///" + Path.GetDirectoryName(assemblyFileName); + bamlDecompilerAppDomainSetup.DisallowBindingRedirects = false; + bamlDecompilerAppDomainSetup.DisallowCodeDownload = true; + bamlDecompilerAppDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; + + // Create the second AppDomain. + appDomain = AppDomain.CreateDomain("BamlDecompiler AD", null, bamlDecompilerAppDomainSetup); + } + return (BamlDecompiler)appDomain.CreateInstanceFromAndUnwrap(typeof(BamlDecompiler).Assembly.Location, typeof(BamlDecompiler).FullName); + } + } } \ No newline at end of file diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 0192c43d3..c01caaf1c 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -324,7 +324,7 @@ namespace ICSharpCode.ILSpy if (fileName.EndsWith(".baml", StringComparison.OrdinalIgnoreCase)) { MemoryStream ms = new MemoryStream(); entryStream.CopyTo(ms); - BamlDecompiler decompiler = TreeNodes.ResourceEntryNode.CreateBamlDecompilerInAppDomain(ref bamlDecompilerAppDomain, assemblyFileName); + var decompiler = Baml.BamlResourceEntryNode.CreateBamlDecompilerInAppDomain(ref bamlDecompilerAppDomain, assemblyFileName); string xaml = null; try { xaml = decompiler.DecompileBaml(ms, assemblyFileName); diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 8ed9cd687..1110bf481 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -157,6 +157,7 @@ + diff --git a/ILSpy/TextView/AvalonEditTextOutput.cs b/ILSpy/TextView/AvalonEditTextOutput.cs index e3934a886..0bcdb7bd5 100644 --- a/ILSpy/TextView/AvalonEditTextOutput.cs +++ b/ILSpy/TextView/AvalonEditTextOutput.cs @@ -62,7 +62,7 @@ namespace ICSharpCode.ILSpy.TextView /// /// Text output implementation for AvalonEdit. /// - sealed class AvalonEditTextOutput : ISmartTextOutput + public sealed class AvalonEditTextOutput : ISmartTextOutput { readonly StringBuilder b = new StringBuilder(); @@ -78,18 +78,17 @@ namespace ICSharpCode.ILSpy.TextView Stack openFoldings = new Stack(); /// List of all foldings that were written to the output - public readonly List Foldings = new List(); + internal readonly List Foldings = new List(); - - public readonly DefinitionLookup DefinitionLookup = new DefinitionLookup(); + internal readonly DefinitionLookup DefinitionLookup = new DefinitionLookup(); /// Embedded UIElements, see . - public readonly List>> UIElements = new List>>(); + internal readonly List>> UIElements = new List>>(); /// /// Gets the list of references (hyperlinks). /// - public TextSegmentCollection References { + internal TextSegmentCollection References { get { return references; } } diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index f069340df..c4c951173 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.ILSpy.TextView /// Manages the TextEditor showing the decompiled code. /// Contains all the threading logic that makes the decompiler work in the background. /// - sealed partial class DecompilerTextView : UserControl + public sealed partial class DecompilerTextView : UserControl { readonly ReferenceElementGenerator referenceElementGenerator; readonly UIElementGenerator uiElementGenerator; diff --git a/ILSpy/TextView/DecompilerTextView.xaml b/ILSpy/TextView/DecompilerTextView.xaml index 161d8c8d5..3d99ced8e 100644 --- a/ILSpy/TextView/DecompilerTextView.xaml +++ b/ILSpy/TextView/DecompilerTextView.xaml @@ -1,4 +1,4 @@ - diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs index e91b048d8..8ab995e39 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs @@ -22,7 +22,7 @@ using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { - class AnalyzerTreeNode : SharpTreeNode + public class AnalyzerTreeNode : SharpTreeNode { Language language; diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index 960e32203..0ead7be94 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -29,7 +29,7 @@ namespace ICSharpCode.ILSpy.TreeNodes /// /// Base class of all ILSpy tree nodes. /// - abstract class ILSpyTreeNode : SharpTreeNode + public abstract class ILSpyTreeNode : SharpTreeNode { FilterSettings filterSettings; bool childrenNeedFiltering; @@ -166,7 +166,7 @@ namespace ICSharpCode.ILSpy.TreeNodes } } - enum FilterResult + public enum FilterResult { /// /// Hides the node. diff --git a/ILSpy/TreeNodes/ResourceEntryNode.cs b/ILSpy/TreeNodes/ResourceEntryNode.cs index dc982bf00..0585dbcda 100644 --- a/ILSpy/TreeNodes/ResourceEntryNode.cs +++ b/ILSpy/TreeNodes/ResourceEntryNode.cs @@ -6,20 +6,24 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using System.ComponentModel.Composition; using System.Windows.Controls; using System.Windows.Media.Imaging; - using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.TextView; using Microsoft.Win32; +using Mono.Cecil; namespace ICSharpCode.ILSpy.TreeNodes { - class ResourceEntryNode : ILSpyTreeNode + /// + /// Entry in a .resources file + /// + public class ResourceEntryNode : ILSpyTreeNode { - string key; - Stream value; + protected readonly string key; + protected readonly Stream data; public override object Text { get { return key.ToString(); } @@ -29,106 +33,93 @@ namespace ICSharpCode.ILSpy.TreeNodes get { return Images.Resource; } } - public ResourceEntryNode(string key, Stream value) + public ResourceEntryNode(string key, Stream data) { + if (key == null) + throw new ArgumentNullException("key"); + if (data == null) + throw new ArgumentNullException("data"); this.key = key; - this.value = value; + this.data = data; } - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + public static ILSpyTreeNode Create(string key, Stream data) { - language.WriteCommentLine(output, string.Format("{0} = {1}", key, value)); + ILSpyTreeNode result = null; + foreach (var factory in App.CompositionContainer.GetExportedValues()) { + result = factory.CreateNode(key, data); + if (result != null) + break; + } + return result ?? new ResourceEntryNode(key, data); } - internal override bool View(DecompilerTextView textView) + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) { - AvalonEditTextOutput output = new AvalonEditTextOutput(); - IHighlightingDefinition highlighting = null; - - if (LoadImage(output)) { - textView.Show(output, highlighting); - } else { - textView.RunWithCancellation( - token => Task.Factory.StartNew( - () => { - try { - if (LoadBaml(output)) - highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".xml"); - } catch (Exception ex) { - output.Write(ex.ToString()); - } - return output; - }), - t => textView.Show(t.Result, highlighting) - ); - } - return true; + language.WriteCommentLine(output, string.Format("{0} = {1}", key, data)); } - bool LoadImage(AvalonEditTextOutput output) + public override bool Save(DecompilerTextView textView) { - try { - value.Position = 0; - BitmapImage image = new BitmapImage(); - image.BeginInit(); - image.StreamSource = value; - image.EndInit(); - output.AddUIElement(() => new Image { Source = image }); - output.WriteLine(); - output.AddButton(Images.Save, "Save", delegate { Save(null); }); - } catch (Exception) { - return false; + SaveFileDialog dlg = new SaveFileDialog(); + dlg.FileName = Path.GetFileName(DecompilerTextView.CleanUpName(key)); + if (dlg.ShowDialog() == true) { + data.Position = 0; + using (var fs = dlg.OpenFile()) { + data.CopyTo(fs); + } } return true; } + } + + [Export(typeof(IResourceNodeFactory))] + sealed class ImageResourceNodeFactory : IResourceNodeFactory + { + static readonly string[] imageFileExtensions = { ".png", ".gif", ".bmp", ".jpg", ".ico" }; - bool LoadBaml(AvalonEditTextOutput output) + public ILSpyTreeNode CreateNode(Mono.Cecil.Resource resource) { - var asm = this.Ancestors().OfType().FirstOrDefault().LoadedAssembly; - - AppDomain bamlDecompilerAppDomain = null; - try { - BamlDecompiler decompiler = CreateBamlDecompilerInAppDomain(ref bamlDecompilerAppDomain, asm.FileName); - - MemoryStream bamlStream = new MemoryStream(); - value.Position = 0; - value.CopyTo(bamlStream); - - output.Write(decompiler.DecompileBaml(bamlStream, asm.FileName)); - return true; - } finally { - if (bamlDecompilerAppDomain != null) - AppDomain.Unload(bamlDecompilerAppDomain); + EmbeddedResource er = resource as EmbeddedResource; + if (er != null) { + return CreateNode(er.Name, er.GetResourceStream()); } + return null; } - public static BamlDecompiler CreateBamlDecompilerInAppDomain(ref AppDomain appDomain, string assemblyFileName) + public ILSpyTreeNode CreateNode(string key, Stream data) { - if (appDomain == null) { - // Construct and initialize settings for a second AppDomain. - AppDomainSetup bamlDecompilerAppDomainSetup = new AppDomainSetup(); - bamlDecompilerAppDomainSetup.ApplicationBase = "file:///" + Path.GetDirectoryName(assemblyFileName); - bamlDecompilerAppDomainSetup.DisallowBindingRedirects = false; - bamlDecompilerAppDomainSetup.DisallowCodeDownload = true; - bamlDecompilerAppDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; - - // Create the second AppDomain. - appDomain = AppDomain.CreateDomain("BamlDecompiler AD", null, bamlDecompilerAppDomainSetup); + foreach (string fileExt in imageFileExtensions) { + if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase)) + return new ImageResourceEntryNode(key, data); } - return (BamlDecompiler)appDomain.CreateInstanceFromAndUnwrap(typeof(BamlDecompiler).Assembly.Location, typeof(BamlDecompiler).FullName); + return null; + } + } + + sealed class ImageResourceEntryNode : ResourceEntryNode + { + public ImageResourceEntryNode(string key, Stream data) : base(key, data) + { } - public override bool Save(DecompilerTextView textView) + internal override bool View(DecompilerTextView textView) { - SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = Path.GetFileName(DecompilerTextView.CleanUpName(key)); - if (dlg.ShowDialog() == true) { - value.Position = 0; - using (var fs = dlg.OpenFile()) { - value.CopyTo(fs); - } + try { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + data.Position = 0; + BitmapImage image = new BitmapImage(); + image.BeginInit(); + image.StreamSource = data; + image.EndInit(); + output.AddUIElement(() => new Image { Source = image }); + output.WriteLine(); + output.AddButton(Images.Save, "Save", delegate { Save(null); }); + textView.Show(output, null); + return true; + } catch (Exception) { + return false; } - return true; } } } diff --git a/ILSpy/TreeNodes/ResourceListTreeNode.cs b/ILSpy/TreeNodes/ResourceListTreeNode.cs index 44ae1e2c5..07ea76324 100644 --- a/ILSpy/TreeNodes/ResourceListTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceListTreeNode.cs @@ -43,7 +43,7 @@ namespace ICSharpCode.ILSpy.TreeNodes protected override void LoadChildren() { foreach (Resource r in module.Resources) - this.Children.Add(new ResourceTreeNode(r)); + this.Children.Add(ResourceTreeNode.Create(r)); } public override FilterResult Filter(FilterSettings settings) @@ -63,110 +63,4 @@ namespace ICSharpCode.ILSpy.TreeNodes } } } - - class ResourceTreeNode : ILSpyTreeNode - { - Resource r; - - public ResourceTreeNode(Resource r) - { - this.LazyLoading = true; - this.r = r; - } - - public Resource Resource { - get { return r; } - } - - public override object Text { - get { return r.Name; } - } - - public override object Icon { - get { return Images.Resource; } - } - - public override FilterResult Filter(FilterSettings settings) - { - if (!settings.ShowInternalApi && (r.Attributes & ManifestResourceAttributes.VisibilityMask) == ManifestResourceAttributes.Private) - return FilterResult.Hidden; - if (settings.SearchTermMatches(r.Name)) - return FilterResult.Match; - else - return FilterResult.Hidden; - } - - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - language.WriteCommentLine(output, string.Format("{0} ({1}, {2})", r.Name, r.ResourceType, r.Attributes)); - - ISmartTextOutput smartOutput = output as ISmartTextOutput; - if (smartOutput != null && r is EmbeddedResource) { - smartOutput.AddButton(Images.Save, "Save", delegate { Save(null); }); - output.WriteLine(); - } - } - - internal override bool View(DecompilerTextView textView) - { - EmbeddedResource er = r as EmbeddedResource; - if (er != null) { - Stream s = er.GetResourceStream(); - if (s != null && s.Length < DecompilerTextView.DefaultOutputLengthLimit) { - s.Position = 0; - FileType type = GuessFileType.DetectFileType(s); - if (type != FileType.Binary) { - s.Position = 0; - AvalonEditTextOutput output = new AvalonEditTextOutput(); - output.Write(FileReader.OpenStream(s, Encoding.UTF8).ReadToEnd()); - string ext; - if (type == FileType.Xml) - ext = ".xml"; - else - ext = Path.GetExtension(DecompilerTextView.CleanUpName(er.Name)); - textView.Show(output, HighlightingManager.Instance.GetDefinitionByExtension(ext)); - return true; - } - } - } - return false; - } - - public override bool Save(TextView.DecompilerTextView textView) - { - EmbeddedResource er = r as EmbeddedResource; - if (er != null) { - SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(er.Name); - if (dlg.ShowDialog() == true) { - Stream s = er.GetResourceStream(); - s.Position = 0; - using (var fs = dlg.OpenFile()) { - s.CopyTo(fs); - } - } - return true; - } - return false; - } - - protected override void LoadChildren() - { - EmbeddedResource er = r as EmbeddedResource; - if (er != null) { - try { - Stream s = er.GetResourceStream(); - s.Position = 0; - if (er.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { - ResourceReader reader = new ResourceReader(s); - foreach (DictionaryEntry entry in reader.Cast().OrderBy(e => e.Key.ToString())) { - if (entry.Value is Stream) - Children.Add(new ResourceEntryNode(entry.Key.ToString(), (Stream)entry.Value)); - } - } - } catch (ArgumentException) { - } - } - } - } } diff --git a/ILSpy/TreeNodes/ResourceTreeNode.cs b/ILSpy/TreeNodes/ResourceTreeNode.cs new file mode 100644 index 000000000..92e6676f8 --- /dev/null +++ b/ILSpy/TreeNodes/ResourceTreeNode.cs @@ -0,0 +1,174 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Resources; +using System.Text; +using System.Windows; +using System.Windows.Threading; +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.AvalonEdit.Utils; +using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.TextView; +using Microsoft.Win32; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.TreeNodes +{ + public class ResourceTreeNode : ILSpyTreeNode + { + Resource r; + + public ResourceTreeNode(Resource r) + { + if (r == null) + throw new ArgumentNullException("r"); + this.r = r; + } + + public Resource Resource { + get { return r; } + } + + public override object Text { + get { return r.Name; } + } + + public override object Icon { + get { return Images.Resource; } + } + + public override FilterResult Filter(FilterSettings settings) + { + if (!settings.ShowInternalApi && (r.Attributes & ManifestResourceAttributes.VisibilityMask) == ManifestResourceAttributes.Private) + return FilterResult.Hidden; + if (settings.SearchTermMatches(r.Name)) + return FilterResult.Match; + else + return FilterResult.Hidden; + } + + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + { + language.WriteCommentLine(output, string.Format("{0} ({1}, {2})", r.Name, r.ResourceType, r.Attributes)); + + ISmartTextOutput smartOutput = output as ISmartTextOutput; + if (smartOutput != null && r is EmbeddedResource) { + smartOutput.AddButton(Images.Save, "Save", delegate { Save(null); }); + output.WriteLine(); + } + } + + internal override bool View(DecompilerTextView textView) + { + EmbeddedResource er = r as EmbeddedResource; + if (er != null) { + Stream s = er.GetResourceStream(); + if (s != null && s.Length < DecompilerTextView.DefaultOutputLengthLimit) { + s.Position = 0; + FileType type = GuessFileType.DetectFileType(s); + if (type != FileType.Binary) { + s.Position = 0; + AvalonEditTextOutput output = new AvalonEditTextOutput(); + output.Write(FileReader.OpenStream(s, Encoding.UTF8).ReadToEnd()); + string ext; + if (type == FileType.Xml) + ext = ".xml"; + else + ext = Path.GetExtension(DecompilerTextView.CleanUpName(er.Name)); + textView.Show(output, HighlightingManager.Instance.GetDefinitionByExtension(ext)); + return true; + } + } + } + return false; + } + + public override bool Save(TextView.DecompilerTextView textView) + { + EmbeddedResource er = r as EmbeddedResource; + if (er != null) { + SaveFileDialog dlg = new SaveFileDialog(); + dlg.FileName = DecompilerTextView.CleanUpName(er.Name); + if (dlg.ShowDialog() == true) { + Stream s = er.GetResourceStream(); + s.Position = 0; + using (var fs = dlg.OpenFile()) { + s.CopyTo(fs); + } + } + return true; + } + return false; + } + + public static ILSpyTreeNode Create(Resource resource) + { + ILSpyTreeNode result = null; + foreach (var factory in App.CompositionContainer.GetExportedValues()) { + result = factory.CreateNode(resource); + if (result != null) + break; + } + return result ?? new ResourceTreeNode(resource); + } + } + + /// + /// This interface allows plugins to create custom nodes for resources. + /// + public interface IResourceNodeFactory + { + ILSpyTreeNode CreateNode(Resource resource); + ILSpyTreeNode CreateNode(string key, Stream data); + } + + [Export(typeof(IResourceNodeFactory))] + sealed class ResourcesFileTreeNodeFactory : IResourceNodeFactory + { + public ILSpyTreeNode CreateNode(Resource resource) + { + EmbeddedResource er = resource as EmbeddedResource; + if (er != null && er.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { + return new ResourcesFileTreeNode(er); + } + return null; + } + + public ILSpyTreeNode CreateNode(string key, Stream data) + { + return null; + } + } + + sealed class ResourcesFileTreeNode : ResourceTreeNode + { + public ResourcesFileTreeNode(EmbeddedResource er) : base(er) + { + this.LazyLoading = true; + } + + protected override void LoadChildren() + { + EmbeddedResource er = this.Resource as EmbeddedResource; + if (er != null) { + Stream s = er.GetResourceStream(); + s.Position = 0; + ResourceReader reader; + try { + reader = new ResourceReader(s); + } catch (ArgumentException) { + return; + } + foreach (DictionaryEntry entry in reader.Cast().OrderBy(e => e.Key.ToString())) { + if (entry.Value is Stream) + Children.Add(ResourceEntryNode.Create(entry.Key.ToString(), (Stream)entry.Value)); + } + } + } + } +}