diff --git a/Debugger/ILSpy.Debugger/Commands/DebuggerCommands.cs b/Debugger/ILSpy.Debugger/Commands/DebuggerCommands.cs index eceee8719..fd3d75ff0 100644 --- a/Debugger/ILSpy.Debugger/Commands/DebuggerCommands.cs +++ b/Debugger/ILSpy.Debugger/Commands/DebuggerCommands.cs @@ -179,12 +179,12 @@ namespace ICSharpCode.ILSpy.Debugger.Commands } } - [ExportContextMenuEntry(Header = "_Debug Assembly", Icon = "Images/application-x-executable.png")] + [ExportContextMenuEntryAttribute(Header = "_Debug Assembly", Icon = "Images/application-x-executable.png")] internal sealed class DebugExecutableNodeCommand : DebuggerCommand, IContextMenuEntry { - public bool IsVisible(SharpTreeNode[] selectedNodes) + public bool IsVisible(TextViewContext context) { - return selectedNodes.All( + return context.SelectedTreeNodes != null && context.SelectedTreeNodes.All( delegate (SharpTreeNode n) { AssemblyTreeNode a = n as AssemblyTreeNode; if (a == null) @@ -194,15 +194,17 @@ namespace ICSharpCode.ILSpy.Debugger.Commands }); } - public bool IsEnabled(SharpTreeNode[] selectedNodes) + public bool IsEnabled(TextViewContext context) { - return selectedNodes.Length == 1; + return context.SelectedTreeNodes != null && context.SelectedTreeNodes.Length == 1; } - public void Execute(SharpTreeNode[] selectedNodes) + public void Execute(TextViewContext context) { + if (context.SelectedTreeNodes == null) + return; if (!CurrentDebugger.IsDebugging) { - AssemblyTreeNode n = selectedNodes[0] as AssemblyTreeNode; + AssemblyTreeNode n = context.SelectedTreeNodes[0] as AssemblyTreeNode; if (DebuggerSettings.Instance.AskForArguments) { var window = new ExecuteProcessWindow { Owner = MainWindow.Instance, diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index e4ae3210e..26e104a96 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -19,17 +19,49 @@ using System; using System.ComponentModel.Composition; using System.Linq; +using System.Windows; using System.Windows.Controls; - +using ICSharpCode.ILSpy.TextView; using ICSharpCode.TreeView; namespace ICSharpCode.ILSpy { public interface IContextMenuEntry { - bool IsVisible(SharpTreeNode[] selectedNodes); - bool IsEnabled(SharpTreeNode[] selectedNodes); - void Execute(SharpTreeNode[] selectedNodes); + bool IsVisible(TextViewContext context); + bool IsEnabled(TextViewContext context); + void Execute(TextViewContext context); + } + + public class TextViewContext + { + /// + /// Returns the selected nodes in the tree view. + /// Returns null, if context menu does not belong to a tree view. + /// + public SharpTreeNode[] SelectedTreeNodes { get; private set; } + + /// + /// Returns the text view the context menu is assigned to. + /// Returns null, if context menu is not assigned to a text view. + /// + public DecompilerTextView TextView { get; private set; } + + /// + /// Returns the reference the mouse cursor is currently hovering above. + /// Returns null, if there was no reference found. + /// + public ReferenceSegment Reference { get; private set; } + + public static TextViewContext Create(SharpTreeNode[] selectedTreeNodes = null, DecompilerTextView textView = null) + { + var reference = textView != null ? textView.GetReferenceSegmentAtMousePosition() : null; + return new TextViewContext { + SelectedTreeNodes = selectedTreeNodes, + TextView = textView, + Reference = reference + }; + } } public interface IContextMenuEntryMetadata @@ -61,23 +93,31 @@ namespace ICSharpCode.ILSpy /// /// Enables extensible context menu support for the specified tree view. /// - public static void Add(SharpTreeView treeView) + public static void Add(SharpTreeView treeView, DecompilerTextView textView = null) { - var provider = new ContextMenuProvider(treeView); + var provider = new ContextMenuProvider(treeView, textView); treeView.ContextMenuOpening += provider.treeView_ContextMenuOpening; // Context menu is shown only when the ContextMenu property is not null before the // ContextMenuOpening event handler is called. treeView.ContextMenu = new ContextMenu(); + if (textView != null) { + textView.ContextMenuOpening += provider.textView_ContextMenuOpening; + // Context menu is shown only when the ContextMenu property is not null before the + // ContextMenuOpening event handler is called. + textView.ContextMenu = new ContextMenu(); + } } readonly SharpTreeView treeView; + readonly DecompilerTextView textView; [ImportMany(typeof(IContextMenuEntry))] Lazy[] entries = null; - private ContextMenuProvider(SharpTreeView treeView) + ContextMenuProvider(SharpTreeView treeView, DecompilerTextView textView = null) { this.treeView = treeView; + this.textView = textView; App.CompositionContainer.ComposeParts(this); } @@ -88,12 +128,34 @@ namespace ICSharpCode.ILSpy e.Handled = true; // don't show the menu return; } - ContextMenu menu = new ContextMenu(); + TextViewContext context = TextViewContext.Create(selectedNodes); + ContextMenu menu; + if (ShowContextMenu(context, out menu)) + treeView.ContextMenu = menu; + else + // hide the context menu. + e.Handled = true; + } + + void textView_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + TextViewContext context = TextViewContext.Create(textView: textView); + ContextMenu menu; + if (ShowContextMenu(context, out menu)) + textView.ContextMenu = menu; + else + // hide the context menu. + e.Handled = true; + } + + bool ShowContextMenu(TextViewContext context, out ContextMenu menu) + { + menu = new ContextMenu(); foreach (var category in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.Category)) { bool needSeparatorForCategory = true; foreach (var entryPair in category) { IContextMenuEntry entry = entryPair.Value; - if (entry.IsVisible(selectedNodes)) { + if (entry.IsVisible(context)) { if (needSeparatorForCategory && menu.Items.Count > 0) { menu.Items.Add(new Separator()); needSeparatorForCategory = false; @@ -107,22 +169,15 @@ namespace ICSharpCode.ILSpy Source = Images.LoadImage(entry, entryPair.Metadata.Icon) }; } - if (entryPair.Value.IsEnabled(selectedNodes)) { - menuItem.Click += delegate - { - entry.Execute(selectedNodes); - }; + if (entryPair.Value.IsEnabled(context)) { + menuItem.Click += delegate { entry.Execute(context); }; } else menuItem.IsEnabled = false; menu.Items.Add(menuItem); } } } - if (menu.Items.Count > 0) - treeView.ContextMenu = menu; - else - // hide the context menu. - e.Handled = true; + return menu.Items.Count > 0; } } } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 0e161e1ed..e484e7c4c 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -230,7 +230,6 @@ namespace ICSharpCode.ILSpy return "x86"; else return "AnyCPU (64-bit preferred)"; - break; case TargetArchitecture.AMD64: return "x64"; case TargetArchitecture.IA64: @@ -250,7 +249,6 @@ namespace ICSharpCode.ILSpy return "x86"; else return "AnyCPU"; - break; case TargetArchitecture.AMD64: return "x64"; case TargetArchitecture.IA64: diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index c11516d48..ac1ae1675 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -90,7 +90,7 @@ namespace ICSharpCode.ILSpy InitMainMenu(); InitToolbar(); - ContextMenuProvider.Add(treeView); + ContextMenuProvider.Add(treeView, decompilerTextView); this.Loaded += new RoutedEventHandler(MainWindow_Loaded); } diff --git a/ILSpy/TextView/AvalonEditTextOutput.cs b/ILSpy/TextView/AvalonEditTextOutput.cs index ce2debeeb..41d260027 100644 --- a/ILSpy/TextView/AvalonEditTextOutput.cs +++ b/ILSpy/TextView/AvalonEditTextOutput.cs @@ -33,7 +33,7 @@ namespace ICSharpCode.ILSpy.TextView /// /// A text segment that references some object. Used for hyperlinks in the editor. /// - sealed class ReferenceSegment : TextSegment + public sealed class ReferenceSegment : TextSegment { public object Reference; public bool IsLocal; diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 4e5498343..05eefd431 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -182,7 +182,7 @@ namespace ICSharpCode.ILSpy.TextView 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); + int offset = textEditor.Document.GetOffset(position.Value.Location); ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault(); if (seg == null) return; @@ -761,6 +761,15 @@ namespace ICSharpCode.ILSpy.TextView } #endregion + internal ReferenceSegment GetReferenceSegmentAtMousePosition() + { + TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(Mouse.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset); + if (position == null) + return null; + int offset = textEditor.Document.GetOffset(position.Value.Location); + return referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault(); + } + public DecompilerTextViewState GetState() { if (decompiledNodes == null) diff --git a/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs b/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs index cb66683e1..fc5a62117 100644 --- a/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs +++ b/ILSpy/TreeNodes/Analyzer/AnalyzeContextMenuEntry.cs @@ -18,22 +18,27 @@ using System; using System.Linq; +using System.Windows; using ICSharpCode.TreeView; using Mono.Cecil; namespace ICSharpCode.ILSpy.TreeNodes.Analyzer { - [ExportContextMenuEntry(Header = "Analyze", Icon = "images/Search.png")] + [ExportContextMenuEntryAttribute(Header = "Analyze", Icon = "images/Search.png")] internal sealed class AnalyzeContextMenuEntry : IContextMenuEntry { - public bool IsVisible(SharpTreeNode[] selectedNodes) + public bool IsVisible(TextViewContext context) { - return selectedNodes.All(n => n is IMemberTreeNode); + if (context.SelectedTreeNodes == null) + return context.Reference != null && context.Reference.Reference is MemberReference; + return context.SelectedTreeNodes.All(n => n is IMemberTreeNode); } - public bool IsEnabled(SharpTreeNode[] selectedNodes) + public bool IsEnabled(TextViewContext context) { - foreach (IMemberTreeNode node in selectedNodes) { + if (context.SelectedTreeNodes == null) + return context.Reference != null && context.Reference.Reference is MemberReference; + foreach (IMemberTreeNode node in context.SelectedTreeNodes) { if (!(node.Member is TypeDefinition || node.Member is FieldDefinition || node.Member is MethodDefinition @@ -45,20 +50,29 @@ namespace ICSharpCode.ILSpy.TreeNodes.Analyzer return true; } - public void Execute(SharpTreeNode[] selectedNodes) + public void Execute(TextViewContext context) { - // TODO: figure out when equivalent nodes are already present - // and focus those instead. - foreach (IMemberTreeNode node in selectedNodes) { - Analyze(node.Member); + var segment = context.Reference; + if (context.SelectedTreeNodes != null) { + // TODO: figure out when equivalent nodes are already present + // and focus those instead. + foreach (IMemberTreeNode node in context.SelectedTreeNodes) { + Analyze(node.Member); + } + } else if (segment != null && segment.Reference is MemberReference) { + if (segment.Reference is MemberReference) + Analyze((MemberReference)segment.Reference); + // TODO: implement support for other references: ParameterReference, etc. } } public static void Analyze(MemberReference member) { - TypeDefinition type = member as TypeDefinition; + TypeDefinition type = null; + if (member is TypeReference) + type = ((TypeReference)member).Resolve(); if (type != null) - AnalyzerTreeView.Instance.Show(new AnalyzedTypeTreeNode(type)); + AnalyzerTreeView.Instance.Show(new AnalyzedTypeTreeNode(type.Resolve())); FieldDefinition field = member as FieldDefinition; if (field != null) AnalyzerTreeView.Instance.Show(new AnalyzedFieldTreeNode(field)); diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index ee11b7b93..ea9ac9af9 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -255,22 +255,26 @@ namespace ICSharpCode.ILSpy.TreeNodes } } - [ExportContextMenuEntry(Header = "_Remove", Icon = "images/Delete.png")] + [ExportContextMenuEntryAttribute(Header = "_Remove", Icon = "images/Delete.png")] sealed class RemoveAssembly : IContextMenuEntry { - public bool IsVisible(SharpTreeNode[] selectedNodes) + public bool IsVisible(TextViewContext context) { - return selectedNodes.All(n => n is AssemblyTreeNode); + if (context.SelectedTreeNodes == null) + return false; + return context.SelectedTreeNodes.All(n => n is AssemblyTreeNode); } - public bool IsEnabled(SharpTreeNode[] selectedNodes) + public bool IsEnabled(TextViewContext context) { return true; } - public void Execute(SharpTreeNode[] selectedNodes) + public void Execute(TextViewContext context) { - foreach (var node in selectedNodes) { + if (context.SelectedTreeNodes == null) + return; + foreach (var node in context.SelectedTreeNodes) { node.Delete(); } } diff --git a/TestPlugin/ContextMenuCommand.cs b/TestPlugin/ContextMenuCommand.cs index dc0f4c576..f19000aed 100644 --- a/TestPlugin/ContextMenuCommand.cs +++ b/TestPlugin/ContextMenuCommand.cs @@ -11,22 +11,24 @@ using Mono.Cecil; namespace TestPlugin { - [ExportContextMenuEntry(Header = "_Save Assembly")] + [ExportContextMenuEntryAttribute(Header = "_Save Assembly")] public class SaveAssembly : IContextMenuEntry { - public bool IsVisible(SharpTreeNode[] selectedNodes) + public bool IsVisible(TextViewContext context) { - return selectedNodes.All(n => n is AssemblyTreeNode); + return context.SelectedTreeNodes != null && context.SelectedTreeNodes.All(n => n is AssemblyTreeNode); } - public bool IsEnabled(SharpTreeNode[] selectedNodes) + public bool IsEnabled(TextViewContext context) { - return selectedNodes.Length == 1; + return context.SelectedTreeNodes != null && context.SelectedTreeNodes.Length == 1; } - public void Execute(SharpTreeNode[] selectedNodes) + public void Execute(TextViewContext context) { - AssemblyTreeNode node = (AssemblyTreeNode)selectedNodes[0]; + if (context.SelectedTreeNodes == null) + return; + AssemblyTreeNode node = (AssemblyTreeNode)context.SelectedTreeNodes[0]; AssemblyDefinition asm = node.LoadedAssembly.AssemblyDefinition as AssemblyDefinition; if (asm != null) { SaveFileDialog dlg = new SaveFileDialog();