Browse Source

Route pe-header nodes onto the datagrid tab

DOS / COFF / Optional / DataDirectories / DebugDirectory now expose
their entries through CreateTab() instead of writing a fixed-width text
table from Decompile. The grid view shows each entry's columns
reflected directly off the row shape — Phase 1's text table was a
stand-in until this view shipped. The existing PE-header tests are
rewritten to assert against MetadataTablePageModel.Items / Columns;
adds a Waiters.WaitForMetadataTabAsync helper used by every grid-tab
test.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
096846a1db
  1. 39
      ILSpy.Tests/Metadata/PEHeaderTreeTests.cs
  2. 17
      ILSpy.Tests/Waiters.cs
  3. 14
      ILSpy/Metadata/CoffHeaderTreeNode.cs
  4. 14
      ILSpy/Metadata/DataDirectoriesTreeNode.cs
  5. 14
      ILSpy/Metadata/DebugDirectoryTreeNode.cs
  6. 14
      ILSpy/Metadata/DosHeaderTreeNode.cs
  7. 14
      ILSpy/Metadata/OptionalHeaderTreeNode.cs

39
ILSpy.Tests/Metadata/PEHeaderTreeTests.cs

@ -71,12 +71,12 @@ public class PEHeaderTreeTests
} }
[AvaloniaTest] [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 // 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 // stable across every PE file. Selecting the node opens a metadata-table tab — one
// every reader can scan, with the magic-number row visible by both its raw value and // row per field, columns reflected from the Entry shape (Member / Offset / Size /
// its semantic meaning. // Value / Meaning).
var window = AppComposition.Current.GetExport<MainWindow>(); var window = AppComposition.Current.GetExport<MainWindow>();
window.Show(); window.Show();
@ -91,19 +91,22 @@ public class PEHeaderTreeTests
var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().Single(); var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().Single();
vm.AssemblyTreeModel.SelectNode(dosNode); vm.AssemblyTreeModel.SelectNode(dosNode);
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
tab.Text.Should().Contain("e_magic"); tab.Title.Should().Be("DOS Header");
tab.Text.Should().Contain("Magic Number (MZ)"); tab.Items.Should().HaveCount(31);
tab.Text.Should().Contain("e_lfanew"); tab.Columns.Select(c => c.Header.ToString()).Should().Contain(["Member", "Offset", "Size", "Value", "Meaning"]);
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 firstRow = (Entry)tab.Items[0];
var fieldCount = System.Text.RegularExpressions.Regex.Matches(tab.Text, @"\be_[a-z0-9_\[\]]+").Count; firstRow.Member.Should().Be("e_magic");
fieldCount.Should().BeGreaterThanOrEqualTo(31); firstRow.Meaning.Should().Be("Magic Number (MZ)");
var lastRow = (Entry)tab.Items[^1];
lastRow.Member.Should().Be("e_lfanew");
} }
[AvaloniaTest] [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<MainWindow>(); var window = AppComposition.Current.GetExport<MainWindow>();
window.Show(); window.Show();
@ -118,10 +121,10 @@ public class PEHeaderTreeTests
var coffNode = metadataNode.Children.OfType<CoffHeaderTreeNode>().Single(); var coffNode = metadataNode.Children.OfType<CoffHeaderTreeNode>().Single();
vm.AssemblyTreeModel.SelectNode(coffNode); vm.AssemblyTreeModel.SelectNode(coffNode);
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
tab.Text.Should().Contain("Machine"); tab.Title.Should().Be("COFF Header");
tab.Text.Should().Contain("Number of Sections"); var members = tab.Items.Cast<Entry>().Select(e => e.Member).ToList();
tab.Text.Should().Contain("Characteristics"); members.Should().Contain(["Machine", "Number of Sections", "Characteristics"]);
} }
} }

17
ILSpy.Tests/Waiters.cs

@ -28,6 +28,7 @@ using Avalonia.VisualTree;
using global::ILSpy.AssemblyTree; using global::ILSpy.AssemblyTree;
using global::ILSpy.Docking; using global::ILSpy.Docking;
using global::ILSpy.TextView; using global::ILSpy.TextView;
using global::ILSpy.ViewModels;
namespace ICSharpCode.ILSpy.Tests; namespace ICSharpCode.ILSpy.Tests;
@ -102,4 +103,20 @@ public static class Waiters
return (DecompilerTabPageModel)documents.ActiveDockable!; return (DecompilerTabPageModel)documents.ActiveDockable!;
} }
public static async Task<MetadataTablePageModel> 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!;
}
} }

14
ILSpy/Metadata/CoffHeaderTreeNode.cs

