mirror of https://github.com/icsharpcode/ILSpy.git
16 changed files with 825 additions and 38 deletions
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2025 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Composition; |
||||
using System.Threading.Tasks; |
||||
|
||||
using ICSharpCode.ILSpy.AssemblyTree; |
||||
using ICSharpCode.ILSpy.Docking; |
||||
using ICSharpCode.ILSpy.TreeNodes; |
||||
using ICSharpCode.ILSpy.ViewModels; |
||||
using ICSharpCode.ILSpy.Views; |
||||
|
||||
namespace ICSharpCode.ILSpy |
||||
{ |
||||
[ExportContextMenuEntry(Header = "Compare...", Order = 9999)] |
||||
[Shared] |
||||
internal sealed class CompareContextMenuEntry(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace) : IContextMenuEntry |
||||
{ |
||||
public void Execute(TextViewContext context) |
||||
{ |
||||
var left = ((AssemblyTreeNode)context.SelectedTreeNodes[0]).LoadedAssembly; |
||||
var right = ((AssemblyTreeNode)context.SelectedTreeNodes[1]).LoadedAssembly; |
||||
|
||||
var tabPage = dockWorkspace.AddTabPage(); |
||||
CompareViewModel.Show(tabPage, left, right, assemblyTreeModel); |
||||
} |
||||
|
||||
public bool IsEnabled(TextViewContext context) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
public bool IsVisible(TextViewContext context) |
||||
{ |
||||
return context.SelectedTreeNodes is [AssemblyTreeNode { LoadedAssembly.IsLoadedAsValidAssembly: true }, AssemblyTreeNode { LoadedAssembly.IsLoadedAsValidAssembly: true }]; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,625 @@
@@ -0,0 +1,625 @@
|
||||
// Copyright (c) 2025 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.Reflection.Metadata; |
||||
|
||||
using ICSharpCode.Decompiler.CSharp.OutputVisitor; |
||||
using ICSharpCode.ILSpy.AssemblyTree; |
||||
using ICSharpCode.ILSpyX; |
||||
|
||||
using TomsToolbox.Wpf; |
||||
|
||||
#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; |
||||
|
||||
class CompareViewModel : ObservableObject |
||||
{ |
||||
private readonly TabPageModel tabPage; |
||||
private readonly AssemblyTreeModel assemblyTreeModel; |
||||
private LoadedAssembly leftAssembly; |
||||
private LoadedAssembly rightAssembly; |
||||
[AllowNull] |
||||
private LoadedAssembly[] assemblies; |
||||
private ComparisonEntryTreeNode root; |
||||
private bool updating = false; |
||||
private bool showIdentical; |
||||
|
||||
public CompareViewModel(TabPageModel tabPage, AssemblyTreeModel assemblyTreeModel, LoadedAssembly left, LoadedAssembly right) |
||||
{ |
||||
this.tabPage = tabPage; |
||||
this.assemblyTreeModel = assemblyTreeModel; |
||||
leftAssembly = left; |
||||
rightAssembly = right; |
||||
|
||||
var leftTree = CreateEntityTree(left.GetTypeSystemOrNull()!); |
||||
var rightTree = CreateEntityTree(right.GetTypeSystemOrNull()!); |
||||
|
||||
this.root = new ComparisonEntryTreeNode(MergeTrees(leftTree.Item2, rightTree.Item2), this); |
||||
|
||||
this.SwapAssembliesCommand = new DelegateCommand(OnSwapAssemblies); |
||||
|
||||
this.PropertyChanged += CompareViewModel_PropertyChanged; |
||||
} |
||||
|
||||
private void CompareViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) |
||||
{ |
||||
switch (e.PropertyName) |
||||
{ |
||||
case nameof(LeftAssembly): |
||||
case nameof(RightAssembly): |
||||
if (updating) |
||||
break; |
||||
updating = true; |
||||
var view = tabPage.Content; |
||||
tabPage.ShowTextView(t => t.RunWithCancellation(token => Task.Run(DoCompare, token), $"Comparing {LeftAssembly.Text} - {RightAssembly.Text}").Then(_ => { |
||||
tabPage.Title = $"Compare {LeftAssembly.Text} - {RightAssembly.Text}"; |
||||
tabPage.SupportsLanguageSwitching = false; |
||||
tabPage.FrozenContent = true; |
||||
tabPage.Content = view; |
||||
updating = false; |
||||
}).HandleExceptions()); |
||||
|
||||
Task<bool> DoCompare() |
||||
{ |
||||
var leftTree = CreateEntityTree(leftAssembly.GetTypeSystemOrNull()!); |
||||
var rightTree = CreateEntityTree(rightAssembly.GetTypeSystemOrNull()!); |
||||
|
||||
this.RootEntry = new ComparisonEntryTreeNode(MergeTrees(leftTree.Item2, rightTree.Item2), this); |
||||
return Task.FromResult(true); |
||||
} |
||||
break; |
||||
case nameof(ShowIdentical): |
||||
this.RootEntry.EnsureChildrenFiltered(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public LoadedAssembly LeftAssembly { |
||||
get => leftAssembly; |
||||
set { |
||||
if (leftAssembly != value) |
||||
{ |
||||
leftAssembly = value; |
||||
OnPropertyChanged(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public LoadedAssembly RightAssembly { |
||||
get => rightAssembly; |
||||
set { |
||||
if (rightAssembly != value) |
||||
{ |
||||
rightAssembly = value; |
||||
OnPropertyChanged(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public ComparisonEntryTreeNode RootEntry { |
||||
get => root; |
||||
set { |
||||
if (root != value) |
||||
{ |
||||
root = value; |
||||
OnPropertyChanged(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public bool ShowIdentical { |
||||
get => showIdentical; |
||||
set { |
||||
if (showIdentical != value) |
||||
{ |
||||
showIdentical = value; |
||||
OnPropertyChanged(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public ICommand SwapAssembliesCommand { get; set; } |
||||
|
||||
void OnSwapAssemblies() |
||||
{ |
||||
var left = this.leftAssembly; |
||||
this.leftAssembly = this.rightAssembly; |
||||
this.rightAssembly = left; |
||||
OnPropertyChanged(nameof(LeftAssembly)); |
||||
OnPropertyChanged(nameof(RightAssembly)); |
||||
} |
||||
|
||||
Entry MergeTrees(Entry a, Entry b) |
||||
{ |
||||
var m = new Entry() { |
||||
Entity = a.Entity, |
||||
OtherEntity = b.Entity, |
||||
Signature = a.Signature, |
||||
}; |
||||
|
||||
if (a.Children?.Count > 0 && b.Children?.Count > 0) |
||||
{ |
||||
var diff = CalculateDiff(a.Children, b.Children); |
||||
m.Children ??= new(); |
||||
|
||||
foreach (var (left, right) in diff) |
||||
{ |
||||
if (left != null && right != null) |
||||
m.Children.Add(MergeTrees(left, right)); |
||||
else if (left != null) |
||||
m.Children.Add(left); |
||||
else if (right != null) |
||||
m.Children.Add(right); |
||||
else |
||||
Debug.Fail("wh00t?"); |
||||
m.Children[^1].Parent = m; |
||||
} |
||||
} |
||||
else if (a.Children?.Count > 0) |
||||
{ |
||||
m.Children ??= new(); |
||||
foreach (var child in a.Children) |
||||
{ |
||||
child.Parent = m; |
||||
m.Children.Add(child); |
||||
} |
||||
} |
||||
else if (b.Children?.Count > 0) |
||||
{ |
||||
m.Children ??= new(); |
||||
foreach (var child in b.Children) |
||||
{ |
||||
child.Parent = m; |
||||
m.Children.Add(child); |
||||
} |
||||
} |
||||
|
||||
return m; |
||||
} |
||||
|
||||
(List<Entry>, Entry) CreateEntityTree(ICompilation typeSystem) |
||||
{ |
||||
var module = (MetadataModule)typeSystem.MainModule!; |
||||
var metadata = module.MetadataFile.Metadata; |
||||
var ambience = new CSharpAmbience(); |
||||
ambience.ConversionFlags = ICSharpCode.Decompiler.Output.ConversionFlags.All & ~ICSharpCode.Decompiler.Output.ConversionFlags.ShowDeclaringType; |
||||
|
||||
List<Entry> results = new(); |
||||
Dictionary<TypeDefinitionHandle, Entry> typeEntries = new(); |
||||
Dictionary<string, Entry> namespaceEntries = new(StringComparer.Ordinal); |
||||
|
||||
Entry root = new Entry { Entity = module, Signature = module.FullAssemblyName }; |
||||
|
||||
// typeEntries need a different signature: must include list of base types
|
||||
|
||||
Entry? TryCreateEntry(IEntity entity) |
||||
{ |
||||
if (entity.EffectiveAccessibility() != Accessibility.Public) |
||||
return null; |
||||
|
||||
Entry? parent = null; |
||||
|
||||
if (entity.DeclaringTypeDefinition != null |
||||
&& !typeEntries.TryGetValue((TypeDefinitionHandle)entity.DeclaringTypeDefinition.MetadataToken, out parent)) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
var entry = new Entry { |
||||
Signature = ambience.ConvertSymbol(entity), |
||||
Entity = entity, |
||||
Parent = parent, |
||||
}; |
||||
|
||||
if (parent != null) |
||||
{ |
||||
parent.Children ??= new(); |
||||
parent.Children.Add(entry); |
||||
} |
||||
|
||||
return entry; |
||||
} |
||||
|
||||
foreach (var typeDefHandle in metadata.TypeDefinitions) |
||||
{ |
||||
var typeDef = module.GetDefinition(typeDefHandle); |
||||
|
||||
if (typeDef.EffectiveAccessibility() != Accessibility.Public) |
||||
continue; |
||||
|
||||
var entry = typeEntries[typeDefHandle] = new Entry { |
||||
Signature = ambience.ConvertSymbol(typeDef), |
||||
Entity = typeDef |
||||
}; |
||||
|
||||
if (typeDef.DeclaringType == null) |
||||
{ |
||||
if (!namespaceEntries.TryGetValue(typeDef.Namespace, out var nsEntry)) |
||||
{ |
||||
namespaceEntries[typeDef.Namespace] = nsEntry = new Entry { Parent = root, Signature = typeDef.Namespace, Entity = ResolveNamespace(typeDef.Namespace, typeDef.ParentModule)! }; |
||||
root.Children ??= new(); |
||||
root.Children.Add(nsEntry); |
||||
} |
||||
|
||||
entry.Parent = nsEntry; |
||||
nsEntry.Children ??= new(); |
||||
nsEntry.Children.Add(entry); |
||||
} |
||||
} |
||||
|
||||
foreach (var fieldHandle in metadata.FieldDefinitions) |
||||
{ |
||||
var fieldDef = module.GetDefinition(fieldHandle); |
||||
var entry = TryCreateEntry(fieldDef); |
||||
|
||||
if (entry != null) |
||||
results.Add(entry); |
||||
} |
||||
|
||||
foreach (var eventHandle in metadata.EventDefinitions) |
||||
{ |
||||
var eventDef = module.GetDefinition(eventHandle); |
||||
var entry = TryCreateEntry(eventDef); |
||||
|
||||
if (entry != null) |
||||
results.Add(entry); |
||||
} |
||||
|
||||
foreach (var propertyHandle in metadata.PropertyDefinitions) |
||||
{ |
||||
var propertyDef = module.GetDefinition(propertyHandle); |
||||
var entry = TryCreateEntry(propertyDef); |
||||
|
||||
if (entry != null) |
||||
results.Add(entry); |
||||
} |
||||
|
||||
foreach (var methodHandle in metadata.MethodDefinitions) |
||||
{ |
||||
var methodDef = module.GetDefinition(methodHandle); |
||||
|
||||
if (methodDef.AccessorOwner != null) |
||||
continue; |
||||
|
||||
var entry = TryCreateEntry(methodDef); |
||||
|
||||
if (entry != null) |
||||
results.Add(entry); |
||||
} |
||||
|
||||
return (results, root); |
||||
|
||||
INamespace? ResolveNamespace(string namespaceName, IModule module) |
||||
{ |
||||
INamespace current = module.RootNamespace; |
||||
string[] parts = namespaceName.Split('.'); |
||||
|
||||
for (int i = 0; i < parts.Length; i++) |
||||
{ |
||||
if (i == 0 && string.IsNullOrEmpty(parts[i])) |
||||
{ |
||||
continue; |
||||
} |
||||
var next = current.GetChildNamespace(parts[i]); |
||||
if (next != null) |
||||
current = next; |
||||
else |
||||
return null; |
||||
} |
||||
|
||||
return current; |
||||
} |
||||
} |
||||
|
||||
List<(Entry? Left, Entry? Right)> CalculateDiff(List<Entry> left, List<Entry> right) |
||||
{ |
||||
Dictionary<string, List<Entry>> leftMap = new(); |
||||
Dictionary<string, List<Entry>> rightMap = new(); |
||||
|
||||
foreach (var item in left) |
||||
{ |
||||
string key = item.Signature; |
||||
if (leftMap.ContainsKey(key)) |
||||
leftMap[key].Add(item); |
||||
else |
||||
leftMap[key] = [item]; |
||||
} |
||||
|
||||
foreach (var item in right) |
||||
{ |
||||
string key = item.Signature; |
||||
if (rightMap.ContainsKey(key)) |
||||
rightMap[key].Add(item); |
||||
else |
||||
rightMap[key] = [item]; |
||||
} |
||||
|
||||
List<(Entry? Left, Entry? Right)> results = new(); |
||||
|
||||
foreach (var (key, items) in leftMap) |
||||
{ |
||||
if (rightMap.TryGetValue(key, out var rightEntries)) |
||||
{ |
||||
foreach (var item in items) |
||||
{ |
||||
var other = rightEntries.Find(_ => EntryComparer.Instance.Equals(_, item)); |
||||
results.Add((item, other)); |
||||
if (other == null) |
||||
{ |
||||
SetKind(item, DiffKind.Remove); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
foreach (var item in items) |
||||
{ |
||||
SetKind(item, DiffKind.Remove); |
||||
results.Add((item, null)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
foreach (var (key, items) in rightMap) |
||||
{ |
||||
if (leftMap.TryGetValue(key, out var leftEntries)) |
||||
{ |
||||
foreach (var item in items) |
||||
{ |
||||
if (!leftEntries.Any(_ => EntryComparer.Instance.Equals(_, item))) |
||||
{ |
||||
results.Add((null, item)); |
||||
SetKind(item, DiffKind.Add); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
foreach (var item in items) |
||||
{ |
||||
SetKind(item, DiffKind.Add); |
||||
results.Add((null, item)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return results; |
||||
|
||||
static void SetKind(Entry item, DiffKind kind) |
||||
{ |
||||
if (item.Children?.Count > 0) |
||||
{ |
||||
foreach (var child in item.Children) |
||||
{ |
||||
SetKind(child, kind); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
item.Kind = kind; |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal static void Show(TabPageModel tabPage, LoadedAssembly left, LoadedAssembly right, AssemblyTreeModel assemblyTreeModel) |
||||
{ |
||||
tabPage.ShowTextView(t => t.RunWithCancellation(token => Task.Run(DoCompare, token), $"Comparing {left.Text} - {right.Text}").Then(vm => { |
||||
tabPage.Title = $"Compare {left.Text} - {right.Text}"; |
||||
tabPage.SupportsLanguageSwitching = false; |
||||
tabPage.FrozenContent = true; |
||||
var compareView = new CompareView(); |
||||
compareView.DataContext = vm; |
||||
tabPage.Content = compareView; |
||||
})); |
||||
|
||||
CompareViewModel DoCompare() |
||||
{ |
||||
return new CompareViewModel(tabPage, assemblyTreeModel, left, right); |
||||
} |
||||
} |
||||
} |
||||
|
||||
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] |
||||
public class Entry |
||||
{ |
||||
private DiffKind? kind; |
||||
|
||||
public DiffKind RecursiveKind { |
||||
get { |
||||
if (kind != null) |
||||
return kind.Value; |
||||
if (Children == null) |
||||
return DiffKind.None; |
||||
|
||||
int addCount = 0, removeCount = 0, updateCount = 0; |
||||
|
||||
foreach (var item in Children) |
||||
{ |
||||
switch (item.RecursiveKind) |
||||
{ |
||||
case DiffKind.Add: |
||||
addCount++; |
||||
break; |
||||
case DiffKind.Remove: |
||||
removeCount++; |
||||
break; |
||||
case DiffKind.Update: |
||||
updateCount++; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (addCount == Children.Count) |
||||
return DiffKind.Add; |
||||
if (removeCount == Children.Count) |
||||
return DiffKind.Remove; |
||||
if (addCount > 0 || removeCount > 0 || updateCount > 0) |
||||
return DiffKind.Update; |
||||
return DiffKind.None; |
||||
} |
||||
} |
||||
|
||||
public DiffKind Kind { |
||||
get => this.kind ?? DiffKind.None; |
||||
set => this.kind = value; |
||||
} |
||||
public required string Signature { get; init; } |
||||
public required ISymbol Entity { get; init; } |
||||
public ISymbol? OtherEntity { get; init; } |
||||
|
||||
public Entry? Parent { get; set; } |
||||
public List<Entry>? Children { get; set; } |
||||
|
||||
private string GetDebuggerDisplay() |
||||
{ |
||||
return $"Entry{Kind}{Entity.ToString() ?? Signature}"; |
||||
} |
||||
} |
||||
|
||||
public class EntryComparer : IEqualityComparer<Entry> |
||||
{ |
||||
public static EntryComparer Instance = new(); |
||||
|
||||
public bool Equals(Entry? x, Entry? y) |
||||
{ |
||||
return x?.Signature == y?.Signature; |
||||
} |
||||
|
||||
public int GetHashCode([DisallowNull] Entry obj) |
||||
{ |
||||
return obj.Signature.GetHashCode(); |
||||
} |
||||
} |
||||
|
||||
public enum DiffKind |
||||
{ |
||||
None = ' ', |
||||
Add = '+', |
||||
Remove = '-', |
||||
Update = '~' |
||||
} |
||||
|
||||
class ComparisonEntryTreeNode : ILSpyTreeNode |
||||
{ |
||||
private readonly Entry entry; |
||||
private readonly CompareViewModel compareViewModel; |
||||
|
||||
public ComparisonEntryTreeNode(Entry entry, CompareViewModel compareViewModel) |
||||
{ |
||||
this.entry = entry; |
||||
this.LazyLoading = entry.Children != null; |
||||
this.compareViewModel = compareViewModel; |
||||
} |
||||
|
||||
protected override void LoadChildren() |
||||
{ |
||||
if (entry.Children == null) |
||||
return; |
||||
|
||||
foreach (var item in entry.Children.OrderBy(e => (-(int)e.RecursiveKind, e.Entity.SymbolKind, e.Signature))) |
||||
{ |
||||
this.Children.Add(new ComparisonEntryTreeNode(item, compareViewModel)); |
||||
} |
||||
} |
||||
|
||||
public override object Text { |
||||
get { |
||||
string? entityText = GetEntityText(entry.Entity); |
||||
string? otherText = GetEntityText(entry.OtherEntity); |
||||
|
||||
return entityText + (otherText != null && entityText != otherText ? " -> " + otherText : ""); |
||||
|
||||
string? GetEntityText(ISymbol? symbol) => symbol switch { |
||||
ITypeDefinition t => this.Language.TypeToString(t, includeNamespace: false) + GetSuffixString(t.MetadataToken), |
||||
IMethod m => this.Language.MethodToString(m, false, false, false) + GetSuffixString(m.MetadataToken), |
||||
IField f => this.Language.FieldToString(f, false, false, false) + GetSuffixString(f.MetadataToken), |
||||
IProperty p => this.Language.PropertyToString(p, false, false, false) + GetSuffixString(p.MetadataToken), |
||||
IEvent e => this.Language.EventToString(e, false, false, false) + GetSuffixString(e.MetadataToken), |
||||
INamespace n => n.FullName, |
||||
IModule m => m.FullAssemblyName, |
||||
_ => null, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
public override object Icon { |
||||
get { |
||||
switch (entry.Entity) |
||||
{ |
||||
case ITypeDefinition t: |
||||
return TypeTreeNode.GetIcon(t); |
||||
case IMethod m: |
||||
return MethodTreeNode.GetIcon(m); |
||||
case IField f: |
||||
return FieldTreeNode.GetIcon(f); |
||||
case IProperty p: |
||||
return PropertyTreeNode.GetIcon(p); |
||||
case IEvent e: |
||||
return EventTreeNode.GetIcon(e); |
||||
case INamespace n: |
||||
return Images.Namespace; |
||||
case IModule m: |
||||
return Images.Assembly; |
||||
default: |
||||
throw new NotSupportedException(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
} |
||||
|
||||
public override FilterResult Filter(LanguageSettings settings) |
||||
{ |
||||
return compareViewModel.ShowIdentical || entry.RecursiveKind != DiffKind.None ? FilterResult.Match : FilterResult.Hidden; |
||||
} |
||||
|
||||
public Brush Background { |
||||
get { |
||||
switch (entry.RecursiveKind) |
||||
{ |
||||
case DiffKind.Add: |
||||
return Brushes.LightGreen; |
||||
case DiffKind.Remove: |
||||
return Brushes.LightPink; |
||||
case DiffKind.Update: |
||||
return Brushes.LightBlue; |
||||
} |
||||
|
||||
return Brushes.Transparent; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
<UserControl x:Class="ICSharpCode.ILSpy.Views.CompareView" |
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:local="clr-namespace:ICSharpCode.ILSpy.Views" |
||||
xmlns:stv="clr-namespace:ICSharpCode.ILSpy.Controls.TreeView" |
||||
mc:Ignorable="d" |
||||
d:DesignHeight="450" d:DesignWidth="800"> |
||||
<Grid> |
||||
<Grid.RowDefinitions> |
||||
<RowDefinition Height="Auto" /> |
||||
<RowDefinition Height="*" /> |
||||
</Grid.RowDefinitions> |
||||
<ToolBar> |
||||
<Label Content="Left:" /> |
||||
<TextBlock VerticalAlignment="Center" Text="{Binding LeftAssembly.Text}" /> |
||||
<Label Content="Right:" /> |
||||
<TextBlock VerticalAlignment="Center" Text="{Binding RightAssembly.Text}" /> |
||||
<Button Content="Swap" Command="{Binding SwapAssembliesCommand}" /> |
||||
<ToggleButton Content="Show identical" IsChecked="{Binding ShowIdentical}" /> |
||||
</ToolBar> |
||||
<stv:SharpTreeView Grid.Row="1" Root="{Binding RootEntry}" ShowRoot="True"> |
||||
<stv:SharpTreeView.ItemContainerStyle> |
||||
<Style TargetType="stv:SharpTreeViewItem"> |
||||
<Setter Property="Template"> |
||||
<Setter.Value> |
||||
<ControlTemplate TargetType="{x:Type stv:SharpTreeViewItem}"> |
||||
<Border Background="Transparent"> |
||||
<Border Background="{TemplateBinding Background}"> |
||||
<stv:SharpTreeNodeView x:Name="nodeView" HorizontalAlignment="Left" /> |
||||
</Border> |
||||
</Border> |
||||
<ControlTemplate.Triggers> |
||||
<DataTrigger Binding="{Binding IsPublicAPI}" Value="False"> |
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> |
||||
</DataTrigger> |
||||
<Trigger Property="IsSelected" Value="True"> |
||||
<Setter TargetName="nodeView" Property="TextBackground" |
||||
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> |
||||
<Setter TargetName="nodeView" Property="Foreground" |
||||
Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" /> |
||||
</Trigger> |
||||
<Trigger Property="IsEnabled" Value="False"> |
||||
<Setter TargetName="nodeView" Property="Foreground" |
||||
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> |
||||
</Trigger> |
||||
</ControlTemplate.Triggers> |
||||
</ControlTemplate> |
||||
</Setter.Value> |
||||
</Setter> |
||||
</Style> |
||||
</stv:SharpTreeView.ItemContainerStyle> |
||||
</stv:SharpTreeView> |
||||
</Grid> |
||||
</UserControl> |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2025 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Windows.Controls; |
||||
|
||||
namespace ICSharpCode.ILSpy.Views |
||||
{ |
||||
/// <summary>
|
||||
/// Interaction logic for CompareView.xaml
|
||||
/// </summary>
|
||||
public partial class CompareView : UserControl |
||||
{ |
||||
public CompareView() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue