diff --git a/ICSharpCode.Decompiler/GraphVizGraph.cs b/ICSharpCode.Decompiler/GraphVizGraph.cs index fcfb65234..62b957f25 100644 --- a/ICSharpCode.Decompiler/GraphVizGraph.cs +++ b/ICSharpCode.Decompiler/GraphVizGraph.cs @@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler public void Save(TextWriter writer) { writer.WriteLine("digraph G {"); - writer.WriteLine("node [fontsize = 14.5];"); + writer.WriteLine("node [fontsize = 16];"); WriteGraphAttribute(writer, "rankdir", rankdir); foreach (GraphVizNode node in nodes) { node.Save(writer); diff --git a/ILSpy/AssemblyList.cs b/ILSpy/AssemblyList.cs index 18ed78c23..2ea211502 100644 --- a/ILSpy/AssemblyList.cs +++ b/ILSpy/AssemblyList.cs @@ -43,6 +43,8 @@ namespace ICSharpCode.ILSpy public TypeTreeNode FindTypeNode(TypeDefinition def) { + if (def == null) + return null; if (def.DeclaringType != null) { TypeTreeNode decl = FindTypeNode(def.DeclaringType); if (decl != null) { @@ -62,6 +64,42 @@ namespace ICSharpCode.ILSpy return null; } + public MethodTreeNode FindMethodNode(MethodDefinition def) + { + if (def == null) + return null; + TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + typeNode.EnsureLazyChildren(); + return typeNode.Children.OfType().FirstOrDefault(m => m.MethodDefinition == def); + } + + public FieldTreeNode FindFieldNode(FieldDefinition def) + { + if (def == null) + return null; + TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + typeNode.EnsureLazyChildren(); + return typeNode.Children.OfType().FirstOrDefault(m => m.FieldDefinition == def); + } + + public PropertyTreeNode FindPropertyNode(PropertyDefinition def) + { + if (def == null) + return null; + TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + typeNode.EnsureLazyChildren(); + return typeNode.Children.OfType().FirstOrDefault(m => m.PropertyDefinition == def); + } + + public EventTreeNode FindEventNode(EventDefinition def) + { + if (def == null) + return null; + TypeTreeNode typeNode = FindTypeNode(def.DeclaringType); + typeNode.EnsureLazyChildren(); + return typeNode.Children.OfType().FirstOrDefault(m => m.EventDefinition == def); + } + public AssemblyTreeNode OpenAssembly(string file) { App.Current.Dispatcher.VerifyAccess(); diff --git a/ILSpy/CaretHighlightAdorner.cs b/ILSpy/CaretHighlightAdorner.cs new file mode 100644 index 000000000..a8c7b119e --- /dev/null +++ b/ILSpy/CaretHighlightAdorner.cs @@ -0,0 +1,60 @@ +// 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.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Threading; +using ICSharpCode.AvalonEdit.Editing; + +namespace ICSharpCode.ILSpy +{ + /// + /// Animated rectangle around the caret. + /// + sealed class CaretHighlightAdorner : Adorner + { + Pen pen; + RectangleGeometry geometry; + + public CaretHighlightAdorner(TextArea textArea) + : base(textArea.TextView) + { + Rect min = textArea.Caret.CalculateCaretRectangle(); + min.Offset(-textArea.TextView.ScrollOffset); + + Rect max = min; + double size = Math.Max(min.Width, min.Height) * 0.25; + max.Inflate(size, size); + + pen = new Pen(TextBlock.GetForeground(textArea.TextView).Clone(), 1); + + geometry = new RectangleGeometry(min, 2, 2); + geometry.BeginAnimation(RectangleGeometry.RectProperty, new RectAnimation(min, max, new Duration(TimeSpan.FromMilliseconds(300))) { AutoReverse = true }); + pen.Brush.BeginAnimation(Brush.OpacityProperty, new DoubleAnimation(1, 0, new Duration(TimeSpan.FromMilliseconds(200))) { BeginTime = TimeSpan.FromMilliseconds(450) }); + } + + public static void DisplayCaretHighlightAnimation(TextArea textArea) + { + AdornerLayer layer = AdornerLayer.GetAdornerLayer(textArea.TextView); + CaretHighlightAdorner adorner = new CaretHighlightAdorner(textArea); + layer.Add(adorner); + + DispatcherTimer timer = new DispatcherTimer(); + timer.Interval = TimeSpan.FromSeconds(1); + timer.Tick += delegate { + timer.Stop(); + layer.Remove(adorner); + }; + timer.Start(); + } + + protected override void OnRender(DrawingContext drawingContext) + { + drawingContext.DrawGeometry(null, pen, geometry); + } + } +} diff --git a/ILSpy/Disassembler/DisassemblerHelpers.cs b/ILSpy/Disassembler/DisassemblerHelpers.cs index 7a9615ba7..1c0898ab2 100644 --- a/ILSpy/Disassembler/DisassemblerHelpers.cs +++ b/ILSpy/Disassembler/DisassemblerHelpers.cs @@ -25,20 +25,38 @@ namespace ICSharpCode.ILSpy.Disassembler { static class DisassemblerHelpers { - #region Debug output(ToString helpers) + static void WriteOffsetReference(ITextOutput writer, int offset) + { + writer.WriteReference(CecilExtensions.OffsetToString(offset), offset); + } + public static void WriteTo(this ExceptionHandler exceptionHandler, ITextOutput writer) { - writer.Write("Try IL_{0:x4}-IL_{1:x4} ", exceptionHandler.TryStart.Offset, exceptionHandler.TryEnd.Offset); + writer.Write("Try "); + WriteOffsetReference(writer, exceptionHandler.TryStart.Offset); + writer.Write('-'); + WriteOffsetReference(writer, exceptionHandler.TryEnd.Offset); writer.Write(exceptionHandler.HandlerType.ToString()); if (exceptionHandler.FilterStart != null) { - writer.Write(" IL_{0:x4}-IL_{1:x4} handler ", exceptionHandler.FilterStart.Offset, exceptionHandler.FilterEnd.Offset); + writer.Write(' '); + WriteOffsetReference(writer, exceptionHandler.FilterStart.Offset); + writer.Write('-'); + WriteOffsetReference(writer, exceptionHandler.FilterEnd.Offset); + writer.Write(" handler "); + } + if (exceptionHandler.CatchType != null) { + writer.Write(' '); + exceptionHandler.CatchType.WriteTo(writer); } - writer.Write(" IL_{0:x4}-IL_{1:x4} ", exceptionHandler.HandlerStart.Offset, exceptionHandler.HandlerEnd.Offset); + writer.Write(' '); + WriteOffsetReference(writer, exceptionHandler.HandlerStart.Offset); + writer.Write('-'); + WriteOffsetReference(writer, exceptionHandler.HandlerEnd.Offset); } public static void WriteTo(this Instruction instruction, ITextOutput writer) { - writer.Write(CecilExtensions.OffsetToString(instruction.Offset)); + writer.WriteDefinition(CecilExtensions.OffsetToString(instruction.Offset), instruction.Offset); writer.Write(": "); writer.Write(instruction.OpCode.Name); if(null != instruction.Operand) { @@ -52,7 +70,7 @@ namespace ICSharpCode.ILSpy.Disassembler writer.Write("("); for(int i = 0; i < instructions.Length; i++) { if(i != 0) writer.Write(", "); - writer.Write(CecilExtensions.OffsetToString(instructions [i].Offset)); + WriteOffsetReference(writer, instructions[i].Offset); } writer.Write(")"); } @@ -65,95 +83,99 @@ namespace ICSharpCode.ILSpy.Disassembler : value.ToString(); } - static void WriteMethodReference(ITextOutput writer, MethodReference method) + static void WriteTo(this MethodReference method, ITextOutput writer) { - writer.Write(FormatTypeReference(method.ReturnType)); + method.ReturnType.WriteTo(writer); writer.Write(' '); - writer.Write(FormatTypeReference(method.DeclaringType)); + method.DeclaringType.WriteTo(writer); writer.Write("::"); - writer.Write(method.Name); + writer.WriteReference(method.Name, method); writer.Write("("); var parameters = method.Parameters; - for(int i=0; i < parameters.Count; ++i) { - if(i > 0) writer.Write(", "); - writer.Write(FormatTypeReference(parameters [i].ParameterType)); + for(int i = 0; i < parameters.Count; ++i) { + if (i > 0) writer.Write(", "); + parameters[i].ParameterType.WriteTo(writer); } writer.Write(")"); } - static string FormatTypeReference(TypeReference type) + static void WriteTo(this TypeReference type, ITextOutput writer) { - string typeName = type.FullName; - switch(typeName) { - case "System.Void": return "void"; - case "System.String": return "string"; - case "System.Int32": return "int32"; - case "System.Long": return "int64"; - case "System.Boolean": return "bool"; - case "System.Single": return "float32"; - case "System.Double": return "float64"; - } - return typeName; + string name = ShortTypeName(type); + if (name != null) + writer.Write(name); + else + writer.WriteReference(type.FullName, type); } public static void WriteOperand(ITextOutput writer, object operand) { - if(null == operand) throw new ArgumentNullException("operand"); + if (operand == null) + throw new ArgumentNullException("operand"); Instruction targetInstruction = operand as Instruction; - if(null != targetInstruction) { - writer.Write(CecilExtensions.OffsetToString(targetInstruction.Offset)); + if (targetInstruction != null) { + WriteOffsetReference(writer, targetInstruction.Offset); return; } - Instruction [] targetInstructions = operand as Instruction []; - if(null != targetInstructions) { + Instruction[] targetInstructions = operand as Instruction[]; + if (targetInstructions != null) { WriteLabelList(writer, targetInstructions); return; } VariableReference variableRef = operand as VariableReference; - if(null != variableRef) { - writer.Write(variableRef.Index.ToString()); + if (variableRef != null) { + writer.WriteReference(variableRef.Index.ToString(), variableRef); return; } MethodReference methodRef = operand as MethodReference; - if(null != methodRef) { - WriteMethodReference(writer, methodRef); + if (methodRef != null) { + methodRef.WriteTo(writer); + return; + } + + TypeReference typeRef = operand as TypeReference; + if (typeRef != null) { + typeRef.WriteTo(writer); return; } string s = operand as string; - if(null != s) { - writer.Write("\"" + s + "\""); + if (s != null) { + writer.Write("\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""); return; } s = ToInvariantCultureString(operand); writer.Write(s); } - #endregion public static string ShortTypeName(this TypeReference type) { switch (type.FullName) { + case "System.SByte": + return "int8"; case "System.Int16": - return "short"; + return "int16"; case "System.Int32": - return "int"; + return "int32"; case "System.Int64": - return "long"; + return "int65"; + case "System.Byte": + return "uint8"; case "System.UInt16": - return "ushort"; + return "uint16"; case "System.UInt32": - return "uint"; + return "uint32"; case "System.UInt64": - return "ulong"; + return "uint64"; case "System.Single": - return "float"; + return "float32"; case "System.Double": - return "double"; + return "float64"; case "System.Void": return "void"; case "System.Boolean": @@ -165,12 +187,7 @@ namespace ICSharpCode.ILSpy.Disassembler case "System.Object": return "object"; default: - string name = type.Name; - int pos = name.LastIndexOf('`'); - if (pos >= 0) - return name.Substring(0, pos); - else - return name; + return null; } } } diff --git a/ILSpy/EventTreeNode.cs b/ILSpy/EventTreeNode.cs index 8a726c224..33f2ad712 100644 --- a/ILSpy/EventTreeNode.cs +++ b/ILSpy/EventTreeNode.cs @@ -37,6 +37,10 @@ namespace ICSharpCode.ILSpy this.LazyLoading = true; } + public EventDefinition EventDefinition { + get { return ev; } + } + public override object Text { get { return ev.Name + " : " + Language.Current.TypeToString(ev.EventType); } } diff --git a/ILSpy/FieldTreeNode.cs b/ILSpy/FieldTreeNode.cs index 5f3fcc9b4..076d27e5c 100644 --- a/ILSpy/FieldTreeNode.cs +++ b/ILSpy/FieldTreeNode.cs @@ -29,6 +29,10 @@ namespace ICSharpCode.ILSpy { readonly FieldDefinition field; + public FieldDefinition FieldDefinition { + get { return field; } + } + public FieldTreeNode(FieldDefinition field) { if (field == null) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index b6787be5e..00bb03ecd 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -73,6 +73,7 @@ + @@ -103,6 +104,7 @@ + diff --git a/ILSpy/ITextOutput.cs b/ILSpy/ITextOutput.cs index dd6a4d241..18e0eb402 100644 --- a/ILSpy/ITextOutput.cs +++ b/ILSpy/ITextOutput.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Text; using ICSharpCode.AvalonEdit.Document; @@ -31,7 +32,13 @@ namespace ICSharpCode.ILSpy void WriteComment(string comment); void WriteLine(); void WriteDefinition(string text, object definition); - void WriteReference(string text, object definition); + void WriteReference(string text, object reference); + } + + sealed class ReferenceSegment : TextSegment + { + public object Reference; + public ILSpyTreeNode TreeNode; } sealed class SmartTextOutput : ITextOutput @@ -39,6 +46,23 @@ namespace ICSharpCode.ILSpy readonly StringBuilder b = new StringBuilder(); int indent; bool needsIndent; + Dictionary definitions = new Dictionary(); + TextSegmentCollection references = new TextSegmentCollection(); + + public TextSegmentCollection References { + get { return references; } + } + + public ILSpyTreeNode CurrentTreeNode; + + public int GetDefinitionPosition(object definition) + { + int val; + if (definitions.TryGetValue(definition, out val)) + return val; + else + return -1; + } public override string ToString() { @@ -93,12 +117,16 @@ namespace ICSharpCode.ILSpy { WriteIndent(); b.Append(text); + definitions[definition] = b.Length; } - public void WriteReference(string text, object definition) + public void WriteReference(string text, object reference) { WriteIndent(); + int start = b.Length; b.Append(text); + int end = b.Length; + references.Add(new ReferenceSegment { StartOffset = start, EndOffset = end, Reference = reference, TreeNode = CurrentTreeNode }); } } } diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 7fe33f836..3f7a45ecc 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -24,10 +24,12 @@ using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Threading; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.TreeView; using Microsoft.Win32; +using Mono.Cecil; using Mono.Cecil.Rocks; namespace ICSharpCode.ILSpy @@ -39,6 +41,7 @@ namespace ICSharpCode.ILSpy { AssemblyList assemblyList = new AssemblyList(); FilterSettings filterSettings = new FilterSettings(); + ReferenceElementGenerator referenceElementGenerator; static readonly Assembly[] initialAssemblies = { typeof(object).Assembly, @@ -64,6 +67,10 @@ namespace ICSharpCode.ILSpy languageComboBox.SelectedItem = languageComboBox.Items[0]; textEditor.Text = "Welcome to ILSpy!"; + + referenceElementGenerator = new ReferenceElementGenerator(this); + textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); + AssemblyListTreeNode assemblyListTreeNode = new AssemblyListTreeNode(assemblyList); assemblyListTreeNode.FilterSettings = filterSettings.Clone(); filterSettings.PropertyChanged += delegate { @@ -73,15 +80,7 @@ namespace ICSharpCode.ILSpy assemblyListTreeNode.FilterSettings = filterSettings.Clone(); }; treeView.Root = assemblyListTreeNode; - assemblyListTreeNode.Select = delegate(SharpTreeNode obj) { - if (obj != null) { - foreach (SharpTreeNode node in obj.Ancestors()) - node.IsExpanded = true; - - treeView.SelectedItem = obj; - treeView.ScrollIntoView(obj); - } - }; + assemblyListTreeNode.Select = SelectNode; foreach (Assembly asm in initialAssemblies) assemblyList.OpenAssembly(asm.Location); @@ -107,6 +106,17 @@ namespace ICSharpCode.ILSpy #endif } + void SelectNode(SharpTreeNode obj) + { + if (obj != null) { + foreach (SharpTreeNode node in obj.Ancestors()) + node.IsExpanded = true; + + treeView.SelectedItem = obj; + treeView.ScrollIntoView(obj); + } + } + #region Debugging CFG #if DEBUG void cfg_Click(object sender, RoutedEventArgs e) @@ -196,14 +206,18 @@ namespace ICSharpCode.ILSpy } } + SmartTextOutput textOutput; + void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) { try { textEditor.SyntaxHighlighting = ILSpy.Language.Current.SyntaxHighlighting; - SmartTextOutput textOutput = new SmartTextOutput(); + textOutput = new SmartTextOutput(); foreach (var node in treeView.SelectedItems.OfType()) { + textOutput.CurrentTreeNode = node; node.Decompile(ILSpy.Language.Current, textOutput); } + referenceElementGenerator.References = textOutput.References; textEditor.Text = textOutput.ToString(); } catch (Exception ex) { textEditor.SyntaxHighlighting = null; @@ -211,6 +225,37 @@ namespace ICSharpCode.ILSpy } } + internal void JumpToReference(ReferenceSegment referenceSegment) + { + object reference = referenceSegment.Reference; + if (textOutput != null) { + int pos = textOutput.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; + } + } + if (reference is TypeReference) { + SelectNode(assemblyList.FindTypeNode(((TypeReference)reference).Resolve())); + } else if (reference is MethodReference) { + SelectNode(assemblyList.FindMethodNode(((MethodReference)reference).Resolve())); + } else if (reference is FieldReference) { + SelectNode(assemblyList.FindFieldNode(((FieldReference)reference).Resolve())); + } else if (reference is PropertyReference) { + SelectNode(assemblyList.FindPropertyNode(((PropertyReference)reference).Resolve())); + } else if (reference is EventReference) { + SelectNode(assemblyList.FindEventNode(((EventReference)reference).Resolve())); + } else if (reference is AssemblyDefinition) { + SelectNode(assemblyList.Assemblies.FirstOrDefault(node => node.AssemblyDefinition == reference)); + } + } + void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { ILSpy.Language.Current = (ILSpy.Language)languageComboBox.SelectedItem; diff --git a/ILSpy/PropertyTreeNode.cs b/ILSpy/PropertyTreeNode.cs index c7ab300a9..939f262c7 100644 --- a/ILSpy/PropertyTreeNode.cs +++ b/ILSpy/PropertyTreeNode.cs @@ -39,6 +39,10 @@ namespace ICSharpCode.ILSpy this.LazyLoading = true; } + public PropertyDefinition PropertyDefinition { + get { return property; } + } + public override object Text { get { return property.Name + " : " + Language.Current.TypeToString(property.PropertyType); } } diff --git a/ILSpy/ReferenceElementGenerator.cs b/ILSpy/ReferenceElementGenerator.cs new file mode 100644 index 000000000..7615ec87f --- /dev/null +++ b/ILSpy/ReferenceElementGenerator.cs @@ -0,0 +1,90 @@ +// 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.Windows.Input; +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Rendering; + +namespace ICSharpCode.ILSpy +{ + sealed class ReferenceElementGenerator : VisualLineElementGenerator + { + MainWindow parentWindow; + public TextSegmentCollection References { get; set; } + + public ReferenceElementGenerator(MainWindow parentWindow) + { + if (parentWindow == null) + throw new ArgumentNullException("parentWindow"); + this.parentWindow = parentWindow; + } + + public override int GetFirstInterestedOffset(int startOffset) + { + if (this.References == null) + return -1; + var segment = this.References.FindFirstSegmentWithStartAfter(startOffset); + return segment != null ? segment.StartOffset : -1; + } + + public override VisualLineElement ConstructElement(int offset) + { + if (this.References == null) + return null; + foreach (var segment in this.References.FindSegmentsContaining(offset)) { + if (offset < segment.EndOffset) { + return new VisualLineReferenceText(CurrentContext.VisualLine, segment.EndOffset - offset, this, segment); + } + } + return null; + } + + internal void JumpToReference(ReferenceSegment referenceSegment) + { + parentWindow.JumpToReference(referenceSegment); + } + } + + /// + /// VisualLineElement that represents a piece of text and is a clickable link. + /// + sealed class VisualLineReferenceText : VisualLineText + { + ReferenceElementGenerator parent; + ReferenceSegment referenceSegment; + + /// + /// Creates a visual line text element with the specified length. + /// It uses the and its + /// to find the actual text string. + /// + public VisualLineReferenceText(VisualLine parentVisualLine, int length, ReferenceElementGenerator parent, ReferenceSegment referenceSegment) : base(parentVisualLine, length) + { + this.parent = parent; + this.referenceSegment = referenceSegment; + } + + /// + protected override void OnQueryCursor(QueryCursorEventArgs e) + { + e.Handled = true; + e.Cursor = Cursors.Hand; + } + + /// + protected override void OnMouseDown(MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left && !e.Handled) { + parent.JumpToReference(referenceSegment); + e.Handled = true; + } + } + + /// + protected override VisualLineText CreateInstance(int length) + { + return new VisualLineReferenceText(ParentVisualLine, length, parent, referenceSegment); + } + } +}