diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 4c7f34411..4406128e3 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -61,7 +61,7 @@ - + @@ -139,16 +139,22 @@ + Margin="-5,0" + BorderThickness="5,0" + HorizontalAlignment="Center" + VerticalAlignment="Stretch" + BorderBrush="Transparent" /> + + + - - + + + @@ -163,21 +169,20 @@ - - - - - - + + + + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index a97756e74..002edb75d 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -33,6 +33,7 @@ using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes.Analyzer; using ICSharpCode.TreeView; using Microsoft.Win32; +using Mono.Cecil; namespace ICSharpCode.ILSpy { @@ -260,6 +261,23 @@ namespace ICSharpCode.ILSpy path.Reverse(); return path.ToArray(); } + + public void JumpToReference(object reference) + { + if (reference is TypeReference) { + SelectNode(assemblyListTreeNode.FindTypeNode(((TypeReference)reference).Resolve())); + } else if (reference is MethodReference) { + SelectNode(assemblyListTreeNode.FindMethodNode(((MethodReference)reference).Resolve())); + } else if (reference is FieldReference) { + SelectNode(assemblyListTreeNode.FindFieldNode(((FieldReference)reference).Resolve())); + } else if (reference is PropertyReference) { + SelectNode(assemblyListTreeNode.FindPropertyNode(((PropertyReference)reference).Resolve())); + } else if (reference is EventReference) { + SelectNode(assemblyListTreeNode.FindEventNode(((EventReference)reference).Resolve())); + } else if (reference is AssemblyDefinition) { + SelectNode(assemblyListTreeNode.FindAssemblyNode((AssemblyDefinition)reference)); + } + } #endregion #region Open/Refresh @@ -380,20 +398,18 @@ namespace ICSharpCode.ILSpy #endregion #region Analyzer - public void Analyze(MethodTreeNode method) + public void AddToAnalyzer(AnalyzerTreeNode node) { if (analyzerTree.Root == null) - analyzerTree.Root = new SharpTreeNode(); + analyzerTree.Root = new AnalyzerTreeNode { Language = sessionSettings.FilterSettings.Language }; - if (analyzerPanel.Visibility != Visibility.Visible) - analyzerPanel.Visibility = Visibility.Visible; + if (!showAnalyzer.IsChecked) + showAnalyzer.IsChecked = true; - var newNode = new AnalyzedMethodTreeNode(method.MethodDefinition) { - Language = sessionSettings.FilterSettings.Language, - IsExpanded = true - }; - - analyzerTree.Root.Children.Add(newNode); + node.IsExpanded = true; + analyzerTree.Root.Children.Add(node); + analyzerTree.SelectedItem = node; + analyzerTree.FocusNode(node); } #endregion @@ -412,7 +428,25 @@ namespace ICSharpCode.ILSpy sessionSettings.ActiveTreeViewPath = GetPathForNode(treeView.SelectedItem as SharpTreeNode); sessionSettings.WindowBounds = this.RestoreBounds; sessionSettings.SplitterPosition = leftColumn.Width.Value / (leftColumn.Width.Value + rightColumn.Width.Value); + if (showAnalyzer.IsChecked) + sessionSettings.AnalyzerSplitterPosition = analyzerRow.Height.Value / (analyzerRow.Height.Value + textViewRow.Height.Value); sessionSettings.Save(); } + + void ShowAnalyzer_Checked(object sender, RoutedEventArgs e) + { + analyzerRow.MinHeight = 100; + if (sessionSettings.AnalyzerSplitterPosition > 0 && sessionSettings.AnalyzerSplitterPosition < 1) { + textViewRow.Height = new GridLength(1 - sessionSettings.AnalyzerSplitterPosition, GridUnitType.Star); + analyzerRow.Height = new GridLength(sessionSettings.AnalyzerSplitterPosition, GridUnitType.Star); + } + } + + void ShowAnalyzer_Unchecked(object sender, RoutedEventArgs e) + { + sessionSettings.AnalyzerSplitterPosition = analyzerRow.Height.Value / (analyzerRow.Height.Value + textViewRow.Height.Value); + analyzerRow.MinHeight = 0; + analyzerRow.Height = new GridLength(0); + } } } \ No newline at end of file diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs index f3c50e2c0..11d8346d3 100644 --- a/ILSpy/SessionSettings.cs +++ b/ILSpy/SessionSettings.cs @@ -49,6 +49,7 @@ namespace ICSharpCode.ILSpy this.WindowState = FromString((string)doc.Element("WindowState"), WindowState.Normal); this.WindowBounds = FromString((string)doc.Element("WindowBounds"), new Rect(10, 10, 750, 550)); this.SplitterPosition = FromString((string)doc.Element("SplitterPosition"), 0.4); + this.AnalyzerSplitterPosition = FromString((string)doc.Element("AnalyzerSplitterPosition"), 0.3); } public event PropertyChangedEventHandler PropertyChanged; @@ -68,6 +69,7 @@ namespace ICSharpCode.ILSpy public WindowState WindowState = WindowState.Normal; public Rect WindowBounds; public double SplitterPosition; + public double AnalyzerSplitterPosition; public void Save() { @@ -82,6 +84,7 @@ namespace ICSharpCode.ILSpy doc.Add(new XElement("WindowState", ToString(this.WindowState))); doc.Add(new XElement("WindowBounds", ToString(this.WindowBounds))); doc.Add(new XElement("SplitterPosition", ToString(this.SplitterPosition))); + doc.Add(new XElement("AnalyzerSplitterPosition", ToString(this.AnalyzerSplitterPosition))); ILSpySettings.SaveSettings(doc); } diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index e4355a1ca..f3d0ccb0f 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -359,20 +359,7 @@ namespace ICSharpCode.ILSpy.TextView return; } } - var assemblyListTreeNode = mainWindow.AssemblyListTreeNode; - if (reference is TypeReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindTypeNode(((TypeReference)reference).Resolve())); - } else if (reference is MethodReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindMethodNode(((MethodReference)reference).Resolve())); - } else if (reference is FieldReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindFieldNode(((FieldReference)reference).Resolve())); - } else if (reference is PropertyReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindPropertyNode(((PropertyReference)reference).Resolve())); - } else if (reference is EventReference) { - mainWindow.SelectNode(assemblyListTreeNode.FindEventNode(((EventReference)reference).Resolve())); - } else if (reference is AssemblyDefinition) { - mainWindow.SelectNode(assemblyListTreeNode.FindAssemblyNode((AssemblyDefinition)reference)); - } + mainWindow.JumpToReference(reference); } #endregion diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs index 0974d12bd..1d0308b84 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodTreeNode.cs @@ -41,6 +41,12 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer get { return MethodTreeNode.GetText(analyzedMethod, Language); } } + public override void ActivateItem(System.Windows.RoutedEventArgs e) + { + e.Handled = true; + MainWindow.Instance.JumpToReference(analyzedMethod); + } + protected override void LoadChildren() { this.Children.Add(new AnalyzedMethodUsedByTreeNode(analyzedMethod)); diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs index 41cf720ba..72f43f6ba 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzedMethodUsedByTreeNode.cs @@ -18,15 +18,17 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; - +using System.Threading.Tasks; using ICSharpCode.NRefactory.Utils; +using ICSharpCode.TreeView; using Mono.Cecil; using Mono.Cecil.Cil; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { - class AnalyzedMethodUsedByTreeNode : ILSpyTreeNode + class AnalyzedMethodUsedByTreeNode : AnalyzerTreeNode { MethodDefinition analyzedMethod; ThreadingSupport threading; @@ -54,39 +56,46 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer threading.LoadChildren(this, FetchChildren); } - IEnumerable FetchChildren(CancellationToken ct) + protected override void OnCollapsing() + { + if (threading.IsRunning) { + this.LazyLoading = true; + threading.Cancel(); + this.Children.Clear(); + } + } + + IEnumerable FetchChildren(CancellationToken ct) { return FindReferences(MainWindow.Instance.AssemblyList.GetAssemblies(), ct); } - IEnumerable FindReferences(LoadedAssembly[] assemblies, CancellationToken ct) + IEnumerable FindReferences(LoadedAssembly[] assemblies, CancellationToken ct) { - foreach (LoadedAssembly asm in assemblies) { + // use parallelism only on the assembly level (avoid locks within Cecil) + return assemblies.AsParallel().WithCancellation(ct).SelectMany((LoadedAssembly asm) => FindReferences(asm, ct)); + } + + IEnumerable FindReferences(LoadedAssembly asm, CancellationToken ct) + { + foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { ct.ThrowIfCancellationRequested(); - foreach (TypeDefinition type in TreeTraversal.PreOrder(asm.AssemblyDefinition.MainModule.Types, t => t.NestedTypes)) { + foreach (MethodDefinition method in type.Methods) { ct.ThrowIfCancellationRequested(); - foreach (MethodDefinition method in type.Methods) { - ct.ThrowIfCancellationRequested(); - bool found = false; - if (!method.HasBody) - continue; - foreach (Instruction instr in method.Body.Instructions) { - if (instr.Operand is MethodReference - && ((MethodReference)instr.Operand).Resolve() == analyzedMethod) { - found = true; - break; - } + bool found = false; + if (!method.HasBody) + continue; + foreach (Instruction instr in method.Body.Instructions) { + if (instr.Operand is MethodReference + && ((MethodReference)instr.Operand).Resolve() == analyzedMethod) { + found = true; + break; } - if (found) - yield return new MethodTreeNode(method); } + if (found) + yield return new AnalyzedMethodTreeNode(method); } } } - - public override void Decompile(Language language, ICSharpCode.Decompiler.ITextOutput output, DecompilationOptions options) - { - throw new NotImplementedException(); - } } } diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs index 0a91760a4..e91b048d8 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzerTreeNode.cs @@ -17,12 +17,48 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Linq; using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { class AnalyzerTreeNode : SharpTreeNode { - public Language Language { get; set; } + Language language; + + public Language Language { + get { return language; } + set { + if (language != value) { + language = value; + foreach (var child in this.Children.OfType()) + child.Language = value; + } + } + } + + public override bool CanDelete() + { + return Parent != null && Parent.IsRoot; + } + + public override void DeleteCore() + { + Parent.Children.Remove(this); + } + + public override void Delete() + { + DeleteCore(); + } + + protected override void OnChildrenChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) { + foreach (AnalyzerTreeNode a in e.NewItems.OfType()) + a.Language = this.Language; + } + base.OnChildrenChanged(e); + } } } diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs index 3e28884b9..7c015d5fd 100644 --- a/ILSpy/TreeNodes/MethodTreeNode.cs +++ b/ILSpy/TreeNodes/MethodTreeNode.cs @@ -21,6 +21,7 @@ using System.Text; using System.Windows.Controls; using System.Windows.Media.Imaging; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.TreeNodes.Analyzer; using Mono.Cecil; namespace ICSharpCode.ILSpy.TreeNodes @@ -99,7 +100,7 @@ namespace ICSharpCode.ILSpy.TreeNodes { ContextMenu menu = new ContextMenu(); MenuItem item = new MenuItem() { Header = "Analyze", Icon = new Image() { Source = Images.Search } }; - item.Click += delegate { MainWindow.Instance.Analyze(this); }; + item.Click += delegate { MainWindow.Instance.AddToAnalyzer(new AnalyzedMethodTreeNode(method)); }; menu.Items.Add(item); diff --git a/ILSpy/TreeNodes/ThreadingSupport.cs b/ILSpy/TreeNodes/ThreadingSupport.cs index 1d0dd1f71..a375ad978 100644 --- a/ILSpy/TreeNodes/ThreadingSupport.cs +++ b/ILSpy/TreeNodes/ThreadingSupport.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; using ICSharpCode.Decompiler; +using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy.TreeNodes { @@ -15,27 +17,39 @@ namespace ICSharpCode.ILSpy.TreeNodes /// class ThreadingSupport { - Task> loadChildrenTask; + Task> loadChildrenTask; + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + public bool IsRunning { + get { return !loadChildrenTask.IsCompleted; } + } + + public void Cancel() + { + cancellationTokenSource.Cancel(); + loadChildrenTask = null; + cancellationTokenSource = new CancellationTokenSource(); + } /// - /// + /// Starts loading the children of the specified node. /// - public void LoadChildren(ILSpyTreeNode node, Func> fetchChildren) + public void LoadChildren(SharpTreeNode node, Func> fetchChildren) { node.Children.Add(new LoadingTreeNode()); - CancellationToken ct = CancellationToken.None; + CancellationToken ct = cancellationTokenSource.Token; var fetchChildrenEnumerable = fetchChildren(ct); - Task> thisTask = null; - thisTask = new Task>( + Task> thisTask = null; + thisTask = new Task>( delegate { - List result = new List(); - foreach (ILSpyTreeNode child in fetchChildrenEnumerable) { + List result = new List(); + foreach (SharpTreeNode child in fetchChildrenEnumerable) { ct.ThrowIfCancellationRequested(); result.Add(child); - App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action( - delegate (ILSpyTreeNode newChild) { + App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action( + delegate (SharpTreeNode newChild) { // don't access "child" here the // background thread might already be running the next loop iteration if (loadChildrenTask == thisTask) { @@ -43,16 +57,27 @@ namespace ICSharpCode.ILSpy.TreeNodes } }), child); } + return result; + }, ct); + loadChildrenTask = thisTask; + thisTask.Start(); + thisTask.ContinueWith( + delegate (Task continuation) { App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action( delegate { if (loadChildrenTask == thisTask) { node.Children.RemoveAt(node.Children.Count - 1); // remove 'Loading...' } + if (continuation.Exception != null) { // observe exception even when task isn't current + if (loadChildrenTask == thisTask) { + foreach (Exception ex in continuation.Exception.InnerExceptions) { + node.Children.Add(new ErrorTreeNode(ex.ToString())); + } + } + } })); - return result; - }, ct); - loadChildrenTask = thisTask; - thisTask.Start(); + }); + // Give the task a bit time to complete before we return to WPF - this keeps "Loading..." // from showing up for very short waits. thisTask.Wait(TimeSpan.FromMilliseconds(200)); @@ -66,7 +91,7 @@ namespace ICSharpCode.ILSpy.TreeNodes loadChildrenTask = this.loadChildrenTask; } if (loadChildrenTask != null) { - foreach (var child in loadChildrenTask.Result) { + foreach (ILSpyTreeNode child in loadChildrenTask.Result.Cast()) { child.Decompile(language, output, options); } } @@ -87,5 +112,28 @@ namespace ICSharpCode.ILSpy.TreeNodes { } } + + sealed class ErrorTreeNode : ILSpyTreeNode + { + string text; + + public override object Text { + get { return text; } + } + + public ErrorTreeNode(string text) + { + this.text = text; + } + + public override FilterResult Filter(FilterSettings settings) + { + return FilterResult.Match; + } + + public override void Decompile(Language language, ICSharpCode.Decompiler.ITextOutput output, DecompilationOptions options) + { + } + } } }