@ -18,12 +18,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
using ILSpy.TreeNodes; using ILSpy.TreeNodes;
using ILSpy.ViewModels;
namespace ILSpy.Metadata namespace ILSpy.Metadata
{ {
@ -45,11 +45,11 @@ namespace ILSpy.Metadata
public override object Icon => Images.Images.MetadataTable; public override object Icon => Images.Images.MetadataTable;
public override string ToString() => "COFF Header"; public override string ToString() => "COFF Header";
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override TabPageModel CreateTab() => new MetadataTablePageModel {
{ Title = "COFF Header",
language.WriteCommentLine(output, "COFF Header"); Items = BuildEntries().Cast<object>().ToList(),
MetadataTextWriter.WriteTable(language, output, BuildEntries()); Columns = MetadataColumnBuilder.For<Entry>(),
} };
IReadOnlyList<Entry> BuildEntries() IReadOnlyList<Entry> BuildEntries()
{ {

14
ILSpy/Metadata/DataDirectoriesTreeNode.cs

@ -18,13 +18,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
using ILSpy.TreeNodes; using ILSpy.TreeNodes;
using ILSpy.ViewModels;
namespace ILSpy.Metadata namespace ILSpy.Metadata
{ {
@ -46,11 +46,11 @@ namespace ILSpy.Metadata
public override object Icon => Images.Images.MetadataTable; public override object Icon => Images.Images.MetadataTable;
public override string ToString() => "Data Directories"; public override string ToString() => "Data Directories";
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override TabPageModel CreateTab() => new MetadataTablePageModel {
{ Title = "Data Directories",
language.WriteCommentLine(output, "Data Directories"); Items = BuildEntries().Cast<object>().ToList(),
MetadataTextWriter.WriteTable(language, output, BuildEntries()); Columns = MetadataColumnBuilder.For<DataDirectoryEntry>(),
} };
IReadOnlyList<DataDirectoryEntry> BuildEntries() IReadOnlyList<DataDirectoryEntry> BuildEntries()
{ {

14
ILSpy/Metadata/DebugDirectoryTreeNode.cs

@ -18,13 +18,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
using ILSpy.TreeNodes; using ILSpy.TreeNodes;
using ILSpy.ViewModels;
namespace ILSpy.Metadata namespace ILSpy.Metadata
{ {
@ -47,11 +47,11 @@ namespace ILSpy.Metadata
public override object Icon => Images.Images.MetadataTable; public override object Icon => Images.Images.MetadataTable;
public override string ToString() => "Debug Directory"; public override string ToString() => "Debug Directory";
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override TabPageModel CreateTab() => new MetadataTablePageModel {
{ Title = "Debug Directory",
language.WriteCommentLine(output, "Debug Directory"); Items = BuildEntries().Cast<object>().ToList(),
MetadataTextWriter.WriteTable(language, output, BuildEntries()); Columns = MetadataColumnBuilder.For<DebugDirectoryEntryView>(),
} };
IReadOnlyList<DebugDirectoryEntryView> BuildEntries() IReadOnlyList<DebugDirectoryEntryView> BuildEntries()
{ {

14
ILSpy/Metadata/DosHeaderTreeNode.cs

@ -18,12 +18,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
using ILSpy.TreeNodes; using ILSpy.TreeNodes;
using ILSpy.ViewModels;
namespace ILSpy.Metadata namespace ILSpy.Metadata
{ {
@ -44,11 +44,11 @@ namespace ILSpy.Metadata
public override object Icon => Images.Images.MetadataTable; public override object Icon => Images.Images.MetadataTable;
public override string ToString() => "DOS Header"; public override string ToString() => "DOS Header";
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override TabPageModel CreateTab() => new MetadataTablePageModel {
{ Title = "DOS Header",
language.WriteCommentLine(output, "DOS Header"); Items = BuildEntries().Cast<object>().ToList(),
MetadataTextWriter.WriteTable(language, output, BuildEntries()); Columns = MetadataColumnBuilder.For<Entry>(),
} };
IReadOnlyList<Entry> BuildEntries() IReadOnlyList<Entry> BuildEntries()
{ {

14
ILSpy/Metadata/OptionalHeaderTreeNode.cs

@ -18,13 +18,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
using ILSpy.TreeNodes; using ILSpy.TreeNodes;
using ILSpy.ViewModels;
namespace ILSpy.Metadata namespace ILSpy.Metadata
{ {
@ -46,11 +46,11 @@ namespace ILSpy.Metadata
public override object Icon => Images.Images.MetadataTable; public override object Icon => Images.Images.MetadataTable;
public override string ToString() => "Optional Header"; public override string ToString() => "Optional Header";
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override TabPageModel CreateTab() => new MetadataTablePageModel {
{ Title = "Optional Header",
language.WriteCommentLine(output, "Optional Header"); Items = BuildEntries().Cast<object>().ToList(),
MetadataTextWriter.WriteTable(language, output, BuildEntries()); Columns = MetadataColumnBuilder.For<Entry>(),
} };
IReadOnlyList<Entry> BuildEntries() IReadOnlyList<Entry> BuildEntries()
{ {

Loading…
Cancel
Save