From 847acf38d4b1ae105c88f7359c519e23d671c5e4 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 24 Jul 2025 09:10:46 +0200 Subject: [PATCH] Add "Export as JSON" feature --- ILSpy/Images/DictionaryContain.svg | 1 + ILSpy/Images/DictionaryContain.xaml | 13 ++++ ILSpy/Images/ExpandAll.svg | 1 + ILSpy/Images/ExpandAll.xaml | 11 +++ ILSpy/Images/README.md | 4 + ILSpy/Images/ResultToJSON.svg | 1 + ILSpy/Images/ResultToJSON.xaml | 10 +++ ILSpy/Images/SwitchSourceOrTarget.svg | 1 + ILSpy/Images/SwitchSourceOrTarget.xaml | 9 +++ ILSpy/ViewModels/CompareViewModel.cs | 102 +++++++++++++++++++++---- ILSpy/Views/CompareView.xaml | 41 +++++++++- 11 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 ILSpy/Images/DictionaryContain.svg create mode 100644 ILSpy/Images/DictionaryContain.xaml create mode 100644 ILSpy/Images/ExpandAll.svg create mode 100644 ILSpy/Images/ExpandAll.xaml create mode 100644 ILSpy/Images/ResultToJSON.svg create mode 100644 ILSpy/Images/ResultToJSON.xaml create mode 100644 ILSpy/Images/SwitchSourceOrTarget.svg create mode 100644 ILSpy/Images/SwitchSourceOrTarget.xaml diff --git a/ILSpy/Images/DictionaryContain.svg b/ILSpy/Images/DictionaryContain.svg new file mode 100644 index 000000000..bb184464c --- /dev/null +++ b/ILSpy/Images/DictionaryContain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ILSpy/Images/DictionaryContain.xaml b/ILSpy/Images/DictionaryContain.xaml new file mode 100644 index 000000000..f0dd255a2 --- /dev/null +++ b/ILSpy/Images/DictionaryContain.xaml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/Images/ExpandAll.svg b/ILSpy/Images/ExpandAll.svg new file mode 100644 index 000000000..6279eada7 --- /dev/null +++ b/ILSpy/Images/ExpandAll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ILSpy/Images/ExpandAll.xaml b/ILSpy/Images/ExpandAll.xaml new file mode 100644 index 000000000..e46b95c82 --- /dev/null +++ b/ILSpy/Images/ExpandAll.xaml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/Images/README.md b/ILSpy/Images/README.md index 05dcc6f09..6d78750cc 100644 --- a/ILSpy/Images/README.md +++ b/ILSpy/Images/README.md @@ -15,9 +15,11 @@ Icons used in ILSpy: | Copy | x | x | VS 2017 Icon Pack (Copy) | | | Delegate | x | x | VS 2017 Icon Pack (Delegate) | | | Delete | x | x | VS 2017 Icon Pack (Remove_color) | | +| DictionaryContain | x | x | VS 2017 Icon Pack (DictionaryContain) | | | Enum | x | x | VS 2017 Icon Pack (Enumerator) | | | EnumValue | x | x | VS 2017 Icon Pack (EnumItem) | | | Event | x | x | VS 2017 Icon Pack (Event) | | +| ExpandAll | x | x | VS 2017 Icon Pack (ExpandAll) | | | ExportOverlay | x | x | slightly modified VS 2017 Icon Pack (Export) | | | ExtensionMethod | x | x | VS 2017 Icon Pack (ExtensionMethod) | | | Field | x | x | VS 2017 Icon Pack (Field) | | @@ -62,6 +64,7 @@ Icons used in ILSpy: | ResourceXml | x | x | VS 2017 Icon Pack (XMLFile) | | | ResourceXsd | x | x | combined VS 2017 Icon Pack (XMLSchema) with the file symbol in ResourceXslt | | | ResourceXsl | x | x | VS 2017 Icon Pack (XMLTransformation) | | +| ResultToJSON | x | x | VS 2017 Icon Pack (ResultToJSON) | | | ResourceXslt | x | x | VS 2017 Icon Pack (XSLTTemplate) | | | Save | x | x | VS 2017 Icon Pack (Save) | | | Search | x | x | VS 2017 Icon Pack (Search) | | @@ -73,6 +76,7 @@ Icons used in ILSpy: | Struct | x | x | VS 2017 Icon Pack (Structure) | | | SubTypes | x | x | based on VS 2017 Icon Pack (BaseType) rotated +90° | | | SuperTypes | x | x | based on VS 2017 Icon Pack (BaseType) rotated -90° | | +| SwitchSourceOrTarget | x | x | VS 2017 Icon Pack (SwitchSourceOrTarget) | | | ViewCode | x | x | VS 2017 Icon Pack (GoToSourceCode) | | | VirtualMethod | x | x | combined VS 2017 Icon Pack (Method) two times | | | Warning | x | x | VS 2017 Icon Pack (StatusWarning) | | diff --git a/ILSpy/Images/ResultToJSON.svg b/ILSpy/Images/ResultToJSON.svg new file mode 100644 index 000000000..9057dc768 --- /dev/null +++ b/ILSpy/Images/ResultToJSON.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ILSpy/Images/ResultToJSON.xaml b/ILSpy/Images/ResultToJSON.xaml new file mode 100644 index 000000000..1dfb33662 --- /dev/null +++ b/ILSpy/Images/ResultToJSON.xaml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/Images/SwitchSourceOrTarget.svg b/ILSpy/Images/SwitchSourceOrTarget.svg new file mode 100644 index 000000000..b51f87a9e --- /dev/null +++ b/ILSpy/Images/SwitchSourceOrTarget.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ILSpy/Images/SwitchSourceOrTarget.xaml b/ILSpy/Images/SwitchSourceOrTarget.xaml new file mode 100644 index 000000000..5a72dc2c1 --- /dev/null +++ b/ILSpy/Images/SwitchSourceOrTarget.xaml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/ILSpy/ViewModels/CompareViewModel.cs b/ILSpy/ViewModels/CompareViewModel.cs index 7ae583564..529e04511 100644 --- a/ILSpy/ViewModels/CompareViewModel.cs +++ b/ILSpy/ViewModels/CompareViewModel.cs @@ -20,29 +20,30 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection.Metadata; +using System.Text.Json; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp.OutputVisitor; +using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.AssemblyTree; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.Views; using ICSharpCode.ILSpyX; - -using TomsToolbox.Wpf; +using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; #nullable enable namespace ICSharpCode.ILSpy.ViewModels { - using System.Linq; - using System.Threading.Tasks; - using System.Windows.Input; - using System.Windows.Media; - - using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.TypeSystem; - using ICSharpCode.ILSpy.Commands; - using ICSharpCode.ILSpy.TreeNodes; - using ICSharpCode.ILSpy.Views; - using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; + + using TomsToolbox.Wpf; class CompareViewModel : ObservableObject { @@ -70,6 +71,7 @@ namespace ICSharpCode.ILSpy.ViewModels this.SwapAssembliesCommand = new DelegateCommand(OnSwapAssemblies); this.ExpandAllCommand = new DelegateCommand(OnExpandAll); + this.CopyToClipboardAsJSONCommand = new DelegateCommand(OnCopyToClipboardAsJSON); this.PropertyChanged += CompareViewModel_PropertyChanged; } @@ -153,6 +155,7 @@ namespace ICSharpCode.ILSpy.ViewModels public ICommand SwapAssembliesCommand { get; set; } public ICommand ExpandAllCommand { get; set; } + public ICommand CopyToClipboardAsJSONCommand { get; set; } void OnSwapAssemblies() { @@ -163,7 +166,7 @@ namespace ICSharpCode.ILSpy.ViewModels OnPropertyChanged(nameof(RightAssembly)); } - public void OnExpandAll() + void OnExpandAll() { foreach (var node in RootEntry.DescendantsAndSelf()) { @@ -171,6 +174,77 @@ namespace ICSharpCode.ILSpy.ViewModels } } + void OnCopyToClipboardAsJSON() + { + var options = new JsonSerializerOptions { + WriteIndented = true + }; + + var jsonEntry = ConvertToJson(this.root.Entry); + var json = JsonSerializer.Serialize(jsonEntry, options); + Clipboard.SetText(json); + } + + private object ConvertToJson(Entry entry) + { + List changedTypes = new(); + List addedTypes = new(); + List removedTypes = new(); + + foreach (var item in TreeTraversal.PreOrder(entry, entry => entry.Children)) + { + if (item.Entity is ITypeDefinition) + { + switch (item.RecursiveKind) + { + case DiffKind.Add: + addedTypes.Add(item); + break; + case DiffKind.Remove: + removedTypes.Add(item); + break; + case DiffKind.Update: + changedTypes.Add(item); + break; + } + } + } + var result = new { + left = LeftAssembly.FileName.Replace('\\', '/'), + right = RightAssembly.FileName.Replace('\\', '/'), + changedTypes = changedTypes.SelectArray(t => new { typeName = ((ITypeDefinition)t.Entity).FullName, changes = GetChanges(t.Children) }), + addedTypes = addedTypes.SelectArray(t => new { typeName = ((ITypeDefinition)t.Entity).FullName, changes = GetChanges(t.Children) }), + removedTypes = removedTypes.SelectArray(t => new { typeName = ((ITypeDefinition)t.Entity).FullName, changes = GetChanges(t.Children) }) + }; + return result; + + string? GetEntityText(ISymbol? symbol) => symbol switch { + ITypeDefinition t => this.assemblyTreeModel.CurrentLanguage.TypeToString(t, includeNamespace: true), + IMethod m => this.assemblyTreeModel.CurrentLanguage.MethodToString(m, false, false, false), + IField f => this.assemblyTreeModel.CurrentLanguage.FieldToString(f, false, false, false), + IProperty p => this.assemblyTreeModel.CurrentLanguage.PropertyToString(p, false, false, false), + IEvent e => this.assemblyTreeModel.CurrentLanguage.EventToString(e, false, false, false), + INamespace n => n.FullName, + IModule m => m.FullAssemblyName, + _ => null, + }; + + IEnumerable GetChanges(List? entries) + { + if (entries == null) + yield break; + foreach (var item in entries) + { + if (item.Kind is not (DiffKind.Add or DiffKind.Remove or DiffKind.Update)) + continue; + yield return new { + name = GetEntityText(item.Entity), + operation = item.Kind switch { DiffKind.Add => "added", DiffKind.Remove => "removed", DiffKind.Update => "changed" } + }; + } + } + } + Entry MergeTrees(Entry a, Entry b) { var m = new Entry() { @@ -546,6 +620,8 @@ namespace ICSharpCode.ILSpy.ViewModels private readonly Entry entry; private readonly CompareViewModel compareViewModel; + internal Entry Entry => entry; + public ComparisonEntryTreeNode(Entry entry, CompareViewModel compareViewModel) { this.entry = entry; diff --git a/ILSpy/Views/CompareView.xaml b/ILSpy/Views/CompareView.xaml index 6279a8de6..fe02ffab6 100644 --- a/ILSpy/Views/CompareView.xaml +++ b/ILSpy/Views/CompareView.xaml @@ -5,17 +5,50 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:ICSharpCode.ILSpy.Views" xmlns:stv="clr-namespace:ICSharpCode.ILSpy.Controls.TreeView" + xmlns:controls="clr-namespace:ICSharpCode.ILSpy.Controls" + xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + + + + + + - + + + + +