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 @@ -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<MainWindow>();
window.Show();
@ -91,19 +91,22 @@ public class PEHeaderTreeTests @@ -91,19 +91,22 @@ public class PEHeaderTreeTests
var dosNode = metadataNode.Children.OfType<DosHeaderTreeNode>().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<MainWindow>();
window.Show();
@ -118,10 +121,10 @@ public class PEHeaderTreeTests @@ -118,10 +121,10 @@ public class PEHeaderTreeTests
var coffNode = metadataNode.Children.OfType<CoffHeaderTreeNode>().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<Entry>().Select(e => e.Member).ToList();
members.Should().Contain(["Machine", "Number of Sections", "Characteristics"]);
}
}

17
ILSpy.Tests/Waiters.cs

@ -28,6 +28,7 @@ using Avalonia.VisualTree; @@ -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 @@ -102,4 +103,20 @@ public static class Waiters
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 @@ @@ -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 @@ -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<object>().ToList(),
Columns = MetadataColumnBuilder.For<Entry>(),
};
IReadOnlyList<Entry> BuildEntries()
{

14
ILSpy/Metadata/DataDirectoriesTreeNode.cs

@ -18,13 +18,13 @@ @@ -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 @@ -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<object>().ToList(),
Columns = MetadataColumnBuilder.For<DataDirectoryEntry>(),
};
IReadOnlyList<DataDirectoryEntry> BuildEntries()
{

14
ILSpy/Metadata/DebugDirectoryTreeNode.cs

@ -18,13 +18,13 @@ @@ -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 @@ -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<object>().ToList(),
Columns = MetadataColumnBuilder.For<DebugDirectoryEntryView>(),
};
IReadOnlyList<DebugDirectoryEntryView> BuildEntries()
{

14
ILSpy/Metadata/DosHeaderTreeNode.cs

@ -18,12 +18,12 @@ @@ -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 @@ -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<object>().ToList(),
Columns = MetadataColumnBuilder.For<Entry>(),
};
IReadOnlyList<Entry> BuildEntries()
{

14
ILSpy/Metadata/OptionalHeaderTreeNode.cs

@ -18,13 +18,13 @@ @@ -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 @@ -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<object>().ToList(),
Columns = MetadataColumnBuilder.For<Entry>(),
};
IReadOnlyList<Entry> BuildEntries()
{

Loading…
Cancel
Save