diff --git a/ILSpy.Tests/Metadata/FlagsTooltipTests.cs b/ILSpy.Tests/Metadata/FlagsTooltipTests.cs
new file mode 100644
index 000000000..029cce143
--- /dev/null
+++ b/ILSpy.Tests/Metadata/FlagsTooltipTests.cs
@@ -0,0 +1,81 @@
+// Copyright (c) 2026 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.Linq;
+using System.Reflection;
+
+using Avalonia.Controls;
+using Avalonia.Headless.NUnit;
+
+using AwesomeAssertions;
+
+using ILSpy.Metadata;
+
+using NUnit.Framework;
+
+namespace ICSharpCode.ILSpy.Tests.Metadata;
+
+///
+/// The rich flags tooltip breaks an enum value into per-bit groups: multiple-choice groups render a
+/// checkbox per flag with the set bits checked, single-choice groups render the selected member of a
+/// mutually exclusive sub-range. (Regression: the Avalonia port had only a plain-text fallback.)
+///
+[TestFixture]
+public class FlagsTooltipTests
+{
+ [AvaloniaTest]
+ public void MultipleChoiceGroup_Checks_The_Set_Bits()
+ {
+ var group = FlagGroup.CreateMultipleChoiceGroup(typeof(MethodAttributes), "Flags:",
+ (int)MethodAttributes.Static, (int)MethodAttributes.Static, includeAll: false);
+
+ var view = (StackPanel)group.Build();
+ var statik = view.Children.OfType()
+ .Single(c => ((string?)c.Content)!.StartsWith("Static"));
+ statik.IsChecked.Should().BeTrue("the Static bit is set in the selected value");
+ }
+
+ [AvaloniaTest]
+ public void SingleChoiceGroup_Shows_The_Selected_Member()
+ {
+ var group = FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Accessibility: ",
+ (int)MethodAttributes.MemberAccessMask, (int)MethodAttributes.Public, includeAny: false);
+
+ group.SelectedFlag.Name.Should().StartWith("Public");
+ }
+
+ [AvaloniaTest]
+ public void MetadataCellTooltip_Renders_A_FlagsTooltip_As_A_Control()
+ {
+ var entry = new EntryWithFlags();
+ var resolved = MetadataCellTooltip.Resolve(entry, "Attributes");
+ resolved.Should().BeAssignableTo("a FlagsTooltip value renders as the rich control");
+ }
+
+ sealed class EntryWithFlags
+ {
+ public MethodAttributes Attributes => MethodAttributes.Public | MethodAttributes.Static;
+
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Accessibility: ",
+ (int)MethodAttributes.MemberAccessMask, (int)(Attributes & MethodAttributes.MemberAccessMask), includeAny: false),
+ FlagGroup.CreateMultipleChoiceGroup(typeof(MethodAttributes), "Flags:",
+ ~(int)MethodAttributes.MemberAccessMask, (int)Attributes, includeAll: false),
+ };
+ }
+}
diff --git a/ILSpy.Tests/Metadata/MetadataTooltipParityTests.cs b/ILSpy.Tests/Metadata/MetadataTooltipParityTests.cs
new file mode 100644
index 000000000..eeda758e2
--- /dev/null
+++ b/ILSpy.Tests/Metadata/MetadataTooltipParityTests.cs
@@ -0,0 +1,70 @@
+// Copyright (c) 2026 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.Linq;
+using System.Threading.Tasks;
+
+using Avalonia.Controls;
+using Avalonia.Headless.NUnit;
+
+using AwesomeAssertions;
+
+using ILSpy.Metadata;
+using ILSpy.Metadata.CorTables;
+using ILSpy.TreeNodes;
+
+using NUnit.Framework;
+
+namespace ICSharpCode.ILSpy.Tests.Metadata;
+
+///
+/// End-to-end check that the per-cell metadata tooltips restored across the table entries actually
+/// resolve against a real assembly: a heap-offset string tooltip, a token tooltip routed through
+/// GenerateTooltip, and a rich per-bit rendered as a control.
+///
+[TestFixture]
+public class MetadataTooltipParityTests
+{
+ [AvaloniaTest]
+ public async Task TypeDef_Row_Tooltips_Resolve_For_System_Object()
+ {
+ var (_, vm) = await TestHarness.BootAsync();
+
+ var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
+ .GetChild()
+ .GetChild()
+ .GetChild();
+
+ vm.AssemblyTreeModel.SelectNode(typeDefNode);
+ var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
+
+ var objectRow = tab.Items.Cast()
+ .Single(e => e.Name == "Object" && e.Namespace == "System");
+
+ // Heap-offset string tooltip on the Name column.
+ MetadataCellTooltip.Resolve(objectRow, "Name").Should().BeOfType()
+ .Which.Should().Contain("Object");
+
+ // Token tooltip routed through GenerateTooltip (System.Object's first method).
+ MetadataCellTooltip.Resolve(objectRow, "MethodList").Should().BeOfType()
+ .Which.Should().NotBeNullOrWhiteSpace();
+
+ // Rich per-bit flags breakdown renders as a control, not a plain string.
+ MetadataCellTooltip.Resolve(objectRow, "Attributes").Should().BeAssignableTo();
+ }
+}
diff --git a/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs
index a562b51ef..96e204f1c 100644
--- a/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs
@@ -67,11 +67,28 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public AssemblyFlags Flags => assemblyRef.Flags;
+ public object FlagsTooltip => new FlagsTooltip((int)assemblyRef.Flags, null) {
+ FlagGroup.CreateMultipleChoiceGroup(typeof(AssemblyFlags), selectedValue: (int)assemblyRef.Flags, includeAll: false)
+ };
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int PublicKeyOrToken => MetadataTokens.GetHeapOffset(assemblyRef.PublicKeyOrToken);
+ public string? PublicKeyOrTokenTooltip {
+ get {
+ if (assemblyRef.PublicKeyOrToken.IsNil)
+ return null;
+ System.Collections.Immutable.ImmutableArray token = metadataFile.Metadata.GetBlobContent(assemblyRef.PublicKeyOrToken);
+ return token.ToHexString(token.Length);
+ }
+ }
+
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(assemblyRef.Name):X} \"{Name}\"";
+
public string Name => metadataFile.Metadata.GetString(assemblyRef.Name);
+ public string CultureTooltip => $"{MetadataTokens.GetHeapOffset(assemblyRef.Culture):X} \"{Culture}\"";
+
public string Culture => metadataFile.Metadata.GetString(assemblyRef.Culture);
public AssemblyRefEntry(MetadataFile metadataFile, AssemblyReferenceHandle handle)
diff --git a/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs b/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs
index 08fdde3a2..4ce79cc51 100644
--- a/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs
@@ -62,13 +62,25 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X4")]
public AssemblyHashAlgorithm HashAlgorithm => assembly.HashAlgorithm;
+ public object HashAlgorithmTooltip => new FlagsTooltip() {
+ FlagGroup.CreateSingleChoiceGroup(typeof(AssemblyHashAlgorithm), selectedValue: (int)assembly.HashAlgorithm, defaultFlag: new Flag("None (0000)", 0, false), includeAny: false)
+ };
+
[ColumnInfo("X4")]
public AssemblyFlags Flags => assembly.Flags;
+ public object FlagsTooltip => new FlagsTooltip() {
+ FlagGroup.CreateMultipleChoiceGroup(typeof(AssemblyFlags), selectedValue: (int)assembly.Flags, includeAll: false)
+ };
+
public Version Version => assembly.Version;
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(assembly.Name):X} \"{Name}\"";
+
public string Name => metadata.GetString(assembly.Name);
+ public string CultureTooltip => $"{MetadataTokens.GetHeapOffset(assembly.Culture):X} \"{Culture}\"";
+
public string Culture => metadata.GetString(assembly.Culture);
public AssemblyEntry(MetadataReader metadata, int metadataOffset)
diff --git a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs
index 1bd746efd..16172be03 100644
--- a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs
@@ -50,7 +50,7 @@ namespace ILSpy.Metadata.CorTables
ushort packingSize = reader.ReadUInt16();
uint classSize = reader.ReadUInt32();
int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new ClassLayoutEntry(rid, packingSize, classSize, MetadataTokens.TypeDefinitionHandle(parentRow)));
+ list.Add(new ClassLayoutEntry(metadataFile, rid, packingSize, classSize, MetadataTokens.TypeDefinitionHandle(parentRow)));
}
return list;
}
@@ -65,14 +65,19 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
+
public ushort PackingSize { get; }
public uint ClassSize { get; }
+ readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle parent;
- public ClassLayoutEntry(int rid, ushort packingSize, uint classSize, TypeDefinitionHandle parent)
+ public ClassLayoutEntry(MetadataFile metadataFile, int rid, ushort packingSize, uint classSize, TypeDefinitionHandle parent)
{
+ this.metadataFile = metadataFile;
RID = rid;
PackingSize = packingSize;
ClassSize = classSize;
diff --git a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs
index bf2a5cca6..08f4a41bb 100644
--- a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs
@@ -63,12 +63,23 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public ConstantTypeCode Type => constant.TypeCode;
+ public string TypeTooltip => constant.TypeCode.ToString();
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(constant.Parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, constant.Parent);
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Value => MetadataTokens.GetHeapOffset(constant.Value);
+ public string? ValueTooltip {
+ get {
+ return null;
+ }
+ }
+
public ConstantEntry(MetadataFile metadataFile, ConstantHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs
index 5b0cbfeab..13f4688e8 100644
--- a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs
@@ -64,12 +64,24 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(customAttr.Parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, customAttr.Parent);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Constructor => MetadataTokens.GetToken(customAttr.Constructor);
+ string? constructorTooltip;
+ public string? ConstructorTooltip => GenerateTooltip(ref constructorTooltip, metadataFile, customAttr.Constructor);
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Value => MetadataTokens.GetHeapOffset(customAttr.Value);
+ public string? ValueTooltip {
+ get {
+ return null;
+ }
+ }
+
public CustomAttributeEntry(MetadataFile metadataFile, CustomAttributeHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs
index d3d72755b..1734628a7 100644
--- a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs
@@ -64,12 +64,27 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(declSecAttr.Parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, declSecAttr.Parent);
+
[ColumnInfo("X8")]
public DeclarativeSecurityAction Action => declSecAttr.Action;
+ public string ActionTooltip {
+ get {
+ return declSecAttr.Action.ToString();
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int PermissionSet => MetadataTokens.GetHeapOffset(declSecAttr.PermissionSet);
+ public string? PermissionSetTooltip {
+ get {
+ return null;
+ }
+ }
+
public DeclSecurityEntry(MetadataFile metadataFile, DeclarativeSecurityAttributeHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs
index 9acedccd6..7440051e1 100644
--- a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs
@@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int eventListRow = eventDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new EventMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.EventDefinitionHandle(eventListRow)));
+ list.Add(new EventMapEntry(metadataFile, rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.EventDefinitionHandle(eventListRow)));
}
return list;
}
@@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int EventList => MetadataTokens.GetToken(eventList);
+ string? eventListTooltip;
+ public string? EventListTooltip => GenerateTooltip(ref eventListTooltip, metadataFile, eventList);
+
+ readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle parent;
readonly EventDefinitionHandle eventList;
- public EventMapEntry(int rid, TypeDefinitionHandle parent, EventDefinitionHandle eventList)
+ public EventMapEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle parent, EventDefinitionHandle eventList)
{
+ this.metadataFile = metadataFile;
RID = rid;
this.parent = parent;
this.eventList = eventList;
diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
index 1c3014de8..606b2bff2 100644
--- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
@@ -64,11 +64,20 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public EventAttributes Attributes => eventDef.Attributes;
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateMultipleChoiceGroup(typeof(EventAttributes), selectedValue: (int)eventDef.Attributes, includeAll: false),
+ };
+
public string Name => metadataFile.Metadata.GetString(eventDef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(eventDef.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Type => MetadataTokens.GetToken(eventDef.Type);
+ string? typeTooltip;
+ public string? TypeTooltip => GenerateTooltip(ref typeTooltip, metadataFile, eventDef.Type);
+
public EventDefEntry(MetadataFile metadataFile, EventDefinitionHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs
index b36199fe1..c49ae1971 100644
--- a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs
@@ -65,16 +65,34 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public TypeAttributes Attributes => type.Attributes;
+ const TypeAttributes otherFlagsMask = ~(TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.ClassSemanticsMask | TypeAttributes.StringFormatMask | TypeAttributes.CustomFormatMask);
+
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Visibility: ", (int)TypeAttributes.VisibilityMask, (int)(type.Attributes & TypeAttributes.VisibilityMask), new Flag("NotPublic (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class layout: ", (int)TypeAttributes.LayoutMask, (int)(type.Attributes & TypeAttributes.LayoutMask), new Flag("AutoLayout (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class semantics: ", (int)TypeAttributes.ClassSemanticsMask, (int)(type.Attributes & TypeAttributes.ClassSemanticsMask), new Flag("Class (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "String format: ", (int)TypeAttributes.StringFormatMask, (int)(type.Attributes & TypeAttributes.StringFormatMask), new Flag("AnsiClass (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Custom format: ", (int)TypeAttributes.CustomFormatMask, (int)(type.Attributes & TypeAttributes.CustomFormatMask), new Flag("Value0 (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateMultipleChoiceGroup(typeof(TypeAttributes), "Flags:", (int)otherFlagsMask, (int)(type.Attributes & otherFlagsMask), includeAll: false),
+ };
+
[ColumnInfo("X8")]
public int TypeDefId => type.GetTypeDefinitionId();
+ public string TypeNameTooltip => $"{MetadataTokens.GetHeapOffset(type.Name):X} \"{TypeName}\"";
+
public string TypeName => metadataFile.Metadata.GetString(type.Name);
+ public string TypeNamespaceTooltip => $"{MetadataTokens.GetHeapOffset(type.Namespace):X} \"{TypeNamespace}\"";
+
public string TypeNamespace => metadataFile.Metadata.GetString(type.Namespace);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Implementation => MetadataTokens.GetToken(type.Implementation);
+ string? implementationTooltip;
+ public string? ImplementationTooltip => GenerateTooltip(ref implementationTooltip, metadataFile, type.Implementation);
+
public ExportedTypeEntry(MetadataFile metadataFile, ExportedTypeHandle handle, ExportedType type)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs
index 28976fb1f..93ad7514a 100644
--- a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs
@@ -47,7 +47,7 @@ namespace ILSpy.Metadata.CorTables
{
int offset = reader.ReadInt32();
int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new FieldLayoutEntry(rid, offset, MetadataTokens.FieldDefinitionHandle(fieldRow)));
+ list.Add(new FieldLayoutEntry(metadataFile, rid, offset, MetadataTokens.FieldDefinitionHandle(fieldRow)));
}
return list;
}
@@ -57,6 +57,7 @@ namespace ILSpy.Metadata.CorTables
// Use a verbatim fieldHandle local because C# 14 made `field` contextual inside property
// accessors — referencing it without `@` would name the auto-property's hidden
// backing field instead of this member.
+ readonly MetadataFile metadataFile;
readonly FieldDefinitionHandle fieldHandle;
public int RID { get; }
@@ -67,11 +68,15 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Field => MetadataTokens.GetToken(fieldHandle);
+ string? fieldTooltip;
+ public string? FieldTooltip => GenerateTooltip(ref fieldTooltip, metadataFile, fieldHandle);
+
[ColumnInfo("X8")]
public int FieldOffset { get; }
- public FieldLayoutEntry(int rid, int fieldOffset, FieldDefinitionHandle field)
+ public FieldLayoutEntry(MetadataFile metadataFile, int rid, int fieldOffset, FieldDefinitionHandle field)
{
+ this.metadataFile = metadataFile;
RID = rid;
FieldOffset = fieldOffset;
fieldHandle = field;
diff --git a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs
index 9bd226b48..562a85abb 100644
--- a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs
@@ -49,7 +49,7 @@ namespace ILSpy.Metadata.CorTables
{
uint parentTag = (uint)(hasFieldMarshalRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
int blobOffset = blobHeapSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new FieldMarshalEntry(rid,
+ list.Add(new FieldMarshalEntry(metadataFile, rid,
MetadataReaderHelpers.FromHasFieldMarshalTag(parentTag),
MetadataTokens.BlobHandle(blobOffset)));
}
@@ -66,14 +66,19 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int NativeType => MetadataTokens.GetHeapOffset(nativeType);
+ readonly MetadataFile metadataFile;
readonly EntityHandle parent;
readonly BlobHandle nativeType;
- public FieldMarshalEntry(int rid, EntityHandle parent, BlobHandle nativeType)
+ public FieldMarshalEntry(MetadataFile metadataFile, int rid, EntityHandle parent, BlobHandle nativeType)
{
+ this.metadataFile = metadataFile;
RID = rid;
this.parent = parent;
this.nativeType = nativeType;
diff --git a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs
index e550a13fc..7f5569533 100644
--- a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs
@@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int rva = reader.ReadInt32();
int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new FieldRVAEntry(rid, rva, MetadataTokens.FieldDefinitionHandle(fieldRow)));
+ list.Add(new FieldRVAEntry(metadataFile, rid, rva, MetadataTokens.FieldDefinitionHandle(fieldRow)));
}
return list;
}
@@ -56,6 +56,7 @@ namespace ILSpy.Metadata.CorTables
public sealed class FieldRVAEntry
{
// Verbatim fieldHandle — see FieldLayoutTableTreeNode for the C# 14 keyword note.
+ readonly MetadataFile metadataFile;
readonly FieldDefinitionHandle fieldHandle;
public int RID { get; }
@@ -66,11 +67,15 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Field => MetadataTokens.GetToken(fieldHandle);
+ string? fieldTooltip;
+ public string? FieldTooltip => GenerateTooltip(ref fieldTooltip, metadataFile, fieldHandle);
+
[ColumnInfo("X8")]
public int RVA { get; }
- public FieldRVAEntry(int rid, int rva, FieldDefinitionHandle field)
+ public FieldRVAEntry(MetadataFile metadataFile, int rid, int rva, FieldDefinitionHandle field)
{
+ this.metadataFile = metadataFile;
RID = rid;
RVA = rva;
fieldHandle = field;
diff --git a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
index 71820c9bf..aa93ef5e5 100644
--- a/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
@@ -64,11 +64,23 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public FieldAttributes Attributes => fieldDef.Attributes;
+ const FieldAttributes otherFlagsMask = ~(FieldAttributes.FieldAccessMask);
+
+ public object AttributesTooltip => new FlagsTooltip() {
+ FlagGroup.CreateSingleChoiceGroup(typeof(FieldAttributes), "Field access: ", (int)FieldAttributes.FieldAccessMask, (int)(fieldDef.Attributes & FieldAttributes.FieldAccessMask), new Flag("CompilerControlled (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateMultipleChoiceGroup(typeof(FieldAttributes), "Flags:", (int)otherFlagsMask, (int)(fieldDef.Attributes & otherFlagsMask), includeAll: false),
+ };
+
public string Name => metadataFile.Metadata.GetString(fieldDef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(fieldDef.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature);
+ string? signatureTooltip;
+ public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
+
public FieldDefEntry(MetadataFile metadataFile, FieldDefinitionHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/FileTableTreeNode.cs b/ILSpy/Metadata/CorTables/FileTableTreeNode.cs
index ea496b45b..873707531 100644
--- a/ILSpy/Metadata/CorTables/FileTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/FileTableTreeNode.cs
@@ -62,11 +62,24 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public int Attributes => assemblyFile.ContainsMetadata ? 1 : 0;
+ public string AttributesTooltip => assemblyFile.ContainsMetadata ? "ContainsMetaData" : "ContainsNoMetaData";
+
public string Name => metadataFile.Metadata.GetString(assemblyFile.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(assemblyFile.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int HashValue => MetadataTokens.GetHeapOffset(assemblyFile.HashValue);
+ public string? HashValueTooltip {
+ get {
+ if (assemblyFile.HashValue.IsNil)
+ return null;
+ System.Collections.Immutable.ImmutableArray token = metadataFile.Metadata.GetBlobContent(assemblyFile.HashValue);
+ return token.ToHexString(token.Length);
+ }
+ }
+
public FileEntry(MetadataFile metadataFile, AssemblyFileHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs
index e159de46b..959cc56c9 100644
--- a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs
@@ -20,6 +20,8 @@ using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@@ -64,9 +66,27 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Owner => MetadataTokens.GetToken(genericParamConstraint.Parameter);
+ string? ownerTooltip;
+ public string? OwnerTooltip {
+ get {
+ if (ownerTooltip == null)
+ {
+ ITextOutput output = new PlainTextOutput();
+ var p = metadataFile.Metadata.GetGenericParameter(genericParamConstraint.Parameter);
+ output.Write("parameter " + p.Index + (p.Name.IsNil ? "" : " (" + metadataFile.Metadata.GetString(p.Name) + ")") + " of ");
+ p.Parent.WriteTo(metadataFile, output, default);
+ ownerTooltip = output.ToString();
+ }
+ return ownerTooltip;
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Type => MetadataTokens.GetToken(genericParamConstraint.Type);
+ string? typeTooltip;
+ public string? TypeTooltip => GenerateTooltip(ref typeTooltip, metadataFile, genericParamConstraint.Type);
+
public GenericParamConstraintEntry(MetadataFile metadataFile, GenericParameterConstraintHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs
index 796c0869e..b9ac48bcb 100644
--- a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs
@@ -66,11 +66,21 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public GenericParameterAttributes Attributes => genericParam.Attributes;
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateSingleChoiceGroup(typeof(GenericParameterAttributes), "Variance: ", (int)GenericParameterAttributes.VarianceMask, (int)(genericParam.Attributes & GenericParameterAttributes.VarianceMask), new Flag("None (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateMultipleChoiceGroup(typeof(GenericParameterAttributes), "Special Constraint: ", (int)GenericParameterAttributes.SpecialConstraintMask, (int)(genericParam.Attributes & GenericParameterAttributes.SpecialConstraintMask), includeAll: false),
+ };
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Owner => MetadataTokens.GetToken(genericParam.Parent);
+ string? ownerTooltip;
+ public string? OwnerTooltip => GenerateTooltip(ref ownerTooltip, metadataFile, genericParam.Parent);
+
public string Name => metadataFile.Metadata.GetString(genericParam.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(genericParam.Name):X} \"{Name}\"";
+
public GenericParamEntry(MetadataFile metadataFile, GenericParameterHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs
index 6e1b6e94a..2f7ccd27e 100644
--- a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs
@@ -53,9 +53,9 @@ namespace ILSpy.Metadata.CorTables
uint memberForwardedTag = (uint)(memberForwardedTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
int importNameOffset = stringHandleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int importScopeRow = moduleRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new ImplMapEntry(rid, mappingFlags,
+ list.Add(new ImplMapEntry(metadataFile, rid, mappingFlags,
MetadataReaderHelpers.FromMemberForwardedTag(memberForwardedTag),
- metadata.GetString(MetadataTokens.StringHandle(importNameOffset)),
+ MetadataTokens.StringHandle(importNameOffset),
MetadataTokens.ModuleReferenceHandle(importScopeRow)));
}
return list;
@@ -71,23 +71,43 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public MethodImportAttributes MappingFlags { get; }
+ const MethodImportAttributes otherFlagsMask = ~(MethodImportAttributes.CallingConventionMask | MethodImportAttributes.CharSetMask);
+
+ public object MappingFlagsTooltip => new FlagsTooltip {
+ FlagGroup.CreateSingleChoiceGroup(typeof(MethodImportAttributes), "Character set: ", (int)MethodImportAttributes.CharSetMask, (int)(MappingFlags & MethodImportAttributes.CharSetMask), new Flag("CharSetNotSpec (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(MethodImportAttributes), "Calling convention: ", (int)MethodImportAttributes.CallingConventionMask, (int)(MappingFlags & MethodImportAttributes.CallingConventionMask), new Flag("Invalid (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateMultipleChoiceGroup(typeof(MethodImportAttributes), "Flags:", (int)otherFlagsMask, (int)(MappingFlags & otherFlagsMask), includeAll: false),
+ };
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MemberForwarded => MetadataTokens.GetToken(memberForwarded);
+ string? memberForwardedTooltip;
+ public string? MemberForwardedTooltip => GenerateTooltip(ref memberForwardedTooltip, metadataFile, memberForwarded);
+
public string ImportName { get; }
+ public string ImportNameTooltip => $"{MetadataTokens.GetHeapOffset(importNameHandle):X} \"{ImportName}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ImportScope => MetadataTokens.GetToken(importScope);
+ string? importScopeTooltip;
+ public string? ImportScopeTooltip => GenerateTooltip(ref importScopeTooltip, metadataFile, importScope);
+
+ readonly MetadataFile metadataFile;
readonly EntityHandle memberForwarded;
+ readonly StringHandle importNameHandle;
readonly ModuleReferenceHandle importScope;
- public ImplMapEntry(int rid, MethodImportAttributes mappingFlags, EntityHandle memberForwarded, string importName, ModuleReferenceHandle importScope)
+ public ImplMapEntry(MetadataFile metadataFile, int rid, MethodImportAttributes mappingFlags, EntityHandle memberForwarded, StringHandle importNameHandle, ModuleReferenceHandle importScope)
{
+ this.metadataFile = metadataFile;
RID = rid;
MappingFlags = mappingFlags;
this.memberForwarded = memberForwarded;
- ImportName = importName;
+ this.importNameHandle = importNameHandle;
+ ImportName = metadataFile.Metadata.GetString(importNameHandle);
this.importScope = importScope;
}
}
diff --git a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs
index 3116335e7..bb4e2c388 100644
--- a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs
@@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int classRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
uint interfaceTag = (uint)(interfaceTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
- list.Add(new InterfaceImplEntry(rid,
+ list.Add(new InterfaceImplEntry(metadataFile, rid,
MetadataTokens.TypeDefinitionHandle(classRow),
MetadataReaderHelpers.FromTypeDefOrRefTag(interfaceTag)));
}
@@ -65,14 +65,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Class => MetadataTokens.GetToken(@class);
+ string? classTooltip;
+ public string? ClassTooltip => GenerateTooltip(ref classTooltip, metadataFile, @class);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Interface => MetadataTokens.GetToken(@interface);
+ string? interfaceTooltip;
+ public string? InterfaceTooltip => GenerateTooltip(ref interfaceTooltip, metadataFile, @interface);
+
+ readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle @class;
readonly EntityHandle @interface;
- public InterfaceImplEntry(int rid, TypeDefinitionHandle @class, EntityHandle @interface)
+ public InterfaceImplEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle @class, EntityHandle @interface)
{
+ this.metadataFile = metadataFile;
RID = rid;
this.@class = @class;
this.@interface = @interface;
diff --git a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs
index 83dfbeca8..5aa988e02 100644
--- a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs
@@ -65,11 +65,18 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public ManifestResourceAttributes Attributes => manifestResource.Attributes;
+ public object AttributesTooltip => manifestResource.Attributes.ToString();
+
public string Name => metadataFile.Metadata.GetString(manifestResource.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(manifestResource.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Implementation => MetadataTokens.GetToken(manifestResource.Implementation);
+ string? implementationTooltip;
+ public string? ImplementationTooltip => GenerateTooltip(ref implementationTooltip, metadataFile, manifestResource.Implementation);
+
public ManifestResourceEntry(MetadataFile metadataFile, ManifestResourceHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs
index c37d6a4cd..e016affbf 100644
--- a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs
@@ -63,11 +63,19 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(memberRef.Parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, memberRef.Parent);
+
public string Name => metadataFile.Metadata.GetString(memberRef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(memberRef.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(memberRef.Signature);
+ string? signatureTooltip;
+ public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
+
public MemberRefEntry(MetadataFile metadataFile, MemberReferenceHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs
index f3f07bf17..df344ed16 100644
--- a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs
@@ -58,12 +58,21 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Type => MetadataTokens.GetToken(methodImpl.Type);
+ string? typeTooltip;
+ public string? TypeTooltip => GenerateTooltip(ref typeTooltip, metadataFile, methodImpl.Type);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MethodDeclaration => MetadataTokens.GetToken(methodImpl.MethodDeclaration);
+ string? methodDeclarationTooltip;
+ public string? MethodDeclarationTooltip => GenerateTooltip(ref methodDeclarationTooltip, metadataFile, methodImpl.MethodDeclaration);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MethodBody => MetadataTokens.GetToken(methodImpl.MethodBody);
+ string? methodBodyTooltip;
+ public string? MethodBodyTooltip => GenerateTooltip(ref methodBodyTooltip, metadataFile, methodImpl.MethodBody);
+
public MethodImplEntry(MetadataFile metadataFile, MethodImplementationHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs
index 5efc61ec3..5aa520390 100644
--- a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs
@@ -41,7 +41,7 @@ namespace ILSpy.Metadata.CorTables
{
var list = new List();
foreach (var row in metadataFile.Metadata.GetMethodSemantics())
- list.Add(new MethodSemanticsEntry(row.Handle, row.Semantics, row.Method, row.Association));
+ list.Add(new MethodSemanticsEntry(metadataFile, row.Handle, row.Semantics, row.Method, row.Association));
return list;
}
@@ -55,18 +55,28 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public MethodSemanticsAttributes Semantics { get; }
+ public string SemanticsTooltip => Semantics.ToString();
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Method => MetadataTokens.GetToken(method);
+ string? methodTooltip;
+ public string? MethodTooltip => GenerateTooltip(ref methodTooltip, metadataFile, method);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Association => MetadataTokens.GetToken(association);
+ string? associationTooltip;
+ public string? AssociationTooltip => GenerateTooltip(ref associationTooltip, metadataFile, association);
+
+ readonly MetadataFile metadataFile;
readonly Handle handle;
readonly MethodDefinitionHandle method;
readonly EntityHandle association;
- public MethodSemanticsEntry(Handle handle, MethodSemanticsAttributes semantics, MethodDefinitionHandle method, EntityHandle association)
+ public MethodSemanticsEntry(MetadataFile metadataFile, Handle handle, MethodSemanticsAttributes semantics, MethodDefinitionHandle method, EntityHandle association)
{
+ this.metadataFile = metadataFile;
this.handle = handle;
Semantics = semantics;
this.method = method;
diff --git a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs
index 8e1d36698..5b67563ce 100644
--- a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs
@@ -20,6 +20,8 @@ using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@@ -63,9 +65,29 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Method => MetadataTokens.GetToken(methodSpec.Method);
+ string? methodTooltip;
+ public string? MethodTooltip => GenerateTooltip(ref methodTooltip, metadataFile, methodSpec.Method);
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(methodSpec.Signature);
+ public string? SignatureTooltip {
+ get {
+ ITextOutput output = new PlainTextOutput();
+ var signature = methodSpec.DecodeSignature(new DisassemblerSignatureTypeProvider(metadataFile, output), default);
+ bool first = true;
+ foreach (var type in signature)
+ {
+ if (first)
+ first = false;
+ else
+ output.Write(", ");
+ type(ILNameSyntax.TypeName);
+ }
+ return output.ToString();
+ }
+ }
+
public MethodSpecEntry(MetadataFile metadataFile, MethodSpecificationHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
index db846f0aa..90879afc8 100644
--- a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
@@ -65,20 +65,49 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public MethodAttributes Attributes => methodDef.Attributes;
+ const MethodAttributes otherFlagsMask = ~(MethodAttributes.MemberAccessMask | MethodAttributes.VtableLayoutMask);
+
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Member access: ", (int)MethodAttributes.MemberAccessMask, (int)(methodDef.Attributes & MethodAttributes.MemberAccessMask), new Flag("CompilerControlled (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Vtable layout: ", (int)MethodAttributes.VtableLayoutMask, (int)(methodDef.Attributes & MethodAttributes.VtableLayoutMask), new Flag("ReuseSlot (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateMultipleChoiceGroup(typeof(MethodAttributes), "Flags:", (int)otherFlagsMask, (int)(methodDef.Attributes & otherFlagsMask), includeAll: false),
+ };
+
[ColumnInfo("X8")]
public MethodImplAttributes ImplAttributes => methodDef.ImplAttributes;
+ public object ImplAttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateSingleChoiceGroup(typeof(MethodImplAttributes), "Code type: ", (int)MethodImplAttributes.CodeTypeMask, (int)(methodDef.ImplAttributes & MethodImplAttributes.CodeTypeMask), new Flag("IL (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(MethodImplAttributes), "Managed type: ", (int)MethodImplAttributes.ManagedMask, (int)(methodDef.ImplAttributes & MethodImplAttributes.ManagedMask), new Flag("Managed (0000)", 0, false), includeAny: false),
+ };
+
[ColumnInfo("X8")]
public int RVA => methodDef.RelativeVirtualAddress;
public string Name => metadataFile.Metadata.GetString(methodDef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(methodDef.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(methodDef.Signature);
+ string? signatureTooltip;
+
+ public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ParamList => MetadataTokens.GetToken(methodDef.GetParameters().FirstOrDefault());
+ string? paramListTooltip;
+ public string? ParamListTooltip {
+ get {
+ var param = methodDef.GetParameters().FirstOrDefault();
+ if (param.IsNil)
+ return null;
+ return GenerateTooltip(ref paramListTooltip, metadataFile, param);
+ }
+ }
+
public MethodDefEntry(MetadataFile metadataFile, MethodDefinitionHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs
index d40abe6da..a9c5eb0b7 100644
--- a/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs
@@ -61,6 +61,8 @@ namespace ILSpy.Metadata.CorTables
public string Name => metadataFile.Metadata.GetString(moduleRef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(moduleRef.Name):X} \"{Name}\"";
+
public ModuleRefEntry(MetadataFile metadataFile, ModuleReferenceHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs b/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs
index a143bc5f3..36fe6da90 100644
--- a/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs
@@ -59,15 +59,23 @@ namespace ILSpy.Metadata.CorTables
public string Name => metadataFile.Metadata.GetString(moduleDef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(moduleDef.Name):X} \"{Name}\"";
+
[ColumnInfo("X8")]
public int Mvid => MetadataTokens.GetHeapOffset(moduleDef.Mvid);
+ public string MvidTooltip => metadataFile.Metadata.GetGuid(moduleDef.Mvid).ToString();
+
[ColumnInfo("X8")]
public int GenerationId => MetadataTokens.GetHeapOffset(moduleDef.GenerationId);
+ public string? GenerationIdTooltip => moduleDef.GenerationId.IsNil ? null : metadataFile.Metadata.GetGuid(moduleDef.GenerationId).ToString();
+
[ColumnInfo("X8")]
public int BaseGenerationId => MetadataTokens.GetHeapOffset(moduleDef.BaseGenerationId);
+ public string? BaseGenerationIdTooltip => moduleDef.BaseGenerationId.IsNil ? null : metadataFile.Metadata.GetGuid(moduleDef.BaseGenerationId).ToString();
+
public ModuleEntry(MetadataFile metadataFile, ModuleDefinitionHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs
index a8b4de244..0918fe144 100644
--- a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs
@@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int nestedRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int enclosingRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new NestedClassEntry(rid, MetadataTokens.TypeDefinitionHandle(nestedRow), MetadataTokens.TypeDefinitionHandle(enclosingRow)));
+ list.Add(new NestedClassEntry(metadataFile, rid, MetadataTokens.TypeDefinitionHandle(nestedRow), MetadataTokens.TypeDefinitionHandle(enclosingRow)));
}
return list;
}
@@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int NestedClass => MetadataTokens.GetToken(nested);
+ string? nestedClassTooltip;
+ public string? NestedClassTooltip => GenerateTooltip(ref nestedClassTooltip, metadataFile, nested);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int EnclosingClass => MetadataTokens.GetToken(enclosing);
+ string? enclosingClassTooltip;
+ public string? EnclosingClassTooltip => GenerateTooltip(ref enclosingClassTooltip, metadataFile, enclosing);
+
+ readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle nested;
readonly TypeDefinitionHandle enclosing;
- public NestedClassEntry(int rid, TypeDefinitionHandle nested, TypeDefinitionHandle enclosing)
+ public NestedClassEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle nested, TypeDefinitionHandle enclosing)
{
+ this.metadataFile = metadataFile;
RID = rid;
this.nested = nested;
this.enclosing = enclosing;
diff --git a/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs
index 3c01624f8..28c12edb3 100644
--- a/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/ParamTableTreeNode.cs
@@ -63,10 +63,16 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public ParameterAttributes Attributes => param.Attributes;
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateMultipleChoiceGroup(typeof(ParameterAttributes), selectedValue: (int)param.Attributes, includeAll: false)
+ };
+
public int Sequence => param.SequenceNumber;
public string Name => metadataFile.Metadata.GetString(param.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(param.Name):X} \"{Name}\"";
+
public ParamEntry(MetadataFile metadataFile, ParameterHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs
index c1d86ce66..50c71af0d 100644
--- a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs
@@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int propertyListRow = propertyDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new PropertyMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.PropertyDefinitionHandle(propertyListRow)));
+ list.Add(new PropertyMapEntry(metadataFile, rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.PropertyDefinitionHandle(propertyListRow)));
}
return list;
}
@@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int PropertyList => MetadataTokens.GetToken(propertyList);
+ string? propertyListTooltip;
+ public string? PropertyListTooltip => GenerateTooltip(ref propertyListTooltip, metadataFile, propertyList);
+
+ readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle parent;
readonly PropertyDefinitionHandle propertyList;
- public PropertyMapEntry(int rid, TypeDefinitionHandle parent, PropertyDefinitionHandle propertyList)
+ public PropertyMapEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle parent, PropertyDefinitionHandle propertyList)
{
+ this.metadataFile = metadataFile;
RID = rid;
this.parent = parent;
this.propertyList = propertyList;
diff --git a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
index 6d16d0dd6..b95803dc0 100644
--- a/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
@@ -64,11 +64,20 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public PropertyAttributes Attributes => propertyDef.Attributes;
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateMultipleChoiceGroup(typeof(PropertyAttributes), selectedValue: (int)propertyDef.Attributes, includeAll: false),
+ };
+
public string Name => metadataFile.Metadata.GetString(propertyDef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(propertyDef.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature);
+ string? signatureTooltip;
+ public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
+
public PropertyDefEntry(MetadataFile metadataFile, PropertyDefinitionHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs
index 71fc2c481..7fe815a8b 100644
--- a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs
@@ -59,7 +59,7 @@ namespace ILSpy.Metadata.CorTables
for (int rid = 1; rid <= length; rid++)
{
int handleRow = handleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
- list.Add(new PtrEntry(rid, Kind, MetadataTokens.EntityHandle(((int)referencedTableKind << 24) | handleRow)));
+ list.Add(new PtrEntry(metadataFile, rid, Kind, MetadataTokens.EntityHandle(((int)referencedTableKind << 24) | handleRow)));
}
return list;
}
@@ -74,11 +74,16 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Handle => MetadataTokens.GetToken(handle);
+ string? handleTooltip;
+ public string? HandleTooltip => GenerateTooltip(ref handleTooltip, metadataFile, handle);
+
+ readonly MetadataFile metadataFile;
readonly TableIndex kind;
readonly EntityHandle handle;
- public PtrEntry(int rid, TableIndex kind, EntityHandle handle)
+ public PtrEntry(MetadataFile metadataFile, int rid, TableIndex kind, EntityHandle handle)
{
+ this.metadataFile = metadataFile;
RID = rid;
this.kind = kind;
this.handle = handle;
diff --git a/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs b/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs
index 6f7c46ade..ff894e679 100644
--- a/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs
@@ -63,6 +63,9 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(standaloneSig.Signature);
+ string? signatureTooltip;
+ public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
+
public StandAloneSigEntry(MetadataFile metadataFile, StandaloneSignatureHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
index 5aac48fd2..0fdaa5523 100644
--- a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
@@ -22,6 +22,8 @@ using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@@ -66,19 +68,77 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public TypeAttributes Attributes => typeDef.Attributes;
+ const TypeAttributes otherFlagsMask = ~(TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.ClassSemanticsMask | TypeAttributes.StringFormatMask | TypeAttributes.CustomFormatMask);
+
+ public object AttributesTooltip => new FlagsTooltip {
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Visibility: ", (int)TypeAttributes.VisibilityMask, (int)(typeDef.Attributes & TypeAttributes.VisibilityMask), new Flag("NotPublic (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class layout: ", (int)TypeAttributes.LayoutMask, (int)(typeDef.Attributes & TypeAttributes.LayoutMask), new Flag("AutoLayout (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class semantics: ", (int)TypeAttributes.ClassSemanticsMask, (int)(typeDef.Attributes & TypeAttributes.ClassSemanticsMask), new Flag("Class (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "String format: ", (int)TypeAttributes.StringFormatMask, (int)(typeDef.Attributes & TypeAttributes.StringFormatMask), new Flag("AnsiClass (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Custom format: ", (int)TypeAttributes.CustomFormatMask, (int)(typeDef.Attributes & TypeAttributes.CustomFormatMask), new Flag("Value0 (0000)", 0, false), includeAny: false),
+ FlagGroup.CreateMultipleChoiceGroup(typeof(TypeAttributes), "Flags:", (int)otherFlagsMask, (int)(typeDef.Attributes & otherFlagsMask), includeAll: false),
+ };
+
public string Name => metadataFile.Metadata.GetString(typeDef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(typeDef.Name):X} \"{Name}\"";
+
public string Namespace => metadataFile.Metadata.GetString(typeDef.Namespace);
+ public string NamespaceTooltip => $"{MetadataTokens.GetHeapOffset(typeDef.Namespace):X} \"{Namespace}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int BaseType => MetadataTokens.GetToken(typeDef.BaseType);
+ public string? BaseTypeTooltip {
+ get {
+ var output = new PlainTextOutput();
+ var provider = new DisassemblerSignatureTypeProvider(metadataFile, output);
+ if (typeDef.BaseType.IsNil)
+ return null;
+ switch (typeDef.BaseType.Kind)
+ {
+ case HandleKind.TypeDefinition:
+ provider.GetTypeFromDefinition(metadataFile.Metadata, (TypeDefinitionHandle)typeDef.BaseType, 0)(ILNameSyntax.Signature);
+ return output.ToString();
+ case HandleKind.TypeReference:
+ provider.GetTypeFromReference(metadataFile.Metadata, (TypeReferenceHandle)typeDef.BaseType, 0)(ILNameSyntax.Signature);
+ return output.ToString();
+ case HandleKind.TypeSpecification:
+ provider.GetTypeFromSpecification(metadataFile.Metadata, new MetadataGenericContext(default(TypeDefinitionHandle), metadataFile.Metadata), (TypeSpecificationHandle)typeDef.BaseType, 0)(ILNameSyntax.Signature);
+ return output.ToString();
+ default:
+ return null;
+ }
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int FieldList => MetadataTokens.GetToken(typeDef.GetFields().FirstOrDefault());
+ string? fieldListTooltip;
+ public string? FieldListTooltip {
+ get {
+ var @field = typeDef.GetFields().FirstOrDefault();
+ if (@field.IsNil)
+ return null;
+ return GenerateTooltip(ref fieldListTooltip, metadataFile, @field);
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MethodList => MetadataTokens.GetToken(typeDef.GetMethods().FirstOrDefault());
+ string? methodListTooltip;
+ public string? MethodListTooltip {
+ get {
+ var method = typeDef.GetMethods().FirstOrDefault();
+ if (method.IsNil)
+ return null;
+ return GenerateTooltip(ref methodListTooltip, metadataFile, method);
+ }
+ }
+
public TypeDefEntry(MetadataFile metadataFile, TypeDefinitionHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs
index 7eba9c933..84dac67fe 100644
--- a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs
@@ -63,10 +63,17 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ResolutionScope => MetadataTokens.GetToken(typeRef.ResolutionScope);
+ string? resolutionScopeTooltip;
+ public string? ResolutionScopeTooltip => GenerateTooltip(ref resolutionScopeTooltip, metadataFile, typeRef.ResolutionScope);
+
public string Name => metadataFile.Metadata.GetString(typeRef.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(typeRef.Name):X} \"{Name}\"";
+
public string Namespace => metadataFile.Metadata.GetString(typeRef.Namespace);
+ public string NamespaceTooltip => $"{MetadataTokens.GetHeapOffset(typeRef.Namespace):X} \"{Namespace}\"";
+
public TypeRefEntry(MetadataFile metadataFile, TypeReferenceHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs
index 8542171c9..8d0d37ef4 100644
--- a/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs
@@ -20,6 +20,8 @@ using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@@ -63,6 +65,14 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(typeSpec.Signature);
+ public string? SignatureTooltip {
+ get {
+ ITextOutput output = new PlainTextOutput();
+ typeSpec.DecodeSignature(new DisassemblerSignatureTypeProvider(metadataFile, output), default)(ILNameSyntax.Signature);
+ return output.ToString();
+ }
+ }
+
public TypeSpecEntry(MetadataFile metadataFile, TypeSpecificationHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs
index b4ef6616d..48b7f4d8a 100644
--- a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs
+++ b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs
@@ -60,12 +60,23 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(debugInfo.Parent);
+ string? parentTooltip;
+ public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, debugInfo.Parent);
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Kind => MetadataTokens.GetHeapOffset(debugInfo.Kind);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Value => MetadataTokens.GetHeapOffset(debugInfo.Value);
+ public string ValueTooltip {
+ get {
+ if (debugInfo.Value.IsNil)
+ return "";
+ return metadataFile.Metadata.GetBlobReader(debugInfo.Value).ToHexString();
+ }
+ }
+
public CustomDebugInformationEntry(MetadataFile metadataFile, CustomDebugInformationHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs b/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs
index 809aa80b0..129fac453 100644
--- a/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs
+++ b/ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs
@@ -16,10 +16,12 @@
// 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.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.DebugTables
@@ -57,15 +59,54 @@ namespace ILSpy.Metadata.DebugTables
public string Name => metadataFile.Metadata.GetString(document.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(document.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int HashAlgorithm => MetadataTokens.GetHeapOffset(document.HashAlgorithm);
+ public string? HashAlgorithmTooltip {
+ get {
+ if (document.HashAlgorithm.IsNil)
+ return null;
+ Guid guid = metadataFile.Metadata.GetGuid(document.HashAlgorithm);
+ if (guid == KnownGuids.HashAlgorithmSHA1)
+ return "SHA1 [ff1816ec-aa5e-4d10-87f7-6f4963833460]";
+ if (guid == KnownGuids.HashAlgorithmSHA256)
+ return "SHA256 [8829d00f-11b8-4213-878b-770e8597ac16]";
+ return $"Unknown [" + guid + "]";
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Hash => MetadataTokens.GetHeapOffset(document.Hash);
+ public string? HashTooltip {
+ get {
+ if (document.Hash.IsNil)
+ return null;
+ System.Collections.Immutable.ImmutableArray token = metadataFile.Metadata.GetBlobContent(document.Hash);
+ return token.ToHexString(token.Length);
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Language => MetadataTokens.GetHeapOffset(document.Language);
+ public string? LanguageTooltip {
+ get {
+ if (document.Language.IsNil)
+ return null;
+ Guid guid = metadataFile.Metadata.GetGuid(document.Language);
+ if (guid == KnownGuids.CSharpLanguageGuid)
+ return "Visual C# [3f5162f8-07c6-11d3-9053-00c04fa302a1]";
+ if (guid == KnownGuids.VBLanguageGuid)
+ return "Visual Basic [3a12d0b8-c26c-11d0-b442-00a0244a1dd2]";
+ if (guid == KnownGuids.FSharpLanguageGuid)
+ return "Visual F# [ab4f38c9-b6e6-43ba-be3b-58080b2ccce3]";
+ return $"Unknown [" + guid + "]";
+ }
+ }
+
public DocumentEntry(MetadataFile metadataFile, DocumentHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs
index b6663f353..dfafaa78f 100644
--- a/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs
+++ b/ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs
@@ -57,6 +57,8 @@ namespace ILSpy.Metadata.DebugTables
public string Name => metadataFile.Metadata.GetString(localConst.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(localConst.Name):X} \"{Name}\"";
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(localConst.Signature);
diff --git a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs
index 7e3fa6516..26ad79333 100644
--- a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs
+++ b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs
@@ -59,6 +59,9 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Method => MetadataTokens.GetToken(localScope.Method);
+ string? methodTooltip;
+ public string? MethodTooltip => GenerateTooltip(ref methodTooltip, metadataFile, localScope.Method);
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ImportScope => MetadataTokens.GetToken(localScope.ImportScope);
diff --git a/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs
index 0442a0b36..e343044a3 100644
--- a/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs
+++ b/ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs
@@ -58,10 +58,16 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8")]
public LocalVariableAttributes Attributes => localVar.Attributes;
+ public object AttributesTooltip => new FlagsTooltip() {
+ FlagGroup.CreateMultipleChoiceGroup(typeof(LocalVariableAttributes)),
+ };
+
public int Index => localVar.Index;
public string Name => metadataFile.Metadata.GetString(localVar.Name);
+ public string NameTooltip => $"{MetadataTokens.GetHeapOffset(localVar.Name):X} \"{Name}\"";
+
public LocalVariableEntry(MetadataFile metadataFile, LocalVariableHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs
index c86483b21..1dbfb9112 100644
--- a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs
+++ b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs
@@ -19,7 +19,10 @@
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using System.Text;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.DebugTables
@@ -58,12 +61,55 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Document => MetadataTokens.GetToken(debugInfo.Document);
+ public string? DocumentTooltip {
+ get {
+ if (debugInfo.Document.IsNil)
+ return null;
+ var document = metadataFile.Metadata.GetDocument(debugInfo.Document);
+ return $"{MetadataTokens.GetHeapOffset(document.Name):X} \"{metadataFile.Metadata.GetString(document.Name)}\"";
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int SequencePoints => MetadataTokens.GetHeapOffset(debugInfo.SequencePointsBlob);
+ public string? SequencePointsTooltip {
+ get {
+ if (debugInfo.SequencePointsBlob.IsNil)
+ return null;
+ StringBuilder sb = new StringBuilder();
+ foreach (var p in debugInfo.GetSequencePoints())
+ {
+ sb.AppendLine($"document='{MetadataTokens.GetToken(p.Document):X8}', offset={p.Offset}, start={p.StartLine};{p.StartColumn}, end={p.EndLine};{p.EndColumn}, hidden={p.IsHidden}");
+ }
+ return sb.ToString().TrimEnd();
+ }
+ }
+
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int LocalSignature => MetadataTokens.GetToken(debugInfo.LocalSignature);
+ public string? LocalSignatureTooltip {
+ get {
+ if (debugInfo.LocalSignature.IsNil)
+ return null;
+ ITextOutput output = new PlainTextOutput();
+ var context = new MetadataGenericContext(default(TypeDefinitionHandle), metadataFile.Metadata);
+ StandaloneSignature localSignature = metadataFile.Metadata.GetStandaloneSignature(debugInfo.LocalSignature);
+ var signatureDecoder = new DisassemblerSignatureTypeProvider(metadataFile, output);
+ int index = 0;
+ foreach (var item in localSignature.DecodeLocalSignature(signatureDecoder, context))
+ {
+ if (index > 0)
+ output.WriteLine();
+ output.Write("[{0}] ", index);
+ item(ILNameSyntax.Signature);
+ index++;
+ }
+ return output.ToString();
+ }
+ }
+
public MethodDebugInformationEntry(MetadataFile metadataFile, MethodDebugInformationHandle handle)
{
this.metadataFile = metadataFile;
diff --git a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs
index e3dd58fc9..0dca42ffc 100644
--- a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs
+++ b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs
@@ -49,29 +49,40 @@ namespace ILSpy.Metadata.DebugTables
{
var moveNext = MetadataTokens.MethodDefinitionHandle(methodDefSize == 2 ? reader.ReadInt16() : reader.ReadInt32());
var kickoff = MetadataTokens.MethodDefinitionHandle(methodDefSize == 2 ? reader.ReadInt16() : reader.ReadInt32());
- list.Add(new StateMachineMethodEntry(rid, moveNext, kickoff));
+ list.Add(new StateMachineMethodEntry(metadataFile, rid, moveNext, kickoff));
}
return list;
}
public sealed class StateMachineMethodEntry
{
+ readonly MetadataFile metadataFile;
+ readonly MethodDefinitionHandle moveNextMethod;
+ readonly MethodDefinitionHandle kickoffMethod;
+
public int RID { get; }
[ColumnInfo("X8")]
public int Token => 0x36000000 + RID;
[ColumnInfo("X8", Kind = ColumnKind.Token)]
- public int MoveNextMethod { get; }
+ public int MoveNextMethod => MetadataTokens.GetToken(moveNextMethod);
+
+ string? moveNextMethodTooltip;
+ public string? MoveNextMethodTooltip => GenerateTooltip(ref moveNextMethodTooltip, metadataFile, moveNextMethod);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
- public int KickoffMethod { get; }
+ public int KickoffMethod => MetadataTokens.GetToken(kickoffMethod);
+
+ string? kickoffMethodTooltip;
+ public string? KickoffMethodTooltip => GenerateTooltip(ref kickoffMethodTooltip, metadataFile, kickoffMethod);
- public StateMachineMethodEntry(int rid, MethodDefinitionHandle moveNext, MethodDefinitionHandle kickoff)
+ public StateMachineMethodEntry(MetadataFile metadataFile, int rid, MethodDefinitionHandle moveNext, MethodDefinitionHandle kickoff)
{
+ this.metadataFile = metadataFile;
RID = rid;
- MoveNextMethod = MetadataTokens.GetToken(moveNext);
- KickoffMethod = MetadataTokens.GetToken(kickoff);
+ moveNextMethod = moveNext;
+ kickoffMethod = kickoff;
}
}
}
diff --git a/ILSpy/Metadata/FlagsTooltip.cs b/ILSpy/Metadata/FlagsTooltip.cs
new file mode 100644
index 000000000..fe88c4af8
--- /dev/null
+++ b/ILSpy/Metadata/FlagsTooltip.cs
@@ -0,0 +1,184 @@
+// Copyright (c) 2026 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;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+using ICSharpCode.Decompiler.Util;
+
+namespace ILSpy.Metadata
+{
+ ///
+ /// Rich cell tooltip for an enum-valued metadata column: it breaks a flags value down into
+ /// per-bit groups, showing each named flag with a checkbox reflecting whether the bit is set
+ /// (multiple-choice groups) or the single selected member of a mutually exclusive sub-range
+ /// (single-choice groups). Entries build one with a collection initializer of
+ /// s; renders it via .
+ ///
+ public sealed class FlagsTooltip : IEnumerable
+ {
+ // The (value, flagsType) ctor parameters are unused but kept so entry call sites read the
+ // same as the value they describe; the groups carry the actual selection state.
+ public FlagsTooltip(int value = 0, Type? flagsType = null)
+ {
+ }
+
+ public List Groups { get; } = new List();
+
+ public void Add(FlagGroup group) => Groups.Add(group);
+
+ public IEnumerator GetEnumerator() => Groups.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => Groups.GetEnumerator();
+
+ ///
+ /// Materialises the tooltip into an Avalonia control: a vertical stack of group views.
+ ///
+ public Control Build()
+ {
+ var panel = new StackPanel { Orientation = Orientation.Vertical };
+ foreach (var group in Groups)
+ panel.Children.Add(group.Build());
+ return panel;
+ }
+ }
+
+ public readonly struct Flag
+ {
+ public string Name { get; }
+ public int Value { get; }
+ public bool IsSelected { get; }
+
+ public Flag(string name, int value, bool isSelected)
+ {
+ this.Name = name;
+ this.Value = value;
+ this.IsSelected = isSelected;
+ }
+ }
+
+ public abstract class FlagGroup
+ {
+ public static MultipleChoiceGroup CreateMultipleChoiceGroup(Type flagsType, string? header = null, int mask = -1, int selectedValue = 0, bool includeAll = true)
+ {
+ return new MultipleChoiceGroup(GetFlags(flagsType, mask, selectedValue, includeAll ? "" : null)) {
+ Header = header,
+ SelectedFlags = selectedValue,
+ };
+ }
+
+ public static SingleChoiceGroup CreateSingleChoiceGroup(Type flagsType, string? header = null, int mask = -1, int selectedValue = 0, Flag defaultFlag = default, bool includeAny = true)
+ {
+ var group = new SingleChoiceGroup(GetFlags(flagsType, mask, selectedValue, includeAny ? "" : null)) {
+ Header = header,
+ };
+ group.SelectedFlag = group.Flags.SingleOrDefault(f => f.Value == selectedValue);
+ if (group.SelectedFlag.Name == null)
+ group.SelectedFlag = defaultFlag;
+ return group;
+ }
+
+ public static IEnumerable GetFlags(Type flagsType, int mask = -1, int selectedValues = 0, string? neutralItem = null)
+ {
+ if (neutralItem != null)
+ yield return new Flag(neutralItem, -1, false);
+
+ foreach (var item in flagsType.GetFields(BindingFlags.Static | BindingFlags.Public))
+ {
+ if (item.Name.EndsWith("Mask", StringComparison.Ordinal))
+ continue;
+ int value = (int)CSharpPrimitiveCast.Cast(TypeCode.Int32, item.GetRawConstantValue(), false);
+ if ((value & mask) == 0)
+ continue;
+ yield return new Flag($"{item.Name} ({value:X4})", value, (selectedValues & value) != 0);
+ }
+ }
+
+ public string? Header { get; set; }
+
+ public IList Flags { get; protected set; } = Array.Empty();
+
+ public abstract Control Build();
+
+ private protected static TextBlock? BuildHeader(string? header)
+ {
+ if (string.IsNullOrEmpty(header))
+ return null;
+ return new TextBlock { Text = header, FontWeight = FontWeight.Bold };
+ }
+ }
+
+ public sealed class MultipleChoiceGroup : FlagGroup
+ {
+ public MultipleChoiceGroup(IEnumerable flags)
+ {
+ this.Flags = flags.ToList();
+ }
+
+ public int SelectedFlags { get; set; }
+
+ public override Control Build()
+ {
+ var panel = new StackPanel { Orientation = Orientation.Vertical, Margin = new Thickness(3) };
+ var header = BuildHeader(Header);
+ if (header != null)
+ {
+ header.Margin = new Thickness(0, 0, 0, 3);
+ panel.Children.Add(header);
+ }
+ foreach (var flag in Flags)
+ {
+ panel.Children.Add(new CheckBox {
+ Content = flag.Name,
+ IsChecked = flag.IsSelected,
+ IsHitTestVisible = false,
+ Margin = new Thickness(3, 1),
+ });
+ }
+ return panel;
+ }
+ }
+
+ public sealed class SingleChoiceGroup : FlagGroup
+ {
+ public SingleChoiceGroup(IEnumerable flags)
+ {
+ this.Flags = flags.ToList();
+ }
+
+ public Flag SelectedFlag { get; set; }
+
+ public override Control Build()
+ {
+ var panel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(3), Spacing = 3 };
+ var header = BuildHeader(Header);
+ if (header != null)
+ panel.Children.Add(header);
+ panel.Children.Add(new TextBlock { Text = SelectedFlag.Name });
+ return panel;
+ }
+ }
+}
diff --git a/ILSpy/Metadata/MetadataCellTooltip.cs b/ILSpy/Metadata/MetadataCellTooltip.cs
index 3caf4fa5a..e56cd0d64 100644
--- a/ILSpy/Metadata/MetadataCellTooltip.cs
+++ b/ILSpy/Metadata/MetadataCellTooltip.cs
@@ -25,8 +25,9 @@ namespace ILSpy.Metadata
///
/// Resolves per-cell tooltips for a metadata-table row. Entry classes opt in by exposing
/// a public {ColumnName}Tooltip property — NameTooltip, FlagsTooltip,
- /// BaseTypeTooltip, and so on. The resolver returns a stringified rendering, so
- /// callers can flow it straight into ToolTip.SetTip.
+ /// BaseTypeTooltip, and so on. A value renders as the rich
+ /// per-bit breakdown control; anything else is stringified, so callers can flow the result
+ /// straight into ToolTip.SetTip.
///
public static class MetadataCellTooltip
{
@@ -34,10 +35,11 @@ namespace ILSpy.Metadata
///
/// Looks up Tooltip on 's
- /// runtime type. Returns the property's stringified value, or
- /// if the item is null, the tooltip property is absent, or the value is null / blank.
+ /// runtime type. Returns a rich control for a value, the
+ /// stringified value otherwise, or if the item is null, the tooltip
+ /// property is absent, or the value is null / blank.
///
- public static string? Resolve(object item, string columnName)
+ public static object? Resolve(object item, string columnName)
{
if (item is null)
return null;
@@ -50,6 +52,8 @@ namespace ILSpy.Metadata
try
{ value = prop.GetValue(item); }
catch { return null; }
+ if (value is FlagsTooltip flags)
+ return flags.Build();
var s = value?.ToString();
return string.IsNullOrWhiteSpace(s) ? null : s;
}
diff --git a/ILSpy/Metadata/MetadataTableTreeNode.cs b/ILSpy/Metadata/MetadataTableTreeNode.cs
index f5465b8c3..0449db696 100644
--- a/ILSpy/Metadata/MetadataTableTreeNode.cs
+++ b/ILSpy/Metadata/MetadataTableTreeNode.cs
@@ -19,8 +19,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
using ILSpy.TreeNodes;
@@ -49,6 +52,77 @@ namespace ILSpy.Metadata
}
public override object Icon => Images.Images.MetadataTable;
+
+ ///
+ /// Builds (and caches into ) a human-readable description of the
+ /// entity a token column points at, e.g. (AssemblyReference) System.Runtime, ....
+ /// Entries expose this from their {Column}Tooltip properties so hovering a token cell
+ /// shows what it refers to. Returns for a nil handle.
+ ///
+ protected static string? GenerateTooltip(ref string? tooltip, MetadataFile module, EntityHandle handle)
+ {
+ if (tooltip == null)
+ {
+ if (handle.IsNil)
+ return null;
+ ITextOutput output = new PlainTextOutput();
+ var context = new MetadataGenericContext(default(TypeDefinitionHandle), module.Metadata);
+ var metadata = module.Metadata;
+ switch (handle.Kind)
+ {
+ case HandleKind.ModuleDefinition:
+ output.Write(metadata.GetString(metadata.GetModuleDefinition().Name));
+ output.Write(" (this module)");
+ break;
+ case HandleKind.ModuleReference:
+ ModuleReference moduleReference = metadata.GetModuleReference((ModuleReferenceHandle)handle);
+ output.Write(metadata.GetString(moduleReference.Name));
+ break;
+ case HandleKind.AssemblyReference:
+ var asmRef = new ICSharpCode.Decompiler.Metadata.AssemblyReference(metadata, (AssemblyReferenceHandle)handle);
+ output.Write(asmRef.ToString());
+ break;
+ case HandleKind.Parameter:
+ var param = metadata.GetParameter((ParameterHandle)handle);
+ output.Write(param.SequenceNumber + " - " + metadata.GetString(param.Name));
+ break;
+ case HandleKind.EventDefinition:
+ var @event = metadata.GetEventDefinition((EventDefinitionHandle)handle);
+ output.Write(metadata.GetString(@event.Name));
+ break;
+ case HandleKind.PropertyDefinition:
+ var prop = metadata.GetPropertyDefinition((PropertyDefinitionHandle)handle);
+ output.Write(metadata.GetString(prop.Name));
+ break;
+ case HandleKind.AssemblyDefinition:
+ var ad = metadata.GetAssemblyDefinition();
+ output.Write(metadata.GetString(ad.Name));
+ output.Write(" (this assembly)");
+ break;
+ case HandleKind.AssemblyFile:
+ var af = metadata.GetAssemblyFile((AssemblyFileHandle)handle);
+ output.Write(metadata.GetString(af.Name));
+ break;
+ case HandleKind.GenericParameter:
+ var gp = metadata.GetGenericParameter((GenericParameterHandle)handle);
+ output.Write(metadata.GetString(gp.Name));
+ break;
+ case HandleKind.ManifestResource:
+ var mfr = metadata.GetManifestResource((ManifestResourceHandle)handle);
+ output.Write(metadata.GetString(mfr.Name));
+ break;
+ case HandleKind.Document:
+ var doc = metadata.GetDocument((DocumentHandle)handle);
+ output.Write(metadata.GetString(doc.Name));
+ break;
+ default:
+ handle.WriteTo(module, output, context);
+ break;
+ }
+ tooltip = "(" + handle.Kind + ") " + output.ToString();
+ }
+ return tooltip;
+ }
}
///