From 270ef0f51933f122a6e7f7971291ce2ef012844a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 8 Jun 2026 00:01:08 +0200 Subject: [PATCH] Restore per-cell tooltips across the metadata tables The metadata-grid hover infrastructure shipped but no table entry fed it, so every cell tooltip the previous version had was silently gone: the heap-offset/value hints on string and blob columns, the entity description on token columns, and the per-bit breakdown on flag columns. Reinstate all of them. FlagsTooltip renders the rich per-bit view (a checkbox per flag plus the selected member of each mutually exclusive sub-range) instead of a flat string; GenerateTooltip on the table base describes what a token column points at; the remaining columns expose their heap-offset and value hints. MetadataCellTooltip now hands a FlagsTooltip back as a built control and stringifies everything else. Assisted-by: Claude:claude-opus-4-8:Claude Code --- ILSpy.Tests/Metadata/FlagsTooltipTests.cs | 81 ++++++++ .../Metadata/MetadataTooltipParityTests.cs | 70 +++++++ .../CorTables/AssemblyRefTableTreeNode.cs | 17 ++ .../CorTables/AssemblyTableTreeNode.cs | 12 ++ .../CorTables/ClassLayoutTableTreeNode.cs | 9 +- .../CorTables/ConstantTableTreeNode.cs | 11 ++ .../CorTables/CustomAttributeTableTreeNode.cs | 12 ++ .../CorTables/DeclSecurityTableTreeNode.cs | 15 ++ .../CorTables/EventMapTableTreeNode.cs | 12 +- .../Metadata/CorTables/EventTableTreeNode.cs | 9 + .../CorTables/ExportedTypeTableTreeNode.cs | 18 ++ .../CorTables/FieldLayoutTableTreeNode.cs | 9 +- .../CorTables/FieldMarshalTableTreeNode.cs | 9 +- .../CorTables/FieldRVATableTreeNode.cs | 9 +- .../Metadata/CorTables/FieldTableTreeNode.cs | 12 ++ ILSpy/Metadata/CorTables/FileTableTreeNode.cs | 13 ++ .../GenericParamConstraintTableTreeNode.cs | 20 ++ .../CorTables/GenericParamTableTreeNode.cs | 10 + .../CorTables/ImplMapTableTreeNode.cs | 28 ++- .../CorTables/InterfaceImplTableTreeNode.cs | 12 +- .../ManifestResourceTableTreeNode.cs | 7 + .../CorTables/MemberRefTableTreeNode.cs | 8 + .../CorTables/MethodImplTableTreeNode.cs | 9 + .../CorTables/MethodSemanticsTableTreeNode.cs | 14 +- .../CorTables/MethodSpecTableTreeNode.cs | 22 +++ .../Metadata/CorTables/MethodTableTreeNode.cs | 29 +++ .../CorTables/ModuleRefTableTreeNode.cs | 2 + .../Metadata/CorTables/ModuleTableTreeNode.cs | 8 + .../CorTables/NestedClassTableTreeNode.cs | 12 +- .../Metadata/CorTables/ParamTableTreeNode.cs | 6 + .../CorTables/PropertyMapTableTreeNode.cs | 12 +- .../CorTables/PropertyTableTreeNode.cs | 9 + ILSpy/Metadata/CorTables/PtrTableTreeNode.cs | 9 +- .../CorTables/StandAloneSigTableTreeNode.cs | 3 + .../CorTables/TypeDefTableTreeNode.cs | 60 ++++++ .../CorTables/TypeRefTableTreeNode.cs | 7 + .../CorTables/TypeSpecTableTreeNode.cs | 10 + .../CustomDebugInformationTableTreeNode.cs | 11 ++ .../DebugTables/DocumentTableTreeNode.cs | 41 ++++ .../DebugTables/LocalConstantTableTreeNode.cs | 2 + .../DebugTables/LocalScopeTableTreeNode.cs | 3 + .../DebugTables/LocalVariableTableTreeNode.cs | 6 + .../MethodDebugInformationTableTreeNode.cs | 46 +++++ .../StateMachineMethodTableTreeNode.cs | 23 ++- ILSpy/Metadata/FlagsTooltip.cs | 184 ++++++++++++++++++ ILSpy/Metadata/MetadataCellTooltip.cs | 14 +- ILSpy/Metadata/MetadataTableTreeNode.cs | 74 +++++++ 47 files changed, 984 insertions(+), 35 deletions(-) create mode 100644 ILSpy.Tests/Metadata/FlagsTooltipTests.cs create mode 100644 ILSpy.Tests/Metadata/MetadataTooltipParityTests.cs create mode 100644 ILSpy/Metadata/FlagsTooltip.cs 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; + } } ///