diff --git a/ILSpy.Tests/Metadata/PEHeaderTreeTests.cs b/ILSpy.Tests/Metadata/PEHeaderTreeTests.cs index baeb8b24f..70754ad9f 100644 --- a/ILSpy.Tests/Metadata/PEHeaderTreeTests.cs +++ b/ILSpy.Tests/Metadata/PEHeaderTreeTests.cs @@ -71,12 +71,12 @@ public class PEHeaderTreeTests } [AvaloniaTest] - public async Task DosHeaderTreeNode_Decompiles_To_A_Table_With_All_31_DOS_Header_Fields() + public async Task DosHeaderTreeNode_Opens_A_DataGrid_Tab_With_Entry_Columns_And_31_Rows() { // DOS header is a fixed 64-byte block at offset 0; the 31 fields are well-known and - // stable across every PE file. Selecting the node should produce a text table that - // every reader can scan, with the magic-number row visible by both its raw value and - // its semantic meaning. + // stable across every PE file. Selecting the node opens a metadata-table tab — one + // row per field, columns reflected from the Entry shape (Member / Offset / Size / + // Value / Meaning). var window = AppComposition.Current.GetExport(); window.Show(); @@ -91,19 +91,22 @@ public class PEHeaderTreeTests var dosNode = metadataNode.Children.OfType().Single(); vm.AssemblyTreeModel.SelectNode(dosNode); - var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); - - tab.Text.Should().Contain("e_magic"); - tab.Text.Should().Contain("Magic Number (MZ)"); - tab.Text.Should().Contain("e_lfanew"); - tab.Text.Should().Contain("File address of new exe header"); - // All 31 DOS-header field rows should be present — count e_ prefixes on member rows. - var fieldCount = System.Text.RegularExpressions.Regex.Matches(tab.Text, @"\be_[a-z0-9_\[\]]+").Count; - fieldCount.Should().BeGreaterThanOrEqualTo(31); + var tab = await vm.DockWorkspace.WaitForMetadataTabAsync(); + + tab.Title.Should().Be("DOS Header"); + tab.Items.Should().HaveCount(31); + tab.Columns.Select(c => c.Header.ToString()).Should().Contain(["Member", "Offset", "Size", "Value", "Meaning"]); + + var firstRow = (Entry)tab.Items[0]; + firstRow.Member.Should().Be("e_magic"); + firstRow.Meaning.Should().Be("Magic Number (MZ)"); + + var lastRow = (Entry)tab.Items[^1]; + lastRow.Member.Should().Be("e_lfanew"); } [AvaloniaTest] - public async Task CoffHeaderTreeNode_Decompiles_To_A_Table_Including_Machine_And_Characteristics() + public async Task CoffHeaderTreeNode_Opens_A_DataGrid_Tab_With_Machine_And_Characteristics_Rows() { var window = AppComposition.Current.GetExport(); window.Show(); @@ -118,10 +121,10 @@ public class PEHeaderTreeTests var coffNode = metadataNode.Children.OfType().Single(); vm.AssemblyTreeModel.SelectNode(coffNode); - var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + var tab = await vm.DockWorkspace.WaitForMetadataTabAsync(); - tab.Text.Should().Contain("Machine"); - tab.Text.Should().Contain("Number of Sections"); - tab.Text.Should().Contain("Characteristics"); + tab.Title.Should().Be("COFF Header"); + var members = tab.Items.Cast().Select(e => e.Member).ToList(); + members.Should().Contain(["Machine", "Number of Sections", "Characteristics"]); } } diff --git a/ILSpy.Tests/Waiters.cs b/ILSpy.Tests/Waiters.cs index 99a61929a..9e516e4db 100644 --- a/ILSpy.Tests/Waiters.cs +++ b/ILSpy.Tests/Waiters.cs @@ -28,6 +28,7 @@ using Avalonia.VisualTree; using global::ILSpy.AssemblyTree; using global::ILSpy.Docking; using global::ILSpy.TextView; +using global::ILSpy.ViewModels; namespace ICSharpCode.ILSpy.Tests; @@ -102,4 +103,20 @@ public static class Waiters return (DecompilerTabPageModel)documents.ActiveDockable!; } + + public static async Task WaitForMetadataTabAsync( + this DockWorkspace dock, + TimeSpan? timeout = null) + { + ArgumentNullException.ThrowIfNull(dock); + var documents = ((ILSpyDockFactory)dock.Factory).Documents + ?? throw new InvalidOperationException("DockWorkspace has no document dock yet."); + + await WaitForAsync( + () => documents.ActiveDockable is MetadataTablePageModel { Items.Count: > 0 }, + timeout, + "active dockable to be a metadata table tab with populated rows"); + + return (MetadataTablePageModel)documents.ActiveDockable!; + } } diff --git a/ILSpy/Metadata/CoffHeaderTreeNode.cs b/ILSpy/Metadata/CoffHeaderTreeNode.cs index 206f35aab..524079dbe 100644 --- a/ILSpy/Metadata/CoffHeaderTreeNode.cs +++ b/ILSpy/Metadata/CoffHeaderTreeNode.cs @@ -18,12 +18,12 @@ using System; using System.Collections.Generic; +using System.Linq; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; using ILSpy.TreeNodes; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -45,11 +45,11 @@ namespace ILSpy.Metadata public override object Icon => Images.Images.MetadataTable; public override string ToString() => "COFF Header"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - language.WriteCommentLine(output, "COFF Header"); - MetadataTextWriter.WriteTable(language, output, BuildEntries()); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "COFF Header", + Items = BuildEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; IReadOnlyList BuildEntries() { diff --git a/ILSpy/Metadata/DataDirectoriesTreeNode.cs b/ILSpy/Metadata/DataDirectoriesTreeNode.cs index 7910d857a..0c34dbd53 100644 --- a/ILSpy/Metadata/DataDirectoriesTreeNode.cs +++ b/ILSpy/Metadata/DataDirectoriesTreeNode.cs @@ -18,13 +18,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection.PortableExecutable; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; using ILSpy.TreeNodes; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -46,11 +46,11 @@ namespace ILSpy.Metadata public override object Icon => Images.Images.MetadataTable; public override string ToString() => "Data Directories"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - language.WriteCommentLine(output, "Data Directories"); - MetadataTextWriter.WriteTable(language, output, BuildEntries()); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "Data Directories", + Items = BuildEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; IReadOnlyList BuildEntries() { diff --git a/ILSpy/Metadata/DebugDirectoryTreeNode.cs b/ILSpy/Metadata/DebugDirectoryTreeNode.cs index 500646bd5..16263a026 100644 --- a/ILSpy/Metadata/DebugDirectoryTreeNode.cs +++ b/ILSpy/Metadata/DebugDirectoryTreeNode.cs @@ -18,13 +18,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection.PortableExecutable; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; using ILSpy.TreeNodes; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -47,11 +47,11 @@ namespace ILSpy.Metadata public override object Icon => Images.Images.MetadataTable; public override string ToString() => "Debug Directory"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - language.WriteCommentLine(output, "Debug Directory"); - MetadataTextWriter.WriteTable(language, output, BuildEntries()); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "Debug Directory", + Items = BuildEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; IReadOnlyList BuildEntries() { diff --git a/ILSpy/Metadata/DosHeaderTreeNode.cs b/ILSpy/Metadata/DosHeaderTreeNode.cs index 99ebbf114..77ec06e20 100644 --- a/ILSpy/Metadata/DosHeaderTreeNode.cs +++ b/ILSpy/Metadata/DosHeaderTreeNode.cs @@ -18,12 +18,12 @@ using System; using System.Collections.Generic; +using System.Linq; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; using ILSpy.TreeNodes; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -44,11 +44,11 @@ namespace ILSpy.Metadata public override object Icon => Images.Images.MetadataTable; public override string ToString() => "DOS Header"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - language.WriteCommentLine(output, "DOS Header"); - MetadataTextWriter.WriteTable(language, output, BuildEntries()); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "DOS Header", + Items = BuildEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; IReadOnlyList BuildEntries() { diff --git a/ILSpy/Metadata/OptionalHeaderTreeNode.cs b/ILSpy/Metadata/OptionalHeaderTreeNode.cs index 267bdf429..6192919d2 100644 --- a/ILSpy/Metadata/OptionalHeaderTreeNode.cs +++ b/ILSpy/Metadata/OptionalHeaderTreeNode.cs @@ -18,13 +18,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection.PortableExecutable; -using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; -using ILSpy.Languages; using ILSpy.TreeNodes; +using ILSpy.ViewModels; namespace ILSpy.Metadata { @@ -46,11 +46,11 @@ namespace ILSpy.Metadata public override object Icon => Images.Images.MetadataTable; public override string ToString() => "Optional Header"; - public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) - { - language.WriteCommentLine(output, "Optional Header"); - MetadataTextWriter.WriteTable(language, output, BuildEntries()); - } + public override TabPageModel CreateTab() => new MetadataTablePageModel { + Title = "Optional Header", + Items = BuildEntries().Cast().ToList(), + Columns = MetadataColumnBuilder.For(), + }; IReadOnlyList BuildEntries() {