Skip to content

Metadata explorer cleanup, flags-filter fixes, and WPF row-details parity - #3759

Merged
siegfriedpammer merged 12 commits into
icsharpcode:masterfrom
siegfriedpammer:ILSpy-cleaning
Jun 10, 2026
Merged

Metadata explorer cleanup, flags-filter fixes, and WPF row-details parity#3759
siegfriedpammer merged 12 commits into
icsharpcode:masterfrom
siegfriedpammer:ILSpy-cleaning

Conversation

@siegfriedpammer

@siegfriedpammer siegfriedpammer commented Jun 10, 2026

Copy link
Copy Markdown
Member

What started as a cleanup sweep over the Avalonia UI grew into a set of metadata-viewer improvements. Grouped by theme:

Cleanup / deduplication

  • ILLanguage: all six member-level Decompile* entry points repeated the same resolver/debug-info wiring; a module-aware CreateDisassembler overload now owns that setup.
  • Analyzer tree: the six Analyzed*TreeNode classes carried verbatim copies of the analyzer-registry walk in LoadChildren; hoisted into AnalyzerEntityTreeNode.AddAnalyzerChildren.
  • SearchPaneModel: replaced four hand-rolled try/catch export lookups with the existing AppComposition.TryGetExport helper they predated.
  • Metadata viewer: the #Strings/#US/#Blob heap nodes shared an open-coded offset-chained walk (now WalkHeap, documenting the handle-0 subtlety once); TypeDef and ExportedType built byte-identical TypeAttributes flag-group tooltips (now FlagsTooltip.ForTypeAttributes).

Flags tooltips & filters

  • The ECMA-335 Forwarder bit (0x00200000, no member in System.Reflection.TypeAttributes) is now visible in TypeAttributes tooltips and filterable in the per-column flags filter (IsTypeForwarder).
  • Mutex filter groups now lead with the enum's declared zero member (NotPublic, AutoLayout, Class, AnsiClass, PrivateScope, ...) instead of a synthesized "(none)", attributed by declaration order (the layout ECMA-335 and the reflection enums share). Saved filters are unaffected — persistence stores numeric values.

Filter popup fixes

  • Clicking inside the open flags filter popup could act on the column header underneath (sorting it / flashing its pressed visual) — both via routed-event bubbling and via the X11 overlay light-dismiss machinery re-targeting presses at the header. The filter UI now lives in an attached Flyout (per Avalonia guidance: built-in light dismiss, Escape, focus handling, theme-correct chrome — the old hard-coded white background was also wrong in dark mode), with the content swallowing unhandled wheel/press/release events. A headless end-to-end regression test drives a real DataGrid with overlay popup hosts and raw input through every flyout control.

Row details (WPF parity)

  • Ports the WPF DataGrid row-details feature: MetadataRowDetailsControl (a DataContext-reactive shell, since ProDataGrid recycles built details content across rows), page-model/view wiring, and a ConfigurePage hook on MetadataTableTreeNode.
  • COFF / Optional header: the Characteristics / DLL Characteristics rows render their 16 flag bits permanently expanded beneath the row.
  • CustomDebugInformation: selecting a row previews its Value blob — typed sub-grids for HoistedLocalScopes / CompilationOptions / MetadataReferences / TupleElementNames, source-link JSON as text, hex otherwise. Row activation ignores gestures inside the details area.
  • Column parity: added the row Offset column and the decoded Kind cell (heap offset + friendly name + GUID).

Infrastructure

  • BuildTools/pre-commit is now tracked executable (mode 100755) — previously every fresh checkout produced a non-executable hook that git silently skipped.

🤖 Generated with Claude Code

