diff --git a/ILSpy/Assets/Icons/Copy.svg b/ILSpy/Assets/Icons/Copy.svg
new file mode 100644
index 000000000..3de60b008
--- /dev/null
+++ b/ILSpy/Assets/Icons/Copy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ILSpy/Assets/Icons/EnumValue.svg b/ILSpy/Assets/Icons/EnumValue.svg
new file mode 100644
index 000000000..aa901ec19
--- /dev/null
+++ b/ILSpy/Assets/Icons/EnumValue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ILSpy/Assets/Icons/ExtensionMethod.svg b/ILSpy/Assets/Icons/ExtensionMethod.svg
new file mode 100644
index 000000000..999349160
--- /dev/null
+++ b/ILSpy/Assets/Icons/ExtensionMethod.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ILSpy/Assets/Icons/FieldReadOnly.svg b/ILSpy/Assets/Icons/FieldReadOnly.svg
new file mode 100644
index 000000000..c79e4b2d6
--- /dev/null
+++ b/ILSpy/Assets/Icons/FieldReadOnly.svg
@@ -0,0 +1,69 @@
+
+
diff --git a/ILSpy/Assets/Icons/Indexer.svg b/ILSpy/Assets/Icons/Indexer.svg
new file mode 100644
index 000000000..ff55f31ff
--- /dev/null
+++ b/ILSpy/Assets/Icons/Indexer.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ILSpy/Assets/Icons/PInvokeMethod.svg b/ILSpy/Assets/Icons/PInvokeMethod.svg
new file mode 100644
index 000000000..4db51d198
--- /dev/null
+++ b/ILSpy/Assets/Icons/PInvokeMethod.svg
@@ -0,0 +1,79 @@
+
+
diff --git a/ILSpy/Assets/Icons/VirtualMethod.svg b/ILSpy/Assets/Icons/VirtualMethod.svg
new file mode 100644
index 000000000..dfb0aeba5
--- /dev/null
+++ b/ILSpy/Assets/Icons/VirtualMethod.svg
@@ -0,0 +1,93 @@
+
+
diff --git a/ILSpy/Commands/CopyFullyQualifiedNameContextMenuEntry.cs b/ILSpy/Commands/CopyFullyQualifiedNameContextMenuEntry.cs
index f503d0361..7af87253c 100644
--- a/ILSpy/Commands/CopyFullyQualifiedNameContextMenuEntry.cs
+++ b/ILSpy/Commands/CopyFullyQualifiedNameContextMenuEntry.cs
@@ -34,7 +34,7 @@ namespace ILSpy.Commands
/// Copies the entity's
/// — the language-independent identifier used by FindNodeByPath etc. — to the clipboard.
///
- [ExportContextMenuEntry(Header = nameof(Resources.CopyName), Category = "Edit", Order = 600)]
+ [ExportContextMenuEntry(Header = nameof(Resources.CopyName), Category = "Edit", Icon = "Images/Copy", Order = 600)]
[Shared]
public sealed class CopyFullyQualifiedNameContextMenuEntry : IContextMenuEntry
{
diff --git a/ILSpy/Images.cs b/ILSpy/Images.cs
index 46b9327af..2607cfedc 100644
--- a/ILSpy/Images.cs
+++ b/ILSpy/Images.cs
@@ -110,14 +110,23 @@ namespace ILSpy.Images
public static readonly IImage Method = LoadSvg(nameof(Method));
public static readonly IImage Constructor = LoadSvg(nameof(Constructor));
public static readonly IImage Operator = LoadSvg(nameof(Operator));
+ public static readonly IImage VirtualMethod = LoadSvg(nameof(VirtualMethod));
+ public static readonly IImage ExtensionMethod = LoadSvg(nameof(ExtensionMethod));
+ public static readonly IImage PInvokeMethod = LoadSvg(nameof(PInvokeMethod));
// Fields / properties / events
public static readonly IImage Field = LoadSvg(nameof(Field));
+ public static readonly IImage FieldReadOnly = LoadSvg(nameof(FieldReadOnly));
+ public static readonly IImage EnumValue = LoadSvg(nameof(EnumValue));
public static readonly IImage Property = LoadSvg(nameof(Property));
+ public static readonly IImage Indexer = LoadSvg(nameof(Indexer));
public static readonly IImage Event = LoadSvg(nameof(Event));
public static readonly IImage Literal = LoadSvg(nameof(Literal));
public static readonly IImage ViewCode = LoadSvg(nameof(ViewCode));
+ // Editing / context-menu icons
+ public static readonly IImage Copy = LoadSvg(nameof(Copy));
+
// Reference overlays (small badge layered onto a base icon for TypeRef/MemberRef nodes).
internal static readonly IImage ReferenceOverlay = LoadSvg(nameof(ReferenceOverlay));
internal static readonly IImage ExportOverlay = LoadSvg(nameof(ExportOverlay));
diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs
index 5a07b063d..1e45a3fdc 100644
--- a/ILSpy/TreeNodes/FieldTreeNode.cs
+++ b/ILSpy/TreeNodes/FieldTreeNode.cs
@@ -45,22 +45,23 @@ namespace ILSpy.TreeNodes
public override object Icon => GetIcon(FieldDefinition);
- // Distinguish enum members and const fields from regular instance/static fields so the
- // tree reads the way the C# source does. The IsReadOnly / FieldReadOnly variant from
- // the WPF era isn't ported because we don't have a dedicated SVG for it yet -- such
- // fields fall through to the base Field icon.
+ // Mirrors WPF's discriminator: enum value, const literal, readonly, plain field.
public static Avalonia.Media.IImage GetIcon(IField field)
{
- // EnumValue: the field's declaring type is an enum and its return type is the enum
- // itself -- this excludes the synthesised int32 'value__' backing field.
+ // EnumValue: declaring type is an enum and the return type is the enum itself --
+ // this excludes the synthesised int32 'value__' backing field.
if (field.DeclaringType.Kind == TypeKind.Enum && field.ReturnType.Kind == TypeKind.Enum)
- return Images.Images.GetIcon(Images.Images.Enum,
+ return Images.Images.GetIcon(Images.Images.EnumValue,
Images.Images.GetOverlay(field.Accessibility));
if (field.IsConst)
return Images.Images.GetIcon(Images.Images.Literal,
Images.Images.GetOverlay(field.Accessibility));
+ if (field.IsReadOnly)
+ return Images.Images.GetIcon(Images.Images.FieldReadOnly,
+ Images.Images.GetOverlay(field.Accessibility), field.IsStatic);
+
return Images.Images.GetIcon(Images.Images.Field,
Images.Images.GetOverlay(field.Accessibility), field.IsStatic);
}
diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs
index e826b5a9f..494c668ff 100644
--- a/ILSpy/TreeNodes/MethodTreeNode.cs
+++ b/ILSpy/TreeNodes/MethodTreeNode.cs
@@ -43,16 +43,37 @@ namespace ILSpy.TreeNodes
public override object NavigationText => Language.EntityToString(MethodDefinition, ConversionFlags.ShowDeclaringType);
- public override object Icon {
- get {
- var baseImage = MethodDefinition.IsConstructor ? Images.Images.Constructor :
- MethodDefinition.IsOperator ? Images.Images.Operator :
- Images.Images.Method;
- return Images.Images.GetIcon(baseImage,
- Images.Images.GetOverlay(MethodDefinition.Accessibility),
- MethodDefinition.IsStatic,
- MethodDefinition.IsExtensionMethod);
- }
+ public override object Icon => GetIcon(MethodDefinition);
+
+ // Mirrors WPF's dispatch order: operator, extension method, constructor,
+ // P/Invoke (DllImport without a body), virtual, plain method. Tested against
+ // every kind in the analyzers fixtures.
+ public static Avalonia.Media.IImage GetIcon(IMethod method)
+ {
+ bool isExtensionMethod = method.IsExtensionMethod
+ || (method.ResolveExtensionInfo()?.InfoOfExtensionMember((IMethod)method.MemberDefinition).HasValue == true);
+
+ if (method.IsOperator)
+ return Images.Images.GetIcon(Images.Images.Operator,
+ Images.Images.GetOverlay(method.Accessibility), false, isExtensionMethod);
+
+ if (isExtensionMethod)
+ return Images.Images.GetIcon(Images.Images.Method,
+ Images.Images.GetOverlay(method.Accessibility), false, true);
+
+ if (method.IsConstructor)
+ return Images.Images.GetIcon(Images.Images.Constructor,
+ Images.Images.GetOverlay(method.Accessibility), method.IsStatic);
+
+ // P/Invoke: extern method tagged with [DllImport]. !HasBody filters out the
+ // managed wrappers that also carry the attribute via attribute-forwarding.
+ if (!method.HasBody && method.HasAttribute(KnownAttribute.DllImport))
+ return Images.Images.GetIcon(Images.Images.PInvokeMethod,
+ Images.Images.GetOverlay(method.Accessibility), true);
+
+ return Images.Images.GetIcon(
+ method.IsVirtual ? Images.Images.VirtualMethod : Images.Images.Method,
+ Images.Images.GetOverlay(method.Accessibility), method.IsStatic);
}
public override bool ShowExpander => false;
diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs
index db76b855c..d4ea66ffd 100644
--- a/ILSpy/TreeNodes/PropertyTreeNode.cs
+++ b/ILSpy/TreeNodes/PropertyTreeNode.cs
@@ -47,8 +47,21 @@ namespace ILSpy.TreeNodes
public override object NavigationText => Language.EntityToString(PropertyDefinition, ConversionFlags.ShowDeclaringType);
- public override object Icon => Images.Images.GetIcon(Images.Images.Property,
- Images.Images.GetOverlay(PropertyDefinition.Accessibility), PropertyDefinition.IsStatic);
+ public override object Icon => GetIcon(PropertyDefinition);
+
+ // Mirrors WPF: indexers get a distinct base glyph; extension properties (C# 14
+ // extension blocks) layer the Extension overlay on top.
+ public static Avalonia.Media.IImage GetIcon(IProperty property)
+ {
+ IMethod? accessor = property.Getter ?? property.Setter;
+ bool isExtension = accessor is not null
+ && property.ResolveExtensionInfo()?.InfoOfExtensionMember((IMethod)accessor.MemberDefinition) != null;
+ return Images.Images.GetIcon(
+ property.IsIndexer ? Images.Images.Indexer : Images.Images.Property,
+ Images.Images.GetOverlay(property.Accessibility),
+ property.IsStatic,
+ isExtension);
+ }
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
=> language.DecompileProperty(PropertyDefinition, output, options);