From 43b958e2a4c24e257c19b77c145d8228f315adef Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 7 May 2026 15:32:10 +0200 Subject: [PATCH] Route heap nodes onto the datagrid tab The grid view's row-virtualisation handles the full heap (CoreLib's truncation-footer wording are gone. Existing heap tests are rewritten to assert against MetadataTablePageModel; the obsolete MetadataHeapTreeNode.PreviewLimit constant follows the cap into the deleted set. Assisted-by: Claude:claude-opus-4-7:Claude Code --- ILSpy.Tests/Metadata/HeapTreeTests.cs | 48 +++++++++++------------- ILSpy/Metadata/BlobHeapTreeNode.cs | 20 ++++------ ILSpy/Metadata/GuidHeapTreeNode.cs | 20 ++++------ ILSpy/Metadata/MetadataHeapTreeNode.cs | 2 - ILSpy/Metadata/StringHeapTreeNode.cs | 20 ++++------ ILSpy/Metadata/UserStringHeapTreeNode.cs | 20 ++++------ 6 files changed, 49 insertions(+), 81 deletions(-) diff --git a/ILSpy.Tests/Metadata/HeapTreeTests.cs b/ILSpy.Tests/Metadata/HeapTreeTests.cs index 9e4d3fa62..a85cea549 100644 --- a/ILSpy.Tests/Metadata/HeapTreeTests.cs +++ b/ILSpy.Tests/Metadata/HeapTreeTests.cs @@ -63,12 +63,11 @@ public class HeapTreeTests } [AvaloniaTest] - public async Task StringHeapTreeNode_Decompiles_Header_Plus_Preview_Of_Entries() + public async Task StringHeapTreeNode_Opens_Grid_Tab_With_Offset_Length_Value_Columns() { - // CoreLib's #Strings holds tens of thousands of entries; rendering all of them as - // text would bloat the tab and bog down the editor. Phase 1 dumps a capped preview - // (header + first N rows + truncation footer) so the user can scan the start of the - // heap; Phase 2 swaps to a virtualising DataGrid for the full set. + // CoreLib's #Strings holds tens of thousands of entries; the DataGrid surfaces every + // row through Avalonia's built-in row virtualisation, so unlike the Phase 1 text + // dump there's no preview cap and no truncation footer. var window = AppComposition.Current.GetExport(); window.Show(); @@ -83,19 +82,16 @@ public class HeapTreeTests var heapNode = metadataNode.Children.OfType().Single(); vm.AssemblyTreeModel.SelectNode(heapNode); - var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); - - tab.Text.Should().Contain("String Heap"); - tab.Text.Should().Contain("Offset"); - tab.Text.Should().Contain("Length"); - tab.Text.Should().Contain("Value"); - tab.Text.Should().Contain("more entries", "CoreLib's #Strings is well over the preview cap"); - // The preview includes a (count entries) header — should match e.g. "(24573 entries)". - tab.Text.Should().MatchRegex(@"\(\d{3,} entries\)"); + var tab = await vm.DockWorkspace.WaitForMetadataTabAsync(); + + tab.Title.Should().Be("String Heap"); + tab.Columns.Select(c => c.Header.ToString()).Should().Equal("Offset", "Length", "Value"); + // CoreLib's #Strings is well over a thousand entries; the grid handles them all. + tab.Items.Should().HaveCountGreaterThan(1000); } [AvaloniaTest] - public async Task GuidHeapTreeNode_Decompiles_Header_Plus_Index_Length_Value_Columns() + public async Task GuidHeapTreeNode_Opens_Grid_Tab_With_Index_Length_Value_Columns() { var window = AppComposition.Current.GetExport(); window.Show(); @@ -110,17 +106,17 @@ public class HeapTreeTests var heapNode = metadataNode.Children.OfType().Single(); vm.AssemblyTreeModel.SelectNode(heapNode); - var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + var tab = await vm.DockWorkspace.WaitForMetadataTabAsync(); - tab.Text.Should().Contain("Guid Heap"); - tab.Text.Should().Contain("Index"); - tab.Text.Should().Contain("Length"); - tab.Text.Should().MatchRegex(@"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b", - "the GUID heap should contain at least one parsed GUID"); + tab.Title.Should().Be("Guid Heap"); + tab.Columns.Select(c => c.Header.ToString()).Should().Equal("Index", "Length", "Value"); + // Each entry's Value is the parsed GUID's lowercase canonical form. + var first = (GuidHeapTreeNode.GuidHeapEntry)tab.Items[0]; + first.Value.Should().MatchRegex(@"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"); } [AvaloniaTest] - public async Task BlobHeapTreeNode_Decompiles_Header_Plus_Preview_Of_Hex_Encoded_Entries() + public async Task BlobHeapTreeNode_Opens_Grid_Tab_With_Offset_Length_Value_Columns() { var window = AppComposition.Current.GetExport(); window.Show(); @@ -135,11 +131,9 @@ public class HeapTreeTests var heapNode = metadataNode.Children.OfType().Single(); vm.AssemblyTreeModel.SelectNode(heapNode); - var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + var tab = await vm.DockWorkspace.WaitForMetadataTabAsync(); - tab.Text.Should().Contain("Blob Heap"); - tab.Text.Should().Contain("Offset"); - tab.Text.Should().Contain("Length"); - tab.Text.Should().Contain("Value"); + tab.Title.Should().Be("Blob Heap"); + tab.Columns.Select(c => c.Header.ToString()).Should().Equal("Offset", "Length", "Value"); } } diff --git a/ILSpy/Metadata/BlobHeapTreeNode.cs b/ILSpy/Metadata/BlobHeapTreeNode.cs index ed7bea309..250909286 100644 --- a/ILSpy/Metadata/BlobHeapTreeNode.cs +++ b/ILSpy/Metadata/BlobHeapTreeNode.cs @@ -17,13 +17,13 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -45,17 +45,11 @@ namespace ILSpy.Metadata public override object Text => $"Blob Heap ({EnsureEntries().Count})"; public override string ToString() => "Blob Heap"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - var all = EnsureEntries(); - var preview = all.Count > PreviewLimit - ? (IReadOnlyList)all.GetRange(0, PreviewLimit) - : all; - language.WriteCommentLine(output, $"Blob Heap ({all.Count} entries)"); - MetadataTextWriter.WriteTable(language, output, preview); - if (all.Count > preview.Count) - language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "Blob Heap", + Items = EnsureEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; List EnsureEntries() { diff --git a/ILSpy/Metadata/GuidHeapTreeNode.cs b/ILSpy/Metadata/GuidHeapTreeNode.cs index 904b119a3..1fda03acf 100644 --- a/ILSpy/Metadata/GuidHeapTreeNode.cs +++ b/ILSpy/Metadata/GuidHeapTreeNode.cs @@ -17,13 +17,13 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -44,17 +44,11 @@ namespace ILSpy.Metadata public override object Text => $"Guid Heap ({EnsureEntries().Count})"; public override string ToString() => "Guid Heap"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - var all = EnsureEntries(); - var preview = all.Count > PreviewLimit - ? (IReadOnlyList)all.GetRange(0, PreviewLimit) - : all; - language.WriteCommentLine(output, $"Guid Heap ({all.Count} entries)"); - MetadataTextWriter.WriteTable(language, output, preview); - if (all.Count > preview.Count) - language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "Guid Heap", + Items = EnsureEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; List EnsureEntries() { diff --git a/ILSpy/Metadata/MetadataHeapTreeNode.cs b/ILSpy/Metadata/MetadataHeapTreeNode.cs index 5b1ce4700..181767a78 100644 --- a/ILSpy/Metadata/MetadataHeapTreeNode.cs +++ b/ILSpy/Metadata/MetadataHeapTreeNode.cs @@ -34,8 +34,6 @@ namespace ILSpy.Metadata /// public abstract class MetadataHeapTreeNode : ILSpyTreeNode { - public const int PreviewLimit = 200; - protected readonly MetadataFile metadataFile; public HandleKind Kind { get; } diff --git a/ILSpy/Metadata/StringHeapTreeNode.cs b/ILSpy/Metadata/StringHeapTreeNode.cs index e61e6d6dd..97ffa17bc 100644 --- a/ILSpy/Metadata/StringHeapTreeNode.cs +++ b/ILSpy/Metadata/StringHeapTreeNode.cs @@ -17,13 +17,13 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -45,17 +45,11 @@ namespace ILSpy.Metadata public override object Text => $"String Heap ({EnsureEntries().Count})"; public override string ToString() => "String Heap"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - var all = EnsureEntries(); - var preview = all.Count > PreviewLimit - ? (IReadOnlyList)all.GetRange(0, PreviewLimit) - : all; - language.WriteCommentLine(output, $"String Heap ({all.Count} entries)"); - MetadataTextWriter.WriteTable(language, output, preview); - if (all.Count > preview.Count) - language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "String Heap", + Items = EnsureEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; List EnsureEntries() { diff --git a/ILSpy/Metadata/UserStringHeapTreeNode.cs b/ILSpy/Metadata/UserStringHeapTreeNode.cs index 09efbc9df..9c41f3a49 100644 --- a/ILSpy/Metadata/UserStringHeapTreeNode.cs +++ b/ILSpy/Metadata/UserStringHeapTreeNode.cs @@ -17,13 +17,13 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -44,17 +44,11 @@ namespace ILSpy.Metadata public override object Text => $"UserString Heap ({EnsureEntries().Count})"; public override string ToString() => "UserString Heap"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - var all = EnsureEntries(); - var preview = all.Count > PreviewLimit - ? (IReadOnlyList)all.GetRange(0, PreviewLimit) - : all; - language.WriteCommentLine(output, $"UserString Heap ({all.Count} entries)"); - MetadataTextWriter.WriteTable(language, output, preview); - if (all.Count > preview.Count) - language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "UserString Heap", + Items = EnsureEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; List EnsureEntries() {