/*
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.Controls;
using dnSpy.Contracts.Documents.TreeView;
using dnSpy.Contracts.Extension;
using dnSpy.Contracts.Images;
using dnSpy.Contracts.Menus;
namespace dnSpy.AsmEditor.Namespace {
[ExportAutoLoaded]
sealed class CommandLoader : IAutoLoaded {
[ImportingConstructor]
CommandLoader(IWpfCommandService wpfCommandService, DeleteNamespaceCommand.EditMenuCommand removeCmd) => wpfCommandService.AddRemoveCommand(removeCmd);
}
[DebuggerDisplay("{Description}")]
sealed class DeleteNamespaceCommand : IUndoCommand {
[ExportMenuItem(Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_DELETE, Order = 70)]
sealed class DocumentsCommand : DocumentsContextMenuHandler {
readonly Lazy undoCommandService;
[ImportingConstructor]
DocumentsCommand(Lazy undoCommandService) => this.undoCommandService = undoCommandService;
public override bool IsVisible(AsmEditorContext context) => DeleteNamespaceCommand.CanExecute(context.Nodes);
public override void Execute(AsmEditorContext context) => DeleteNamespaceCommand.Execute(undoCommandService, context.Nodes);
public override string? GetHeader(AsmEditorContext context) => GetCommandName(context.Nodes.Length);
}
[Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_DELETE, Order = 70)]
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) => DeleteNamespaceCommand.CanExecute(context.Nodes);
public override void Execute(AsmEditorContext context) => DeleteNamespaceCommand.Execute(undoCommandService, context.Nodes);
public override string? GetHeader(AsmEditorContext context) => GetCommandName(context.Nodes.Length);
}
static string GetCommandName(int count) =>
count == 1 ?
dnSpy_AsmEditor_Resources.DeleteNamespaceCommand :
string.Format(dnSpy_AsmEditor_Resources.DeleteNamespacesCommand, count);
static bool CanExecute(DocumentTreeNodeData[] nodes) =>
nodes is not null &&
nodes.Length > 0 &&
nodes.All(a => a is NamespaceNode);
static void Execute(Lazy undoCommandService, DocumentTreeNodeData[] nodes) {
if (!CanExecute(nodes))
return;
var nsNodes = nodes.Cast().ToArray();
undoCommandService.Value.Add(new DeleteNamespaceCommand(nsNodes));
}
struct DeleteModelNodes {
ModuleInfo[]? infos;
sealed class ModuleInfo {
public readonly ModuleDef Module;
public readonly TypeDef[] Types;
public readonly int[] Indexes;
public ModuleInfo(ModuleDef module, int count) {
Module = module;
Types = new TypeDef[count];
Indexes = new int[count];
}
}
public void Delete(NamespaceNode[] nodes, DocumentTreeNodeData[] parents) {
Debug2.Assert(parents is not null && nodes.Length == parents.Length);
Debug2.Assert(infos is null);
if (infos is not null)
throw new InvalidOperationException();
infos = new ModuleInfo[nodes.Length];
for (int i = 0; i < infos.Length; i++) {
var node = nodes[i];
var module = parents[i].GetModule();
Debug2.Assert(module is not null);
if (module is null)
throw new InvalidOperationException();
var info = new ModuleInfo(module, node.TreeNode.Children.Count);
infos[i] = info;
for (int j = 0; j < node.TreeNode.Children.Count; j++) {
var typeNode = (TypeNode)node.TreeNode.Children[j].Data;
int index = module.Types.IndexOf(typeNode.TypeDef);
Debug.Assert(index >= 0);
if (index < 0)
throw new InvalidOperationException();
module.Types.RemoveAt(index);
info.Types[j] = typeNode.TypeDef;
info.Indexes[j] = index;
}
}
}
public void Restore(NamespaceNode[] nodes, DocumentTreeNodeData[] parents) {
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 info = infos[i];
for (int j = info.Types.Length - 1; j >= 0; j--)
info.Module.Types.Insert(info.Indexes[j], info.Types[j]);
}
infos = null;
}
}
DocumentTreeNodeData[] parents;
DeletableNodes nodes;
DeleteModelNodes modelNodes;
DeleteNamespaceCommand(NamespaceNode[] nodes) {
parents = nodes.Select(a => (DocumentTreeNodeData)a.TreeNode.Parent!.Data).ToArray();
this.nodes = new DeletableNodes(nodes);
modelNodes = new DeleteModelNodes();
}
public string Description => GetCommandName(nodes.Count);
public void Execute() {
nodes.Delete();
modelNodes.Delete(nodes.Nodes, parents);
}
public void Undo() {
modelNodes.Restore(nodes.Nodes, parents);
nodes.Restore();
}
public IEnumerable