.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

725 lines
26 KiB

// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using System.Xml;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.Debugger;
using ICSharpCode.ILSpy.Debugger.AvalonEdit;
using ICSharpCode.ILSpy.Debugger.Bookmarks;
using ICSharpCode.ILSpy.Debugger.Services;
using ICSharpCode.ILSpy.Debugger.Tooltips;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.XmlDoc;
using ICSharpCode.NRefactory.Documentation;
using Microsoft.Win32;
using Mono.Cecil;
using TextEditorWeakEventManager = ICSharpCode.ILSpy.Debugger.AvalonEdit.TextEditorWeakEventManager;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Manages the TextEditor showing the decompiled code.
/// Contains all the threading logic that makes the decompiler work in the background.
/// </summary>
[Export, PartCreationPolicy(CreationPolicy.Shared)]
public sealed partial class DecompilerTextView : UserControl
{
readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator;
List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>();
FoldingManager foldingManager;
DefinitionLookup definitionLookup;
CancellationTokenSource currentCancellationTokenSource;
[Import("IconMargin")]
IconBarMargin iconMargin = null;
[Import("TextMarkerService")]
TextMarkerService textMarkerService = null;
#region Constructor
public DecompilerTextView()
{
HighlightingManager.Instance.RegisterHighlighting(
"ILAsm", new string[] { ".il" },
delegate {
using (Stream s = typeof(DecompilerTextView).Assembly.GetManifestResourceStream(typeof(DecompilerTextView), "ILAsm-Mode.xshd")) {
using (XmlTextReader reader = new XmlTextReader(s)) {
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
}
});
InitializeComponent();
this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink);
textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator);
this.uiElementGenerator = new UIElementGenerator();
textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator);
textEditor.Options.RequireControlModifierForHyperlinkClick = false;
textEditor.TextArea.TextView.MouseHover += TextViewMouseHover;
textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped;
// wire the events
TextEditorWeakEventManager.MouseHover.AddListener(textEditor, TextEditorListener.Instance);
TextEditorWeakEventManager.MouseHoverStopped.AddListener(textEditor, TextEditorListener.Instance);
TextEditorWeakEventManager.MouseDown.AddListener(textEditor, TextEditorListener.Instance);
textEditor.TextArea.TextView.VisualLinesChanged += (s, e) => iconMargin.InvalidateVisual();
this.Loaded += new RoutedEventHandler(DecompilerTextView_Loaded);
}
void DecompilerTextView_Loaded(object sender, RoutedEventArgs e)
{
// add marker service & margin
textMarkerService.CodeEditor = textEditor;
textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService);
textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService);
textEditor.TextArea.LeftMargins.Add(iconMargin);
}
#endregion
#region Tooltip support
ToolTip tooltip;
void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
if (tooltip != null)
tooltip.IsOpen = false;
}
void TextViewMouseHover(object sender, MouseEventArgs e)
{
TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset);
if (position == null)
return;
int offset = textEditor.Document.GetOffset(position.Value);
ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault();
if (seg == null)
return;
object content = GenerateTooltip(seg);
if (tooltip != null)
tooltip.IsOpen = false;
if (content != null)
tooltip = new ToolTip() { Content = content, IsOpen = true };
}
object GenerateTooltip(ReferenceSegment segment)
{
if (segment.Reference is Mono.Cecil.Cil.OpCode) {
Mono.Cecil.Cil.OpCode code = (Mono.Cecil.Cil.OpCode)segment.Reference;
string encodedName = code.Code.ToString();
string opCodeHex = code.Size > 1 ? string.Format("0x{0:x2}{1:x2}", code.Op1, code.Op2) : string.Format("0x{0:x2}", code.Op2);
XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation;
if (docProvider != null){
string documentation = docProvider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + encodedName);
if (documentation != null) {
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText(string.Format("{0} ({1}) - ", code.Name, opCodeHex));
renderer.AddXmlDocumentation(documentation);
return renderer.CreateTextBlock();
}
}
return string.Format("{0} ({1})", code.Name, opCodeHex);
} else if (segment.Reference is MemberReference) {
MemberReference mr = (MemberReference)segment.Reference;
// if possible, resolve the reference
if (mr is TypeReference) {
mr = ((TypeReference)mr).Resolve() ?? mr;
} else if (mr is MethodReference) {
mr = ((MethodReference)mr).Resolve() ?? mr;
}
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText(MainWindow.Instance.CurrentLanguage.GetTooltip(mr));
XmlDocumentationProvider docProvider = XmlDocLoader.LoadDocumentation(mr.Module);
if (docProvider != null) {
string documentation = docProvider.GetDocumentation(XmlDocKeyProvider.GetKey(mr));
if (documentation != null) {
renderer.AppendText(Environment.NewLine);
renderer.AddXmlDocumentation(documentation);
}
}
return renderer.CreateTextBlock();
}
return null;
}
#endregion
#region RunWithCancellation
/// <summary>
/// Switches the GUI into "waiting" mode, then calls <paramref name="taskCreation"/> to create
/// the task.
/// When the task completes without being cancelled, the <paramref name="taskCompleted"/>
/// callback is called on the GUI thread.
/// When the task is cancelled before completing, the callback is not called; and any result
/// of the task (including exceptions) are ignored.
/// </summary>
public void RunWithCancellation<T>(Func<CancellationToken, Task<T>> taskCreation, Action<Task<T>> taskCompleted)
{
if (waitAdorner.Visibility != Visibility.Visible) {
waitAdorner.Visibility = Visibility.Visible;
waitAdorner.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.5)), FillBehavior.Stop));
}
CancellationTokenSource previousCancellationTokenSource = currentCancellationTokenSource;
var myCancellationTokenSource = new CancellationTokenSource();
currentCancellationTokenSource = myCancellationTokenSource;
// cancel the previous only after current was set to the new one (avoid that the old one still finishes successfully)
if (previousCancellationTokenSource != null)
previousCancellationTokenSource.Cancel();
var task = taskCreation(myCancellationTokenSource.Token);
Action continuation = delegate {
try {
if (currentCancellationTokenSource == myCancellationTokenSource) {
currentCancellationTokenSource = null;
waitAdorner.Visibility = Visibility.Collapsed;
taskCompleted(task);
} else {
try {
task.Wait();
} catch (AggregateException) {
// observe the exception (otherwise the task's finalizer will shut down the AppDomain)
}
}
} finally {
myCancellationTokenSource.Dispose();
}
};
task.ContinueWith(delegate { Dispatcher.BeginInvoke(DispatcherPriority.Normal, continuation); });
}
void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel();
// Don't set to null: the task still needs to produce output and hide the wait adorner
}
}
#endregion
#region ShowOutput
/// <summary>
/// Shows the given output in the text view.
/// Cancels any currently running decompilation tasks.
/// </summary>
public void Show(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null)
{
// Cancel the decompilation task:
if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel();
currentCancellationTokenSource = null; // prevent canceled task from producing output
}
this.nextDecompilationRun = null; // remove scheduled decompilation run
ShowOutput(textOutput, highlighting);
}
/// <summary>
/// Shows the given output in the text view.
/// </summary>
void ShowOutput(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null, DecompilerTextViewState state = null)
{
Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength);
Stopwatch w = Stopwatch.StartNew();
textEditor.ScrollToHome();
if (foldingManager != null) {
FoldingManager.Uninstall(foldingManager);
foldingManager = null;
}
textEditor.Document = null; // clear old document while we're changing the highlighting
uiElementGenerator.UIElements = textOutput.UIElements;
referenceElementGenerator.References = textOutput.References;
definitionLookup = textOutput.DefinitionLookup;
textEditor.SyntaxHighlighting = highlighting;
// Change the set of active element generators:
foreach (var elementGenerator in activeCustomElementGenerators) {
textEditor.TextArea.TextView.ElementGenerators.Remove(elementGenerator);
}
activeCustomElementGenerators.Clear();
foreach (var elementGenerator in textOutput.elementGenerators) {
textEditor.TextArea.TextView.ElementGenerators.Add(elementGenerator);
activeCustomElementGenerators.Add(elementGenerator);
}
Debug.WriteLine(" Set-up: {0}", w.Elapsed); w.Restart();
textEditor.Document = textOutput.GetDocument();
Debug.WriteLine(" Assigning document: {0}", w.Elapsed); w.Restart();
if (textOutput.Foldings.Count > 0) {
if (state != null) {
state.RestoreFoldings(textOutput.Foldings);
textEditor.ScrollToVerticalOffset(state.VerticalOffset);
textEditor.ScrollToHorizontalOffset(state.HorizontalOffset);
}
foldingManager = FoldingManager.Install(textEditor.TextArea);
foldingManager.UpdateFoldings(textOutput.Foldings.OrderBy(f => f.StartOffset), -1);
Debug.WriteLine(" Updating folding: {0}", w.Elapsed); w.Restart();
}
}
#endregion
#region Decompile (for display)
// more than 5M characters is too slow to output (when user browses treeview)
public const int DefaultOutputLengthLimit = 5000000;
// more than 75M characters can get us into trouble with memory usage
public const int ExtendedOutputLengthLimit = 75000000;
DecompilationContext nextDecompilationRun;
/// <summary>
/// Starts the decompilation of the given nodes.
/// The result is displayed in the text view.
/// </summary>
public void Decompile(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options)
{
// Some actions like loading an assembly list cause several selection changes in the tree view,
// and each of those will start a decompilation action.
bool isDecompilationScheduled = this.nextDecompilationRun != null;
this.nextDecompilationRun = new DecompilationContext(language, treeNodes.ToArray(), options);
if (!isDecompilationScheduled) {
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(
delegate {
var context = this.nextDecompilationRun;
this.nextDecompilationRun = null;
if (context != null)
DoDecompile(context, DefaultOutputLengthLimit);
}
));
}
}
sealed class DecompilationContext
{
public readonly ILSpy.Language Language;
public readonly ILSpyTreeNode[] TreeNodes;
public readonly DecompilationOptions Options;
public DecompilationContext(ILSpy.Language language, ILSpyTreeNode[] treeNodes, DecompilationOptions options)
{
this.Language = language;
this.TreeNodes = treeNodes;
this.Options = options;
}
}
void DoDecompile(DecompilationContext context, int outputLengthLimit)
{
// close popup
TextEditorListener.Instance.ClosePopup();
bool isDecompilationOk = true;
RunWithCancellation(
delegate (CancellationToken ct) { // creation of the background task
context.Options.CancellationToken = ct;
return DecompileAsync(context, outputLengthLimit);
},
delegate (Task<AvalonEditTextOutput> task) { // handling the result
try {
AvalonEditTextOutput textOutput = task.Result;
ShowOutput(textOutput, context.Language.SyntaxHighlighting, context.Options.TextViewState);
} 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;
AvalonEditTextOutput output = new AvalonEditTextOutput();
if (ex is OutputLengthExceededException) {
WriteOutputLengthExceededMessage(output, context, outputLengthLimit == DefaultOutputLengthLimit);
} else {
output.WriteLine(ex.ToString());
}
ShowOutput(output);
isDecompilationOk = false;
} finally {
this.UpdateDebugUI(isDecompilationOk);
}
});
}
void UpdateDebugUI(bool isDecompilationOk)
{
// sync bookmarks
iconMargin.SyncBookmarks();
if (isDecompilationOk) {
if (DebugData.DebugStepInformation != null) {
// repaint bookmarks
iconMargin.InvalidateVisual();
// show the currentline marker
int token = DebugData.DebugStepInformation.Item1;
int ilOffset = DebugData.DebugStepInformation.Item2;
int line;
MemberReference member;
DebugData.CodeMappings[token].GetInstructionByTokenAndOffset(token, ilOffset, out member, out line);
DebuggerService.RemoveCurrentLineMarker();
DebuggerService.JumpToCurrentLine(member, line, 0, line, 0);
// create marker
var bm = CurrentLineBookmark.Instance;
DocumentLine docline = textEditor.Document.GetLineByNumber(line);
bm.Marker = bm.CreateMarker(textMarkerService, docline.Offset + 1, docline.Length);
UnfoldAndScroll(line);
}
} else {
// remove currentline marker
CurrentLineBookmark.Remove();
}
}
static Task<AvalonEditTextOutput> DecompileAsync(DecompilationContext context, int outputLengthLimit)
{
Debug.WriteLine("Start decompilation of {0} tree nodes", context.TreeNodes.Length);
TaskCompletionSource<AvalonEditTextOutput> tcs = new TaskCompletionSource<AvalonEditTextOutput>();
if (context.TreeNodes.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)
tcs.SetResult(new AvalonEditTextOutput());
return tcs.Task;
}
Thread thread = new Thread(new ThreadStart(
delegate {
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached) {
try {
AvalonEditTextOutput textOutput = new AvalonEditTextOutput();
textOutput.LengthLimit = outputLengthLimit;
DecompileNodes(context, textOutput);
textOutput.PrepareDocument();
tcs.SetResult(textOutput);
} catch (AggregateException ex) {
tcs.SetException(ex);
} catch (OperationCanceledException ex) {
tcs.SetException(ex);
}
} else
#endif
{
try {
AvalonEditTextOutput textOutput = new AvalonEditTextOutput();
textOutput.LengthLimit = outputLengthLimit;
DecompileNodes(context, textOutput);
textOutput.PrepareDocument();
tcs.SetResult(textOutput);
} catch (Exception ex) {
tcs.SetException(ex);
}
}
}));
thread.Start();
return tcs.Task;
}
static void DecompileNodes(DecompilationContext context, ITextOutput textOutput)
{
// reset data
DebugData.OldCodeMappings = DebugData.CodeMappings;
DebugData.CodeMappings = null;
DebugData.LocalVariables = null;
DebugData.DecompiledMemberReferences = null;
// set the language
DebugData.Language = MainWindow.Instance.sessionSettings.FilterSettings.Language.Name.StartsWith("IL") ? DecompiledLanguages.IL : DecompiledLanguages.CSharp;
var nodes = context.TreeNodes;
context.Language.DecompileFinished += Language_DecompileFinished;
for (int i = 0; i < nodes.Length; i++) {
if (i > 0)
textOutput.WriteLine();
context.Options.CancellationToken.ThrowIfCancellationRequested();
nodes[i].Decompile(context.Language, textOutput, context.Options);
}
context.Language.DecompileFinished -= Language_DecompileFinished;
}
static void Language_DecompileFinished(object sender, DecompileEventArgs e)
{
if (e != null) {
if (DebugData.CodeMappings == null) {
DebugData.CodeMappings = e.CodeMappings;
DebugData.LocalVariables = e.LocalVariables;
DebugData.DecompiledMemberReferences = e.DecompiledMemberReferences;
} else {
DebugData.CodeMappings.AddRange(e.CodeMappings);
DebugData.DecompiledMemberReferences.AddRange(e.DecompiledMemberReferences);
if (e.LocalVariables != null)
DebugData.LocalVariables.AddRange(e.LocalVariables);
}
}
}
#endregion
#region WriteOutputLengthExceededMessage
/// <summary>
/// Creates a message that the decompiler output was too long.
/// The message contains buttons that allow re-trying (with larger limit) or saving to a file.
/// </summary>
void WriteOutputLengthExceededMessage(ISmartTextOutput output, DecompilationContext context, bool wasNormalLimit)
{
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();
if (wasNormalLimit) {
output.AddButton(
Images.ViewCode, "Display Code",
delegate {
DoDecompile(context, ExtendedOutputLengthLimit);
});
output.WriteLine();
}
output.AddButton(
Images.Save, "Save Code",
delegate {
SaveToDisk(context.Language, context.TreeNodes, context.Options);
});
output.WriteLine();
}
#endregion
#region JumpToReference
/// <summary>
/// Jumps to the definition referred to by the <see cref="ReferenceSegment"/>.
/// </summary>
internal void JumpToReference(ReferenceSegment referenceSegment)
{
object reference = referenceSegment.Reference;
if (definitionLookup != null) {
int pos = definitionLookup.GetDefinitionPosition(reference);
if (pos >= 0) {
textEditor.TextArea.Focus();
textEditor.Select(pos, 0);
textEditor.ScrollTo(textEditor.TextArea.Caret.Line, textEditor.TextArea.Caret.Column);
Dispatcher.Invoke(DispatcherPriority.Background, new Action(
delegate {
CaretHighlightAdorner.DisplayCaretHighlightAnimation(textEditor.TextArea);
}));
return;
}
}
MainWindow.Instance.JumpToReference(reference);
}
/// <summary>
/// Filters all ReferenceSegments that are no real links.
/// </summary>
bool IsLink(ReferenceSegment referenceSegment)
{
return true;
}
#endregion
#region SaveToDisk
/// <summary>
/// Shows the 'save file dialog', prompting the user to save the decompiled nodes to disk.
/// </summary>
public void SaveToDisk(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options)
{
if (!treeNodes.Any())
return;
SaveFileDialog dlg = new SaveFileDialog();
dlg.DefaultExt = language.FileExtension;
dlg.Filter = language.Name + "|*" + language.FileExtension + "|All Files|*.*";
dlg.FileName = CleanUpName(treeNodes.First().ToString()) + language.FileExtension;
if (dlg.ShowDialog() == true) {
SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), dlg.FileName);
}
}
public void SaveToDisk(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options, string fileName)
{
SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), fileName);
}
/// <summary>
/// Starts the decompilation of the given nodes.
/// The result will be saved to the given file name.
/// </summary>
void SaveToDisk(DecompilationContext context, string fileName)
{
RunWithCancellation(
delegate (CancellationToken ct) {
context.Options.CancellationToken = ct;
return SaveToDiskAsync(context, fileName);
},
delegate (Task<AvalonEditTextOutput> task) {
try {
ShowOutput(task.Result);
} 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;
AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine(ex.ToString());
ShowOutput(output);
}
});
}
Task<AvalonEditTextOutput> SaveToDiskAsync(DecompilationContext context, string fileName)
{
TaskCompletionSource<AvalonEditTextOutput> tcs = new TaskCompletionSource<AvalonEditTextOutput>();
Thread thread = new Thread(new ThreadStart(
delegate {
try {
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
using (StreamWriter w = new StreamWriter(fileName)) {
try {
DecompileNodes(context, new PlainTextOutput(w));
} catch (OperationCanceledException) {
w.WriteLine();
w.WriteLine("Decompiled was cancelled.");
throw;
}
}
stopwatch.Stop();
AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine("Decompilation complete in " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + " seconds.");
output.WriteLine();
output.AddButton(null, "Open Explorer", delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); });
output.WriteLine();
tcs.SetResult(output);
#if DEBUG
} catch (OperationCanceledException ex) {
tcs.SetException(ex);
} catch (AggregateException ex) {
tcs.SetException(ex);
#else
} catch (Exception ex) {
tcs.SetException(ex);
#endif
}
}));
thread.Start();
return tcs.Task;
}
/// <summary>
/// Cleans up a node name for use as a file name.
/// </summary>
internal static string CleanUpName(string text)
{
int pos = text.IndexOf(':');
if (pos > 0)
text = text.Substring(0, pos);
pos = text.IndexOf('`');
if (pos > 0)
text = text.Substring(0, pos);
text = text.Trim();
foreach (char c in Path.GetInvalidFileNameChars())
text = text.Replace(c, '-');
return text;
}
#endregion
public DecompilerTextViewState GetState()
{
var state = new DecompilerTextViewState();
if (foldingManager != null)
state.SaveFoldingsState(foldingManager.AllFoldings);
state.VerticalOffset = textEditor.VerticalOffset;
state.HorizontalOffset = textEditor.HorizontalOffset;
return state;
}
#region Unfold
public void UnfoldAndScroll(int lineNumber)
{
if (lineNumber <= 0 || lineNumber > textEditor.Document.LineCount)
return;
var line = textEditor.Document.GetLineByNumber(lineNumber);
// unfold
var foldings = foldingManager.GetFoldingsContaining(line.Offset);
if (foldings != null) {
foreach (var folding in foldings) {
if (folding.IsFolded) {
folding.IsFolded = false;
}
}
}
// scroll to
textEditor.ScrollTo(lineNumber, 0);
}
#endregion
}
public class DecompilerTextViewState
{
private List<Tuple<int, int>> ExpandedFoldings;
private int FoldingsChecksum;
public double VerticalOffset;
public double HorizontalOffset;
public void SaveFoldingsState(IEnumerable<FoldingSection> foldings)
{
ExpandedFoldings = foldings.Where(f => !f.IsFolded).Select(f => Tuple.Create(f.StartOffset, f.EndOffset)).ToList();
FoldingsChecksum = foldings.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b);
}
internal void RestoreFoldings(List<NewFolding> list)
{
var checksum = list.Select(f => f.StartOffset * 3 - f.EndOffset).Aggregate((a, b) => a + b);
if (FoldingsChecksum == checksum)
foreach (var folding in list)
folding.DefaultClosed = !ExpandedFoldings.Any(f => f.Item1 == folding.StartOffset && f.Item2 == folding.EndOffset);
}
}
}