/*
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.Diagnostics;
using System.Linq;
using dnlib.DotNet;
using dnSpy.AsmEditor.Commands;
using dnSpy.AsmEditor.Properties;
using dnSpy.AsmEditor.UndoRedo;
using dnSpy.Contracts.App;
using dnSpy.Contracts.Controls;
using dnSpy.Contracts.Documents.Tabs;
using dnSpy.Contracts.Documents.TreeView;
using dnSpy.Contracts.Extension;
using dnSpy.Contracts.Images;
using dnSpy.Contracts.Menus;
using dnSpy.Contracts.Utilities;
namespace dnSpy.AsmEditor.Method {
[ExportAutoLoaded]
sealed class CommandLoader : IAutoLoaded {
[ImportingConstructor]
CommandLoader(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, DeleteMethodDefCommand.EditMenuCommand removeCmd, DeleteMethodDefCommand.CodeCommand removeCmd2, MethodDefSettingsCommand.EditMenuCommand settingsCmd, MethodDefSettingsCommand.CodeCommand settingsCmd2) {
wpfCommandService.AddRemoveCommand(removeCmd);
wpfCommandService.AddRemoveCommand(removeCmd2, documentTabService);
wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd, settingsCmd2);
}
}
[DebuggerDisplay("{Description}")]
sealed class DeleteMethodDefCommand : IUndoCommand {
[ExportMenuItem(Header = "res:DeleteMethodCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_DELETE, Order = 30)]
sealed class DocumentsCommand : DocumentsContextMenuHandler {
readonly Lazy undoCommandService;
[ImportingConstructor]
DocumentsCommand(Lazy undoCommandService) => this.undoCommandService = undoCommandService;
public override bool IsVisible(AsmEditorContext context) => DeleteMethodDefCommand.CanExecute(context.Nodes);
public override void Execute(AsmEditorContext context) => DeleteMethodDefCommand.Execute(undoCommandService, context.Nodes);
public override string? GetHeader(AsmEditorContext context) => DeleteMethodDefCommand.GetHeader(context.Nodes);
}
[Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:DeleteMethodCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_DELETE, Order = 30)]
internal sealed class EditMenuCommand : EditMenuHandler {
readonly Lazy undoCommandService;
[ImportingConstructor]
EditMenuCommand(Lazy undoCommandService, IDocumentTreeView documentTreeView)
: base(documentTreeView) => this.undoCommandService = undoCommandService;
public override bool IsVisible(AsmEditorContext context) => DeleteMethodDefCommand.CanExecute(context.Nodes);
public override void Execute(AsmEditorContext context) => DeleteMethodDefCommand.Execute(undoCommandService, context.Nodes);
public override string? GetHeader(AsmEditorContext context) => DeleteMethodDefCommand.GetHeader(context.Nodes);
}
[Export, ExportMenuItem(Header = "res:DeleteMethodCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_DELETE, Order = 30)]
internal sealed class CodeCommand : CodeContextMenuHandler {
readonly Lazy undoCommandService;
[ImportingConstructor]
CodeCommand(Lazy undoCommandService, IDocumentTreeView documentTreeView)
: base(documentTreeView) => this.undoCommandService = undoCommandService;
public override bool IsEnabled(CodeContext context) => context.IsDefinition && DeleteMethodDefCommand.CanExecute(context.Nodes);
public override void Execute(CodeContext context) => DeleteMethodDefCommand.Execute(undoCommandService, context.Nodes);
public override string? GetHeader(CodeContext context) => DeleteMethodDefCommand.GetHeader(context.Nodes);
}
static string GetHeader(DocumentTreeNodeData[] nodes) {
if (nodes.Length == 1)
return string.Format(dnSpy_AsmEditor_Resources.DeleteX, UIUtilities.EscapeMenuItemHeader(UIUtilities.TruncateWithElipsis(nodes[0].ToString())));
return string.Format(dnSpy_AsmEditor_Resources.DeleteMethodsCommand, nodes.Length);
}
static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length > 0 && nodes.All(n => n is MethodNode);
static void Execute(Lazy undoCommandService, DocumentTreeNodeData[] nodes) {
if (!CanExecute(nodes))
return;
if (!AskDeleteDef(dnSpy_AsmEditor_Resources.AskDeleteMethod))
return;
var methodNodes = nodes.Cast().ToArray();
undoCommandService.Value.Add(new DeleteMethodDefCommand(methodNodes));
}
internal static bool AskDeleteDef(string msg) {
var res = MsgBox.Instance.ShowIgnorableMessage(new Guid("DA7D935C-F5ED-44A4-BFA8-CC794AD0F105"), msg, MsgBoxButton.Yes | MsgBoxButton.No);
return res is null || res == MsgBoxButton.Yes;
}
struct DeleteModelNodes {
ModelInfo[]? infos;
readonly struct ModelInfo {
public readonly TypeDef OwnerType;
public readonly int MethodIndex;
public readonly List PropEventInfos;
public enum PropEventType {
PropertyGetter,
PropertySetter,
PropertyOther,
EventAdd,
EventInvoke,
EventRemove,
EventOther,
}
public readonly struct PropEventInfo {
public readonly ICodedToken PropOrEvent;
public readonly PropEventType PropEventType;
public readonly int Index;
public PropEventInfo(ICodedToken propOrEvt, PropEventType propEventType, int index) {
PropOrEvent = propOrEvt;
PropEventType = propEventType;
Index = index;
}
}
public ModelInfo(MethodDef method) {
OwnerType = method.DeclaringType;
MethodIndex = OwnerType.Methods.IndexOf(method);
Debug.Assert(MethodIndex >= 0);
PropEventInfos = new List();
}
public void AddMethods(ICodedToken propOrEvent, PropEventType propEvtType, IList propEvtMethods, MethodDef method) {
while (true) {
int index = propEvtMethods.IndexOf(method);
if (index < 0)
break;
propEvtMethods.RemoveAt(index);
PropEventInfos.Add(new PropEventInfo(propOrEvent, propEvtType, index));
}
}
}
public void Delete(MethodNode[] nodes) {
Debug2.Assert(infos is null);
if (infos is not null)
throw new InvalidOperationException();
infos = new ModelInfo[nodes.Length];
for (int i = 0; i < infos.Length; i++) {
var node = nodes[i];
var info = new ModelInfo(node.MethodDef);
infos[i] = info;
foreach (var prop in info.OwnerType.Properties) {
info.AddMethods(prop, ModelInfo.PropEventType.PropertyGetter, prop.GetMethods, node.MethodDef);
info.AddMethods(prop, ModelInfo.PropEventType.PropertySetter, prop.SetMethods, node.MethodDef);
info.AddMethods(prop, ModelInfo.PropEventType.PropertyOther, prop.OtherMethods, node.MethodDef);
}
foreach (var evt in info.OwnerType.Events) {
if (evt.AddMethod == node.MethodDef) {
evt.AddMethod = null;
info.PropEventInfos.Add(new ModelInfo.PropEventInfo(evt, ModelInfo.PropEventType.EventAdd, -1));
}
if (evt.InvokeMethod == node.MethodDef) {
evt.InvokeMethod = null;
info.PropEventInfos.Add(new ModelInfo.PropEventInfo(evt, ModelInfo.PropEventType.EventInvoke, -1));
}
if (evt.RemoveMethod == node.MethodDef) {
evt.RemoveMethod = null;
info.PropEventInfos.Add(new ModelInfo.PropEventInfo(evt, ModelInfo.PropEventType.EventRemove, -1));
}
info.AddMethods(evt, ModelInfo.PropEventType.EventOther, evt.OtherMethods, node.MethodDef);
}
info.OwnerType.Methods.RemoveAt(info.MethodIndex);
}
}
public void Restore(MethodNode[] nodes) {
Debug2.Assert(infos is not null);
if (infos is null)
throw new InvalidOperationException();
Debug.Assert(infos.Length == nodes.Length);
if (infos.Length != nodes.Length)
throw new InvalidOperationException();
for (int i = infos.Length - 1; i >= 0; i--) {
var node = nodes[i];
ref readonly var info = ref infos[i];
info.OwnerType.Methods.Insert(info.MethodIndex, node.MethodDef);
for (int j = info.PropEventInfos.Count - 1; j >= 0; j--) {
var pinfo = info.PropEventInfos[i];
EventDef evt;
switch (pinfo.PropEventType) {
case ModelInfo.PropEventType.PropertyGetter:
((PropertyDef)pinfo.PropOrEvent).GetMethods.Insert(pinfo.Index, node.MethodDef);
break;
case ModelInfo.PropEventType.PropertySetter:
((PropertyDef)pinfo.PropOrEvent).SetMethods.Insert(pinfo.Index, node.MethodDef);
break;
case ModelInfo.PropEventType.PropertyOther:
((PropertyDef)pinfo.PropOrEvent).OtherMethods.Insert(pinfo.Index, node.MethodDef);
break;
case ModelInfo.PropEventType.EventAdd:
evt = (EventDef)pinfo.PropOrEvent;
Debug2.Assert(evt.AddMethod is null);
if (evt.AddMethod is not null)
throw new InvalidOperationException();
evt.AddMethod = node.MethodDef;
break;
case ModelInfo.PropEventType.EventInvoke:
evt = (EventDef)pinfo.PropOrEvent;
Debug2.Assert(evt.InvokeMethod is null);
if (evt.InvokeMethod is not null)
throw new InvalidOperationException();
evt.InvokeMethod = node.MethodDef;
break;
case ModelInfo.PropEventType.EventRemove:
evt = (EventDef)pinfo.PropOrEvent;
Debug2.Assert(evt.RemoveMethod is null);
if (evt.RemoveMethod is not null)
throw new InvalidOperationException();
evt.RemoveMethod = node.MethodDef;
break;
case ModelInfo.PropEventType.EventOther:
((EventDef)pinfo.PropOrEvent).OtherMethods.Insert(pinfo.Index, node.MethodDef);
break;
default:
throw new InvalidOperationException();
}
}
}
infos = null;
}
}
DeletableNodes nodes;
DeleteModelNodes modelNodes;
DeleteMethodDefCommand(MethodNode[] methodNodes) => nodes = new DeletableNodes(methodNodes);
public string Description => dnSpy_AsmEditor_Resources.DeleteMethodCommand;
public void Execute() {
nodes.Delete();
modelNodes.Delete(nodes.Nodes);
}
public void Undo() {
modelNodes.Restore(nodes.Nodes);
nodes.Restore();
}
public IEnumerable