diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index af2a6d707..0c4c39f5f 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -31,6 +31,10 @@ namespace ICSharpCode.ILSpy get { return "C#"; } } + public override string FileExtension { + get { return ".cs"; } + } + public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) { throw new NotImplementedException(); diff --git a/ILSpy/ILLanguage.cs b/ILSpy/ILLanguage.cs index 2b1984524..069e6b39a 100644 --- a/ILSpy/ILLanguage.cs +++ b/ILSpy/ILLanguage.cs @@ -39,8 +39,8 @@ namespace ICSharpCode.ILSpy get { return detectControlStructure ? "IL (structured)" : "IL"; } } - public override ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting { - get { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinition("ILAsm"); } + public override string FileExtension { + get { return ".il"; } } public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 309936e65..5e947cc24 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -101,6 +101,8 @@ + + @@ -111,8 +113,10 @@ + + @@ -204,8 +208,5 @@ - - - \ No newline at end of file diff --git a/ILSpy/Images/Images.cs b/ILSpy/Images/Images.cs index 56313f3f7..30e987862 100644 --- a/ILSpy/Images/Images.cs +++ b/ILSpy/Images/Images.cs @@ -15,6 +15,9 @@ namespace ICSharpCode.ILSpy return image; } + public static readonly BitmapImage ViewCode = LoadBitmap("ViewCode"); + public static readonly BitmapImage Save = LoadBitmap("SaveFile"); + public static readonly BitmapImage Assembly = LoadBitmap("Assembly"); public static readonly BitmapImage AssemblyWarning = LoadBitmap("AssemblyWarning"); public static readonly BitmapImage Library = LoadBitmap("Library"); diff --git a/ILSpy/Images/SaveFile.png b/ILSpy/Images/SaveFile.png new file mode 100644 index 000000000..81acdcb98 Binary files /dev/null and b/ILSpy/Images/SaveFile.png differ diff --git a/ILSpy/Images/ViewCode.png b/ILSpy/Images/ViewCode.png new file mode 100644 index 000000000..1b8949fb6 Binary files /dev/null and b/ILSpy/Images/ViewCode.png differ diff --git a/ILSpy/Language.cs b/ILSpy/Language.cs index 92ac30b07..903f32159 100644 --- a/ILSpy/Language.cs +++ b/ILSpy/Language.cs @@ -30,9 +30,10 @@ namespace ICSharpCode.ILSpy public abstract class Language { public abstract string Name { get; } + public abstract string FileExtension { get; } public virtual ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting { - get { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinition(this.Name); } + get { return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinitionByExtension(this.FileExtension); } } public virtual void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index f08f929b4..05e70539f 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -25,6 +25,12 @@ + + + + + + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 0c586a877..2ac7bdeec 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -280,7 +280,16 @@ namespace ICSharpCode.ILSpy void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) { - decompilerTextView.Decompile(sessionSettings.FilterSettings.Language, treeView.SelectedItems.OfType()); + decompilerTextView.Decompile(sessionSettings.FilterSettings.Language, + treeView.GetTopLevelSelection().OfType(), + new DecompilationOptions()); + } + + void saveCode_Click(object sender, RoutedEventArgs e) + { + decompilerTextView.SaveToDisk(sessionSettings.FilterSettings.Language, + treeView.GetTopLevelSelection().OfType(), + new DecompilationOptions()); } protected override void OnStateChanged(EventArgs e) diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 1d89ff7b5..e137495f7 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -18,20 +18,25 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using System.Xml; + using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting.Xshd; +using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.TreeNodes; +using Microsoft.Win32; using Mono.Cecil; namespace ICSharpCode.ILSpy.TextView @@ -42,12 +47,14 @@ namespace ICSharpCode.ILSpy.TextView sealed partial class DecompilerTextView : UserControl { readonly ReferenceElementGenerator referenceElementGenerator; + readonly UIElementGenerator uiElementGenerator; readonly FoldingManager foldingManager; internal MainWindow mainWindow; DefinitionLookup definitionLookup; CancellationTokenSource currentCancellationTokenSource; + #region Constructor public DecompilerTextView() { HighlightingManager.Instance.RegisterHighlighting( @@ -63,11 +70,15 @@ namespace ICSharpCode.ILSpy.TextView InitializeComponent(); this.referenceElementGenerator = new ReferenceElementGenerator(this); textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); + this.uiElementGenerator = new UIElementGenerator(); + textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator); textEditor.Text = "Welcome to ILSpy!"; foldingManager = FoldingManager.Install(textEditor.TextArea); } + #endregion - public void Decompile(ILSpy.Language language, IEnumerable treeNodes) + #region RunWithCancellation + void RunWithCancellation(Func> taskCreation, Action> taskCompleted) { if (waitAdorner.Visibility != Visibility.Visible) { waitAdorner.Visibility = Visibility.Visible; @@ -80,35 +91,13 @@ namespace ICSharpCode.ILSpy.TextView if (previousCancellationTokenSource != null) previousCancellationTokenSource.Cancel(); - DecompilationOptions options = new DecompilationOptions(); - options.CancellationToken = myCancellationTokenSource.Token; - - var task = RunDecompiler(language, treeNodes.ToArray(), options); + var task = taskCreation(myCancellationTokenSource.Token); Action continuation = delegate { try { if (currentCancellationTokenSource == myCancellationTokenSource) { currentCancellationTokenSource = null; waitAdorner.Visibility = Visibility.Collapsed; - textEditor.ScrollToHome(); - foldingManager.Clear(); - try { - SmartTextOutput textOutput = task.Result; - referenceElementGenerator.References = textOutput.References; - definitionLookup = textOutput.DefinitionLookup; - textEditor.SyntaxHighlighting = language.SyntaxHighlighting; - textEditor.Text = textOutput.ToString(); - foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1); - } catch (AggregateException aggregateException) { - textEditor.SyntaxHighlighting = null; - referenceElementGenerator.References = null; - definitionLookup = null; - // Unpack aggregate exceptions as long as there's only a single exception: - // (assembly load errors might produce nested aggregate exceptions) - Exception ex = aggregateException; - while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1) - ex = ex.InnerException; - textEditor.Text = ex.ToString(); - } + taskCompleted(task); } else { try { task.Wait(); @@ -123,8 +112,71 @@ namespace ICSharpCode.ILSpy.TextView task.ContinueWith(delegate { Dispatcher.BeginInvoke(DispatcherPriority.Normal, continuation); }); } - static Task RunDecompiler(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options) + void cancelButton_Click(object sender, RoutedEventArgs e) { + if (currentCancellationTokenSource != null) + currentCancellationTokenSource.Cancel(); + } + #endregion + + #region ShowOutput + void ShowOutput(SmartTextOutput textOutput, ILSpy.Language language = null) + { + textEditor.ScrollToHome(); + foldingManager.Clear(); + uiElementGenerator.UIElements = textOutput.UIElements; + referenceElementGenerator.References = textOutput.References; + definitionLookup = textOutput.DefinitionLookup; + textEditor.SyntaxHighlighting = language != null ? language.SyntaxHighlighting : null; + textEditor.Text = textOutput.ToString(); + foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1); + } + #endregion + + #region Decompile (for display) + const int defaultOutputLengthLimit = 5000000; // more than 5M characters is too slow to output (when user browses treeview) + const int extendedOutputLengthLimit = 75000000; // more than 75M characters can get us into trouble with memory usage + + public void Decompile(ILSpy.Language language, IEnumerable treeNodes, DecompilationOptions options) + { + Decompile(language, treeNodes.ToArray(), defaultOutputLengthLimit, options); + } + + void Decompile(ILSpy.Language language, ILSpyTreeNodeBase[] treeNodes, int outputLengthLimit, DecompilationOptions options) + { + RunWithCancellation( + delegate (CancellationToken ct) { // creation of the background task + options.CancellationToken = ct; + return RunDecompiler(language, treeNodes, options, outputLengthLimit); + }, + delegate (Task task) { // handling the result + try { + SmartTextOutput textOutput = task.Result; + Debug.WriteLine("Decompiler finished; output size = {0} characters", textOutput.TextLength); + ShowOutput(textOutput, language); + } catch (AggregateException aggregateException) { + textEditor.SyntaxHighlighting = null; + Debug.WriteLine("Decompiler crashed: " + aggregateException.ToString()); + // Unpack aggregate exceptions as long as there's only a single exception: + // (assembly load errors might produce nested aggregate exceptions) + Exception ex = aggregateException; + while (ex is AggregateException && (ex as AggregateException).InnerExceptions.Count == 1) + ex = ex.InnerException; + if (ex is OutputLengthExceededException) { + ShowOutputLengthExceededMessage(language, treeNodes, options, outputLengthLimit == defaultOutputLengthLimit); + } else { + SmartTextOutput output = new SmartTextOutput(); + output.WriteLine(ex.ToString()); + ShowOutput(output); + } + } + }); + } + + static Task RunDecompiler(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, int outputLengthLimit) + { + Debug.WriteLine("Start decompilation of {0} nodes", nodes.Length); + if (nodes.Length == 0) { // If there's nothing to be decompiled, don't bother starting up a thread. // (Improves perf in some cases since we don't have to wait for the thread-pool to accept our task) @@ -136,16 +188,77 @@ namespace ICSharpCode.ILSpy.TextView return Task.Factory.StartNew( delegate { SmartTextOutput textOutput = new SmartTextOutput(); - bool first = true; - foreach (var node in nodes) { - if (first) first = false; else textOutput.WriteLine(); - options.CancellationToken.ThrowIfCancellationRequested(); - node.Decompile(language, textOutput, options); - } + textOutput.LengthLimit = outputLengthLimit; + DecompileNodes(language, nodes, options, textOutput); return textOutput; }); } + static void DecompileNodes(ILSpy.Language language, ILSpyTreeNodeBase[] nodes, DecompilationOptions options, ITextOutput textOutput) + { + bool first = true; + foreach (var node in nodes) { + if (first) first = false; else textOutput.WriteLine(); + options.CancellationToken.ThrowIfCancellationRequested(); + node.Decompile(language, textOutput, options); + } + } + #endregion + + #region ShowOutputLengthExceededMessage + void ShowOutputLengthExceededMessage(ILSpy.Language language, ILSpyTreeNodeBase[] treeNodes, DecompilationOptions options, bool wasNormalLimit) + { + SmartTextOutput output = new SmartTextOutput(); + if (wasNormalLimit) { + output.WriteLine("You have selected too much code for it to be displayed automatically."); + } else { + output.WriteLine("You have selected too much code; it cannot be displayed here."); + } + output.WriteLine(); + Button button; + if (wasNormalLimit) { + output.AddUIElement(MakeButton( + Images.ViewCode, "Display Code", + delegate { + Decompile(language, treeNodes, extendedOutputLengthLimit, options); + })); + output.WriteLine(); + } + + output.AddUIElement(MakeButton( + Images.Save, "Save Code", + delegate { + SaveToDisk(language, treeNodes, options); + })); + output.WriteLine(); + + ShowOutput(output); + } + + Func