/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Runtime.InteropServices; using System.Text; using System.Windows; using System.Windows.Input; using dnSpy.Contracts.Controls; using dnSpy.Contracts.Extension; using dnSpy.Contracts.Images; using dnSpy.Contracts.Menus; using dnSpy.Contracts.TreeView; namespace dnSpy.Analyzer { abstract class OpenReferenceCtxMenuCommandBase : MenuItemBase { readonly Lazy analyzerService; readonly bool newTab; readonly bool useCodeRef; protected OpenReferenceCtxMenuCommandBase(Lazy analyzerService, bool newTab, bool useCodeRef) { this.analyzerService = analyzerService; this.newTab = newTab; this.useCodeRef = useCodeRef; } public override void Execute(IMenuItemContext context) { var @ref = GetReference(context); if (@ref is null) return; analyzerService.Value.FollowNode(@ref, newTab, useCodeRef); } public override bool IsVisible(IMenuItemContext context) => GetReference(context) is not null; TreeNodeData? GetReference(IMenuItemContext context) { if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID)) return null; var nodes = context.Find(); if (nodes is null || nodes.Length != 1) return null; if (nodes[0] is IMDTokenNode tokenNode && tokenNode.Reference is not null) { if (!analyzerService.Value.CanFollowNode(nodes[0], useCodeRef)) return null; return nodes[0]; } return null; } } [ExportMenuItem(Header = "res:GoToReferenceInCodeCommand", InputGestureText = "res:DoubleClick", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 0)] sealed class OpenReferenceInCodeCtxMenuCommand : OpenReferenceCtxMenuCommandBase { [ImportingConstructor] OpenReferenceInCodeCtxMenuCommand(Lazy analyzerService) : base(analyzerService, false, true) { } } [ExportMenuItem(Header = "res:GoToReferenceInCodeNewTabCommand", InputGestureText = "res:ShiftDoubleClick", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 10)] sealed class OpenReferenceInCodeNewTabCtxMenuCommand : OpenReferenceCtxMenuCommandBase { [ImportingConstructor] OpenReferenceInCodeNewTabCtxMenuCommand(Lazy analyzerService) : base(analyzerService, true, true) { } } [ExportMenuItem(Header = "res:GoToReferenceCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 20)] sealed class OpenReferenceCtxMenuCommand : OpenReferenceCtxMenuCommandBase { [ImportingConstructor] OpenReferenceCtxMenuCommand(Lazy analyzerService) : base(analyzerService, false, false) { } } [ExportMenuItem(Header = "res:GoToReferenceNewTabCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_TABS, Order = 30)] sealed class OpenReferenceNewTabCtxMenuCommand : OpenReferenceCtxMenuCommandBase { [ImportingConstructor] OpenReferenceNewTabCtxMenuCommand(Lazy analyzerService) : base(analyzerService, true, false) { } } [ExportAutoLoaded] sealed class BreakpointsContentCommandLoader : IAutoLoaded { [ImportingConstructor] BreakpointsContentCommandLoader(IWpfCommandService wpfCommandService, Lazy analyzerService) { var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_ANALYZER_TREEVIEW); cmds.Add(ApplicationCommands.Copy, (s, e) => CopyCtxMenuCommand.ExecuteInternal(analyzerService), (s, e) => e.CanExecute = CopyCtxMenuCommand.CanExecuteInternal(analyzerService)); } } [ExportMenuItem(Header = "res:CopyCommand", InputGestureText = "res:ShortCutKeyCtrlC", Icon = DsImagesAttribute.Copy, Group = MenuConstants.GROUP_CTX_ANALYZER_TOKENS, Order = -1)] sealed class CopyCtxMenuCommand : MenuItemBase { readonly Lazy analyzerService; [ImportingConstructor] CopyCtxMenuCommand(Lazy analyzerService) => this.analyzerService = analyzerService; public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); public override bool IsEnabled(IMenuItemContext context) => CanExecuteInternal(analyzerService); public override void Execute(IMenuItemContext context) => ExecuteInternal(analyzerService); public static bool CanExecuteInternal(Lazy analyzerService) => analyzerService.Value.TreeView.SelectedItems.Length > 0; public static void ExecuteInternal(Lazy analyzerService) { var items = analyzerService.Value.TreeView.SelectedItems; var sb = new StringBuilder(); int count = 0; foreach (var t in GetNodes(analyzerService.Value.TreeView, items)) { if (count > 0) sb.Append(Environment.NewLine); sb.Append(new string('\t', t.level)); sb.Append(t.node.ToString()); count++; } if (count > 1) sb.Append(Environment.NewLine); if (sb.Length > 0) { try { Clipboard.SetText(sb.ToString()); } catch (ExternalException) { } } } sealed class State { public readonly int Level; public int Index; public readonly IList Nodes; public State(ITreeNode node, int level) { Level = level; Nodes = node.Children; } } static IEnumerable<(int level, TreeNodeData node)> GetNodes(ITreeView treeView, IEnumerable nodes) { var hash = new HashSet(nodes); var stack = new Stack(); stack.Push(new State(treeView.Root, 0)); while (stack.Count > 0) { var state = stack.Pop(); if (state.Index >= state.Nodes.Count) continue; var child = state.Nodes[state.Index++]; if (hash.Contains(child.Data)) yield return (state.Level, child.Data); stack.Push(state); stack.Push(new State(child, state.Level + 1)); } } } [ExportMenuItem(Header = "res:ShowMetadataTokensCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_OPTIONS, Order = 0)] sealed class ShowTokensCtxMenuCommand : MenuItemBase { readonly AnalyzerSettingsImpl analyzerSettings; [ImportingConstructor] ShowTokensCtxMenuCommand(AnalyzerSettingsImpl analyzerSettings) => this.analyzerSettings = analyzerSettings; public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); public override bool IsChecked(IMenuItemContext context) => analyzerSettings.ShowToken; public override void Execute(IMenuItemContext context) => analyzerSettings.ShowToken = !analyzerSettings.ShowToken; } [ExportMenuItem(Header = "res:SyntaxHighlightCommand", Group = MenuConstants.GROUP_CTX_ANALYZER_OPTIONS, Order = 10)] sealed class SyntaxHighlightCtxMenuCommand : MenuItemBase { readonly AnalyzerSettingsImpl analyzerSettings; [ImportingConstructor] SyntaxHighlightCtxMenuCommand(AnalyzerSettingsImpl analyzerSettings) => this.analyzerSettings = analyzerSettings; public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); public override bool IsChecked(IMenuItemContext context) => analyzerSettings.SyntaxHighlight; public override void Execute(IMenuItemContext context) => analyzerSettings.SyntaxHighlight = !analyzerSettings.SyntaxHighlight; } [ExportMenuItem(Header = "res:SingleClickExpandNodes", Group = MenuConstants.GROUP_CTX_ANALYZER_OPTIONS, Order = 20)] sealed class SingleClickExpandNodesCtxMenuCommand : MenuItemBase { readonly AnalyzerSettingsImpl analyzerSettings; [ImportingConstructor] SingleClickExpandNodesCtxMenuCommand(AnalyzerSettingsImpl analyzerSettings) => this.analyzerSettings = analyzerSettings; public override bool IsVisible(IMenuItemContext context) => context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_ANALYZER_TREEVIEW_GUID); public override bool IsChecked(IMenuItemContext context) => analyzerSettings.SingleClickExpandsChildren; public override void Execute(IMenuItemContext context) => analyzerSettings.SingleClickExpandsChildren = !analyzerSettings.SingleClickExpandsChildren; } }