All six member-level Decompile* entry points wired the same assembly
resolver and debug info onto a fresh disassembler before dispatching;
the module-aware CreateDisassembler overload now owns that setup.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
All six entity nodes repeated the same registry walk in LoadChildren;
AddAnalyzerChildren gives analyzer registration a single home, with
subclasses adding their entity-specific rows (accessors, backing
fields) before calling it.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
The pane carried four hand-rolled try/catch export lookups that
predate AppComposition.TryGetExport and were missed when the other
call sites were converted.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
The #Strings/#US/#Blob nodes each open-coded the same offset-chained
walk, whose subtlety (handle 0 is a real entry despite being the nil
handle) is now documented once in WalkHeap. The TypeDef and
ExportedType entries built byte-identical TypeAttributes flag-group
tooltips; FlagsTooltip.ForTypeAttributes is the shared factory.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
0x00200000 is the ECMA-335 Forwarder bit (II.23.1.15), set on
ExportedType rows that forward a type to another assembly.
System.Reflection.TypeAttributes has no member for it, so the
enum-driven flag enumeration left it invisible in the Flags group.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
The blob was tracked 100644, so every fresh checkout (and any branch
switch in a worktree hosting the shared .git/hooks symlink target)
produces a non-executable hook script, which git then silently skips
with only an 'ignoredHook' hint. With core.fileMode=false on this
clone a plain chmod is git-invisible; the mode has to be fixed in the
index.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
The schema inferer only walks the enum's declared fields, so the
ECMA-335 Forwarder bit (0x00200000, no member in
System.Reflection.TypeAttributes) was invisible to the per-column
flags filter even though the cell tooltip already shows it. An
explicit per-enum extras table appends it as an independent flag,
making forwarders filterable on the ExportedType table.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
ECMA-335 names the zero state of most mutually exclusive sub-ranges
(NotPublic, AutoLayout, Class, AnsiClass, PrivateScope, ReuseSlot, ...)
and the reflection enums declare those members, but the schema inferer
skipped zero-valued fields and synthesised a generic '(none)' entry
instead. Zero fits every mask, so the member is attributed to a group
by declaration order: ECMA-335 and the reflection enums declare each
mask directly followed by its members. Filter persistence stores
numeric values, so saved filters are unaffected by the label change.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
Clicking inside the open flags filter popup could act on the
DataGridColumnHeader underneath: unhandled press/release pairs bubbled
out of the popup into the header (which treats them as a sort click
and flashes its pressed visual), and on X11 overlay popups the
light-dismiss machinery could even re-target a click's press directly
at the header while the popup stayed open.

The filter UI is now hosted in a Flyout attached to the funnel icon
instead of a hand-rolled Popup parked in the header's panel. Avalonia
positions Popup as the low-level primitive and recommends Flyout for
attached pickers: light dismiss, Escape-to-close, focus handling, and
theme-correct presenter chrome come built in (the old hard-coded white
background was also wrong in dark mode). The flyout content swallows
unhandled wheel and press/release events, since its internal popup is
still logically parented to the funnel inside the header.

A headless end-to-end regression test drives a real DataGrid with
overlay popup hosts (the X11 OverlayPopups=true configuration the app
runs with) and raw input through the funnel and every visible flyout
control, asserting the header never sorts and never receives an
unhandled press or release.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
WPF ILSpy used DataGrid row details in three places: the COFF and
Optional header views (flag-bit breakdown of the Characteristics /
DLL Characteristics words, permanently expanded) and the
CustomDebugInformation table (decoded Value blob, visible when
selected). The Avalonia port already carried the data — both header
nodes populate Entry.RowDetails and the column builder skips it — but
nothing displayed it.

ProDataGrid builds the row-details template once per details element
and recycles the built control across rows, swapping only the
DataContext, so a WPF-style template selector would go stale;
MetadataRowDetailsControl re-runs its content factory on every
DataContext change instead. Per-row initial visibility has no
SetDetailsVisibilityForItem equivalent and recycling resets
AreDetailsVisible, so the view re-derives it in LoadingRow. Row
activation now ignores gestures originating inside the details
presenter — selecting text in a details blob must not navigate away.

This lands the infrastructure plus the two header consumers; the
CustomDebugInformation details build on the EmbeddedSource decoding
work and follow with it. ConfigurePage on MetadataTableTreeNode is
the hook that consumer overrides.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
Selecting a row in the CustomDebugInformation table now previews its
Value blob beneath it, completing WPF row-details parity: state-machine
hoisted local scopes, compilation options, metadata references, and
tuple element names parse into typed sub-grid rows; source-link JSON
shows as text; everything else — unrecognized kinds, embedded source,
and malformed structured blobs — degrades to a hex dump, as in WPF.

The parser switches on the Kind GUID directly via KnownGuids; the
entry deliberately carries no decoded-kind state, leaving that to the
kind-column work that builds on this.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
WPF showed a row Offset column and decoded the Kind GUID into one cell
combining heap offset, friendly name, and raw GUID; the Avalonia table
only carried the bare heap offsets. Offset follows the GetRowOffset
convention every other table uses. Kind is plain text rather than a
hex column plus tooltip so the per-column filter matches the friendly
names.

Assisted-by: Claude:claude-fable-5[1m]:Claude Code
@siegfriedpammer siegfriedpammer changed the title Deduplicate analyzer, language, search, and metadata plumbing in the Avalonia UI Metadata explorer cleanup, flags-filter fixes, and WPF row-details parity Jun 10, 2026
@siegfriedpammer
siegfriedpammer merged commit 87e54cd into icsharpcode:master Jun 10, 2026
6 of 7 checks passed
@siegfriedpammer
siegfriedpammer deleted the ILSpy-cleaning branch June 10, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant